@attalabs/vinaya 0.25.0 → 0.27.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -4
- package/aeg-root/contracts/architect-planner.md +5 -5
- package/aeg-root/contracts/developer-reviewer.md +7 -7
- package/aeg-root/contracts/planner-developer.md +142 -0
- package/aeg-root/contracts/reviewer-archivist.md +2 -2
- package/aeg-root/contracts/tranche-archivist-planner.md +12 -11
- package/aeg-root/enforcement.md +17 -7
- package/aeg-root/milestone-model.md +2 -0
- package/aeg-root/process.md +53 -75
- package/aeg-root/roles/archivist.md +3 -3
- package/aeg-root/roles/developer.md +10 -10
- package/aeg-root/roles/planner.md +90 -23
- package/aeg-root/roles/principal.md +15 -13
- package/aeg-root/roles/reviewer.md +16 -12
- package/aeg-root/roles/security.md +13 -9
- package/aeg-root/roles/tranche-archivist.md +2 -2
- package/aeg-root/skills/aeg/SKILL.md +5 -5
- package/aeg-root/skills/aeg-roles/SKILL.md +7 -7
- package/aeg-root/state-machine.md +35 -34
- package/aeg-root/task-model.md +3 -3
- package/aeg-root/templates/brief-template.md +2 -2
- package/aeg-root/templates/issue-rationale-template.md +3 -3
- package/aeg-root/tranche-model.md +21 -21
- package/dist/checks/bin/check-body-bare-digits.js +233 -30
- package/dist/checks/bin/check-branch-topology.js +253 -32
- package/dist/checks/bin/check-brief-shape.js +291 -873
- package/dist/checks/bin/check-changeset-coverage.js +1012 -37
- package/dist/checks/bin/check-closes-n.js +253 -32
- package/dist/checks/bin/check-coherence.js +253 -32
- package/dist/checks/bin/check-dead-branch-push.js +215 -30
- package/dist/checks/bin/check-dispatch-readiness.js +256 -35
- package/dist/checks/bin/check-doc-coverage-push.js +1012 -37
- package/dist/checks/bin/check-doc-coverage.js +1014 -39
- package/dist/checks/bin/check-doctrine-no-procedures.js +215 -30
- package/dist/checks/bin/check-doctrine-portability.js +1012 -37
- package/dist/checks/bin/check-evidence-fresh.js +755 -71
- package/dist/checks/bin/check-exec-bits.js +1012 -37
- package/dist/checks/bin/check-first-push-dispatch.js +253 -32
- package/dist/checks/bin/check-issue-assignment.js +253 -32
- package/dist/checks/bin/check-main-branch-refusal.js +216 -31
- package/dist/checks/bin/check-no-disk-state.js +215 -30
- package/dist/checks/bin/check-pr-premise-reassert.js +5489 -0
- package/dist/checks/bin/check-pr-report-density.js +215 -30
- package/dist/checks/bin/check-quoted-command.js +1010 -35
- package/dist/checks/bin/check-reader-resolvable-prose.js +1010 -35
- package/dist/checks/bin/check-registry-gates.js +246 -32
- package/dist/checks/bin/check-retired-vocabulary.js +1010 -35
- package/dist/checks/bin/check-review-gate.js +274 -85
- package/dist/checks/bin/check-single-plan-pr.js +215 -30
- package/dist/checks/bin/check-surface-scope.js +5846 -0
- package/dist/checks/bin/check-test-plan.js +215 -30
- package/dist/checks/bin/check-token-collection-wired.js +215 -30
- package/dist/checks/bin/check-token-report.js +227 -37
- package/dist/checks/bin/check-workspace-escape.js +1010 -35
- package/dist/index.js +2407 -607
- package/package.json +1 -1
- package/aeg-root/contracts/brief-developer.md +0 -141
- package/aeg-root/contracts/planner-brief.md +0 -143
- package/aeg-root/roles/brief-author.md +0 -116
- package/aeg-root/skills/brief-authoring/SKILL.md +0 -509
package/aeg-root/enforcement.md
CHANGED
|
@@ -74,6 +74,11 @@ The same check implementations run at ring 0 and ring 1 — one codebase, two en
|
|
|
74
74
|
|
|
75
75
|
**The fixed-position rule.** A gate reads its signal from a fixed position — a named anchor, an HTML-comment marker, a line-anchored field, a bounded read window — never by scanning free text for a phrase that means the right thing. Free text is caller-controlled: whoever writes the artifact can put a signal-shaped sentence anywhere in it, including inside a quotation of somebody else's, and a scanning gate cannot tell a cast verdict from a mention of one. Fixing the position is what closes that: a caller-supplied field rendered outside the window the gate reads can no longer reach a position the gate treats as structural, however the writer formats it.
|
|
76
76
|
|
|
77
|
+
**The two rules a gate author meets before adding a check:**
|
|
78
|
+
|
|
79
|
+
1. **Decide from parsed structure, never from prose.** A gate that must decide whether a task touches a domain, resolves a path, or crosses a boundary decides from a field a writer fills in a fixed grammar — a glob list, a numbered citation, a table row — never from scanning prose for a phrase that would mean the right thing if read charitably. Prose can name a thing to include it or to exclude it, and a scanning gate cannot tell the two apart; a heuristic built to guess which one a sentence meant is wrong in both directions, and a blocking gate that is wrong in either direction is worse than one that runs less often. Where the parsed structure does not yet exist for an artifact (an Issue below the cutover that first mandates a `## Surface`), the gate degrades to the old prose scan for that artifact only — it neither invents structure nobody asked for, nor extends the prose scan's blast radius to cover stock that predates it. `issue-validation.ts`'s `checkBlastRadiusScope` is the worked example: cutover-gated, it decides from `## Surface`'s `in:` glob list once one is mandatory, never again from `Boundary`/`Project(s) + blast radius` prose, whatever that prose says — naming a shared package to explicitly *exclude* it no longer trips a gate that used to read prose span-blind to intent.
|
|
80
|
+
2. **One fact, one implementation, resolved once and passed down.** A fact two gates must agree on — whether a glob resolves to a real tracked file, whether a domain falls under a path — is computed by one function and passed to every caller that needs the answer; it is never re-derived by a second hand-written implementation that merely happens to agree with the first today. Two implementations of the same predicate are a race the moment either one changes: the day they diverge is invisible until an artifact one side accepts and the other refuses reaches the seam between them, and by then neither implementation is provably the bug. Inject the shared function into whichever side cannot compute it itself — a pure, `fs`-free module (`issue-validation.ts`) takes the answer as an injected parameter (a `(glob: string) => boolean`, a resolved list); the impure caller that already computes it (`forge-write.ts`, injecting `brief-assembly.ts`'s own `expandGlob`) is the one and only place that does, so the authoring gate and the brief renderer read the identical resolution and can never disagree about whether a Surface resolves.
|
|
81
|
+
|
|
77
82
|
**The own-PR fixture rule.** A pull request that adds a check which reads a PR body ships a fixture test running that check over **that PR's own body**. A body-reading check is the one class whose real input exists at the moment the PR opens and is never exercised by a synthetic fixture the author also wrote: the author's fixture agrees with the author's mental model by construction, and the first real body it meets is the one it was supposed to grade. Running it over the body in hand costs one test and converts "it should work on a real body" from a belief into a passing assertion.
|
|
78
83
|
|
|
79
84
|
**The `Audience` column, read once for all three tables below:** `product` — this row's implementation is a named check the product registers, so it ships to every adopter through `vinaya check`. `repo-own` — everything else: a mechanism specific to how this repository enforces itself on top of the product — a hand-written CI job, the check runner itself, a forge-write command, or a check this repo runs against its own doctrine tree but has deliberately not registered as a named, adopter-facing check. A row carrying no `Audience` cell at all (older doctrine, or an un-upgraded adopter copy) reads as `repo-own` — the safe default, since it makes no shipped claim to verify. G6, below, blocks CI on any `product` row whose implementation does not actually resolve in that registration.
|
|
@@ -90,7 +95,7 @@ The same check implementations run at ring 0 and ring 1 — one codebase, two en
|
|
|
90
95
|
| Creating a pull request, or editing its title/description | Ever opened a PR that was missing half the info reviewers needed? | event | Refuses to open or edit a pull request until it carries everything a reviewer needs to judge it. | **Forge command gate** — the raw command is refused; the validated wrapper is the only path | The title follows the naming convention. The description carries every required section of the task brief: the impact tier, the executing model, the project(s) touched, a tagged test plan, the file surface the task may touch, the documentation it must update, the isolated-worktree setup step, the stop conditions, the standing autonomy clause, the Issue this work closes,. A *plan* pull request must never close a task Issue. **The brief itself never reaches this description** — the Developer still pastes it into the body-file's `## Reference` section, but `vinaya pr create` splits that section out before the body ever reaches `gh`, posting it as its own comment marked `<!-- aeg:brief -->`, so the description carries the report (summary, test plan, evidence, scope) only. The documentation required by the change's impact tier is present — a doc-coverage finding here is honored ONLY by an actor-verified `vinaya/waiver:docs` label, never a body field. The branch, its planned task row, and the Issue it closes all agree with each other. **Single-plan-PR guard (task 19):** a diff that touches a tranche's topology file (`aeg-root/tranches/*.md`, excluding `completed/`) is refused outright if another OPEN pull request's diff already touches that SAME tranche's topology file — closing the race that produced two concurrent plan pull requests for the same tranche, each cut before the other's newly-added Issue was visible. Ordinary task pull requests never touch a topology file, so this never fires for them. A PR opened via the web UI bypasses this wrapper entirely, so this predicate (extracted to `packages/aeg-core/src/single-plan-pr.ts`, task 24 item 3) now also has a ring-1 CI backstop (see below) — one implementation, two enforcement points. **`vinaya pr create` also runs every registry check declaring `PR_BODY` over the body before it ever reaches the forge (task 12)** — `brief-shape`, `pr-report-density`, `doc-coverage`, and every other `PR_BODY`-declaring check, with `PR_NUMBER` explicitly unset (no PR exists yet) and `localOnly: true` skipping the `requiresOpenPr` set outright. The same set CI's `vinaya-checks.yml` runs against the live body, so a body that opens here is a body that passes there too. Skipped entirely when `rings.ring1_forgeWriteInterception` is `true` — the same accelerator that already skips `validateForgeWrite`'s config-driven sections. | repo-own | `packages/aeg-core/bin/open-pr.ts` |
|
|
91
96
|
| Creating a task Issue, or editing its title/description | Ever seen a ticket with zero context on why it exists? | event | Refuses to open or edit a task Issue until it carries the full reasoning behind the task. | **Forge command gate** → validated wrapper | The title follows the naming convention, and the Issue carries the complete planning rationale — what the task is and is not, its sizing, the projects and blast radius it touches, why its dependency edges exist, the known traps, the suggested agent class, the stop-and-escalate conditions, and the documents it must keep coherent. Issues outside the task system pass through untouched. **Beyond presence, four content checks block: every name on the task's line-anchored `Project:` field must resolve to a row in the project registry (`.vinaya/projects.md`), which is the sole authority for valid project names — an unregistered project has no specs to read and no registry row for anything to resolve against. The names are resolved by the same parser that derives a task's project for the board and dispatch, so the gate and the derivation cannot disagree about what a task declares; the rationale's declared surface must not reach into a shared collision domain (live-derived `packages/*` workspace members, plus a built-in cross-cutting default set, plus any `vinaya.config.json` `blastRadius.extraDomains` entries) that none of its declared projects owns without either a second **registered** project or an explicit `blast-radius-ack:` line — this check reads the same line-anchored field through the same parser, and counts only names that resolve to a registry row, since an unregistered name adds no review lens and so cannot buy the multi-project benefit of the doubt; the body must carry no brief-shaped section (`## References`, `Technical surface map`, `Premise`, `Step 0`, `Test Plan`) — brief content goes stale before work starts and belongs in the brief; and the rationale must name at least one concrete doc/skill path (or the explicit `no-doc-surface` sentinel), which is the only read-obligation signal a forge write produces, since the skill-check hook fires on file edits and cutting an Issue edits no file. A fourth check warns, never blocks: two open task Issues naming the same collision domain with no mutual `Conflicts-with` edge.** **Label-name validity is checked here too:** a tranche label is the one label whose value the Planner invents, and `vinaya/tranche:` spends 15 of GitHub's 50-character label budget before the slug starts — so a slug that reads fine in prose can be one the forge refuses to create. The wrapper refuses an over-long slug with the exact overshoot, at the point the label is first applied, rather than letting `gh` fail with an opaque 422. **Dependency-edge amendments have exactly one path — the `amend-deps` subcommand, which rewrites the structured `Dependency rationale` field AND appends the `**Amendment (...)**` note in one atomic write, gated on a runtime round-trip parse (`parseRationaleDeps` must read back exactly the requested edges) — no code path changes one representation without the other (closing a five-incident field-vs-amendment drift class this session; modeled on the actor-verified waiver pattern: the sanctioned path is the only path).** | repo-own | `packages/aeg-core/bin/open-issue.ts` |
|
|
92
97
|
| Merging | Ever had a broken build get merged anyway? | event | Holds the merge on the forge side while anything the gates check is still failing. | **Required review-gate check + branch protection** | No agent-side merge hook is installed in this repo. The merge gate is ring 1: the required `review-gate` check (`vinaya check review-gate`; a clean code-reviewer APPROVE and a clean security-review PASS verdict from principal-allowlisted authors AND at least one reported, green non-review check-run for the PR's current head, or a principal-applied, actor-verified `vinaya/waiver:review` label) plus branch protection marking that check required. | product | `apps/cli/src/checks/bin/check-review-gate.ts` |
|
|
93
|
-
| Starting the
|
|
98
|
+
| Starting the Planner's dispatch act (before dispatching a task's brief) / starting Step 0 (before executing one) / **every push on a task branch before its PR exists** | Ever started work only to find out halfway it was blocked on something else? | hook | Refuses to start work until every precondition for the task is checked live and found clear. Optionally (task 10, `PREMISE_FILE=<brief.md> vinaya check dispatch-readiness`), also re-asserts a local brief's `Premise:` pins against current on-disk state and fails on any pin that no longer holds. | **`dispatch-readiness` check** (`vinaya check dispatch-readiness` — mandated by `roles/developer.md` and `roles/planner.md` § The dispatch act; **now also hook-automated** — task 25; known parity gap: the shipped check's prior-tranche-archival predicate always reports empty — on this repo's own toolchain the unabridged derivation, including that predicate for real, is `packages/aeg-core/bin/verify-dispatch.ts`, named in the implementation column below) | Every dispatch precondition is re-checked live against the forge (the task's row/Issue are derived directly from a Milestone + `vinaya/tranche:<slug>`-labeled Issues as of the forge-native migration — no `origin/main` file read involved; leftover-branch detection still fetches `origin/main` for its own commit-count comparison): the task's row and Issue exist; the Issue passes the rationale gate; every `depends-on` PR is merged and no `conflicts-with` PR is open/in-flight; every named project's prior tranche is archived. A parsed `depends-on` edge that points back at its own task is refused as an INTERNAL parser-bug error, never reported as an unmerged dependency: a self-dependency is unsatisfiable by construction, so it can only mean the edge text or the rationale parser produced something impossible, and the ordinary unmerged-dependency wording reads as a legitimate gate state an agent is invited to route around rather than escalate. The `coherence` D1 check refuses the same shape the same way. (2026-07-13: the immediately-prior task's three-predicate archival bar — the row-adjacency predicate — was removed once the provenance-posting signal it existed to protect became automated.) A `NOT READY` result is a stop condition. Every `gh` call this script makes carries an explicit `-R <owner>/<repo>` (task 23) — from a linked `git worktree add` checkout, an untargeted `gh issue list`/`gh issue view`/`gh pr list` silently scopes to the wrong repo (or returns nothing), which previously produced false prior-tranche-archival blockers. **the managed `.git/hooks/pre-push` block** now runs this gate itself on a `task/*/*` branch's first push (before a PR exists), refusing on genuine `NOT READY` and failing OPEN — loudly — when a `gh auth status` reachability probe fails or `verify-dispatch`'s own repo/token resolution fails (task 18's fail-open precedent). The gate reads only the `dispatch-readiness:` predicate, not verify-dispatch's combined exit code, which also folds in `leftover-detection` (a pre-Step-0 "safe to branch fresh" advisory that would otherwise false-block every push after the first on a task legitimately mid-flight — discovered live while building this gate). Once a PR exists, later pushes skip the gate: dispatch was already validated once. | product | `packages/aeg-core/bin/verify-dispatch.ts` |
|
|
94
99
|
| Opening a task PR whose surface includes real code | Ever had a PR's description quietly stop matching what the code does? | event | Refuses a code-carrying pull request whose description no longer matches what it changes. | **Brief Validation — `checkPremiseCoverage`** | The PR body carries a `Premise:` block with at least one assertion (`contains`/`absent`/`sha256`) whose path falls inside the task's surface map — re-assertable at any point via `verify-dispatch --premise` or `verify-task`. | repo-own | `packages/aeg-core/src/brief-validation.ts` |
|
|
95
100
|
| Opening a task PR (final self-check before creation) | Ever opened a PR and only then discovered the tests were failing? | event | Runs the whole exit check before a pull request is created, so failures surface first. | **`verify-task` CLI** (`packages/aeg-core/bin/verify-task.ts` — mandated by `roles/developer.md`; **now also hook-automated** — task 25) | Typecheck, lint, tests, build, `verify-docs --pr`, and the premise coverage/recheck pair all pass, as one summary, against the PR's actual diff, before `open-pr.ts` is invoked. **`open-pr.ts` now runs this composite itself** for task branches (via `gatePlanForBranch`), invoked wholesale rather than partially re-implemented — non-task branches are unaffected (byte-identical gate set). `verify-docs --pr` runs twice on a task-branch PR-open as an accepted, measured overlap (~4s with a warm turbo cache). | repo-own | `packages/aeg-core/bin/verify-task.ts` |
|
|
96
101
|
| Spawning a check (`vinaya check`, ring-0 pre-push AND ring-1 CI) | Ever had a check quietly read a secret it had no business seeing, because nothing scoped what it could reach? | hook | Governs which environment variables a spawned check's child process can see, instead of every check inheriting the full parent environment unconditionally. | **Env allowlist** (`CheckSpec['env']`, `apps/cli/src/checks/contract.ts`; construction: `buildCheckEnv`, `apps/cli/src/checks/runner.ts`) | **⚠️ BREAKING, live (task 3):** a spawned check's child process sees only a fixed baseline (`PATH`, `LANG`, `HOME`, `HTTPS_PROXY`, `HTTP_PROXY`, `NO_PROXY`, `TMPDIR`) plus whatever its own `env` declaration (`true` / `{ optional: true }` / `{ anyOf: [...] }` / a literal string) explicitly forwards — **no longer the full parent environment.** An undeclared variable a check's own code reads is now genuinely invisible to it, not merely warned about; a `true` or unsatisfied `anyOf` declaration missing from the caller's environment synthesizes a `CheckError` and the check never spawns at all. Every core check in `registry.ts` carries an audited declaration, and every core check that spawns `gh` or reaches the forge through the token-resolution chain additionally forwards `GITHUB_TOKEN`/`GH_TOKEN` as `{ optional: true }` — on a CI runner those env vars are `gh`'s only authentication path, so a check that shells out without forwarding them runs unauthenticated there (hard-failing or silently degrading, depending on the bin's failure mode) while passing locally on `gh`'s keyring; the pairing is coupling-tested (`apps/cli/tests/checks/registry-env.test.ts` detects `gh` invocations in any call shape — array-form, string-form, and template-literal — and demands both declarations); that detection is source-text pattern matching, so it does not yet catch a check that reaches the forge indirectly through `createForgeSource` (`@attalabs/vinaya-sources`) with no literal `gh` call of its own — found live: `closes-n` reaches the forge this way and had shipped without either token declared, passing locally under a developer's own `gh` keyring and failing unauthenticated on every CI runner, a known gap in the coupling test's own coverage rather than in the rule it's checking. This repo's own core checks are therefore unaffected — an adopter's **custom** check that reads `process.env` directly with no `env` declared will lose that access at this minor; declare `env` on it (`vinaya.config.json`'s `checks.<name>.env`) before upgrading. `vinaya check`'s prior warn-phase print (task 2) is retired now that the behavior it warned about is live; `vinaya doctor` still carries the same missing-declaration diagnostic permanently, at `info` severity, so a custom check's gap remains visible after upgrade even though `vinaya check` no longer prints it inline. A second field, `requiresOpenPr?: boolean` (same no-privileged-field discipline as `env` — a custom check can declare it exactly like a core one), marks a check that can only evaluate meaningfully once a pull request exists; the generated `pre-commit`/`pre-push` hooks pass a new `--local` flag that skips every check declaring it, while CI (`vinaya-checks.yml`, `pull_request`-triggered) omits the flag and always runs them for real. Found live: `closes-n`/`test-plan` ran unconditionally in both hooks with no `requiresOpenPr` field to opt out on, so the first commit on a fresh task branch could never satisfy either — no PR exists yet at commit or push time, only after. Found live again, same missing-declaration class: `brief-shape` read `BRANCH` from `process.env` in its own bin without declaring it in `registry.ts`'s allowlist, so the runner stripped the variable before the child ever spawned — the non-task-branch bypass its logic depended on (skipping the `Closes #N` requirement for a standalone brief with no Issue to close) silently never fired; `registry.ts` now declares `BRANCH: { optional: true }` on that entry alongside `PR_BODY`, mirroring `test-plan`'s existing declaration for the same pair. `vinaya.config.json`'s `principals` field (the review-gate/waiver trust anchor, `apps/cli/src/lib/config.ts`'s `resolvePrincipalAllowlist`) is resolved via `loadTrustAnchorConfig()` — a `gh api` read of the repository's DEFAULT BRANCH, never local git, the PR's working tree, or any env var. Three successive attempts got this wrong before landing (all caught pre-merge): reading the PR's working tree; reading `git show ${BASE_SHA}:…` where `BASE_SHA` was an env var; and reading `git show origin/main:…`, where `origin/main` is a LOCAL remote-tracking ref the PR's own workflow can `git update-ref`. The rule they establish: **inside a `pull_request`-triggered workflow nothing on the job's own disk or environment is a trust boundary against the PR author**, because the workflow definition itself comes from the PR. `check-doc-coverage.ts`/`check-doc-coverage-push.ts` keep their own, separate, legitimately-overridable `BASE_SHA` for diff-scoping only, never reused for a trust decision. The API read raises the bar but is not itself the boundary — and the boundary is not where the earlier version of this sentence put it. **Branch protection with the check marked required is still worth enabling** — `init` prints that command and `doctor` reports its absence — but state exactly what it buys: a required status check is satisfied by a conclusion reported under its name, and it does not certify that the conclusion came from running the real check. Deleting the gate's *step* does not stop the *job* reporting green; only removing the job or the workflow outright produces the never-reports case that leaves a PR unmergeable, and a step edited to `exit 0` reports success under the required name having run nothing at all. So the rule stated in bold above extends one step further, to the verdict itself: **the trust boundary is who controls the workflow definition that produces the required check, and under a `pull_request` trigger that is the PR author — whatever the check is packaged as.** Packaging decides the blast radius, not the boundary. Where CI invokes an immutable published artifact, the invocation is the only thing the PR can rewrite. Where a repo vendors the CLI — its own workspaces declaring the published package name, which needs a build-and-run CI shape because `npx` matches on package **name** before any version spec is read and would otherwise exec an unbuilt local bin — the PR additionally controls the check sources, the build script, and the **dependency lifecycle scripts** the install step executes in a base-branch-scoped cache: the surface becomes all code the PR controls. The same widening arrives with no packaging question at all whenever a required workflow checks out the PR's head and runs a gate from that tree — this harness's own review gate does exactly that, so it sits in the wider class today by a hand-written workflow, not by any generator. Two things are therefore NOT claimed here. Not that verdict tampering is closed for an ordinary adopter: it is not, and an adopter who enables branch protection and stops there has closed merge-without-a-report and nothing else. And not that the vendored CI shape is something generated today — as of 2026-08-14 `init` writes only the published `npx` invocation, which in a vendoring repo misresolves to the unbuilt local bin and kills the job, so such a repo's required check currently yields no verdict at all rather than an untrustworthy one; that is its own governance problem, and the build-and-run generator that fixes it is not yet merged. What closes the residual gap is not a mechanism: a repo whose required checks are built from code its own pull requests can edit is governing itself, and there the last line is the reviewer — changes to check sources, the build script, or the dependency manifest are reviewed as governance changes, not as ordinary code. | repo-own | `apps/cli/src/checks/runner.ts` |
|
|
@@ -109,7 +114,7 @@ The same check implementations run at ring 0 and ring 1 — one codebase, two en
|
|
|
109
114
|
|
|
110
115
|
**Gate-read field recognition, specifically** (task 30): every gate-read PR-body field (`Tier:`, `Project:`, `Closes #N`, the `Premise:` block, the Test Plan section) is anchor-recognized where present — an `AEG:<FIELD>` HTML-comment pair (`packages/aeg-core/src/anchored-region.ts`) is read exclusively when it exists, so identical-looking text elsewhere in the body (a pasted reference brief, a quoted example) can never masquerade as the field — with the original prose/heading recognition as the compatibility fallback for every body that carries no anchors.
|
|
111
116
|
|
|
112
|
-
**Issue self-assignment at first push, specifically** (task 33): the pre-push hook carries one deliberate non-gate side effect — on a `task/*/*` branch's genuinely first push (the remote ref is being created; its pre-push remote-sha is all zeros), `packages/aeg-core/bin/assign-task-issue.ts` assigns the task's Issue to the authenticated `gh` user, i.e. the actual pusher. This mechanizes the signal Studio's dispatch-visibility chip reads (task 26: `assigned = assigneesCount > 0`), which until this task was populated only when the Principal remembered to assign by hand (live-fire gap: task 28 showed no chip despite two real pushed commits). Deliberately **assignment-at-first-push, not at
|
|
117
|
+
**Issue self-assignment at first push, specifically** (task 33): the pre-push hook carries one deliberate non-gate side effect — on a `task/*/*` branch's genuinely first push (the remote ref is being created; its pre-push remote-sha is all zeros), `packages/aeg-core/bin/assign-task-issue.ts` assigns the task's Issue to the authenticated `gh` user, i.e. the actual pusher. This mechanizes the signal Studio's dispatch-visibility chip reads (task 26: `assigned = assigneesCount > 0`), which until this task was populated only when the Principal remembered to assign by hand (live-fire gap: task 28 showed no chip despite two real pushed commits). Deliberately **assignment-at-first-push, not at dispatch time** — a rendered-but-unstarted brief is not "in flight", and assigning earlier would reintroduce the false positive task 26 was built to avoid. Idempotent (an already-assigned Issue is a no-op, so a deleted-and-recreated branch cannot double-assign; a force-push to an existing branch never re-triggers at all) and **fail-open by contract**: it runs last, after every blocking gate has passed, warns and exits 0 on any API failure, and the hook never gates on its exit code — a failed assignment can never block a legitimate push.
|
|
113
118
|
|
|
114
119
|
---
|
|
115
120
|
|
|
@@ -132,9 +137,9 @@ Every pull request, on open and on every push, re-runs the same checks in CI:
|
|
|
132
137
|
| Typecheck + unit tests | Ever had a change silently break something it wasn't even touching? | ci | Re-runs the type checker and the unit tests for every package this change can reach. | Packages affected by this PR's diff, plus their full transitive-dependent set (`turbo --affected`; application builds are verified by the deployment pipeline). A diff touching no package (docs/workflow-only) runs 0 packages — sound, since repo-file gates like `verify-docs`/coherence run in their own jobs regardless (task 27) | repo-own | `.github/workflows/ci.yml` |
|
|
133
138
|
| Conventions | Ever opened a PR full of inconsistent formatting and naming? | ci | States where formatting/naming conventions stand in this repo: currently unenforced. | **Nothing re-checks lint/format or commit-message grammar in this repo today** — the managed hooks run only the `vinaya check` suite, and the one CI workflow that builds the code (`ci.yml`, the implementation named here; its typecheck + unit-test substance belongs to the `Typecheck + unit tests` row) carries no lint or commitlint step. Those conventions are discipline, not a gate, until a step is added. | repo-own | `.github/workflows/ci.yml` |
|
|
134
139
|
| AI review | Ever wished every PR got a second pair of eyes, even at 2am? | ci | Requires independent review verdicts to exist on every pull request before merge. | No automated reviewer workflow is installed in this repo — review passes are separate, fresh-context dispatched agent sessions that post verdict comments on the PR. `vinaya-review-verdict.yml` evaluates a landed verdict comment (only principal-allowlisted authors count — the same verdict-author verification rule the Review gate applies) and re-runs the required review-gate check so it can go green natively. Until clean verdicts exist, the required check stays red by design. | repo-own | `.github/workflows/vinaya-review-verdict.yml` |
|
|
135
|
-
| Review gate | Ever had a PR merge with nobody actually approving it? | ci | Holds the merge until the required review verdicts actually exist. | Required, blocking: a task-branch PR must carry a clean code-reviewer `APPROVE` verdict comment AND a clean security-review `PASS` verdict comment before merge — reusing `extractCodeReviewVerdict`/`extractSecurityReviewVerdict` (`packages/aeg-core/src/verdict-extraction.ts`), extracted out of the post-merge Archivist's `archive-task.ts` so both the pre-merge gate and the post-merge provenance assembly read the identical detection logic (one implementation, two call sites). Picks the MOST RECENT comment carrying a clear value, not the first comment merely matching the loose marker pattern — fixed live during this task, since the original single-verdict `.find()` broke on real multi-comment PRs (a `REQUEST_CHANGES` verdict followed by fixes and a later clean `APPROVE`, or the post-merge provenance comment's own DANGLING-note prose, which contains the word "verdict" and would otherwise poison a naive first-match search). **A verdict is bound to the commit it judged** (reviewed-commit binding): the winning comment must also carry a same-comment `Judged head: <sha>` line (parsed by `verdict-extraction.ts`, same anchor discipline as the `VERDICT:` marker itself, abbreviated or full sha both accepted), and `checkReviewGate` requires it to cover the PR's *current* `headRefOid` — a verdict that predates a later push, or carries no binding at all, no longer counts as clean; the gate names both the verdict's sha and the current head in its failure message. **Verdict authors are verified** (2026-08-09): only comments whose author is on the principal allowlist (`PRINCIPAL_ALLOWLIST` — the same trust anchor the waiver actor-check uses) participate in verdict extraction; forged, bot, and unresolvable-author comments are ignored, never fatal — a later forged `APPROVE` can no longer override a real `REQUEST_CHANGES`. **What this precondition does and does not establish:** it defeats a *forged* or *bot*-authored comment; it does not defeat a comment genuinely posted by a dispatched reviewer agent sharing the Principal's own local `gh` credential — that comment IS on the allowlist, correctly, because it really is the Principal's forge identity, so author verification alone cannot tell the Principal's own verdict apart from an agent's verdict cast under that same shared credential (`vinaya review post`'s `Cast by:` line records which role/session actually cast it, making the inheritance auditable without closing it — the close requires running the reviewer somewhere the Principal's credential is not, tracked separately). A principal-actor-verified `vinaya/waiver:review` label (the same `isWaiverLabelActorVerified` pattern, now parameterized by label name and reused — not duplicated — for this second label) skips the requirement for one PR; label presence alone is never sufficient. **Tool-layer protection extended** (`check-forge-gates.sh`): mutating `vinaya/waiver:review` from an agent session is denied, exactly like `vinaya/waiver:docs` — without this, an agent sharing the Principal's own `gh` credential could self-apply the waiver and inherit the Principal's identity on the labeling event, defeating actor verification entirely (confirmed live during this task before the hook was extended). Non-task branches (plan PRs) bypass — a plan PR touching only topology files has no code to review. Going-forward only: never re-evaluates already-merged history. **Mechanical checks are a third precondition:** a task-branch PR must additionally carry at least one reported, green (non-review) check-run for the PR's current head — `checkReviewGate` reads a caller-supplied `mechanicalChecks` list (populated via `gh pr checks --json name,bucket`, with this repo's own `vinaya review gate` check-run name excluded so the gate cannot judge its own status) and fails the same way an unclean or unbound verdict does, naming which check is not green, or that none have reported yet. The exclusion of the gate's own check-run name is repo-specific and lives in the thin CLI shims that resolve check-run status via `gh`, never inside the pure evaluator itself, since that evaluator ships to every adopter under a different workflow name. **A verdict is also bound by PATCH IDENTITY** (this task): the gate compares each side's `git diff <base>...<sha>` output run through `git patch-id --stable`, for the judged head and the current head, and a verdict whose sha binding has lapsed still counts when the two identities are equal and neither is `null`. A merge from the main branch, or a rebase that leaves the PR's own patch untouched, therefore no longer voids a review that already read exactly those changes — the round it used to cost proved nothing. Fails closed on every uncertainty: `null` on either side (an unreachable judged head after a force-push, a shallow clone, no git) is "cannot answer", never "they match". The known limit is stated rather than papered over — a base that moved under an identical patch can carry a semantic conflict the earlier review could not have seen, and this binding still holds; that is the same limit GitHub's own stale-review rule has, and the green-mechanical-checks requirement this gate already imposes at the new head is the guard for it. **A verdict is also bound to the OBJECTIVES LIST it was judged against**: the winning comment's `Objectives version:` line (a hash `objectivesVersion` computes over the closed Issue's, or the PR body's own, `## Objectives` list) must equal the current version of that same list — a verdict cast before an edit to the objectives no longer counts as clean, even when its head binding still holds. `input.objectivesVersion === null` (an Issue below the objectives cutover, or no resolvable list at all) skips this half of the binding entirely, so the pre-cutover PR stock keeps passing unchanged. **Resolving that `objectivesVersion` value diverges between the two bins that call `checkReviewGate`**: this repo's live pre-merge path, `apps/cli/src/checks/bin/check-review-gate.ts`, resolves it for real (Issue-then-body, fail-closed on every unresolvable case, waiver-checked first so an actor-verified `vinaya/waiver:review` label is never blocked by that resolution); the older reference script named in this row's own implementation column, `packages/aeg-core/bin/verify-review-gate.ts`, has no equivalent Issue-fetch machinery and always passes `objectivesVersion: null`, unconditionally skipping this half of the binding. Read the objectives-version paragraph above as describing `checkReviewGate`'s pure evaluation logic (shared by both callers); for which file actually resolves the value it is handed, see `check-review-gate.ts`, not this row's pointer. | product | `apps/cli/src/checks/bin/check-review-gate.ts` |
|
|
140
|
+
| Review gate | Ever had a PR merge with nobody actually approving it? | ci | Holds the merge until the required review verdicts actually exist. | Required, blocking: a task-branch PR must carry a clean code-reviewer `APPROVE` verdict comment AND a clean security-review `PASS` verdict comment before merge — reusing `extractCodeReviewVerdict`/`extractSecurityReviewVerdict` (`packages/aeg-core/src/verdict-extraction.ts`), extracted out of the post-merge Archivist's `archive-task.ts` so both the pre-merge gate and the post-merge provenance assembly read the identical detection logic (one implementation, two call sites). Picks the MOST RECENT comment carrying a clear value, not the first comment merely matching the loose marker pattern — fixed live during this task, since the original single-verdict `.find()` broke on real multi-comment PRs (a `REQUEST_CHANGES` verdict followed by fixes and a later clean `APPROVE`, or the post-merge provenance comment's own DANGLING-note prose, which contains the word "verdict" and would otherwise poison a naive first-match search). **A verdict is bound to the commit it judged** (reviewed-commit binding): the winning comment must also carry a same-comment `Judged head: <sha>` line (parsed by `verdict-extraction.ts`, same anchor discipline as the `VERDICT:` marker itself, abbreviated or full sha both accepted), and `checkReviewGate` requires it to cover the PR's *current* `headRefOid` — a verdict that predates a later push, or carries no binding at all, no longer counts as clean; the gate names both the verdict's sha and the current head in its failure message. **Verdict authors are verified** (2026-08-09): only comments whose author is on the principal allowlist (`PRINCIPAL_ALLOWLIST` — the same trust anchor the waiver actor-check uses) participate in verdict extraction; forged, bot, and unresolvable-author comments are ignored, never fatal — a later forged `APPROVE` can no longer override a real `REQUEST_CHANGES`. **What this precondition does and does not establish:** it defeats a *forged* or *bot*-authored comment; it does not defeat a comment genuinely posted by a dispatched reviewer agent sharing the Principal's own local `gh` credential — that comment IS on the allowlist, correctly, because it really is the Principal's forge identity, so author verification alone cannot tell the Principal's own verdict apart from an agent's verdict cast under that same shared credential (`vinaya review post`'s `Cast by:` line records which role/session actually cast it, making the inheritance auditable without closing it — the close requires running the reviewer somewhere the Principal's credential is not, tracked separately). A principal-actor-verified `vinaya/waiver:review` label (the same `isWaiverLabelActorVerified` pattern, now parameterized by label name and reused — not duplicated — for this second label) skips the requirement for one PR; label presence alone is never sufficient. **Tool-layer protection extended** (`check-forge-gates.sh`): mutating `vinaya/waiver:review` from an agent session is denied, exactly like `vinaya/waiver:docs` — without this, an agent sharing the Principal's own `gh` credential could self-apply the waiver and inherit the Principal's identity on the labeling event, defeating actor verification entirely (confirmed live during this task before the hook was extended). Non-task branches (plan PRs) bypass — a plan PR touching only topology files has no code to review. Going-forward only: never re-evaluates already-merged history. **Mechanical checks are a third precondition:** a task-branch PR must additionally carry at least one reported, green (non-review) check-run for the PR's current head — `checkReviewGate` reads a caller-supplied `mechanicalChecks` list (populated via `gh pr checks --json name,bucket`, with this repo's own `vinaya review gate` check-run name excluded so the gate cannot judge its own status) and fails the same way an unclean or unbound verdict does, naming which check is not green, or that none have reported yet. The exclusion of the gate's own check-run name is repo-specific and lives in the thin CLI shims that resolve check-run status via `gh`, never inside the pure evaluator itself, since that evaluator ships to every adopter under a different workflow name. **A verdict is also bound by PATCH IDENTITY** (this task): the gate compares each side's `git diff <base>...<sha>` output run through `git patch-id --stable`, for the judged head and the current head, and a verdict whose sha binding has lapsed still counts when the two identities are equal and neither is `null`. A merge from the main branch, or a rebase that leaves the PR's own patch untouched, therefore no longer voids a review that already read exactly those changes — the round it used to cost proved nothing. Fails closed on every uncertainty: `null` on either side (an unreachable judged head after a force-push, a shallow clone, no git) is "cannot answer", never "they match". The known limit is stated rather than papered over — a base that moved under an identical patch can carry a semantic conflict the earlier review could not have seen, and this binding still holds; that is the same limit GitHub's own stale-review rule has, and the green-mechanical-checks requirement this gate already imposes at the new head is the guard for it. **A verdict is also bound to the OBJECTIVES LIST it was judged against**: the winning comment's `Objectives version:` line (a hash `objectivesVersion` computes over the closed Issue's, or the PR body's own, `## Objectives` list) must equal the current version of that same list — a verdict cast before an edit to the objectives no longer counts as clean, even when its head binding still holds. `input.objectivesVersion === null` (an Issue below the objectives cutover, or no resolvable list at all) skips this half of the binding entirely, so the pre-cutover PR stock keeps passing unchanged. **A verdict is also bound to the newest PRINCIPAL RULING on the PR** (`review-validity-v1` task 3): the winning comment's `Ruling ordinal: <k>` line (`0` when the PR carried no ruling at cast time — this line renders UNCONDITIONALLY, unlike the objectives-version line) must equal `input.rulingOrdinal`, the newest ruling ordinal `check-review-gate.ts` counts off the PR's own principal-authored `<!-- aeg:principal:ruling:<pr>-<k> -->` comments — a ruling posted after a verdict was cast no longer counts as clean, even when its head and objectives bindings still hold, and the gate names the newer ruling in its failure message. Unlike `objectivesVersion`, there is no `null`-means-skip input value here — a PR's ruling count is never ambiguous — but a verdict predating this feature (no `Ruling ordinal:` line at all) still binds when the PR's newest ruling ordinal is `0`, the same "nothing to bind against" case expressed as an equality rather than a skip. **Resolving that `objectivesVersion` value diverges between the two bins that call `checkReviewGate`**: this repo's live pre-merge path, `apps/cli/src/checks/bin/check-review-gate.ts`, resolves it for real (Issue-then-body, fail-closed on every unresolvable case, waiver-checked first so an actor-verified `vinaya/waiver:review` label is never blocked by that resolution); the older reference script named in this row's own implementation column, `packages/aeg-core/bin/verify-review-gate.ts`, has no equivalent Issue-fetch machinery and always passes `objectivesVersion: null`, unconditionally skipping this half of the binding. Read the objectives-version paragraph above as describing `checkReviewGate`'s pure evaluation logic (shared by both callers); for which file actually resolves the value it is handed, see `check-review-gate.ts`, not this row's pointer. | product | `apps/cli/src/checks/bin/check-review-gate.ts` |
|
|
136
141
|
| G1 — implementation exists | Ever read about a safeguard that turned out not to actually exist? | ci | Re-checks that every gate the doctrine describes has real code behind it. | Every ring's non-empty `implementation` cell on this very page resolves to a real path on disk — makes this page's registry columns load-bearing instead of decorative. | product | `packages/aeg-core/bin/verify-registry.ts` |
|
|
137
|
-
| G2 — no orphan hook/CLI | Ever found a script nobody remembers the purpose of? | ci | Re-checks that every hook and CLI in the repo is one the doctrine claims, and that a row scaffolded to fix that stays visibly incomplete until a human finishes it. | Every file under `.husky/*`, `.claude/hooks/*.sh`, `packages/aeg-core/bin/*.ts`, `apps/cli/src/checks/bin/*.ts` is named by some row's `implementation
|
|
142
|
+
| G2 — no orphan hook/CLI | Ever found a script nobody remembers the purpose of? | ci | Re-checks that every hook and CLI in the repo is one the doctrine claims, and that a row scaffolded to fix that stays visibly incomplete until a human finishes it. | Every file under `.husky/*`, `.claude/hooks/*.sh`, `packages/aeg-core/bin/*.ts`, `apps/cli/src/checks/bin/*.ts` is named by some row's `implementation`, OR — **twin-form recognition (O14)** — resolves, through the same `claimedCheckNames` derivation G6 uses, to a `coreCheckRegistry()` name some OTHER row's `implementation` already claims: the same enforcement mechanism routinely ships as both a `packages/aeg-core/bin/*.ts` standalone form and an `apps/cli/src/checks/bin/check-*.ts` CLI-registered form, and a row's single `implementation` cell can only name one — the other is not a second undocumented fact, it is the first fact read through its other physical shape. A `NON_GATE_BINS`-listed aeg-core bin (a forge writer, a one-shot reporter, an eval harness — `report-tokens.ts`, `eval-agent-compliance.ts`, …) and a listed non-gate hook script (`.claude/hooks/track-transcript.sh`, real Stop-hook plumbing feeding two already-documented mechanisms' own inputs, with no pass/fail decision of its own) implement no enforcement mechanism to be named for — the honest fix for those is that this gate stops asking, never an invented row (`.husky/*` matches nothing in this repo today — that directory does not exist here). **Blocking as of this task** (re-graded from report-only: the twin-form gap above was G2's entire non-scaffold-placeholder backlog, and a 0-finding G2 run means something again now that it can fail). **Scaffold-writer** (task 22, later widened to cover that second location): `verify-registry.ts --scaffold` auto-inserts a stub row for an orphan candidate whose ring is mechanically derivable — a `packages/aeg-core/bin/*.ts` file resolving through `GATE_AUDIENCE` to a registered check (its ring comes from that check's own `apps/cli/src/checks/registry.ts` entry), an `apps/cli/src/checks/bin/*.ts` file whose `check-`-stripped name resolves through the analogous `CLI_CHECK_RING` mirror table, or a `.husky`/`.claude/hooks` file (ring 0 by source). An orphan with no derivable ring (an unregistered bin under either directory) gets no stub and stays a plain finding — the writer never guesses. A second, independent half of G2 scans every row's `summary`/`description`/`spec` cells for the literal placeholder marker `[undocumented — fill in why]` the scaffold writes into every non-mechanical cell: a stub row that filled only `implementation` would otherwise read as "documented" to the orphan half above and silence G2 outright, which is the exact trap this second half exists to close. The writer only ever appends a new row after a ring table's last existing row — it never edits, reorders, or removes one, so a hand-authored row (including `workspace-escape`'s and `main-branch-refusal`'s own) is untouched. Repo-side only: the writer lives on `verify-registry.ts` (aeg-core), never on the shipped `check-registry-gates.ts` (apps/cli) — an adopter's checks stay read-only over doctrine. | product | `packages/aeg-core/bin/verify-registry.ts` |
|
|
138
143
|
| G3 — no seventh way into GitHub | Ever discovered a backdoor that skipped all your checks? | ci | Re-checks that no route into GitHub exists beyond the ones the doctrine gates. | Every file making a GitHub-mutating call of the gated class (PR/Issue create, PR/Issue body/title edit, `gh api` create/edit, raw curl/wget writes) is named by some Ring-0 row's `implementation` — a mutating call outside that set would be an unguarded hole in the prevention model. (The class was originally defined by attalabs's `check-forge-gates.sh` tool-interception hook; this repo has no such hook — see the raw-API ring-1 row above — but the scan's definition of "mutating call" is unchanged.) | product | `packages/aeg-core/bin/verify-registry.ts` |
|
|
139
144
|
| G4 — cited forge numbers resolve | Ever read a doc that cited a ticket number that didn't exist? | ci | Re-checks that every Issue and PR number cited in the docs resolves to a real one. | Every `#NNN` cited anywhere in this page's body is a real Issue or PR in the forge — a fabricated citation fails the build. **Currently scans nothing by design** (task 14): this page's body carries zero forge citations, since task 3 banned them from `aeg-root/**` as doctrine and the `reader-resolvable-prose` check (task 15) now enforces that ban directly. G4 is a standing guard against reintroduction, not a live proof of ongoing citation correctness — it fires the moment a `#NNN` reappears here and doesn't resolve, but has nothing to scan while the ban holds. Verified live: `packages/aeg-core/src/registry-checks.test.ts` appends a fabricated citation to this page's real content and asserts `checkG4` catches it. | product | `packages/aeg-core/bin/verify-registry.ts` |
|
|
140
145
|
| G5 — role/contract integrity | Ever had a process doc reference a role that was never actually defined? | ci | Re-checks that every role and contract the doctrine references is really defined. | Every `aeg-root/contracts/*.md`'s `producer`/`consumer` names a real `role_id` from `aeg-root/roles/*.md`; every role's `performs`/`refuses_when` frontmatter is present and non-empty. | product | `packages/aeg-core/bin/verify-registry.ts` |
|
|
@@ -147,8 +152,13 @@ Every pull request, on open and on every push, re-runs the same checks in CI:
|
|
|
147
152
|
| workspace-escape | Ever deleted a package only to break a sibling's tests through a path nobody's dependency graph saw? | ci | Re-checks that no source file's constructed filesystem reference reaches outside its own workspace package, or points at a path that does not exist. | Every `readFileSync`/`readFile` call with a literal relative-path argument, and every `new URL('…', import.meta.url)`, across every `.ts`/`.tsx` file under `apps/*`/`packages/*` — resolved against the citing file's own directory and judged against its own two-segment workspace-package prefix (`apps/<name>`, `packages/<name>`), never against `import`/`require` module specifiers (the dependency graph already owns those). Zero I/O in `@attalabs/aeg-core`'s `findWorkspaceEscapes`; the bin supplies the only I/O, walking the working tree once for source content and once for the existence-check universe (files AND directories, so a reference to a directory itself resolves as existing). Registered in `coreCheckRegistry()` (task 17), report-only like `reader-resolvable-prose`/`retired-vocabulary`/`doctrine-portability`. `*.test.ts`/`*.test.tsx` files are excluded from the swept surface entirely (O2, found live 2026-09-04) — this repo's own corpus carried a genuine escape at `packages/sources/src/commands-router-coverage.test.ts` reaching into `apps/cli/src/index.ts`, structurally the same incident this check exists to catch, until that exclusion silenced it alongside `workspace-escape.test.ts`'s own fixture-string false positive; report-only severity stays regardless, since an ordinary (non-test) source file's constructed reference can still exist with nobody having triaged it yet. **Line-scoped under `--diff-only`** (this task): a finding prints only when its own line falls inside a changed hunk of that file's diff against the base. A full sweep is unchanged and still reports every finding in the tree. The hunk parser lives in exactly one place — the shared diff-evidence helper both this and every sibling sweep call — never a second regex per check. Without this, a diff that touched one line of a long doctrine page reported that page's whole standing backlog as if this PR had caused it, and a real new finding was indistinguishable from inherited noise. Registered with `include: ['aeg-root/**/*.md']` (task 12) — pinning/documentation only today, since `runner.ts`'s `shouldSkip` returns before consulting `include` for a `scope: 'full'` entry (out of that task's surface); a future `runner.ts` change extending `--diff-only` skipping to `scope: 'full'` finds this already declared. (this sweep's real domain is `apps`/`packages`, not `aeg-root` — named per the Issue's own list; harmless either way while `include` has no runtime effect on a `scope: 'full'` entry.) | product | `apps/cli/src/checks/bin/check-workspace-escape.ts` |
|
|
148
153
|
| changeset-coverage | Ever shipped a change to a published package and the release train never picked it up? | ci | Re-checks that a diff touching a published package's own shipped files also carries a changeset in the same diff. | For each member of `.changeset/config.json`'s `fixed` group, a changed path counts as SHIPPED iff it falls under that member's own `package.json` `files` allowlist, read live from every workspace member's own manifest — never a hardcoded path list, never a directory heuristic. A diff that hits at least one shipped path with no `.changeset/*.md` entry (excluding `README.md`) in the same diff is a finding, naming the shipped paths hit. The Changesets-release branch itself is exempt by construction (its diff IS the changesets being consumed) — the exemption reuses `@attalabs/aeg-core`'s own release-branch constant, the same one `body-bare-digits`'s Changesets-release exemption is keyed on, rather than inventing a second branch-name special-case; unlike that check it never live-fetches a PR author, since a report-only, always-exit-`0` check has no gate for a spoofed branch name to bypass. Written after four separate published-package pull requests, over the course of one day, each merged with no changeset and caught only by a human after the fact — the obligation existed in no enforceable place before this check: not in `roles/developer.md`'s deliverable conventions (now amended alongside this row), not in any registered check. Zero I/O in the pure predicate (`changeset-coverage-logic.ts`); the bin supplies the only I/O — reading the config, every fixed-group member's own manifest, and the diff. Registered in `coreCheckRegistry()`, report-only like `reader-resolvable-prose`/`retired-vocabulary`/`doctrine-portability`/`workspace-escape` above — graduating to a blocking check, and any waiver-label escape that would need, is a later, separately-dispatched decision once the false-positive rate is observed; v1 carries no such escape. | product | `apps/cli/src/checks/bin/check-changeset-coverage.ts` |
|
|
149
154
|
| quoted-command | Ever read a doc that quoted a command as "what runs today", and the command changed underneath it? | ci | Re-checks that a doc's explicitly marked quote of a command or config line still matches, verbatim, the file it names as its source. | Marker-based only, by explicit Principal decision, not inferred from command-looking spans: a doc opts a span in with `<!-- AEG:QUOTES-FILE:START:<path> -->…<!-- AEG:QUOTES-FILE:END -->` (the same invisible-on-render, code-blind HTML-comment idiom `anchored-region.ts` uses for its own seven PR/Issue-body fields, reusing that file's masking primitive rather than a second parser — but a distinct marker kind, since the cited path is free text, not one of that file's seven closed field names). An adopter-facing command in a README with no marker is instruction, not a claim, and is never flagged — inference over command-looking spans was explicitly rejected as the false-positive shape that gets a gate disabled. `findCitedQuotes` sweeps the identical `ships`/`reader-facing` corpus `reader-resolvable-prose` sweeps (never a second notion of "governed doc"); `evaluateCitedQuotes` then checks each marked span against its cited file's live content, wherever that file lives in the repo, naming both sides in a finding — what the doc claims, and which file no longer contains it verbatim. Zero I/O in `@attalabs/aeg-core`'s pure predicate (`quoted-command.ts`), corpus-tested against the real `aeg-root/**` tree; the bin supplies the only I/O. Ships with one real, live annotation (this very paragraph's own sibling callout above, quoting the CI invocation this page names) rather than only synthetic fixtures. Written after a measured incident: a CI invocation was pinned to a version in one pull request; this page quoted the pre-pin form verbatim as current fact; every registered check passed, `doctor` reported healthy, security passed, and a code-review agent found the drift only after independent re-derivation. Registered in `coreCheckRegistry()`, report-only like `reader-resolvable-prose`/`retired-vocabulary`/`doctrine-portability`/`workspace-escape`/`changeset-coverage` above — graduating to a blocking check is a later, separately-dispatched decision once the false-positive rate is observed against real adopter corpora; v1 carries no waiver escape. | product | `apps/cli/src/checks/bin/check-quoted-command.ts` |
|
|
150
|
-
| token-report | Ever seen a cost figure nobody could trace to a source? | ci | Re-checks that a PR's "Token report" section actually carries real numeric Tokens in/out figures, on any host this repo can prove is metering-capable. | The presence and shape of the `## Token report` block `roles/developer.md` requires every self-metering role to paste at turn-end — never whether the reported figures are TRUE, which CI structurally cannot recompute (the same bounded honesty this page's `evidence-fresh` row states for its own Group B). Runs `resolveMeteringCapability` (task 1's probe, `@attalabs/aeg-core`) fresh in THIS process; an incapable verdict (any reason) passes silently — the sanctioned operator-metered case, same discipline `token-collection-wired` above already applies. **Ring derived mechanically as `1`, not chosen** (a 2026-08-29 amendment correcting the original brief rationale's now-retracted ring-0/1 instruction): `requiresOpenPr: true` makes this check CI-only by `CoreCheckRing`'s own derivation rule, since the PR body it reads does not exist before a PR does. A probe that itself fails to run (an unexpected `exists`/`readFile` throw the probe does not catch) is never read as a clean incapable verdict: the bin lets it propagate uncaught, which the runner surfaces as `status: 'error'`, distinct from the `status: 'pass'` a real incapable verdict produces. | product | `apps/cli/src/checks/bin/check-token-report.ts` |
|
|
151
|
-
|
|
155
|
+
| token-report | Ever seen a cost figure nobody could trace to a source? | ci | Re-checks that a PR's "Token report" section actually carries real numeric Tokens in/out figures, on any host this repo can prove is metering-capable. | The presence and shape of the `## Token report` block `roles/developer.md` requires every self-metering role to paste at turn-end — never whether the reported figures are TRUE, which CI structurally cannot recompute (the same bounded honesty this page's `evidence-fresh` row states for its own Group B). Runs `resolveMeteringCapability` (task 1's probe, `@attalabs/aeg-core`) fresh in THIS process; an incapable verdict (any reason) passes silently — the sanctioned operator-metered case, same discipline `token-collection-wired` above already applies. **Ring derived mechanically as `1`, not chosen** (a 2026-08-29 amendment correcting the original brief rationale's now-retracted ring-0/1 instruction): `requiresOpenPr: true` makes this check CI-only by `CoreCheckRing`'s own derivation rule, since the PR body it reads does not exist before a PR does. A probe that itself fails to run (an unexpected `exists`/`readFile` throw the probe does not catch) is never read as a clean incapable verdict: the bin lets it propagate uncaught, which the runner surfaces as `status: 'error'`, distinct from the `status: 'pass'` a real incapable verdict produces. **A ledger row must exist, not merely be well-formed when present** (O13): before this task, `!capability.capable` returned a silent pass BEFORE checking whether the body carried a "Token report" row at all, so a merged task on an incapable host could leave the ledger with no row at all for its own spend — a silent hole, never a stated `—`. The row-existence check now runs unconditionally (any capability), and only the numeric-cell shape check stays capability-gated — `tranche-model.md` §12's operator-metered case writes `—` in a row; it never omits the section. | product | `apps/cli/src/checks/bin/check-token-report.ts` |
|
|
156
|
+
| Bare code-fact digits in a PR body | Ever had a PR body cite a line number that drifted the moment the file changed? | ci | Re-checks that a pull request's narrative prose carries no bare `<path>.<ext>:<digits>` code-fact pointer outside a fenced code span or a `Premise:` pin. | A bare digit anywhere in the body outside a fenced/indented code block or a documented anchor (`Closes`/`Project`/`Tier`/`Evidence`) — an Issue/PR ref, a date, a version, a path, a section number, a pass count, a duration, all need their own backticks or a fenced block, never bare prose. Reports every violation the body carries in one pass, not only the first (O12) — a mechanically-fixable body is corrected in one round, not one round-trip per digit. Its own workflow (`ownWorkflow: true`), `pull_request_target`-triggered only, never `pull_request` — a live-fetched author/branch check a `pull_request`-triggered run of this same file could otherwise spoof via the PR's own workflow YAML. | product | `apps/cli/src/checks/bin/check-body-bare-digits.ts` |
|
|
157
|
+
| Evidence-block freshness | Ever seen a PR's own "tests pass" claim go stale the moment someone pushed again? | ci | Re-checks that a PR's `AEG:EVIDENCE` block still matches a fresh recompute at the PR's current head. | Group A (the diff stat) is recomputed exactly and byte-compared — a hand-typed or stale diff stat cannot survive this. Group B (the real gate run) and Group C (the `[agent]` fenced command list `vinaya pr report` runs) are checked for staleness against the current head, never re-run here — this closes fabrication for the two facts a checker can cheaply recompute, never for the Decisions section's prose. Resolves the real PR head via `gh pr view --json headRefOid`, never `HEAD` (the merge commit in CI). | product | `apps/cli/src/checks/bin/check-evidence-fresh.ts` |
|
|
158
|
+
| Documentation gate (PR open/edit) | Ever opened a PR, watched the doc-coverage gate pass, then pushed a second commit that broke it? | ci | Re-checks C5 doc-coverage — the SAME code→doc binding the push-time row above enforces — again at PR create/edit time, not only on push. | Every code change matching a `.vinaya/doc-owners` binding carries its owning doc in the same diff, a `Doc-ack:`/`Doc-neutral:` declaration, or an actor-verified `vinaya/waiver:docs` label, resolved live via `gh` from `PR_NUMBER` (fixing a prior silent-unreachable label lookup in an adopter's CI, where the label was never actually forwarded to this check). `roles/developer.md` names this as one of two chokepoints "at every push... and again at pull-request creation and editing" — this row is the second, previously undocumented despite already shipping and already running from `vinaya pr create`'s own pre-flight. | product | `apps/cli/src/checks/bin/check-doc-coverage.ts` |
|
|
159
|
+
| Surface-scope (out-of-boundary file) | Ever had a "small" task quietly touch a file its own plan said it wouldn't? | ci | Re-checks that a task branch's changed files stay inside its own Issue's declared `## Surface` — never inside a declared `out:` glob. | Every file in the branch's diff against `origin/main` is checked against the task's own Issue (resolved from the branch name → forge topology → Issue number → `## Surface` `out:` list) with the same `globCoversPath` predicate the Issue-authoring gate and the blast-radius check (O4) both already use — an undeclared boundary crossing is caught mechanically, naming the file and the glob it crosses, rather than depending on a reviewer noticing. Same check runs at the `git push` hook (ring 0) and again here — same runner, one codebase, two enforcement points. Dormant (never blocks) when the branch is not a task branch, the forge/repo cannot be resolved or reached, the task's row carries no Issue yet, the Issue's `## Surface` doesn't parse (below the brief-sections cutover, or malformed — caught at authoring time instead), or the Issue declares no `out:` globs at all. | product | `apps/cli/src/checks/bin/check-surface-scope.ts` |
|
|
160
|
+
| PR-body premise reassertion | Ever had a brief's pinned fact quietly go stale, and the merge never noticed? | ci | Re-checks, in CI, that a pull request body's `Premise:` pins still hold against the PR's own current tree — not only at Step 0, authoring time. | Every `contains`/`absent`/`sha256` pin `parsePremiseBlock` extracts from the PR body, re-asserted via the same frozen `checkPremises`/`reassertPremiseFile` (`apps/cli/src/checks/premise-reassert-logic.ts`) `verify-dispatch --premise` already uses at Step 0 — a second caller, not a second implementation, so a pin the PR's own diff falsifies fails CI instead of merging as decoration. Trigger is the block's presence alone: a body with no `Premise:` header parses to zero pins and this check is silent, on every branch — there is no branch-name condition anywhere in it. | product | `apps/cli/src/checks/bin/check-pr-premise-reassert.ts` |
|
|
161
|
+
**G-checks rollout (task 3):** G1/G2 shipped report-only at first — they could only ever print an `info` finding, never fail CI, so pre-existing gaps (four orphan hooks, several orphan `bin/*.ts` CLIs found on first run) surfaced as visible debt without retroactively failing in-flight work. G3/G4/G5 have been blocking since that same tranche. **G1 flipped to blocking in task 8**, once its report-only window had let the orphan backlog get cleaned up — a permanent `info` finding on every run is indistinguishable from silence, which is exactly how the gap task 8 closes (G6, below) stayed invisible for as long as it did. **G2 flipped to blocking in this same wave**, the same way: its remaining backlog was fourteen files that were never orphans at all, only the second physical form of an already-documented mechanism G2's own string-equality test was too narrow to see (closed by the twin-form recognition described in G2's own row, above) plus two genuinely non-gate files (`NON_GATE_BINS` already named them) — a standing `info` finding on every run had become exactly the same silence-by-permanence G1's report-only window risked, which is the failure a gate that "trains its readers to ignore warnings" produces. **G6 — doctrine-registry parity**, also added in task 8, ships blocking from the start: it checks that every row this page marks `product` (the `Audience` column, above) actually resolves to a real `coreCheckRegistry()` entry, closing the gap where a row could claim shipped enforcement that no adopter's `vinaya check` ever actually ran.
|
|
152
162
|
|
|
153
163
|
**reader-resolvable-prose / retired-vocabulary rollout (task 15; task 7 registered both in `coreCheckRegistry()`):** both ship report-only, same precedent as G1/G2 above — each can only ever print a `warning` finding, never fail CI (the check's own exit code stays `0`), so any real backlog surfaces as visible debt rather than a wave of newly-red pull requests (161 findings, all class 2, across `aeg-root` on the day `reader-resolvable-prose` first shipped). A later, separately-dispatched task flips either to blocking once its own backlog is cleared. **`reader-resolvable-prose`'s `ships`/`reader-facing`/vocabulary classes stay report-only under this rollout; `retired-vocabulary` is unaffected in full.** Only `reader-resolvable-prose`'s fourth, `product` class (see the row above) graduated to blocking, ahead of the rest: a tranche-slug citation in product code fails outright, at both `git push` and CI. **Exception, both checks (checkout-independent doctrine-root resolution):** the unconfigured doctrine-root default now resolves relative to the repo actually under check (its own real repository root) first, falling back to the check's own package-relative "shipped copy" resolution only when the repo under check has no local doctrine tree of its own — so the same commit sweeps identically regardless of where it happens to be checked out. When NEITHER resolves (no doctrine tree findable at all, relative to either anchor), that is not a `warning` finding and not a clean pass — it is reported as its own distinct outcome, with a non-`0`/non-`1` exit code, so a scan that never ran cannot be mistaken for a scan that ran and found nothing.
|
|
154
164
|
|
|
@@ -203,7 +213,7 @@ A full audit of this page against the installed hooks and CI workflows (2026-07-
|
|
|
203
213
|
| `verify-task` (pre-PR exit composite) | ✅ **new** — `open-pr.ts` runs it wholesale for task branches (`gatePlanForBranch`); on `edit <n>` the gate set now resolves from the target PR's **actual forge head branch** (`gh pr view --json headRefName`, fetch failure = hard refusal), not the local checkout/`BRANCH` env, which silently skipped `closes-n`/`verify-task` on task-PR body edits from a `main` checkout (task 36 bundled finding) | — | — | **Forced, ring-0 only (this task, item 2)**; **edit-path gate-selection bypass fixed** (task 36) | was (b) — closed; ring-1/2 backstop not yet built (same accepted shape as the branch-ID check below) |
|
|
204
214
|
| Runtime Test Plan checkbox state (`verify-test-plan`) | n/a (CI-only check) | ✅ now a step of the AEG gate suite job (`verify-test-plan.yml` deleted — task 31; still runs on PR-body `edited` events) — **reads `BRANCH`; parses both the inline `**Test Plan:**` marker and the `## N. Test Plan` heading form** (task 25, item 3) | — | Paired (pre-existing); **parser bug fixed** | n/a — not a coverage gap, a correctness fix (a live-fire gap: heading-form sections were never matched, so a task PR with an unticked `[principal]` box advisory-PASSED) |
|
|
205
215
|
| Waiver-label-actor verification (task 29; **extended to `vinaya/waiver:review`, review-gate task 1**) | ✅ **new** — pre-push is warn-only (no `vinaya/waiver:docs`/`vinaya/waiver:review` label can exist before a PR does) **+ tool-layer (attalabs only)**: the attalabs reference implementation adds a session hook denying any agent-session command that mutates either label; this repo has no tool-interception layer (see the raw-API ring-0 row), so the shared-credential hole is OPEN here — a local agent using the Principal's own `gh` credential can apply a waiver label whose timeline actor then reads as the Principal, and the actor-verification step cannot distinguish it. Until an interception layer exists, waiver labels in this repo rest on credential discipline, not a gate | ✅ **new** — a GraphQL `LABELED_EVENT`/REST timeline step resolves the label's actor, verified by `isWaiverLabelActorVerified` (now parameterized by label name) and fed into `verify-docs --pr`'s C5 waiver check (`vinaya/waiver:docs`) and `verify-review-gate.ts`'s review check (`vinaya/waiver:review`) | — | **New (this task)**; **extended (review-gate task 1)** | n/a — new mechanism |
|
|
206
|
-
| Review gate — code-reviewer + security-review verdicts (review-gate task 1) | n/a (CI-only check; the underlying verdict comments come from a human, a dispatched reviewer subagent, or the AI-review job) | ✅ **new** — `verify-review-gate.ts`, a step of the AEG gate suite job. Blocks merge unless a clean code-reviewer `APPROVE` AND a clean security-review `PASS` verdict comment exist, both bound to the PR's current head commit (`headRefOid` — a stale or unbound verdict fails the same as a missing one), or the PR carries an actor-verified `vinaya/waiver:review` label | — | **New (this task)**; **reviewed-commit binding added (review-gate-verdict-sha-binding)**; **objectives-version binding added by a later task — implemented in `apps/cli/src/checks/bin/check-review-gate.ts`, this repo's actual live gate path, not in this row's own `verify-review-gate.ts`, which never resolves it (see the Ring 1 Review gate row above)** | n/a — new mechanism (closes the gap where task PRs could previously merge with no review pass at all — the post-merge Archivist's `extractVerdict`/DANGLING flag was advisory-only; the reviewed-commit binding closed a second gap where a clean verdict could still merge stale code because nothing checked which commit it covered) |
|
|
216
|
+
| Review gate — code-reviewer + security-review verdicts (review-gate task 1) | n/a (CI-only check; the underlying verdict comments come from a human, a dispatched reviewer subagent, or the AI-review job) | ✅ **new** — `verify-review-gate.ts`, a step of the AEG gate suite job. Blocks merge unless a clean code-reviewer `APPROVE` AND a clean security-review `PASS` verdict comment exist, both bound to the PR's current head commit (`headRefOid` — a stale or unbound verdict fails the same as a missing one), or the PR carries an actor-verified `vinaya/waiver:review` label | — | **New (this task)**; **reviewed-commit binding added (review-gate-verdict-sha-binding)**; **objectives-version binding added by a later task — implemented in `apps/cli/src/checks/bin/check-review-gate.ts`, this repo's actual live gate path, not in this row's own `verify-review-gate.ts`, which never resolves it (see the Ring 1 Review gate row above)**; **ruling-freshness binding added by `review-validity-v1` task 3 — no divergence here: both `check-review-gate.ts` and `verify-review-gate.ts` count real ruling comments off `pr.comments`, already fetched by each before `checkReviewGate` is called** | n/a — new mechanism (closes the gap where task PRs could previously merge with no review pass at all — the post-merge Archivist's `extractVerdict`/DANGLING flag was advisory-only; the reviewed-commit binding closed a second gap where a clean verdict could still merge stale code because nothing checked which commit it covered) |
|
|
207
217
|
|
|
208
218
|
This table describes **installed mechanisms only** (the page's own change-discipline rule, below). The `verify-dispatch`/`verify-task` rows and the `verify-test-plan` fix landed in task 25's own change set, per that rule.
|
|
209
219
|
|
|
@@ -58,6 +58,8 @@ The same three states a tranche derives (`tranche-model.md` §11, one altitude d
|
|
|
58
58
|
|
|
59
59
|
Nobody sets this by hand and nothing writes it back to the Milestone object — it's asked of the forge the same way every other altitude is.
|
|
60
60
|
|
|
61
|
+
`vinaya milestone status <n>` is that ask, made concrete: for every `- <slug>: …` line in a Milestone's `### Tranche intents` section, it prints the slug, its derived lifecycle, and its labeled Issues' counts (`merged`/`open`/`not planned`) — read-only, nothing written. A slug with an intent line and zero labeled Issues yet prints `planned` with `0 issues`, per §1: a real, not-yet-started tranche, never an error.
|
|
62
|
+
|
|
61
63
|
### The trap this level actually has: a closed legacy Milestone with real work still under it
|
|
62
64
|
|
|
63
65
|
`vinaya milestone adopt` closes the old one-tranche Milestone it retires — closed, never deleted, so the provenance survives. The closed Milestone still legacy-title-matches its slug forever (§1's exception). Found live, `0.19.1`: a reader that trusts a closed legacy Milestone's own `state` unconditionally reports the tranche `complete` even when its real, still-open Issues have already moved to a new Milestone via `adopt` — the tranche's true lifecycle lives in the label population, not in the Milestone object `adopt` walked away from. The fix: when a legacy-matched Milestone is closed AND its slug's labeled Issues are non-empty, derive from those Issues, not from the closed Milestone's `state`. An empty label population under a closed legacy Milestone still means what it always meant — a genuinely historical tranche, or one nobody has adopted away from yet.
|
package/aeg-root/process.md
CHANGED
|
@@ -5,30 +5,28 @@ sidebar_title: Process
|
|
|
5
5
|
|
|
6
6
|
This document describes how work flows through the AEG operational model — from the moment the Principal has an idea to the moment that work merges to `main` with all specs and skills updated.
|
|
7
7
|
|
|
8
|
-
It is the canonical "how do we actually work?" document. Every other PM doc (`state-machine.md`, `tranche-model.md`, role docs
|
|
8
|
+
It is the canonical "how do we actually work?" document. Every other PM doc (`state-machine.md`, `tranche-model.md`, role docs) describes a slice of this process. This document stitches them together into a single readable walkthrough.
|
|
9
9
|
|
|
10
|
-
If you are starting a new session and need to understand the workflow, read this first. Then the `aeg` skill's session-start forge queries for orientation, then the role doc that applies to you, then any project-specific specs.
|
|
10
|
+
If you are starting a new session and need to understand the workflow, read this first. Then the `aeg` skill's session-start forge queries for orientation, then the role doc that applies to you, then any project-specific specs. Coined vocabulary used below (Dispatch, Impact tier, Ratification, …) is defined in the glossary at aeg-root/glossary.md.
|
|
11
11
|
|
|
12
12
|
---
|
|
13
13
|
|
|
14
14
|
## Where tasks come from: the tranche
|
|
15
15
|
|
|
16
|
-
The
|
|
16
|
+
The phases below are the **per-task** flow. Tasks do not appear from nowhere — they are produced by the **Planner**'s plan act when a tranche is planned: the Planner turns an intent plus a slice of tickets into a set of **forge Issues** (one per task) plus a thin topology file declaring their `depends-on` / `conflicts-with` edges (`tranche-model.md`, `roles/planner.md`). Each Issue that enters the flow below is a task the Planner already shaped — its rationale and judgment sections already written onto the Issue at plan time.
|
|
17
17
|
|
|
18
18
|
**Status is never stored.** Throughout every phase, a task's status is *derived* from the forge — Issue open/assigned, branch existence, PR open, review decision, merge — never written to a label or a file. When a phase below says a task "becomes in-review," it means *a PR was opened*, not that anyone set a status field.
|
|
19
19
|
|
|
20
20
|
---
|
|
21
21
|
|
|
22
|
-
## The
|
|
22
|
+
## The phases
|
|
23
23
|
|
|
24
|
-
Every piece of work moves through some subset of these. Trivial work (Tier 0) skips most; complex work (Tier 3) hits all and may loop back from review.
|
|
24
|
+
Every piece of work moves through some subset of these. Trivial work (Tier 0) skips most; complex work (Tier 3) hits all and may loop back from review. Numbered 1–13 for historical continuity with every cross-reference below and across `aeg-root/**`, but **not thirteen distinct phases any more**: the former Phases 3–5 (brief authoring, brief validation, dispatch) collapsed into one Planner phase, still numbered 3, performed by one command (`vinaya task dispatch`) — see Phase 3 below for why the numbers 4 and 5 are retired rather than reused. Every phase from 6 onward keeps its original number unchanged, so no downstream cross-reference in the doctrine tree needed to move.
|
|
25
25
|
|
|
26
26
|
```
|
|
27
27
|
1. Idea origination
|
|
28
28
|
2. Pressure-testing (optional; high-stakes only)
|
|
29
|
-
3.
|
|
30
|
-
4. Brief validation
|
|
31
|
-
5. Dispatch
|
|
29
|
+
3. Dispatch (brief authoring + validation + dispatch, one Planner phase — 4 and 5 retired into this one)
|
|
32
30
|
6. Execution
|
|
33
31
|
7. Escalation (optional; only when Developer blocks)
|
|
34
32
|
8. Task Done verification
|
|
@@ -49,11 +47,11 @@ After merge, the **Archivist** runs close-out (`roles/archivist.md`). That's the
|
|
|
49
47
|
|
|
50
48
|
**Who:** Principal and Planner.
|
|
51
49
|
|
|
52
|
-
The Principal brings an idea. The
|
|
50
|
+
The Principal brings an idea. The Planner pressure-tests, pushes back, surfaces related decisions, checks whether it's already specced. The Planner's job here is **not** to immediately agree and plan — it's to:
|
|
53
51
|
- Read the relevant specs to confirm the idea isn't already settled
|
|
54
52
|
- Push back if it's wrong, premature, or duplicative
|
|
55
53
|
- Identify the impact tier (0 / 1 / 3) — this drives everything downstream
|
|
56
|
-
- Identify the Type 1 (irreversible — Principal ratifies) vs Type 2 (reversible —
|
|
54
|
+
- Identify the Type 1 (irreversible — Principal ratifies) vs Type 2 (reversible — Planner ratifies) profile
|
|
57
55
|
|
|
58
56
|
If already locked or specced, the conversation ends here. If genuinely new, it produces a shared understanding of what the work is, why now, its tier, and its decision profile — which the Planner then turns into Issues (the tranche).
|
|
59
57
|
|
|
@@ -65,76 +63,56 @@ If already locked or specced, the conversation ends here. If genuinely new, it p
|
|
|
65
63
|
|
|
66
64
|
## Phase 2: Pressure-testing (optional)
|
|
67
65
|
|
|
68
|
-
**When:** high-stakes only — architectural locks, project-direction shifts, decisions blocking weeks of downstream work, or when the Principal's instinct and the
|
|
66
|
+
**When:** high-stakes only — architectural locks, project-direction shifts, decisions blocking weeks of downstream work, or when the Principal's instinct and the Planner's read disagree. **Not** for tactical decisions, naming, or style. The Principal may waive it and ratify in-session (the pull request notes the skip, for audit honesty).
|
|
69
67
|
|
|
70
|
-
**Who:**
|
|
68
|
+
**Who:** Planner orchestrates; external AI reviewers (vendor-diverse — independently hosted, each from a provider other than the one drafting the brainstorming brief) participate via pasted briefs.
|
|
71
69
|
|
|
72
|
-
The
|
|
70
|
+
The Planner writes a brainstorming brief (idea, sketch, alternatives, what to pushback on), pairs it with `reviewer-prompt.md`, and pastes to each reviewer. The Planner synthesizes. Converge on a flaw → back to Phase 1. Validate → proceed. **Max two rounds** — if two don't converge, the issue is framing, not a third round.
|
|
73
71
|
|
|
74
72
|
This phase pressure-tests an *idea*; Phase 10 reviews *shipped code*. Different things.
|
|
75
73
|
|
|
76
|
-
**Exit:**
|
|
74
|
+
**Exit:** Planner and Principal agree the direction holds.
|
|
77
75
|
|
|
78
76
|
---
|
|
79
77
|
|
|
80
|
-
## Phase 3:
|
|
78
|
+
## Phase 3: Dispatch (brief authoring + validation + dispatch)
|
|
81
79
|
|
|
82
|
-
**Who:** Planner
|
|
80
|
+
**Who:** Planner, in its dispatch act (`roles/planner.md` § The dispatch act).
|
|
83
81
|
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
82
|
+
**Formerly three phases (brief authoring, brief validation, dispatch); now one.** The brief is no longer hand-authored just-in-time by a separate Brief Author role — its judgment content (the eight-field rationale, `## Objectives`, and the four judgment sections `## Surface`/`## Parts`/`## Test plan`/`## Stop conditions`) was already written onto the task Issue at plan time (Phase 1), and validated then, by the same ring-0 creation gate and R1 continuous check that grade every open task Issue (`roles/planner.md` § Rationale grammar). What used to be three sequential human/CI steps is now one mechanical render plus one gate check, both performed by a single command.
|
|
83
|
+
|
|
84
|
+
Before dispatching a task, the Planner checks four gates (`roles/planner.md` § The dispatch act):
|
|
85
|
+
1. **Issue exists** — a real forge Issue number, not `#TBD`, not blank.
|
|
86
|
+
2. **Dependencies merged** — every `depends-on` task's PR is on `main`.
|
|
87
|
+
3. **No open conflicting sibling** — no `conflicts-with` task has an open PR.
|
|
88
|
+
4. **Render complete** — the brief render can derive every mechanical section from the Issue and the tree with no gap.
|
|
89
|
+
|
|
90
|
+
`vinaya check dispatch-readiness`, run against the task, re-derives gates 1–3 mechanically in one command.
|
|
91
|
+
|
|
92
|
+
**Running the dispatch:** `vinaya task dispatch <tranche> <n> [--agent claude | codex | gemini]` performs the whole phase once the gates pass: it renders the brief (the same render `brief render` uses, from `aeg-root/templates/brief-template.md` and every derivable fact), posts it once, frozen, as the task Issue's own `aeg:brief:v1` comment, then — with `--agent` — starts the Developer through `dispatchRole`. Without `--agent` it prints the rendered brief and the manual dispatch instruction and exits `0`, so a human can paste it instead. `vinaya task dispatch` is deprecated: `vinaya task run <tranche> <n> --agent <claude | codex | gemini>` is the one command that now runs this whole phase AND the phases after it — preparation through this same render/freeze/post, then the developer/review loop — unattended, from one planned Issue to a reviewed pull request, exactly one developer started; a paused run resumes with the loop's own `vinaya dev-review-loop --resume <pr>`. The brief itself MUST include (all mechanically derived or refused on):
|
|
93
|
+
- Impact tier (0 / 1 / 3), rendered from the Issue's tier declaration
|
|
94
|
+
- Type 1 / Type 2 declaration if architectural decisions are expected, gated at dispatch (a PENDING Type 1 decision blocks dispatch)
|
|
87
95
|
- `principal_delegate:` if the work runs while the Principal is offline
|
|
88
96
|
- Spike flag (`spike: true`) if exploratory
|
|
89
|
-
- The mandatory worktree-first Step 0 (`git worktree add .worktrees/task/<tranche>/<n> -b task/<tranche>/<n> origin/main` — no exceptions)
|
|
90
|
-
- An explicit documentation-update list tied to the tier
|
|
97
|
+
- The mandatory worktree-first Step 0 (`git worktree add .worktrees/task/<tranche>/<n> -b task/<tranche>/<n> --no-track origin/main` — no exceptions), rendered from the task's forge-derived branch id
|
|
98
|
+
- An explicit documentation-update list tied to the tier, mechanically derived against `.vinaya/doc-owners`
|
|
91
99
|
- Optional `Ticket:` (reference-only provenance) and, in a multi-project repo, `Project:` (resolves against `projects.md`)
|
|
92
100
|
- Clear scope, stop conditions, Task Done checklist
|
|
93
101
|
- A deliverable section stating "done" means "passed Phase 11 verification," not "PR opened"
|
|
94
102
|
|
|
95
|
-
A brief
|
|
103
|
+
A brief with any `[NEEDS CLARIFICATION: …]` marker left in it is not dispatched — the Planner resolves the ambiguity on the Issue's rationale (or puts it to the Principal) and re-renders. A brief is self-contained and executable without further conversation.
|
|
96
104
|
|
|
97
|
-
**Where the brief lives:**
|
|
105
|
+
**Where the brief lives:** the task Issue's own `aeg:brief:v1` comment, posted once, frozen, before the Developer starts — never in the PR body, which carries only the Developer's report. It is **not** committed and **not** put in the Issue's own body — the Issue body (written by the Planner at plan time) holds task identity + metadata + the rationale + judgment sections; the brief is a rendered comment on it, not a field of it.
|
|
98
106
|
|
|
99
107
|
**Artifacts:** the brief (a markdown block, not a committed file). The task's Issue already exists from tranche planning.
|
|
100
108
|
|
|
101
|
-
Before dispatch, a fresh-context Reviewer runs **Brief review mode** (`roles/reviewer.md`) on the brief
|
|
102
|
-
|
|
103
|
-
**Exit:** the brief is well-formed, has passed Brief review mode, and is ready to dispatch.
|
|
104
|
-
|
|
105
|
-
---
|
|
106
|
-
|
|
107
|
-
## Phase 4: Brief validation
|
|
108
|
-
|
|
109
|
-
**Who:** Archivist gate (automated) + the Developer's own entry gate.
|
|
110
|
-
|
|
111
|
-
Before work begins, the brief is checked for well-formedness:
|
|
112
|
-
- `tier:` present and valid; brief structure follows the skill (scope, stop conditions, deliverable)
|
|
113
|
-
- `principal_delegate:` valid if declared
|
|
114
|
-
- `Project:` (if present) resolves against the registry
|
|
115
|
-
- No obvious contradiction with the ratified specs (string-match heuristic)
|
|
116
|
-
- The task's Issue carries only execution metadata — **no** planning fields (priority/estimates/points), which the required Issue template + a CI check reject
|
|
117
|
-
|
|
118
|
-
If it fails, it's not dispatchable until fixed. The Developer also re-checks well-formedness as its entry gate (`roles/developer.md`) — a malformed brief is refused, not guessed at. (Brief Validation is a real blocking gate — the `Brief Validation` step (9/9) of the `aeg-gate-suite` job; see `state-machine.md` for mechanically-enforced vs trusted.)
|
|
119
|
-
|
|
120
|
-
**Exit:** the brief is well-formed and dispatchable.
|
|
121
|
-
|
|
122
|
-
---
|
|
123
|
-
|
|
124
|
-
## Phase 5: Dispatch
|
|
125
|
-
|
|
126
|
-
**Who:** Principal (or delegated Brief Author within ratification-window scope), by hand or via an automation layer.
|
|
127
|
-
|
|
128
|
-
Dispatch starts the task. There are two equivalent routes:
|
|
129
|
-
|
|
130
|
-
- **Manual:** `vinaya task dispatch <tranche> <n>` posts the frozen `aeg:brief:v1` Issue comment, then the Principal pastes that comment's content into the coding agent. The brief's worktree-first Step 0 makes the Developer create its own worktree (`.worktrees/task/<tranche>/<n>/`, branch `task/<tranche>/<n>`, from `origin/main`) as its first action.
|
|
131
|
-
- **Automated:** an automation layer creates the worktree, generates the agent's config, spawns the Developer in it, and streams progress. This is a convenience; the semantics are identical to manual.
|
|
109
|
+
Before dispatch, a fresh-context Reviewer runs **Brief review mode** (`roles/reviewer.md`) on the rendered brief — a separate, time-boxed pass from the post-dispatch code review, under five minutes, returning one line, `BRIEF: READY` or `BRIEF: NOT READY`, with findings limited to two classes: a contradiction between two of the brief's own sentences, or a design the brief specifies that the party it constrains can defeat or that fails on an input the brief never named. `BRIEF: NOT READY` returns the rationale to the Planner rather than letting the render reach dispatch.
|
|
132
110
|
|
|
133
|
-
|
|
111
|
+
Once dispatched: **before starting, the Developer independently re-checks the same dispatch gates against the forge** (`roles/developer.md` entry gate) — every `depends-on` task's PR merged, no `conflicts-with` sibling's PR open, the branch-name suffix literal-matching the forge-derived id. If a gate isn't satisfied, it does not start (the task serializes). Opening the branch *is* the `todo → in-flight` transition; nobody writes a status label.
|
|
134
112
|
|
|
135
113
|
The branch name `task/<tranche>/<n>` is the convention that links the task to its branch and PR, so any role can derive its live status with one forge query.
|
|
136
114
|
|
|
137
|
-
**Exit:** the Developer is working in its worktree.
|
|
115
|
+
**Exit:** the brief is rendered, has passed Brief review mode, is posted frozen on the Issue, and the Developer is working in its worktree.
|
|
138
116
|
|
|
139
117
|
---
|
|
140
118
|
|
|
@@ -158,15 +136,15 @@ The Developer cannot author its own briefs, expand scope without escalation, mod
|
|
|
158
136
|
|
|
159
137
|
The Developer escalates through the escalation mechanism — a manual escalation note, or, if dispatched by an automation layer, its request-input mechanism — tagged with a `severity` that routes it:
|
|
160
138
|
|
|
161
|
-
- `severity: execution` →
|
|
162
|
-
- `severity: strategy` →
|
|
139
|
+
- `severity: execution` → Planner. Most common: a deprecated dependency, an unanticipated flag, a "null or throw?" call.
|
|
140
|
+
- `severity: strategy` → Planner. Less common: the brief's approach has a structural problem; the work touches an undiscussed area.
|
|
163
141
|
- `severity: product` → Principal. Rare: user-visible behavior the brief didn't address; a Type 1 decision is required.
|
|
164
142
|
|
|
165
143
|
The task is marked `blocked` (an `aeg:blocked` label — the one status with no native forge fact) until a reply arrives. The responder (Planner or Principal) formulates a reply and the Developer resumes.
|
|
166
144
|
|
|
167
145
|
**Type 1 during execution:** if the question needs an irreversible decision and the Principal isn't available, the Issue/PR stays labeled `needs:principal-input`; the next window resolves it. The Developer may terminate and resume via a follow-up dispatch after the window.
|
|
168
146
|
|
|
169
|
-
**Brief amendment:** if the brief itself is wrong in a way that blocks all paths, the
|
|
147
|
+
**Brief amendment:** if the brief itself is wrong in a way that blocks all paths, the Planner issues an amendment (logged as a separate event, not a brief edit — briefs are frozen after dispatch) or kills the task.
|
|
170
148
|
|
|
171
149
|
**Exit:** the Developer is unblocked and resumes.
|
|
172
150
|
|
|
@@ -193,7 +171,7 @@ Before opening the PR, the Developer runs the tier-appropriate Task Done checkli
|
|
|
193
171
|
|
|
194
172
|
The Developer opens a PR with:
|
|
195
173
|
- Title in commitlint format (`Type: Subject`)
|
|
196
|
-
- **No brief in the body at all** — its permanent home is the task Issue's frozen `aeg:brief:v1` comment (posted at Phase
|
|
174
|
+
- **No brief in the body at all** — its permanent home is the task Issue's frozen `aeg:brief:v1` comment (posted at Phase 3, before the Developer ever started), which is where the Reviewer and Archivist read it
|
|
197
175
|
- A `Tier:` declaration (`Tier: 0|1|3`) so verify-docs reads the correct tier
|
|
198
176
|
- `Closes #N` linking the task's Issue (so the merge auto-closes it)
|
|
199
177
|
- Body following the PR template (what shipped, validated mechanism, what's not in scope, next steps)
|
|
@@ -208,20 +186,20 @@ CI runs typecheck, lint, tests, `verify-docs` (the load-bearing doc gate — fai
|
|
|
208
186
|
|
|
209
187
|
## Phase 10: Review
|
|
210
188
|
|
|
211
|
-
Two stages: independent **agent passes** (fresh-context), then **human reviews** (Principal +
|
|
189
|
+
Two stages: independent **agent passes** (fresh-context), then **human reviews** (Principal + Planner). Agent passes run first and feed the human reviews — they do not replace them.
|
|
212
190
|
|
|
213
191
|
```
|
|
214
|
-
code-reviewer pass → security pass → Principal code review →
|
|
192
|
+
code-reviewer pass → security pass → Principal code review → Planner spec review → merge
|
|
215
193
|
```
|
|
216
194
|
|
|
217
195
|
### Stage A — Agent review passes
|
|
218
196
|
|
|
219
|
-
Each pass is a **separate fresh-context invocation** with no memory of writing the code (the independence rule). Manual: the Principal pastes the review prompt. Automated: the automation layer dispatches the `code-reviewer` and `security-reviewer` passes. The agent reads its role doc + the PR diff + **the brief
|
|
197
|
+
Each pass is a **separate fresh-context invocation** with no memory of writing the code (the independence rule). Manual: the Principal pastes the review prompt. Automated: the automation layer dispatches the `code-reviewer` and `security-reviewer` passes. The agent reads its role doc + the PR diff + **the brief, read from the task Issue's frozen `aeg:brief:v1` comment**, and emits a structured verdict. Review agents do not edit code, do not merge, and do not write status.
|
|
220
198
|
|
|
221
199
|
1. **Code-reviewer pass** — `roles/reviewer.md`. Brief conformance, scope violations, test honesty, code quality, doc coupling, lock awareness, multi-project reach. Emits `VERDICT: APPROVE | REQUEST CHANGES` (BLOCKER / MAJOR / MINOR).
|
|
222
200
|
2. **Security pass** — `roles/security.md`. Secret leakage, BYOK/crypto, auth/permissions, MCP/agent-tooling exposure, injection surfaces, dependency risk. Runs a config-security scan over the agent/MCP/hook config when that config is touched. Emits `VERDICT: PASS | FAIL` (CRITICAL / HIGH / MEDIUM / LOW).
|
|
223
201
|
|
|
224
|
-
A BLOCKER (code) or CRITICAL/HIGH (security) returns the PR to the Developer, who fixes on the **same branch**; the pass re-runs. (Pushing fixes returns the PR's review decision to open — the `changes-requested → in-review` transition, derived.) A verdict binds to the head it judged and the objectives list it judged against; a push or an objectives edit — including a mid-PR scope change made through `vinaya issue objectives edit` — voids the verdict, and a body edit alone changes nothing the gate reads. A voided verdict is named as such: `vinaya review status` prints `push after verdict — re-review required` for the head case, `objectives moved — re-review required` for the objectives case, and merge waits on a fresh review round either way. An escalation (`--escalate authority | strategy | product`) is its own review outcome, never a finding — it routes to
|
|
202
|
+
A BLOCKER (code) or CRITICAL/HIGH (security) returns the PR to the Developer, who fixes on the **same branch**; the pass re-runs. (Pushing fixes returns the PR's review decision to open — the `changes-requested → in-review` transition, derived.) A verdict binds to the head it judged and the objectives list it judged against; a push or an objectives edit — including a mid-PR scope change made through `vinaya issue objectives edit` — voids the verdict, and a body edit alone changes nothing the gate reads. A voided verdict is named as such: `vinaya review status` prints `push after verdict — re-review required` for the head case, `objectives moved — re-review required` for the objectives case, and merge waits on a fresh review round either way. An escalation (`--escalate authority | strategy | product`) is its own review outcome, never a finding — it routes to Planner (`strategy`) or Principal (`authority`/`product`).
|
|
225
203
|
|
|
226
204
|
**A fix commit adds no mechanism beyond what the finding names.** The finding bounds the fix: a flag, a gate, a window, a config knob or a second code path that the finding did not ask for is new design, arriving inside a round that exists to close a defect and reviewed by nobody as design. A finding that genuinely cannot be answered without new mechanism is not a fix at all — it is escalated (`--escalate strategy`) and waits, and the mechanism it needs enters the work the way every other design does: as a constraint amended into the next brief. This is the round-count rule's twin. Rounds multiply when each one both closes something and opens something; a round that only closes is a round the loop can converge out of.
|
|
227
205
|
|
|
@@ -229,7 +207,7 @@ A BLOCKER (code) or CRITICAL/HIGH (security) returns the PR to the Developer, wh
|
|
|
229
207
|
|
|
230
208
|
**Code review (Principal).** The Principal reviews the diff — does it match the brief, scope violations, honest tests, spot-check quality. The agent verdict is an input, not a substitute; the Principal can overrule either way.
|
|
231
209
|
|
|
232
|
-
**Spec review (
|
|
210
|
+
**Spec review (Planner).** Do the specs describe what was built? Is the pull request's stated reasoning honest about what changed? Coherence, not technical correctness (that's the Principal's code review).
|
|
233
211
|
|
|
234
212
|
If both pass (and agent verdicts are APPROVE and PASS, and surfaced findings have been shown to the Principal at the go) → merge. If issues are found → back to the Developer with specific feedback. The loop ends by finding identity, never by round count — per the Review Cycle specification, round two judges only the lines changed since the previously judged head, for every non-blocking severity: a finding outside that delta is surfaced for the Principal's go rather than driving the verdict. A BLOCKER, CRITICAL or HIGH outside the delta still drives the verdict on any round; only MAJOR, MINOR, MEDIUM and LOW outside the delta are surfaced for the Principal's go. After round two the Principal decides, and there is no round three unless the Principal orders it. The loop pauses when a resolved finding reappears, when two consecutive rounds resolve no prior finding, or when one finding stays open three consecutive rounds while others resolve — every trigger is measured from the finding id sets, never from anyone's narrative of progress, and round five is a backstop, not a trigger. On a pause the Principal applies the `vinaya/needs:principal-input` label and works the stall menu cheapest first: a different role in the seat, resume with the trigger overridden, reseed the developer, abandon — a ruling re-enters the work as a constraint amended into the brief.
|
|
235
213
|
|
|
@@ -243,7 +221,7 @@ If both pass (and agent verdicts are APPROVE and PASS, and surfaced findings hav
|
|
|
243
221
|
|
|
244
222
|
**Who:** the Developer-agent (for `[agent]` items) and the Principal (for `[principal]` items). Verification is a *phase*, not a new actor — see `roles/developer.md` § Verification.
|
|
245
223
|
|
|
246
|
-
A PR that has passed code review and security review still has not been run. The reviews read the diff; the static gates of Phase 8 prove the code compiles and types and tests; CI does not boot the app. Phase 11 boots it, executes the brief's **Test Plan** (a required brief field, `
|
|
224
|
+
A PR that has passed code review and security review still has not been run. The reviews read the diff; the static gates of Phase 8 prove the code compiles and types and tests; CI does not boot the app. Phase 11 boots it, executes the brief's **Test Plan** (a required brief field, rendered from the Issue's `## Test plan` section — see `roles/planner.md` § The Planner's rationale), and posts the results onto the PR. Doctrine: **CI green ≠ app boots ≠ feature works** — runtime verification is its own gate.
|
|
247
225
|
|
|
248
226
|
The test plan is split by who can structurally execute each item:
|
|
249
227
|
|
|
@@ -262,7 +240,7 @@ The test plan is split by who can structurally execute each item:
|
|
|
262
240
|
|
|
263
241
|
## Phase 12: Merge
|
|
264
242
|
|
|
265
|
-
**Who:** Principal (or
|
|
243
|
+
**Who:** Principal (or the Planner if explicit per-PR delegation was set in the brief's `principal_delegate:` field).
|
|
266
244
|
|
|
267
245
|
The Principal merges. Tier 3 work merges during a ratification window (`roles/principal.md`); Tier 0/1 anytime. The merge **auto-closes the linked Issue** (via `Closes #N`) — and the merge *is* the `merged` status; nobody writes a label. An automation layer may surface a completion notification.
|
|
268
246
|
|
|
@@ -284,7 +262,7 @@ When the last task of a tranche has merged, the Principal declares it done and d
|
|
|
284
262
|
|
|
285
263
|
2. **Write the retrospective** — append a new section to `aeg-project/lessons.md` with observations on what went well, what stalled, carry-forward lessons, decisions made, and unbuilt tasks. Assembled from merged PR summaries and topology — not invented.
|
|
286
264
|
|
|
287
|
-
3. **Archive the tranche** — set `Lifecycle: complete` as the first line after the
|
|
265
|
+
3. **Archive the tranche** — close the tranche's Milestone. That closed Milestone is the current signal to the Planner's readiness gate (`contracts/tranche-archivist-planner.md`) and to any reader that the tranche is no longer active. **Legacy exception:** for a tranche still carrying a pre-cutover topology file, also set `Lifecycle: complete` as the first line after the file's heading and move it from `aeg-root/tranches/` to `aeg-root/tranches/completed/` (one commit: `git mv`) — kept for tranches created before the forge-native cutover; a forge-native tranche carries no such file to move.
|
|
288
266
|
|
|
289
267
|
4. **Update state docs** — refresh `aeg-project/state.md` (last-updated date, current focus pointer, recently shipped section, clear any resolved pending-manual-ops). Active-work state is derived from the forge — no `now.md`.
|
|
290
268
|
|
|
@@ -312,7 +290,7 @@ The Principal eventually removes the worktree (`git worktree remove …`) — de
|
|
|
312
290
|
`spike: true` → reduced Task Done (typecheck + lint, with what was tried and learned recorded in the pull request). Spike code does not merge — it rebases away or converts to a full Tier 1+ task in a separate brief.
|
|
313
291
|
|
|
314
292
|
### Tier 0 work (trivial)
|
|
315
|
-
Skips Phase 2; short brief; minimal checklist; light Phase 10 (a code-reviewer pass is cheap insurance, but the security pass and
|
|
293
|
+
Skips Phase 2; short brief; minimal checklist; light Phase 10 (a code-reviewer pass is cheap insurance, but the security pass and Planner spec review can be skipped when there's no config/auth surface and no spec change). Declare `Tier: 0` in the PR body so verify-docs doesn't require doc updates.
|
|
316
294
|
|
|
317
295
|
### Multi-developer parallel work
|
|
318
296
|
Each Developer gets its own worktree, branched from `origin/main`. Parallel safety is the dispatch gates: a task does not start while a `conflicts-with` sibling's PR is open, or before a `depends-on`'s PR merges (`tranche-model.md` §8). Conflicts are declared at planning time as package-level collision domains — the coordination lives in the tranche's edges, not in ad-hoc scope-checking. When unsure two tasks collide, the Planner declares the conflict and serializes.
|
|
@@ -327,17 +305,17 @@ A rollback is its own task with its own brief. The decision to roll back is a Ty
|
|
|
327
305
|
|
|
328
306
|
## Anti-patterns
|
|
329
307
|
|
|
330
|
-
- **Going straight to
|
|
331
|
-
- **Dispatching
|
|
308
|
+
- **Going straight to dispatch without Phase 1** — a rationale written on top of an unexamined idea produces briefs that solve the wrong problem.
|
|
309
|
+
- **Dispatching a task whose gates aren't met, or forcing a render past a gap it flagged** — the Developer's own entry gate refuses it anyway; bypassing the gate manually just moves the failure one stage later.
|
|
332
310
|
- **Letting the Developer review its own work** — the Phase 10 agent passes are separate fresh-context invocations for a reason.
|
|
333
311
|
- **Writing status anywhere** — status is derived from the forge. Setting a label or editing the tranche file to record state recreates the racing status model the design eliminated.
|
|
334
|
-
- **
|
|
312
|
+
- **Hand-writing brief content into the Issue's own body** — the Issue body is task identity + metadata + the Planner's rationale + judgment sections only; the brief itself is a mechanically rendered comment on it, posted by `vinaya task dispatch`, never typed by hand into either the Issue or the PR body.
|
|
335
313
|
- **Developer scope creep** — "while I'm here…" is a new task and a new brief.
|
|
336
|
-
- **
|
|
314
|
+
- **Planner self-ratifying Type 1 decisions in solo sessions** — they queue as PENDING for a ratification window.
|
|
337
315
|
- **Skipping the Task Done checklist under deadline pressure** — it's the load-bearing discipline; skipping it is how the BYOK gap happened.
|
|
338
316
|
- **Treating "PR opened" as "done"** — done is "passed Phase 11 verification" (which requires Phase 10 review to have already passed).
|
|
339
317
|
- **Treating "review passed" as "ready to merge"** — review reads the diff; verification runs the booted app. CI green ≠ app boots ≠ feature works. An unticked Test Plan box is the merge gate even when the reviews are clean.
|
|
340
|
-
- **Inventing a Test Plan at verification time when the
|
|
318
|
+
- **Inventing a Test Plan at verification time when the Issue's rationale omitted one** — the Test Plan is written onto the Issue at plan time and rendered mechanically at dispatch; verification *executes* the plan, it does not author it. A missing Test Plan is a rationale defect, caught by the ring-0 creation gate or the render's own refusal, not something verification improvises around.
|
|
341
319
|
- **Mis-tagging a `[principal]` Test Plan item as `[agent]`** to make the agent half look complete — the asymmetry is structural (the agent surface lacks auth/keys/eyes); reclassifying loses the point of the split.
|
|
342
320
|
- **Building a dynamic conflict scanner** to catch what the Planner missed — declare conflicts conservatively and serialize instead (`tranche-model.md` §9).
|
|
343
321
|
|
|
@@ -345,7 +323,7 @@ A rollback is its own task with its own brief. The decision to roll back is a Ty
|
|
|
345
323
|
|
|
346
324
|
## How this process maps to file artifacts
|
|
347
325
|
|
|
348
|
-
For which files get mutated in which phase by which actor, see `state-machine.md` (the artifact + mutation matrix). For the roles, see `roles/principal.md`, `roles/planner.md`, `
|
|
326
|
+
For which files get mutated in which phase by which actor, see `state-machine.md` (the artifact + mutation matrix). For the roles, see `roles/principal.md`, `roles/planner.md`, `developer.md`, `reviewer.md`, `security.md`, `archivist.md`. For the tranche/task model, see `tranche-model.md` and `roles/planner.md`. For dispatching a task, see `roles/planner.md` § The dispatch act.
|
|
349
327
|
|
|
350
328
|
---
|
|
351
329
|
|