docs(slice1): row 35 SR round 1 review record (darkwing)

Verdict changes, #1517 comment 26709. Gitea 1.27.1 and Vikunja 2.7.0
source checks for Sage's three questions; two mint defects.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
2026-10-04 22:09:21 -05:00
co-authored by Claude Opus 5.5
parent fe537f68a1
commit 4d51c1304d
@@ -0,0 +1,181 @@
# Queue row 35 (SR), round 1 review (#1517)
Darkwing, 2026-10-04. Request: #1517 comment 26707. Candidate: commit
c9c1699a, `docs/guides/slice-1-identities.md`. Brief:
`docs/plans/2026-10-04_slice-1.md`, row SR.
Verdict: changes. The scope tables are right, and so are Sage's three
assumptions, with one correction to the push claim. Two command defects
need fixing before Jason runs the guide: the `mint` error guidance can't
be seen, and a missing token field writes the word `null` into a token
file that then passes the section 4 check. The rest are small text
changes.
I read the source and didn't run anything live. Gitea's version endpoint
on our instance reports 1.27.1, so I read tag v1.27.1. Vikunja is tag
v2.7.0, the version the guide pins. Line numbers below are from those
tags.
## Sage's three questions
### 1. Reviewer-bot: `write:repository` plus Read access
The scope half is right. In `routers/api/v1/api.go` the `/repos` group
that holds `POST /pulls`, `POST /pulls/{index}/reviews` and
`POST .../reviews/{id}` closes at line 1548 with
`tokenRequiresScopes(AccessTokenScopeCategoryRepository)`. Issue
comments are in the group closing at line 1684 with the issue category.
`tokenRequiresScopes` (line 323) asks for the write level on POST, PUT,
PATCH and DELETE. So a review needs `write:repository`, and a comment
needs `write:issue`.
A Read collaborator can create and submit all three review types. The
`/pulls` group applies `mustAllowPulls`, `reqRepoReader(unit.TypeCode)`
and `reqToken()`, and no writer check. The handler refuses only an
approval or rejection of your own PR (`pull_review.go:688-699`).
The push half needs a correction. A branch or tag push is refused, but
not by the HTTP permission check. For receive-pack,
`routers/web/repo/githttp.go` drops the required access to Read when git
supports proc-receive (lines 184-187). The refusal comes later, in the
pre-receive hook: `assertCanWriteRef` in
`routers/private/hook_pre_receive.go` needs code write and answers 403.
The exception is `refs/for/<branch>`, the AGit flow. It needs only read
access to pull requests (`CanCreatePullRequest`, line 88). With this
token, reviewer-bot can push to `refs/for/next` and open a pull request
with any content it likes.
That doesn't break slice 1, because the broker holds the token and has
no action that runs a git push for the reviewer role. But "Read access
is what stops it pushing" overstates it. Suggested text for lines 69-72:
> 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.
One more thing worth a line. Gitea counts a review toward required
approvals only when it's official, and a Read user's review isn't
official by default (`IsOfficialReviewer`, `models/issues/review.go:276`;
`IsUserOfficialReviewer`, `models/git/protected_branch.go:234`). The
exception is a protected branch with the approvals whitelist turned on
and reviewer-bot on it. If a protection rule on `next` requires
approvals and reviewer-bot's verdict should count, the guide has to say
to whitelist it. If merges stay Jason's and approvals are advisory, say
that instead.
### 2. Vikunja `/api/v2/login` returns `token`
Confirmed. `pkg/routes/api/v2/auth_login.go`, `authLogin` (lines
87-106), returns the body type `authTokenBody` (line 43), whose JWT
field is `token`. The body also carries a `$schema` link, because
`huma.go:74` uses `huma.DefaultConfig`. The script ignores extra keys,
so that's harmless. The route exists only when local or LDAP login is
enabled (line 63), which section 2 already requires. Keep the stop for a
missing field, but replace "assumed from v1" at line 138 with "confirmed
in the v2.7.0 source (`auth_login.go`)".
### 3. Bot names `bot-<business>-<role>`
Keep them. Vikunja usernames are global to the instance, and the guide's
reason (two businesses on one instance) holds. Amend the PRD's REQ-CRED-1
wording from `bot-<role>` to match. The `bot-` prefix itself is
Vikunja's rule for bot accounts.
The Gitea bot names have the same problem and the guide doesn't address
it. `pm-bot`, `cto-bot`, `coder-bot` and `reviewer-bot` are global
Gitea users, so a second business on the same Gitea can't reuse them.
Either name them `<business>-pm-bot` and so on, or state that one set of
Gitea bots serves every business on the instance. I'd take the first;
it keeps a business's revocation from touching another business.
## Defects in the commands
### D1. `mint` hides the error it tells you to read (lines 164, 216, 228-230)
`api` uses `curl -sf`. With `-f`, an HTTP error status makes curl exit
22 and write nothing, so the 400 body with code 14002 never appears.
And `rm -f "$resp"` runs after the failed chain, so even
`--fail-with-body` would lose it. Suggested `mint` body:
```sh
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]}' |
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"
}
```
An error body carries no token, so printing `code` and `message` is
safe. `--fail-with-body` needs curl 7.76 or later.
### D2. A missing `token` field writes `null` (line 217)
`jq -j .token` on a body without `token` prints `null` and exits 0. I
checked: `echo '{}' | jq -j .token` gives `null`, exit 0. The file is
then 0600 with size 4, and section 4's "600 and a nonzero size" passes.
`jq -je '.token | strings'` prints nothing and exits 4, so the chain
stops. That's the change in D1.
## Smaller changes
1. Gitea tokens, step 4 (line 73). "Copy with an editor" can leave a
swap or backup file next to the token, in `$S` or wherever the editor
keeps them. Suggest instead, in the same `umask 077` shell:
`read -rs t && printf %s "$t" > "$S/pm-gitea.token"; unset t`.
`read` and `printf` are builtins, so the value doesn't reach `ps` or
the history. Do the same for the Gitea rotation in section 5.
2. Trailing newlines. That step also says "one line". An editor adds a
newline, `printf %s` doesn't. Row S3's broker will trim one trailing
newline either way; I'll put that in S3.
3. `EXP` is a timestamp (line 32), and the business file's `expires` is
`YYYY-MM-DD` (row S1 validates that). Say to write the date part of
`EXP`. The broker treats 00:00Z on that date as the expiry, which is
the same instant `EXP` names.
4. The labels template. Section 3 step 4 refers to "the labels the
business file template lists". Row SR's brief owns
`templates/business/mosaic-stack.example.json`, and it isn't in the
candidate. Either add it or list the labels in the guide. S1 will
send the business file example the template should follow.
5. Path B (lines 113-115). The binary is `/app/vikunja/vikunja`, as the
guide says (Dockerfile lines 49-50). `user create` without `-p`
prompts through `term.ReadPassword` (`pkg/cmd/user.go:151`). Without
a TTY it exits through `log.Fatalf`, so the guide's `-it` matters.
Replace "If your build doesn't prompt, stop" with "Keep `-it`; without
a terminal the command exits instead of prompting."
6. Path B, `--user "$(id -u):$(id -g)"`. Right. The image runs as uid
1000, but nothing at `/db` or `/app/vikunja/files` has to belong to
1000. Both mounts are required, because `/app/vikunja` itself isn't
writable to another uid and Vikunja writes a test file into the files
directory at startup. Worth one sentence so nobody drops a mount.
7. Rotation, Vikunja. The owner can delete a bot's token: the v2.7.0
route's description says so, and `APIToken.CanDelete`
(`pkg/models/api_tokens_permissions.go:25`) checks for it. The
rotation step logs in again, which recreates
`$S/vikunja-owner.hdr`, and `api` and `mint` were defined in another
shell. Say to rerun section 0, the owner login and the two function
definitions, and to finish with section 4's `rm -f`.
## What I checked and found right
- The scope JSON in section 3 matches addendum B section 2 byte for
byte: sync, pm and the shared worker file.
- Shares: role bots write (1), sync read (0).
- The secret handling: `umask 077` up front, the service secret through
`printf` and command substitution, the owner password through
`getpass`, the header passed as `-H @file`, the header file deleted in
section 4, and `stat` rather than `cat`. Nine token files is right:
four Gitea, five Vikunja.
- The Gitea scope table. pm-bot needs only `read:repository`, since its
labels, assignees and closes go through issue routes. The three
workers need `write:repository` for reviews and pull requests.
- The revocation paths in section 5 end in a 401 or 403, which the
broker refuses rather than retries.