Compare commits

...
Author SHA1 Message Date
jason.woltje 21c123ae84 row 287 (#1792): BUILD.md records sage-2958: gitea entries need scopes (role.mjs:100-111), equal to SEAT_SCOPES; seven chief-of-staff.json readers; doc only 2026-10-22 14:10:41 -05:00
jason.woltje 13e4a23ee6 row 287 (#1792): BUILD.md records sage-2950: 285 runs the bus suite whole and has the no-scopes check, dewey-0865 withdrawn, mos-1333 drops the 285 publish fence so sage-2916 stands; doc only 2026-10-22 14:05:45 -05:00
jason.woltje 81b03c9404 row 287 (#1792): BUILD.md answers dewey-0865: the definitions' gitea credential is row 285's (C2-08b brief :1823, mos-1309 item 5), 287 builds after it; scopes and the five bus readers of chief-of-staff.json noted for 285; doc only 2026-10-22 13:59:40 -05:00
jason.woltje f70f83635f row 287 (#1792): BUILD.md folds Dewey's early look dewey-0862: Q2 repositories as remote URLs and the notice, the read line, block lines B-a to B-c, Q1 test L10, Q3 fork-of-library in L4, Q4 count line, exact permission keys, chief by definition chief-of-staff and a fired chief in B-2; doc only 2026-10-22 13:53:47 -05:00
jason.woltje 5bcfef0ada row 287 (#1792): BUILD.md records sage-2916: 287 owns business.mjs :283-291 and its business.test.mjs cases (files-owned extension), the refusal rewritten to creative or chief with a gitea credential, B-1 to B-4 and M5, the old-message grep before in-review, PRD 1.15 REQ-LIB-6 check; Q1-Q4 accepted as assumptions; doc only 2026-10-22 13:43:06 -05:00
jason.woltje eea8c5b784 row 287 (#1792): BUILD.md design draft for C2-21 off 94c33a43c: B1 business.mjs:286 refuses a library project in publish, Q1-Q4 (hash, event, mixed raises, Docker token search), the change, tests L1-L9 and mutants; doc only, not run 2026-10-22 13:41:55 -05:00
+192
View File
@@ -0,0 +1,192 @@
# Row 287 design notes: C2-21, the grant executor for a library project (#1792)
Builder Megavolt, reviewer Dewey, gate owner Sage (sage-2910, Q226, mos-1310). Branch megavolt/row287, a
doc-only draft off 94c33a43c, written during Gizmoduck's slot with local git only. The build starts on the
candidates of row 285 (C2-08b, Scrooge) and row 226 (C2-13b, Gosalyn) and rebases onto their landings
(Q177). Nothing here has been run.
The brief is "C2-21 the grant executor for a library project" in docs/plans/2026-10-20_case2-rows.md at
94c33a43c. Its gate: Dewey approves the manifest; the bus suite, the Gitea Docker test and the install test
are green on Sage's gate, along with every `scripts/test-*.sh`; a search of the test run's output for the
fixture admin and seat tokens finds nothing.
## Sources (line cites at 94c33a43c)
- `packages/bus/src/gitea-grant.mjs`: the admin api (`adminApi`, no collaborator call yet), the seat api
(`forkGet`, `fork`, `repos`), `identitySha256`, `REPO_PAGE`/`REPO_PAGES`.
- `packages/bus/src/broker.mjs`: `#giteaRole` :2132, `#giteaTargets` :2149, `#giteaGrantBinding` and its
block :2167-2196, `#giteaRecheck` :2200, `#giteaSteps` step 4 :2425-2441 and step 5 :2442-2473, the
`grant.applied` body and notice :2480-2488, the host refusals `host-creative-role` :1106 and :1254.
- `packages/business/src/business.mjs` :266-288: a `kind: "library"` project goes through `checkMapping`, and
:286 refuses any role whose `publish` names a library project.
- `packages/library/src/mapping.mjs`: a library entry carries `remote`, `branch` and an optional `user`.
- `packages/bus/tests/gitea-grant.test.mjs` :297-334 pins the `grant.applied` body (`forks: [...]`) and the
block; `fake-gitea-api.mjs` keeps collaborators as a list of logins with no permission.
- `packages/bus/tests/gitea-grant-docker.test.mjs`: the scratch gitea/gitea:1.27.1 container; it uses
`os.tmpdir()`.
## 1. Rulings, blockers and questions
- B1, the publish rule. `business.mjs:286` refuses any `publish` entry that names a library project
("library repositories cannot be published by agents"). The brief's chief needs `publish: ["books"]`, and a
creative seat needs the library project in `publish` for `#giteaRole` and `#giteaTargets` to pass. No test
under packages/*/tests or scripts pins the :286 message (positive control: "publish names" hits
agent-identity.test.mjs).
- Ruling sage-2916 (18:42Z 10-22), files-owned extension: row 287 owns `packages/business/src/business.mjs`
:283-291 and the `packages/business/tests/business.test.mjs` cases for it. Row 285 leaves business.mjs
alone. Conditions:
- The refusal is rewritten, not deleted. A library project in `publish` loads only for a creative
instance (`CREATIVE_DEFINITIONS`) or the chief of staff, each with a gitea credential. Any other
instance still refuses, with a new message that names the rule. Tests cover both sides. The grant path
serves an allowed one with collaborator reach, never a fork. (To confirm on the base: how business.mjs
names the chief, by definition `chief-of-staff` or by `launch.by`.)
- Before in-review, grep 'packages/*/tests/*' and `scripts/test-*.sh` again for the old message text,
with a positive control.
- PRD 1.15 REQ-LIB-6 is the authority for this. If Mos changes 1.15 (sage-2914, `library.write`
routing), check this text against what lands.
- Q1 to Q4 below: Sage accepted them as stated assumptions (sage-2916). Dewey checks them in the round.
- Q1, the hash. `identitySha256` covers identity, publish and remotes, not a project's kind or the role's
definition name, and the level comes from the definition name. Proposal: add a `library` key
(`{project: level}`) to the hash input only when a publish project is a library, so every non-library
grant's hash stays byte-identical and no open grant turns `decision-mismatch` on upgrade.
- Q2, the event. `grant.applied` and its notice carry `forks`, and the test at :322 pins that body.
Proposal: `forks` stays (empty when every target is a library), and a `repositories:
[{project, repository, level}]` key appears only when a library target exists. The notice names them the
same way.
- Q3, mixed raises. A creative raise must name only library projects (mos-1320 item 2). The chief may
name a library project next to forked ones in one raise; step 4 forks the others and adds the collaborator
on the library one. Proposal: allow that, and test it. Under sage-2916, any other non-creative instance
can't hold a library project in `publish` at all, so the business file refuses it before any raise.
- Q4, the token search for the Docker test. The fake API's tokens are fixtures and can be grepped. The
Docker test's tokens are minted by the container at run time, so the gate grep can't know them in advance.
Proposal: the Docker test captures its own diagnostics and asserts that no token it minted appears in
them, and the gate greps the run's output for every fake-API fixture token.
### Early look, Dewey (dewey-0862, 18:53Z 10-22, on 5bcfef0ad, read only), folded
These replace the matching proposals in sections 2 and 3 where they differ.
- Q2 accepted. `forks` stays and is `[]` when every target is a library. `repositories:
[{project, repository, level}]` appears only when a library target exists. `repository` is the remote
URL, the same form as `forks`; the `full_name` that step 5 matched is asserted in a test and doesn't go
in the body. The only src reader of `forks` is the notice (:2483). The notice drops its forks clause when
the list is empty and adds "repositories: <url> (<level>)". A non-library notice keeps today's text.
- The read line: "The token can read every path of this repository. It can't push to it, because the user
is a read collaborator." The seat scope is still `write:repository` (:2189).
- B-a: `\nForks:` (:2187) prints only when a fork target exists.
- B-b: "Step 5 checks that this is only its forks." (:2193) is unchanged with no library target. With one, it
reads "Step 5 checks that this is only its forks and the repositories above at their level."
- B-c: `others` (:2181, :2194) splits. An untargeted library project in publish gets its own line, "It may
also reach these repositories as a collaborator: <url> (<level>)", because a revoke leaves the
collaborator entry and step 5 accepts it publish-wide. Forked others keep today's line. L8 covers B-a to
B-c.
- Q1 holds. Test: a project flipped to `kind: "library"` between the raise and the recheck gives
`decision-mismatch` (L10).
- Q3 holds. Library projects stay out of step 5's fork upstream set (:2446), so a fork of her repository
refuses `gitea-reach-wider`; L4 covers it.
- Q4 holds if the assertion covers what the test prints, TAP stdout and stderr, not only an in-test
buffer. The Docker test prints one line, "minted N, hits 0", for the gate to grep.
- Step 5 compares exactly the keys `admin`, `push` and `pull`. BUILD.md records F-B's actual `permissions`
object, so an extra Gitea key can't pass or fail by accident.
- The chief is named by definition `chief-of-staff`, not `launch.by`, matching broker `STAFF_TO` (:240) and
row 286 (dewey-0860). A fired chief keeps its roles entry (instance-settings.mjs:355), so B-2 also loads a
fired chief's entry.
### The definitions' gitea credential (dewey-0865)
At 94c33a43c, chief-of-staff.json and all six creative definitions have `credentials: []`, so `#giteaRole`
(:2137-2143) would refuse them all `gitea-not-declared`. Row 287 doesn't add the credential. Row 285 (C2-08b)
owns those role files, and its brief (case2-rows.md :1823-1824) gives each creative role and the chief
`[{"service": "gitea"}]`, which Mos authorized in mos-1309 item 5. That is why 287 builds after 285 lands. Two
notes for 285, not for this row:
- The grant path reads only `service === 'gitea'`. The seat token's scopes come from `SEAT_SCOPES` in code,
not from the definition. coder.json adds `scopes`, and 285's validator decides whether the bare form passes.
- Five bus tests read roles/chief-of-staff.json directly (chief-of-staff, fire-revokes-grants,
instances-verb, names, routes-verb), and 285's gate names the business suite, not the bus suite.
- Settled (sage-2950, 19:06Z 10-22): 285's gate runs the bus suite whole (sage-2946), and Scrooge has the
role.mjs no-scopes check. Dewey withdrew dewey-0865 (dewey-0867). Mos withdrew the mos-1332 item 3 publish
fence (mos-1333), so business.mjs stays out of 285's files and sage-2916 stands as written.
- Correction (sage-2958, Scrooge): role.mjs refuses `{"service": "gitea"}` with no scopes
(checkGiteaScopes :100-111, util.mjs:42); only a host entry may omit them (:134). So 285's entries name
their scopes, equal to SEAT_SCOPES (sage-2915). The chief-of-staff.json readers are seven, not five: add
host-run and reload. 287's tests that build definitions give gitea entries `scopes: ["read:user",
"write:repository"]`.
## 2. The change (proposed)
- Order in the executor: step 2 (user) and step 3 (mint) as today. Step 4, for each target: a library
project gets `PUT /api/v1/repos/{owner}/{repo}/collaborators/{user}` with `{permission}` through the admin
api, op `collaborator-add`, the `token` header, logged like every call. Expect 204, or anything else fails
with `gitea-failed`. A non-library project forks as today.
- The level: `write` when `CREATIVE_DEFINITIONS.includes(role.definition)`, `read` otherwise. The raise has
no field for it; `giteaGrantCheck` still refuses any key other than method, agent, rotateBy and target.
- Step 5: the upstream set stays for forks. A repository is accepted when it is the user's fork of a
non-library publish upstream (today's rule), or when its `full_name` is a library publish project's
`owner/repo` and its `permissions` are exactly `{admin: false, push: level === 'write', pull: true}`. Any
other repository, a wider permission or `admin: true` refuses `gitea-reach-wider` with the repository
named.
- `#giteaRole`: the `CREATIVE_DEFINITIONS` refusal becomes "unless every project the raise names is a
library project". `#giteaRole` gets the raise's project list from its two callers (the binding at
:2168 and the recheck at :2201), so it can decide. The comment cites mos-1320 item 2 and keeps
mos-1163 for every other project and every host grant. The host refusals at :1106 and :1254 and their
tests stay as they are.
- `#giteaTargets` returns `{project, upstream, library: true, repository, level}` for a library project and
today's `{project, upstream, fork}` otherwise. `forkProblem` still runs for both: a library remote on
another host refuses `remote-host-mismatch`, and one the seat user owns refuses `remote-not-fork`.
- The block: a library target prints `Repository: <remote>, level <write|read>.` in place of its fork line,
plus the brief's line "The token can write every path of this repository. Only the bus's path check
limits it to supporting folders." for a `write` level. For a `read` level the line says the token can read
every path. (The brief gives only the write line; Dewey can rule the read wording.)
- Revoke deletes the token as today; the collaborator entry stays, as forks do. A fire sets
`prohibit_login` as today. Neither path changes.
- `scripts/case2-install.sh` (row 226's file): the chief of staff's instance gets `identity` and
`publish: ["books"]`. If `#grantableAgent` refuses the chief as its own target, the row stops and goes to
Sage. Read of :2037-2042 says it passes when the chief is `launch.by`; a test proves it.
## 3. Tests (proposed)
Docker (`gitea-grant-docker.test.mjs`), each fact first, before any code is trusted:
- F-A: the admin token with `ADMIN_SCOPES` adds a collaborator to a repository the admin account doesn't
own. F-B: `/user/repos` with the seat token lists that repository with the granted `permissions`. Either
failing stops the row; the output goes to Mos.
- F-C (recorded, not gating): a protected file pattern on her branch refuses a seat user's push to a story
path while her own push passes. F-D (recorded): branch protection refuses a seat user's force-push while a
normal push passes. Each logs its result as a diagnostic and doesn't fail the file; BUILD.md and a message
to Mos carry them.
Fake API (`gitea-grant.test.mjs`, `fake-gitea-api.mjs`): collaborators carry a permission, `repoJson`
returns `permissions`, and the PUT route requires a site admin token with `write:repository`.
- L1 a creative definition gets `write`; L2 the chief gets `read`, including a mixed raise of the library
project and a forked one (Q3).
- L4 a repository with `push: true` for a read grant, or `admin: true`, or a fork of her library repository,
refuses `gitea-reach-wider` naming it.
- L5 revoke deletes the token and leaves the collaborator.
- L6 a non-library project still forks, and its `grant.applied` body is byte-identical to today's.
- L7 a creative raise naming a non-library project, or a library and a non-library one, refuses
`gitea-not-declared`.
- L8 the block holds the repository, the level and the brief's line, plus B-a to B-c (dewey-0862); the notice
has no "forks: ." on a library-only grant.
- L10 a project flipped to `kind: "library"` between the raise and the recheck gives `decision-mismatch`.
- L9 the chief raises its own grant through `credential.mint`.
- Install test: the business file gives the chief `identity` and `publish`.
Business (`business.test.mjs`, sage-2916): B-1 a creative instance with identity, a gitea credential and a
library project in `publish` loads; B-2 so does the chief; B-3 any other instance with a library project in
`publish` refuses with the new message; B-4 a creative instance or the chief without a gitea credential
still refuses under the existing `publish needs identity and a gitea definition` rule. Mutant: M5 drop the
creative-or-chief condition (any instance loads), killed by B-3.
Mutants: M1 drop the library-only condition from `#giteaRole` (the brief's), killed by L7. Others to list
once the code exists: the level forced to `write` (L2), step 5's exact-permission check loosened to
`push || pull` (L4), the fork path taken for a library project (L1, a fork call counted).
## 4. Gate plan (draft)
On Sage's CLEAR, with TMPDIR from `mktemp -d /mnt/storage/scratch/tmp/gXXXXXX`, stopping at the first red:
the touched bus test files whole, then the mutants, the bus suite, the Docker test (checking for `^skip`
first, since a missing image skips it), the install test, every `scripts/test-*.sh` under row 281's limits,
then the token grep over the saved output. Timeouts and limits go in this file before the run.
## Runs
Filled in after the runs.