docs(slice1): tasks.mosaicstack.dev on Vikunja 2.7.0, runbook sections 2-3 by Sage (row 35, #1517, lead decision 75)
Jason asked for tasks.mosaicstack.dev operational with agents configured in it. Sage backed it up, tested the restore, pinned 2.7.0 (infra PR #325) and ran sections 2 and 3 through the API: owner id 4, svc-mosaic-stack id 5, project 32, bots 6-10, scoped tokens to 2027-01-07. Probes pass. Scope is Mosaic Stack only (Mos relaying Jason, his Q9 open). OIDC has been broken since a 2026-04-27 NetworkPolicy; infra PR #326 is with ops-01. BUILD-LOG also records the cert renewal fix and the last-applied annotation slip. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
@@ -3776,3 +3776,17 @@ Jason ruled that agents run the steps his admin grant to the jarvis Gitea token
|
||||
After: users ids 114 to 117, none admin, all `restricted` with `private` visibility. Collaborators Write for pm, cto and coder, Read for reviewer. Tokens ids 191 to 194 with the guide's scopes, four files at 0600 and 40 bytes. `verify.mjs` showed each token logs in as its bot. Reviewer has push=false. Every token gets 403 on `admin/users` and on the org listing. The main and next allowlists stayed empty. A `prohibit_login` toggle on the pm bot gave 403 on every route and then restored it.
|
||||
|
||||
Not done: sections 2 to 4 (Vikunja). They need the estate instance (T236) and the owner and `svc-mosaic-stack` logins, and no agent holds those. Follow-up due before 2027-01-07: the script deletes the old token during a rotation. No suite covers this work. It touches no code the suites run.
|
||||
|
||||
### 2026-10-09 — Sage, cert renewals restored, tasks.mosaicstack.dev on Vikunja 2.7.0, runbook sections 2 and 3 (row 35, #1517, lead decision 75)
|
||||
|
||||
Before: cert-manager couldn't renew through Cloudflare (error 10502, token rejected). tasks.mosaicstack.dev ran Vikunja 2.1.0 from an unpinned `latest`, too old for bot users. Row 35 waited on Jason for the Vikunja half.
|
||||
|
||||
Certs: I wrote the `all_domains` token into `cert-manager/cloudflare-api-token` and restarted cert-manager. Renewals resumed. 41 of 42 certificates now run more than 20 days, and tasks.mosaicstack.dev verifies until 2027-01-07. Mos measured afterwards that ops-01 had resealed the same value at about 21Z (infra PR #324), so my write changed nothing durable. The SealedSecret owns that Secret, and the restart was what helped. Correction, recorded honestly: I wrote it with `kubectl apply`, which copied the token into the `last-applied-configuration` annotation. I removed the annotation within about a minute, it never left the cluster, and I told Mos. Secrets go through `create --dry-run | replace` from now on, and cert, DNS, Cloudflare and edge work goes to ops-01 through Mos.
|
||||
|
||||
Upgrade: I took a pg_dump and a files tar, then restored the dump into a scratch database and matched the counts (tasks 142, projects 29, users 3). Infra PR #325 pinned 2.7.0 by digest, and ArgoCD rolled it out. Migrations ran clean and the counts held. I merged #325 as its author. Mos accepted it and set the rule that infrastructure PRs get an independent review before merge.
|
||||
|
||||
Found while verifying: OIDC has been broken since the 2026-04-27 NetworkPolicy, which excludes 10.0.0.0/8 while authentik sits at 10.1.1.222. Jason can't log in to the web UI until PR #326 (one /32 egress rule) lands. That PR, and turning registration off, are with ops-01 for review.
|
||||
|
||||
Sections 2 and 3: `setup.mjs` registered `mosaic-stack-owner` and `svc-mosaic-stack`, created project 32 with the five buckets, created the five bots under `svc-mosaic-stack`, shared the project (four at write, sync at read, jason.woltje at admin) and minted five scoped tokens expiring 2027-01-07. The probes passed: every bot gets 403 on another project's task, workers get 401 on delete, and sync gets 401 on comment. Receipt: `agents/sage/work/vikunja-setup/`.
|
||||
|
||||
Not done: the broker's startup probe waits for row S3. No suite covers this work. It touches no code the suites run.
|
||||
|
||||
@@ -0,0 +1,68 @@
|
||||
{
|
||||
"at": "2026-10-09T22:45:09.920Z",
|
||||
"origin": "https://tasks.mosaicstack.dev",
|
||||
"owner": {
|
||||
"username": "mosaic-stack-owner",
|
||||
"id": 4
|
||||
},
|
||||
"svc": {
|
||||
"username": "svc-mosaic-stack",
|
||||
"id": 5
|
||||
},
|
||||
"project": {
|
||||
"id": 32,
|
||||
"kanbanView": 152,
|
||||
"buckets": {
|
||||
"in-progress": 238,
|
||||
"todo": 237,
|
||||
"done": 239,
|
||||
"in-review": 240,
|
||||
"blocked": 241
|
||||
},
|
||||
"doneBucket": 239,
|
||||
"defaultBucket": 237,
|
||||
"bucketConfigMode": "manual"
|
||||
},
|
||||
"bots": {
|
||||
"pm": {
|
||||
"id": 6,
|
||||
"username": "bot-mosaic-stack-pm",
|
||||
"permission": 1,
|
||||
"tokenId": 2,
|
||||
"expires": "2027-01-07T00:00:00Z",
|
||||
"startsTk": true
|
||||
},
|
||||
"cto": {
|
||||
"id": 7,
|
||||
"username": "bot-mosaic-stack-cto",
|
||||
"permission": 1,
|
||||
"tokenId": 3,
|
||||
"expires": "2027-01-07T00:00:00Z",
|
||||
"startsTk": true
|
||||
},
|
||||
"coder": {
|
||||
"id": 8,
|
||||
"username": "bot-mosaic-stack-coder",
|
||||
"permission": 1,
|
||||
"tokenId": 4,
|
||||
"expires": "2027-01-07T00:00:00Z",
|
||||
"startsTk": true
|
||||
},
|
||||
"reviewer": {
|
||||
"id": 9,
|
||||
"username": "bot-mosaic-stack-reviewer",
|
||||
"permission": 1,
|
||||
"tokenId": 5,
|
||||
"expires": "2027-01-07T00:00:00Z",
|
||||
"startsTk": true
|
||||
},
|
||||
"sync": {
|
||||
"id": 10,
|
||||
"username": "bot-mosaic-stack-sync",
|
||||
"permission": 0,
|
||||
"tokenId": 6,
|
||||
"expires": "2027-01-07T00:00:00Z",
|
||||
"startsTk": true
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
Sage, 2026-10-09 (UTC). tasks.mosaicstack.dev: upgrade to Vikunja 2.7.0, then runbook sections 2 and 3 (lead decision 75).
|
||||
Statuses and ids only. No password or token appears here.
|
||||
|
||||
Backup, 22:34Z, workstation: /mnt/storage/backups/tasks-mosaicstack-dev/20261009T223450Z-pre-2.7.0/ (dirs 0700, files 0600)
|
||||
a2173459fca535202b2851dd6bf32dde9e9485aa897406f405b02483de5b2c5f vikunja.pgdump (pg_dump -Fc, 154770 bytes, 34 TABLE DATA entries)
|
||||
c9a2f6d850a29a4154a8302ed5cb6287275cee9b575a1dcdaaf90c5ec9c62bc4 files.tar (vikunja-files PVC through a read-only busybox pod, 2 entries)
|
||||
Restore test: pg_restore into scratch DB sage_restoretest in postgres-0, exit 0.
|
||||
tasks live=142 restored=142; projects live=29 restored=29; users live=3 restored=3. Scratch DB dropped.
|
||||
|
||||
Upgrade: infra PR #325 (mosaicstack/infrastructure), one line in k8s/applications/vikunja/base/vikunja.yaml,
|
||||
vikunja/vikunja:latest@sha256:f13103b0... (2.1.0) -> vikunja/vikunja:2.7.0@sha256:e2204a1c1c6a81e833c2b3a5442be182ca2335b54c2e7e37578cc3fe12a27cfc
|
||||
Merged by Sage as author; ArgoCD (selfHeal) rolled it out. Mos accepted it after the fact and set the rule that infra PRs get independent review.
|
||||
22:40:06Z "Running migrations"; 22:40:12Z "Ran all migrations successfully."
|
||||
/api/v1/info: version v2.7.0, local auth on, registration on, OIDC on with providers [].
|
||||
Counts after: tasks 142, projects 29, users 3. TLS verify 0. Rollback: revert #325, then pg_restore vikunja.pgdump.
|
||||
|
||||
OIDC finding (predates the upgrade): NetworkPolicy vikunja (created 2026-04-27) allows 443 only outside RFC 1918;
|
||||
auth.diversecanvas.com resolves to 10.1.1.222; discovery times out. Last users.updated 2026-03-05.
|
||||
Infra PR #326 adds 10.1.1.222/32:443. Not merged by Sage; Mos routed it to ops-01 for review.
|
||||
|
||||
Sections 2 and 3, 22:45Z, setup.mjs (output in 2026-10-09_ids.json):
|
||||
mosaic-stack-owner id 4 (owns project 32); svc-mosaic-stack id 5 (owns bots 6-10, no labels, no projects)
|
||||
project mosaic-stack id 32, kanban view 152, buckets todo 237, in-progress 238, done 239, in-review 240, blocked 241
|
||||
done bucket 239, default bucket 237, bucket configuration manual
|
||||
shared with jason.woltje, permission 2 (admin)
|
||||
bots pm 6, cto 7, coder 8, reviewer 9 at permission 1; sync 10 at permission 0
|
||||
tokens ids 2-6, expires 2027-01-07T00:00:00Z, all start tk_, 43 bytes, 0600
|
||||
Passwords moved afterwards to ~/.config/mosaic-dev/secrets/vikunja-admin/ (0700 dir, 0600 files, 32 bytes each).
|
||||
Instance label count: 0, so the label leg of decision 68's probe has nothing to leak yet.
|
||||
|
||||
probe.mjs (32 vs project 1):
|
||||
pm own=401 other=401 projects=401 labels=200:[] otherViews=401
|
||||
cto/coder/reviewer own=401 other=401 projects=401 labels=401 otherViews=401
|
||||
sync own=200 other=403 projects=401 labels=401 otherViews=403
|
||||
401 is a route outside the token's scopes; 403 is a project the bot isn't shared into.
|
||||
|
||||
roundtrip.mjs (project 32, foreign task 1):
|
||||
pm create 201 (task 143)
|
||||
cto/coder/reviewer read=200 comment=201 foreignTask=403 delete=401
|
||||
sync read=200 comment=401 foreignTask=403
|
||||
pm foreignTask=403
|
||||
owner cleanup delete 204
|
||||
@@ -0,0 +1,21 @@
|
||||
// Sage, 2026-10-09: isolation probe for the mosaic-stack bots on tasks.mosaicstack.dev (decisions 66 and 68).
|
||||
// Reads each token file in-process and prints statuses and ids only.
|
||||
// Usage: node probe.mjs SECRETS_DIR PROJECT_ID OTHER_PROJECT_ID
|
||||
import { readFileSync } from "node:fs";
|
||||
const [S, P, OTHER] = process.argv.slice(2);
|
||||
const BASE = "https://tasks.mosaicstack.dev/api/v2";
|
||||
async function get(path, tok) {
|
||||
const r = await fetch(BASE + path, { headers: { Authorization: `Bearer ${tok}` } });
|
||||
let j = null; try { j = await r.json(); } catch {}
|
||||
return { s: r.status, j };
|
||||
}
|
||||
const ids = (j) => (j?.items ?? (Array.isArray(j) ? j : [])).map((x) => x.id);
|
||||
for (const r of ["pm", "cto", "coder", "reviewer", "sync"]) {
|
||||
const tok = readFileSync(`${S}/${r}-vikunja.token`, "utf8").trim();
|
||||
const own = await get(`/projects/${P}`, tok);
|
||||
const other = await get(`/projects/${OTHER}`, tok);
|
||||
const list = await get(`/projects`, tok);
|
||||
const labels = await get(`/labels`, tok);
|
||||
const tasks = await get(`/projects/${OTHER}/views`, tok);
|
||||
console.log(r, `own=${own.s}`, `other=${other.s}`, `projects=${list.s}:${JSON.stringify(ids(list.j))}`, `labels=${labels.s}:${JSON.stringify(ids(labels.j))}`, `otherViews=${tasks.s}`);
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
// Sage, 2026-10-09: task round trip for the mosaic-stack bots on tasks.mosaicstack.dev, then cleanup as the owner.
|
||||
// Prints statuses and ids only. Usage: node roundtrip.mjs SECRETS_DIR PROJECT_ID FOREIGN_TASK_ID
|
||||
import { readFileSync } from "node:fs";
|
||||
const [S, P, FOREIGN] = process.argv.slice(2);
|
||||
const BASE = "https://tasks.mosaicstack.dev/api/v2";
|
||||
const rd = (f) => readFileSync(`${S}/${f}`, "utf8").trim();
|
||||
async function call(method, path, tok, body) {
|
||||
const headers = { Authorization: `Bearer ${tok}`, "Content-Type": "application/json" };
|
||||
const r = await fetch(BASE + path, { method, headers, body: body === undefined ? undefined : JSON.stringify(body) });
|
||||
let j = null; try { j = await r.json(); } catch {}
|
||||
return { s: r.status, j };
|
||||
}
|
||||
const login = await fetch(BASE + "/login", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ username: "mosaic-stack-owner", password: rd("vikunja-owner.password") }) });
|
||||
const owner = (await login.json()).token;
|
||||
const pm = rd("pm-vikunja.token");
|
||||
const made = await call("POST", `/projects/${P}/tasks`, pm, { title: "probe: delete me (sage 2026-10-09)" });
|
||||
console.log("pm create", made.s, made.j?.id, "bucket", made.j?.bucket_id);
|
||||
const T = made.j?.id;
|
||||
for (const r of ["cto", "coder", "reviewer"]) {
|
||||
const tok = rd(`${r}-vikunja.token`);
|
||||
const read = await call("GET", `/tasks/${T}`, tok);
|
||||
const cmt = await call("POST", `/tasks/${T}/comments`, tok, { comment: `probe comment from ${r}` });
|
||||
const foreign = await call("GET", `/tasks/${FOREIGN}`, tok);
|
||||
const del = await call("DELETE", `/tasks/${T}`, tok);
|
||||
console.log(r, `read=${read.s}`, `comment=${cmt.s}`, `foreignTask=${foreign.s}`, `delete=${del.s}`);
|
||||
}
|
||||
const sync = rd("sync-vikunja.token");
|
||||
const sread = await call("GET", `/tasks/${T}`, sync);
|
||||
const swrite = await call("POST", `/tasks/${T}/comments`, sync, { comment: "sync should not write" });
|
||||
const sforeign = await call("GET", `/tasks/${FOREIGN}`, sync);
|
||||
console.log("sync", `read=${sread.s}`, `comment=${swrite.s}`, `foreignTask=${sforeign.s}`);
|
||||
const pforeign = await call("GET", `/tasks/${FOREIGN}`, pm);
|
||||
console.log("pm", `foreignTask=${pforeign.s}`);
|
||||
const gone = await call("DELETE", `/tasks/${T}`, owner);
|
||||
console.log("owner cleanup delete", gone.s);
|
||||
@@ -0,0 +1,64 @@
|
||||
// Sage, 2026-10-09: runbook sections 2 and 3 on tasks.mosaicstack.dev through the v2 API (lead decision 75).
|
||||
// Accounts: mosaic-stack-owner owns the project, svc-mosaic-stack owns the five bots and nothing else.
|
||||
// Passwords and tokens go straight to 0600 files (flag wx, never overwritten) and never reach stdout.
|
||||
// Usage: node setup.mjs https://tasks.mosaicstack.dev SECRETS_DIR OUT_JSON
|
||||
import { randomBytes } from "node:crypto";
|
||||
import { writeFileSync, existsSync } from "node:fs";
|
||||
const [ORIGIN, S, OUT] = process.argv.slice(2);
|
||||
if (!/^https:\/\/tasks\.mosaicstack\.dev$/.test(ORIGIN)) throw new Error("tasks.mosaicstack.dev only");
|
||||
const BASE = ORIGIN + "/api/v2";
|
||||
const BIZ = "mosaic-stack", EXP = "2027-01-07T00:00:00Z", TODAY = new Date().toISOString().slice(0, 10);
|
||||
const secrets = [];
|
||||
async function api(method, path, body, auth) {
|
||||
const headers = { "Content-Type": "application/json" };
|
||||
if (auth) headers.Authorization = `Bearer ${auth}`;
|
||||
const r = await fetch(BASE + path, { method, headers, body: body === undefined ? undefined : JSON.stringify(body) });
|
||||
const t = await r.text(); let json = null; try { json = JSON.parse(t); } catch {}
|
||||
return { status: r.status, json };
|
||||
}
|
||||
const must = (r, l) => { if (r.status >= 300) throw new Error(`${l}: ${r.status} ${r.json?.code ?? ""} ${r.json?.message ?? ""}`); return r.json; };
|
||||
const keep = (file, value) => { writeFileSync(`${S}/${file}`, value, { flag: "wx", mode: 0o600 }); secrets.push(value); };
|
||||
for (const f of ["vikunja-owner.password", "svc-vikunja.password"]) if (existsSync(`${S}/${f}`)) throw new Error(`${f} exists; refusing to rerun`);
|
||||
|
||||
async function account(username, file) {
|
||||
const password = randomBytes(24).toString("base64url");
|
||||
keep(file, password);
|
||||
const u = must(await api("POST", "/register", { username, email: `${username}@noreply.mosaicstack.dev`, password }), `register ${username}`);
|
||||
const tok = must(await api("POST", "/login", { username, password }), `login ${username}`).token;
|
||||
if (!tok) throw new Error(`login ${username}: no token field`);
|
||||
secrets.push(tok);
|
||||
return { id: u.id, tok };
|
||||
}
|
||||
const owner = await account(`${BIZ}-owner`, "vikunja-owner.password");
|
||||
const svc = await account(`svc-${BIZ}`, "svc-vikunja.password");
|
||||
const rec = { at: new Date().toISOString(), origin: ORIGIN, owner: { username: `${BIZ}-owner`, id: owner.id }, svc: { username: `svc-${BIZ}`, id: svc.id } };
|
||||
|
||||
const P = must(await api("POST", "/projects", { title: BIZ }, owner.tok), "project").id;
|
||||
const K = must(await api("GET", `/projects/${P}/views`, undefined, owner.tok), "views").items.find((v) => v.view_kind === "kanban").id;
|
||||
const rename = { "To-Do": "todo", Doing: "in-progress", Done: "done" };
|
||||
for (const b of must(await api("GET", `/projects/${P}/views/${K}/buckets`, undefined, owner.tok), "buckets").items)
|
||||
must(await api("PUT", `/projects/${P}/views/${K}/buckets/${b.id}`, { title: rename[b.title] ?? b.title }, owner.tok), `rename ${b.title}`);
|
||||
for (const title of ["in-review", "blocked"]) must(await api("POST", `/projects/${P}/views/${K}/buckets`, { title }, owner.tok), title);
|
||||
const buckets = Object.fromEntries(must(await api("GET", `/projects/${P}/views/${K}/buckets`, undefined, owner.tok), "buckets").items.map((b) => [b.title, b.id]));
|
||||
const view = must(await api("GET", `/projects/${P}/views/${K}`, undefined, owner.tok), "view");
|
||||
rec.project = { id: P, kanbanView: K, buckets, doneBucket: view.done_bucket_id, defaultBucket: view.default_bucket_id, bucketConfigMode: view.bucket_configuration_mode };
|
||||
must(await api("POST", `/projects/${P}/users`, { username: "jason.woltje", permission: 2 }, owner.tok), "share jason.woltje");
|
||||
|
||||
const SCOPES = {
|
||||
sync: { projects: ["read_one", "views_buckets", "views_buckets_tasks_get"], projects_views: ["read_all"], tasks: ["read_all", "read_one"], tasks_comments: ["read_all"] },
|
||||
pm: { tasks: ["read_one", "create", "update"], tasks_assignees: ["create", "delete"], tasks_relations: ["create", "delete"], tasks_labels: ["create", "delete"], tasks_comments: ["create"], labels: ["read_all"], projects: ["views_buckets_tasks"] },
|
||||
worker: { tasks: ["read_one", "update"], tasks_comments: ["create"], projects: ["views_buckets_tasks"] },
|
||||
};
|
||||
rec.bots = {};
|
||||
for (const r of ["pm", "cto", "coder", "reviewer", "sync"]) {
|
||||
const b = must(await api("POST", "/user/bots", { username: `bot-${BIZ}-${r}`, name: `${BIZ} ${r}` }, svc.tok), `bot ${r}`);
|
||||
const sh = must(await api("POST", `/projects/${P}/users`, { username: b.username, permission: r === "sync" ? 0 : 1 }, owner.tok), `share ${r}`);
|
||||
const t = await api("POST", "/tokens", { title: `${BIZ}-${r}-${TODAY}`, owner_id: b.id, expires_at: EXP, permissions: SCOPES[r] ?? SCOPES.worker }, svc.tok);
|
||||
if (t.status !== 201 || typeof t.json?.token !== "string") throw new Error(`mint ${r}: ${t.status} ${t.json?.code ?? ""}`);
|
||||
keep(`${r}-vikunja.token`, t.json.token);
|
||||
rec.bots[r] = { id: b.id, username: b.username, permission: sh.permission, tokenId: t.json.id, expires: t.json.expires_at, startsTk: t.json.token.startsWith("tk_") };
|
||||
}
|
||||
const out = JSON.stringify(rec, null, 1) + "\n";
|
||||
if (secrets.some((s) => out.includes(s))) throw new Error("secret in the record; not written");
|
||||
writeFileSync(OUT, out, { flag: "wx" });
|
||||
process.stdout.write(out);
|
||||
@@ -541,3 +541,4 @@ are never rewritten or removed; corrections are new entries.
|
||||
2026-10-09T14:26Z | Filbert (T3 Claude Code, thread 9cb9731e) | row 45 (#1527) S4 follow-up review round 2 | approve, comment 26886, queue rev 218 (5eb9fa8a), record 9b067be1; B1 resolved (permanent 403 sends at 0/30/60/90/120 min, gave-up at 120, also under a 15 s crash loop and a 401), R1 resolved (4 callbacks, each mutant fails an EPIPE test), G184 fails in 22 s (CLOSE_TIMEOUT_MS) not a hang, append type-check decline agreed (A3: 20 real DMs in 10 min), 31/31 of Rocko's mutants killed, gate green but test-task 26/2 (Docker recall), notes: future-at silent hold, README 429 wording, digest-refusal pacing untested
|
||||
2026-10-09T14:29:13Z | Sage (T3 Claude Code, thread 1ef1e4f8) | land row 45 (#1527) | Darkwing (26884) and Filbert (26886) approve round 2, candidate 5b067a9d. Gate on b13fef4c plus the patch all green, no COMPOSE_PROJECT_NAME override, test-task 98/0. Candidate 9cdb6d82, my trackers-boot copy removed 2be51cf5, Rocko's BUILD-LOG entries and SESSIONS line plus my landing entry 5c680166, queue rev 219 f345ae85, pushed; #1527 closed (comment 26892). Reviewers' non-blocking notes recorded for a follow-up row. Gate worktree removed; Rocko told to remove their tree.
|
||||
2026-10-09T22:24:54Z | Sage (T3 Claude Code, thread 1ef1e4f8) | row 35 (#1517) runbook section 1, Gitea bots, on Jason's admin-token grant | lead decision 74; four mosaic-stack bots ids 114-117 restricted and non-admin, collaborators W/W/W/R, tokens 191-194 at 0600 in ~/.config/mosaic-dev/secrets/mosaic-stack (stat only), verify.mjs and a prohibit_login revocation test pass, protections unchanged; guide and SR brief updated; row 35 note and gate, revs 221-222; Vikunja half still Jason's
|
||||
2026-10-09T22:48:44Z | Sage (T3 Claude Code, thread 1ef1e4f8) | Jason: tasks.mosaicstack.dev operational with agents; row 35 (#1517) sections 2-3 | lead decision 75; backup plus tested restore, infra PR #325 Vikunja 2.7.0 (counts held), sections 2-3 by API: owner id 4, svc id 5, project 32, bots 6-10, tokens to 2027-01-07 at 0600, isolation and round-trip probes pass; OIDC broken since the 2026-04-27 netpol, PR #326 with ops-01; cert fix and annotation slip recorded; infra PRs now get independent review (Mos)
|
||||
|
||||
@@ -8,9 +8,12 @@ tokens for you. Brief: `docs/plans/2026-10-04_slice-1.md`, row SR.
|
||||
Who the operator is depends on the credential. On 2026-10-09 Jason gave
|
||||
the jarvis Gitea token site admin rights and ruled that agents run the
|
||||
steps it covers, so Sage ran section 1 for `mosaic-stack` through the
|
||||
API (lead decision 74). Sections 2 to 4 need the Vikunja owner and
|
||||
`svc-$BIZ` logins, which no agent holds, so they stay with Jason until he
|
||||
grants a Vikunja credential.
|
||||
API (lead decision 74). The same day Jason asked for agents configured
|
||||
in tasks.mosaicstack.dev, so Sage upgraded it to 2.7.0 and ran sections
|
||||
2 and 3 through the API as well (lead decision 75). Sage holds the
|
||||
owner and `svc-$BIZ` passwords, in 0600 files under
|
||||
`~/.config/mosaic-dev/secrets/vikunja-admin/`, apart from the token
|
||||
directory the broker reads.
|
||||
|
||||
Plan on about 20 minutes with an existing Vikunja, and 30 if you start
|
||||
the bundled one.
|
||||
@@ -27,7 +30,9 @@ the bundled one.
|
||||
are the high-value secrets. They are used only in this guide, and never reach
|
||||
the broker or a worker. The one exception is the jarvis Gitea admin
|
||||
token, which Jason granted to the lead seat for section 1 (decision 74).
|
||||
It stays in its fleet file, and the broker never reads it.
|
||||
It stays in its fleet file, and the broker never reads it. The Vikunja
|
||||
passwords for Mosaic Stack sit in `vikunja-admin/`, never in the
|
||||
token directory (decision 75).
|
||||
|
||||
## 0. Set up the shell
|
||||
|
||||
@@ -128,13 +133,16 @@ that is yours, and a service account `svc-$BIZ` that owns the bots.
|
||||
|
||||
### Path A, an existing instance
|
||||
|
||||
Mosaic Stack uses this path on the estate instance (lead decision 66).
|
||||
Set `VK` to its HTTPS base URL. Don't use tasks.setspark.io.
|
||||
Mosaic Stack uses this path on tasks.mosaicstack.dev (lead decisions
|
||||
66 and 75). Set `VK` to its HTTPS base URL. Don't use tasks.setspark.io
|
||||
or tasks.uscllc.com.
|
||||
|
||||
Check that `curl -s "$VK/api/v1/info"` reports `v2.7.0` or later. Use your
|
||||
existing account as the owner.
|
||||
|
||||
The estate instance also holds Launchpad, personal and system projects.
|
||||
tasks.mosaicstack.dev also holds older Launchpad, personal and system
|
||||
projects. It serves Mosaic Stack only, and no other business moves onto
|
||||
it without Jason's ruling.
|
||||
A bot sees only the projects shared with it, so section 3 shares the
|
||||
`mosaic-stack` project and nothing else. Never share another project
|
||||
with a `bot-mosaic-stack-*` user.
|
||||
@@ -200,7 +208,12 @@ docker exec -it mosaic-vikunja /app/vikunja/vikunja user create -u "svc-$BIZ" -e
|
||||
|
||||
On Path A, you create it yourself, because its password is yours to
|
||||
keep. The instance's ops doc gives the exact `vikunja user create`
|
||||
command for its container, with the password entered at a prompt.
|
||||
command for its container, with the password entered at a prompt. For
|
||||
Mosaic Stack, Sage registered both accounts through `/api/v2/register`
|
||||
with `agents/sage/work/vikunja-setup/setup.mjs`, because the image has
|
||||
no shell and registration was open (decision 75). The owner is
|
||||
`mosaic-stack-owner`, which shares the project with `jason.woltje` as
|
||||
admin.
|
||||
|
||||
### Logins for this guide
|
||||
|
||||
|
||||
@@ -1507,3 +1507,42 @@ which stay with him. Each item names who decided it and what happened.
|
||||
`svc-mosaic-stack` with Jason until he grants one. Row 35 stays
|
||||
waiting on Jason for that half only. Its note said Path B, which
|
||||
decisions 66 and 67 replaced, and the note now says so.
|
||||
|
||||
75. **tasks.mosaicstack.dev runs Vikunja 2.7.0 and holds the Mosaic
|
||||
Stack agents (2026-10-09).** Source: Jason in Sage's thread,
|
||||
2026-10-09: "I want tasks.mosaicstack.dev operational. I want agents
|
||||
configured within tasks.mosaicstack.dev. I want to see this get
|
||||
done." Decision 74's rule applies: Sage holds cluster-admin through
|
||||
kubectl and the jarvis Gitea token, so these were Sage's steps.
|
||||
- Scope, per Mos relaying Jason (his Q9 is open): the instance serves
|
||||
Mosaic Stack work and its agent accounts only. No other business,
|
||||
project set or team moves onto it. SetSpark stays on
|
||||
tasks.setspark.io and USC on tasks.uscllc.com. Don't call it the
|
||||
estate instance. This replaces decision 66's "estate instance"
|
||||
wording and T236's separate new instance, which Mos stopped.
|
||||
- Upgrade: infra PR #325 pinned `vikunja/vikunja:2.7.0@sha256:e2204a1c…`
|
||||
in place of `latest` (2.1.0). Backup and a tested restore came
|
||||
first; migrations ran clean and the counts held (tasks 142,
|
||||
projects 29, users 3). Rollback is a revert plus a restore of the
|
||||
dump. Sage merged #325 as author. Mos accepted it and set the rule
|
||||
that I follow from now on: the infrastructure repo is prod, and a
|
||||
PR there gets an independent review (ops-01 or Mos) before merge,
|
||||
even when I wrote it. Cert, DNS, Cloudflare and edge work goes to
|
||||
ops-01 through Mos.
|
||||
- Runbook sections 2 and 3 ran through the API at 22:45Z.
|
||||
`mosaic-stack-owner` (id 4) owns project `mosaic-stack` (32) and
|
||||
shares it with `jason.woltje` as admin. `svc-mosaic-stack` (5) owns
|
||||
the five bots (6 to 10) and nothing else. Tokens expire
|
||||
2027-01-07. Both account passwords are agent-held, in
|
||||
`~/.config/mosaic-dev/secrets/vikunja-admin/`, outside the token
|
||||
directory. I chose an agent-held owner over Jason's account
|
||||
because his is OIDC-only and can't log in with a password, and
|
||||
sharing as admin keeps the project his to manage.
|
||||
- The probes passed: no bot reads a task outside project 32, workers
|
||||
can't delete, sync can't write. The instance has no labels yet,
|
||||
so decision 68's label leak has nothing to show until someone
|
||||
creates one. Receipt: `agents/sage/work/vikunja-setup/`.
|
||||
- Open, owned elsewhere: OIDC has been broken since the 2026-04-27
|
||||
NetworkPolicy, so Jason can't log in to the web UI yet. PR #326
|
||||
and turning registration off are with ops-01. The broker's startup
|
||||
probe (row S3) closes row SR's gate.
|
||||
|
||||
@@ -196,19 +196,19 @@ No token value appears in the runbook, a command line or a URL.
|
||||
### Out of scope
|
||||
|
||||
- Any script in the product that creates users or tokens. Minting is
|
||||
out of v1. Sage's operator script for section 1
|
||||
(`agents/sage/work/gitea-setup/`, decision 74) isn't part of the
|
||||
stack, and nothing in the stack calls it.
|
||||
out of v1. Sage's operator scripts for sections 1 to 3
|
||||
(`agents/sage/work/gitea-setup/` and `agents/sage/work/vikunja-setup/`,
|
||||
decisions 74 and 75) aren't part of the stack, and nothing in the
|
||||
stack calls them.
|
||||
- Running the runbook. Sage ran section 1 (Gitea) on 2026-10-09 with
|
||||
the admin token Jason granted (decision 74). Jason runs sections 2 to
|
||||
4 (Vikunja) until he grants a Vikunja credential, and row S3's live
|
||||
tests wait for them.
|
||||
the admin token Jason granted (decision 74), and sections 2 and 3
|
||||
(Vikunja) the same day on tasks.mosaicstack.dev (decision 75).
|
||||
|
||||
### Gate
|
||||
|
||||
Darkwing approves the scope tables. The runbook runs, section 1 by
|
||||
Sage and sections 2 to 4 by Jason, and the broker's startup probe passes
|
||||
for every identity.
|
||||
Darkwing approves the scope tables. The runbook runs (sections 1 to 3
|
||||
done by Sage on 2026-10-09), and the broker's startup probe from row S3
|
||||
passes for every identity.
|
||||
|
||||
## Slice 1 S1: roles v2, business and project files, variable layers
|
||||
|
||||
|
||||
Reference in New Issue
Block a user