Compare commits
6
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
21c123ae84 | ||
|
|
13e4a23ee6 | ||
|
|
81b03c9404 | ||
|
|
f70f83635f | ||
|
|
5bcfef0ada | ||
|
|
eea8c5b784 |
@@ -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.
|
||||
Reference in new issue
Block a user