(motir-core, manual) Mint `MOTIR_GUARD_TOKEN` — a Motir PAT granted `project:browse` and nothing else — and store it as a repository Actions secret, so the acceptance-lane guard has a credential to bind with
Surfaced 2026-09-02 by a motir run MOTIR-4093 halted at run.md guard #4. MOTIR-4093 cannot ship until this exists, and nothing a coding agent can do produces it: it is a token-mint page and a GitHub settings page.
The gap, verified against the PROVIDER
MOTIR-4093's AC 1 wires a credential into ci.yml's test job. Its 2026-09-01 amendment retired MOTIR_UPLOAD_TOKEN for not being configured; its 2026-09-02 11:58 comment re-pointed it at MOTIR_GUARD_TOKEN. That one is not configured either, and neither is vars.MOTIR_BASE_URL:
| checked | command | answer |
|---|---|---|
| repository secrets | gh secret list --repo moooon-B-V/motir-core | 12 secrets — BETTER_AUTH_SECRET, FLY_API_TOKEN, NEXT_PUBLIC_SENTRY_DSN, NEXT_SERVER_ACTIONS_ENCRYPTION_KEY, NPM_TOKEN, RELEASE_APP_ID, RELEASE_APP_PRIVATE_KEY, SEED_DATABASE_URL, SENTRY_AUTH_TOKEN, SENTRY_ORG, SENTRY_PROJECT, VERCEL_TOKEN. No MOTIR_GUARD_TOKEN, no MOTIR_UPLOAD_TOKEN |
| organization secrets visible to the repo | gh api repos/moooon-B-V/motir-core/actions/organization-secrets | total_count: 0 |
| environment secrets | gh api repos/.../environments/{Preview,Production}/secrets | total_count: 0 each |
| repository variables | gh api repos/moooon-B-V/motir-core/actions/variables | one — FLY_EXPECTED_MACHINE_COUNT. No MOTIR_BASE_URL |
After MOTIR-4096 retired the publisher, the string MOTIR_BASE_URL appears nowhere in .github/** — so the origin half has no existing expression to copy either. That half is harmless (the guard already falls back to a literal, and MOTIR-4093 can write https://app.motir.co fresh); the TOKEN half is what blocks.
Why a human, and why not the keyless arm
MOTIR-4144 shipped BOTH arms of the read. The keyless one — MOTIR_GUARD_AUTH=github-oidc — needs no secret, and was considered and rejected here: authenticateGithubOidc (lib/github/oidcAuth.ts) resolves a verified repository claim to the workspace and returns the workspace OWNER's identity, and authenticateCiPublisher then returns it WITHOUT checking requiredPermission at all. Granting id-token: write to ci.yml's test job would therefore let any file in the twelve-leg Vitest suite, on any non-fork pull request, mint a token that authenticates to production Motir as the workspace owner. A PAT scoped to one read permission is both smaller and the arm MOTIR-4093's own handover comment names.
Acceptance criteria
- A Motir personal access token exists, minted at
/settings/account/tokensonhttps://app.motir.coby a member whose role can browse themotirproject, with a grant ofproject:browseand nothing else.project:browseis the keyACCEPTANCE_STATUS_READ_PERMISSIONnames (lib/tokens/grant.ts:81) and is grantable (lib/permissions/catalog.ts:166); a grant narrows and never widens the owner's role, so the token can rewrite nothing. - It is stored as the repository Actions secret
MOTIR_GUARD_TOKENonmoooon-B-V/motir-core(Settings → Secrets and variables → Actions). A repository secret, not an environment one:ci.yml'stestjob declares noenvironment:, so an environment secret would not reach it. - PROVE IT READS, from outside — not "the secret page shows the name". Run, with the token's own value:
and record the status code on this card. 200 is the pass. 405 means MOTIR-4144's route is not deployed on that origin yet — it was still 405 at 2026-09-02T14:33Z, with the deploy ofcurl -s -o /dev/null -w '%{http_code}\n' -H "Authorization: Bearer $MOTIR_GUARD_TOKEN" \ https://app.motir.co/api/work-items/MOTIR-1627/acceptance-evidencecd236496din flight — so re-run it after the deploy; 401 means the token is wrong or expired; 403 means the grant is missingproject:browse. - Record on this card the token's expiry (or that it has none) and who holds it. A credential that silently expires turns the guard MOTIR-4093 ships into a red
testjob on every pull request, and the run that meets that red will have no way to tell an expired token from a mis-wired one.
Context refs
- MOTIR-4093 — the card this unblocks; its AC 1 consumes exactly this secret.
- MOTIR-4144 — the read path (
GET /api/work-items/<key>/acceptance-evidence) the token is proved against. lib/tokens/grant.ts—ACCEPTANCE_STATUS_READ_PERMISSION(project:browse) and why the read does not ask for the publish permission.lib/github/oidcAuth.ts— the keyless arm this card deliberately does not take..github/workflows/ci.yml— thetestjob, where MOTIR-4093 lands theenv:.