diff --git a/BUILD-LOG.md b/BUILD-LOG.md index 7c371778..0c185c05 100644 --- a/BUILD-LOG.md +++ b/BUILD-LOG.md @@ -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. diff --git a/agents/sage/work/vikunja-setup/2026-10-09_ids.json b/agents/sage/work/vikunja-setup/2026-10-09_ids.json new file mode 100644 index 00000000..3355f7df --- /dev/null +++ b/agents/sage/work/vikunja-setup/2026-10-09_ids.json @@ -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 + } + } +} diff --git a/agents/sage/work/vikunja-setup/2026-10-09_run.txt b/agents/sage/work/vikunja-setup/2026-10-09_run.txt new file mode 100644 index 00000000..3986ca93 --- /dev/null +++ b/agents/sage/work/vikunja-setup/2026-10-09_run.txt @@ -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 diff --git a/agents/sage/work/vikunja-setup/probe.mjs b/agents/sage/work/vikunja-setup/probe.mjs new file mode 100644 index 00000000..8fff387c --- /dev/null +++ b/agents/sage/work/vikunja-setup/probe.mjs @@ -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}`); +} diff --git a/agents/sage/work/vikunja-setup/roundtrip.mjs b/agents/sage/work/vikunja-setup/roundtrip.mjs new file mode 100644 index 00000000..fc741e62 --- /dev/null +++ b/agents/sage/work/vikunja-setup/roundtrip.mjs @@ -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); diff --git a/agents/sage/work/vikunja-setup/setup.mjs b/agents/sage/work/vikunja-setup/setup.mjs new file mode 100644 index 00000000..adb29375 --- /dev/null +++ b/agents/sage/work/vikunja-setup/setup.mjs @@ -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); diff --git a/docs/SESSIONS.md b/docs/SESSIONS.md index 016fc295..6ef370f8 100644 --- a/docs/SESSIONS.md +++ b/docs/SESSIONS.md @@ -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) diff --git a/docs/guides/slice-1-identities.md b/docs/guides/slice-1-identities.md index d001dc0d..b14f6938 100644 --- a/docs/guides/slice-1-identities.md +++ b/docs/guides/slice-1-identities.md @@ -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 diff --git a/docs/plans/2026-09-26_lead-decisions.md b/docs/plans/2026-09-26_lead-decisions.md index 78a0d2c0..31252398 100644 --- a/docs/plans/2026-09-26_lead-decisions.md +++ b/docs/plans/2026-09-26_lead-decisions.md @@ -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. diff --git a/docs/plans/2026-10-04_slice-1.md b/docs/plans/2026-10-04_slice-1.md index cb08a3ce..72d11de2 100644 --- a/docs/plans/2026-10-04_slice-1.md +++ b/docs/plans/2026-10-04_slice-1.md @@ -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