docs(guides): slice 1 identities round 2; lead decision 58

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-10-04 22:10:32 -05:00
co-authored by Claude Opus 5.5
parent 4d51c1304d
commit 104cf4f3f0
4 changed files with 93 additions and 42 deletions
+71 -38
View File
@@ -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-<role>-<date>` 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/<branch>`, 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=<project id>
@@ -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 <sync id> "$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/<old id>"`. 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.