From 104cf4f3f015b565006e6e4a094582832861edbf Mon Sep 17 00:00:00 2001 From: Jason Woltje Date: Sun, 4 Oct 2026 22:10:32 -0500 Subject: [PATCH] docs(guides): slice 1 identities round 2; lead decision 58 Co-Authored-By: Claude Opus 5.5 --- docs/SESSIONS.md | 1 + docs/guides/slice-1-identities.md | 109 +++++++++++++++--------- docs/plans/2026-09-26_lead-decisions.md | 16 ++++ docs/prd/mosaic-stack.md | 9 +- 4 files changed, 93 insertions(+), 42 deletions(-) diff --git a/docs/SESSIONS.md b/docs/SESSIONS.md index 4028eba9..99d51741 100644 --- a/docs/SESSIONS.md +++ b/docs/SESSIONS.md @@ -487,3 +487,4 @@ are never rewritten or removed; corrections are new entries. 2026-10-04 | rocko | Slice 1 S2 #1519 row 37 | Started after schema v3 pin verification and Sage authorization; fixture credentials only; Darkwing review and Sage commit pending. 2026-10-05T02:58:34Z | Sage (T3 Claude Code, thread 1ef1e4f8) | slice 1 SR runbook, S5 rulings | docs/guides/slice-1-identities.md drafted for Darkwing's review (row 35); lead decision 56 accepts Dewey's Q1-Q5; Rocko started row 37 2026-10-05T03:03:02Z | Sage (T3 Claude Code, thread 1ef1e4f8) | schema v3a | Darkwing's v3a (29daa482) rerun matched on Node 26; lead decision 57; row 37 note +2026-10-05T03:10:32Z | Sage (T3 Claude Code, thread 1ef1e4f8) | row 35 SR round 2 | guide revised per Darkwing's round 1 (comment 26709): mint error path, jq strings, read -rs Gitea tokens, business-prefixed bot names; PRD 0.4 REQ-CRED-1 naming; lead decision 58 diff --git a/docs/guides/slice-1-identities.md b/docs/guides/slice-1-identities.md index 297184ae..07e60163 100644 --- a/docs/guides/slice-1-identities.md +++ b/docs/guides/slice-1-identities.md @@ -32,47 +32,69 @@ GITEA=https://git.example # your Gitea base URL EXP=$(date -u -d '+90 days' +%Y-%m-%dT00:00:00Z) # Vikunja token expiry ``` -Write `EXP` down. It goes in the business file as each Vikunja token's -`expires`. +Write down the date part of `EXP`. It goes in the business file as +each Vikunja token's `expires`, in `YYYY-MM-DD` form. The broker treats +00:00Z on that date as the expiry, the same instant `EXP` names. + +The commands need curl 7.76 or later, for `--fail-with-body`. ## 1. Gitea: four bot users and their tokens Gitea tokens don't expire, and the HTTP route for creating one needs a password, so this part uses the web UI. -1. As a site admin, create the users `pm-bot`, `cto-bot`, `coder-bot` - and `reviewer-bot` (Site Administration, User Accounts, Create). Give +1. As a site admin, create the users `mosaic-stack-pm-bot`, + `mosaic-stack-cto-bot`, `mosaic-stack-coder-bot` and + `mosaic-stack-reviewer-bot` (Site Administration, User Accounts, + Create). Gitea usernames are global to the instance, so the business + id in the name keeps one business's bots, and revoking them, apart + from another's. Give each a long random password, which you won't need again after step 3. None of them is an admin. 2. In `mosaicstack/stack`, add each bot as a collaborator: | Bot | Repository access | |---|---| - | pm-bot | Write (labels, assignees and closing need it) | - | cto-bot | Write | - | coder-bot | Write | - | reviewer-bot | Read | + | mosaic-stack-pm-bot | Write (labels, assignees and closing need it) | + | mosaic-stack-cto-bot | Write | + | mosaic-stack-coder-bot | Write | + | mosaic-stack-reviewer-bot | Read | - No bot is on a protected branch's push or merge allowlist. Pushing to - a protected branch is a gated action, and it stays Jason's. + No bot is on a protected branch's push, merge or approvals allowlist. + Pushing to a protected branch is a gated action, and merges stay + Jason's. Reviewer-bot's approvals in Gitea are advisory and don't + count toward required approvals. The review verdict that gates a row + lives in the queue and the bus. 3. Log in as each bot, go to Settings, Applications, and generate one token named `mosaic-stack--` with these scopes: | Bot | Scopes | |---|---| - | pm-bot | `write:issue`, `read:repository`, `read:user` | - | cto-bot | `write:issue`, `write:repository`, `read:user` | - | coder-bot | `write:issue`, `write:repository`, `read:user` | - | reviewer-bot | `write:issue`, `write:repository`, `read:user` | + | mosaic-stack-pm-bot | `write:issue`, `read:repository`, `read:user` | + | mosaic-stack-cto-bot | `write:issue`, `write:repository`, `read:user` | + | mosaic-stack-coder-bot | `write:issue`, `write:repository`, `read:user` | + | mosaic-stack-reviewer-bot | `write:issue`, `write:repository`, `read:user` | Leave every other scope unset, and set none to "public only". - Reviewer-bot's `write:repository` is there because Gitea files pull - request reviews under the repository scope. Its Read collaborator - access is what stops it pushing. Row SR's review checks that before - anyone relies on it. -4. Copy each token into its file with an editor, never `echo`: - `$S/pm-gitea.token`, `$S/cto-gitea.token`, `$S/coder-gitea.token`, - `$S/reviewer-gitea.token`. One line, the token only. + Reviewer-bot's `write:repository` is there because Gitea checks pull + request reviews against the repository scope. Its Read collaborator + access stops pushes to branches and tags. It does not stop an AGit + push to `refs/for/`, which opens a pull request. That's + acceptable only because the broker holds the token and offers the + reviewer no push action. +4. Write each token to its file from a silent prompt, in the same + `umask 077` shell. Paste the token at the prompt and press Enter: + + ```sh + for r in pm cto coder reviewer; do + printf '%s token: ' "$r"; read -rs t; echo + printf %s "$t" > "$S/$r-gitea.token"; unset t + done + ``` + + `read` and `printf` are shell builtins, so the value never reaches + `ps` or the history. Don't use an editor, which can leave a swap or + backup copy behind. 5. Log out of each bot account. Record today's date as each Gitea token's `rotateBy` base. Section 5 has the rotation schedule. @@ -110,9 +132,13 @@ docker exec -it mosaic-vikunja /app/vikunja/vikunja user create -u jason -e you@ VK=http://127.0.0.1:3456 ``` -`user create` asks for the password when you leave out `-p`. If your -build doesn't prompt, stop and set it some other way. Don't put the -password on the command line. +`user create` asks for the password when you leave out `-p`. Keep `-it`, +because without a terminal the command exits instead of prompting. Never +put the password on the command line. + +Both mounts are required. The container runs as your uid, and +`/app/vikunja` isn't writable to it, so Vikunja needs the files mount +for its startup write check and the db mount for its database. ### Owner login for this guide @@ -135,8 +161,8 @@ print("owner header written") PY ``` -The probes logged in at `/api/v2/login`. The `token` field name is -assumed from v1, so the script stops if it's missing. Section 4 deletes +The `token` field is confirmed in the v2.7.0 source (`auth_login.go`). +The script still stops if it's missing. Section 4 deletes the header file. ## 3. Vikunja: the project, the board and the bots @@ -150,9 +176,9 @@ In the web UI, as the owner: `blocked`. Each title appears once. 3. In the view's settings, check that the done bucket is `done` and the default bucket is `todo`. Leave bucket configuration on manual. -4. Create the labels the business file template lists, and note each id. - The business file names labels by id until row S3 settles which - labels a bot can see. +4. Slice 1 needs no labels, since the requirement id lives in the task, + not in a label. If you create any, note each id. The business file + names labels by id until row S3 settles which labels a bot can see. ```sh P= @@ -213,8 +239,10 @@ mint() { # mint ROLE BOT_ID SCOPES_FILE local r=$1 id=$2 sc=$3 resp="$S/.mint-$1.json" jq -n --arg t "$BIZ-$r-$(date +%F)" --argjson o "$id" --arg e "$EXP" --slurpfile p "$sc" \ '{title:$t, owner_id:$o, expires_at:$e, permissions:$p[0]}' | - api -X POST "$VK/api/v2/tokens" --data-binary @- -o "$resp" && - jq -j .token "$resp" > "$S/$r-vikunja.token" && + curl -s --fail-with-body -H @"$S/vikunja-owner.hdr" -H 'Content-Type: application/json' \ + -X POST "$VK/api/v2/tokens" --data-binary @- -o "$resp" || + { jq -c '{code, message}' "$resp"; rm -f "$resp"; return 1; } + jq -je '.token | strings' "$resp" > "$S/$r-vikunja.token" && jq -c '{id, owner_id, expires_at, starts_tk: (.token | startswith("tk_"))}' "$resp" rm -f "$resp" } @@ -226,9 +254,11 @@ mint sync "$S/scopes-sync.json" ``` Check that each line shows `starts_tk: true` and the `expires_at` you -set. Vikunja accepts a past expiry without complaint, so read it. A 400 -with code 14002 means a scope name is wrong. Fix the scope file and mint -again. +set. Vikunja accepts a past expiry without complaint, so read it. On an +error, `mint` prints the response's `code` and `message`, which carry no +token. Code 14002 means a scope name is wrong. Fix the scope file and +mint again. If the response has no `token` field, `mint` writes nothing +usable and stops; delete the empty file before you retry. ## 4. Check and clean up @@ -261,14 +291,17 @@ role. The stack never writes this file. ## 5. Rotation -- **Vikunja**, before `expires`: log in as the owner (section 2), mint a - new token for the same bot with `mint`, update `expires` in the - business file, and restart the broker. Then revoke the old token: +- **Vikunja**, before `expires`: in a new shell, rerun section 0, the + owner login in section 2, and the `api` and `mint` definitions in + section 3. Mint a new token for the same bot, update `expires` in the + business file, restart the broker, and finish with section 4's + `rm -f`. Then revoke the old token: `api -X DELETE "$VK/api/v2/tokens/"`. The broker refuses to start with a token past its `expires`, and it treats any 401 as a refusal, never a retry. - **Gitea**, every 90 days or at once if a token may have leaked: log in - as the bot, generate the replacement, write it to the file, restart + as the bot, generate the replacement, write it to the file with the + `read -rs` loop from section 1, restart the broker, then delete the old token in the same settings page. Update `rotateBy`. Each rotation gets one line in `docs/SESSIONS.md`: date, who, which identities, and no values. diff --git a/docs/plans/2026-09-26_lead-decisions.md b/docs/plans/2026-09-26_lead-decisions.md index 34ea6ba9..9b17d495 100644 --- a/docs/plans/2026-09-26_lead-decisions.md +++ b/docs/plans/2026-09-26_lead-decisions.md @@ -952,3 +952,19 @@ which stay with him. Each item names who decided it and what happened. - The broker, not a trigger, links `action.*` and `review.*` events to a task, because the kind alone doesn't say when one is about a task. +58. **Row SR round 1 rulings (2026-10-04).** Source: Darkwing's review, + #1517 comment 26709, record `agents/darkwing/work/slice1-sr-review-r1-2026-10-04.md`. + Every change requested is taken into the guide. + - Bot names carry the business id on both services, because usernames + are global to each instance: Gitea `--bot`, Vikunja + `bot--`. PRD draft 0.4's REQ-CRED-1 wording changes + to match. That is a naming correction inside a draft, not a new + requirement. + - Reviewer-bot's Gitea approvals are advisory. It joins no approvals + allowlist, merges stay Jason's, and the verdict that gates a row + lives in the queue and the bus. + - The guide says plainly that reviewer-bot's token can open a pull + request through an AGit push. That's acceptable only because the + broker holds the token and gives the reviewer no push action. + - Slice 1 needs no Vikunja labels. The business file example comes + from row S1, and the template file waits for it. diff --git a/docs/prd/mosaic-stack.md b/docs/prd/mosaic-stack.md index 2334d2d0..d184ae2b 100644 --- a/docs/prd/mosaic-stack.md +++ b/docs/prd/mosaic-stack.md @@ -122,10 +122,11 @@ parent requirement is refused. (Gitea, Vikunja). The broker holds them, and they never enter an agent's environment or files. In v1 Jason creates them by hand from a runbook Sage writes (round 3, 1A): - - in Gitea, a new bot user per role: pm-bot, cto-bot, coder-bot and - reviewer-bot (round 3, 2A); - - in Vikunja, a `bot-` user per role, plus one read-only - `bot--sync` that does all polling (lead decision 52). + - in Gitea, a new bot user per role, named `--bot`, + for example `mosaic-stack-pm-bot` (round 3, 2A; lead decision 58); + - in Vikunja, a `bot--` user per role, plus one + read-only `bot--sync` that does all polling (lead + decisions 52 and 58). - **REQ-CRED-2.** A session that finds only founder credentials stops. ### Decisions