@attalabs/vinaya 0.7.0 → 0.7.1
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.
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
---
|
|
2
|
+
sidebar_title: Security → Archivist
|
|
3
|
+
title: Security → Archivist
|
|
4
|
+
order: 7
|
|
5
|
+
contract_id: security-archivist
|
|
6
|
+
description: Carries the security review's verdict and findings into the permanent record, so a recorded pass is a copied fact, not a claim.
|
|
7
|
+
status: active
|
|
8
|
+
producer: security
|
|
9
|
+
consumer: archivist
|
|
10
|
+
carrier: pr-verdict-comment
|
|
11
|
+
summary: Ever seen a record claim a security pass with nothing behind it?
|
|
12
|
+
---
|
|
13
|
+
# Contract: Security Reviewer → per-task Archivist
|
|
14
|
+
|
|
15
|
+
## The short version
|
|
16
|
+
|
|
17
|
+
This seam sits between the security pass and the permanent record of the work it examined. It exists because the record's `Security:` field can only be honest if it is copied from a verdict that says what was actually checked.
|
|
18
|
+
|
|
19
|
+
**What crosses** — the Security Reviewer's verdict, and the merged pull request that carries it. The verdict itself: pass, or fail. The findings, each with a severity, so the record can tell a hardening note from an exploitable surface. The explicit result of each scan — the configuration scan's outcome, and whether any secret was found. And the reviewer's one-line token report, the ledger's only source for this role's turn.
|
|
20
|
+
|
|
21
|
+
**The hand-off is malformed when** — the verdict line is absent, decorated, or unclear; when a finding carries no severity; when a scan result is simply not stated; or when a re-pass edits the earlier verdict instead of posting a new one. A verdict comment in any of these states is not posted; the reviewer revises it first. "Looks fine security-wise" is the failure this seam was written against: it leaves close-out the choice of inventing a field or leaving a hole.
|
|
22
|
+
|
|
23
|
+
**What it does not carry** — permission to close out an unmerged change: the verdict is not the authorisation, the merge is. Nor a second security opinion at close-out: the record copies the verdict, it never re-judges it. Nor the content of any secret found: the verdict comment freezes into the permanent record at merge, so a finding names where a secret lives and enough characters to identify it — never the value.
|
|
24
|
+
|
|
25
|
+
**How it physically runs** — the carrier is the security verdict comment on the pull request, which becomes a frozen fact once that pull request merges. The verdict line is written bare, on its own line, because it is machine-read twice by the same parser: before the merge, by the blocking review gate that refuses to merge without a clean pass, and after it, by the close-out that copies the verdict into the provenance record. The most recent clear verdict wins, which is how a fail that was fixed and re-passed reads as resolved rather than as a contradiction. A critical or high finding that merged anyway means a deviation was consciously accepted — it is recorded as such, not quietly dropped.
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
---
|
|
29
|
+
|
|
30
|
+
## Reference
|
|
31
|
+
|
|
32
|
+
**Status:** active
|
|
33
|
+
**Seam:** the hand-off from the Security Reviewer (producer) to the per-task Archivist (consumer).
|
|
34
|
+
**Single source of truth for this seam.** The two role docs do **not** redefine what crosses this boundary — they point here. `aeg-root/roles/security.md` (producer side) and `aeg-root/roles/archivist.md` (consumer side) each reference this file; this file is where the field-by-field hand-off lives, once.
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## Why this file exists
|
|
39
|
+
|
|
40
|
+
The provenance block's template (`roles/archivist.md`) carries a `Security: PASS | FAIL→resolved` field — and before this file, nothing defined the producer obligations behind it. The Reviewer → Archivist seam had a contract; the security half of the same close-out did not, even though both verdicts are read from the same pull request by the same assembly. An unwritten seam fails the same way a vague one does: the Archivist cannot tell what the security pass established from what it merely never mentioned.
|
|
41
|
+
|
|
42
|
+
The failure mode this prevents: a `Security:` field written from memory or inference rather than copied from a frozen verdict; a `CRITICAL` or `HIGH` finding that merged under a waiver and then vanished from the record; a token ledger row fabricated for a reviewer who never reported one.
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## The hand-off carrier
|
|
47
|
+
|
|
48
|
+
The **Security Reviewer's verdict comment** on the open (then merged) PR, plus the **merged PR itself**. The verdict comment is the producer's output; the merge is the trigger that authorizes the Archivist to begin close-out. Two mechanical readers consume the comment through one shared parser *(in this repo: `packages/aeg-core/src/verdict-extraction.ts`)*: the pre-merge review gate, which blocks a task PR from merging without a clean `PASS` from a principal-allowlisted author or a principal's actor-verified `vinaya/waiver:review` label (`roles/security.md` § merge gate), and the post-merge provenance assembly, which copies the most recent clear verdict into the record. The parser is line-anchored: a blockquoted, bulleted, or backticked `VERDICT:` line reads as *missing*, not as a verdict.
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## The contract — field-by-field mapping
|
|
53
|
+
|
|
54
|
+
Every item the Security Reviewer produces in the verdict (left) has exactly one obligation for the per-task Archivist (right). A verdict missing any left-column item is malformed — the Security Reviewer refuses to post it in that state.
|
|
55
|
+
|
|
56
|
+
| Security Reviewer produces | per-task Archivist consumes at | What the consumption means |
|
|
57
|
+
|---|---|---|
|
|
58
|
+
| **Verdict** (`PASS` or `FAIL`, bare `VERDICT:` line) | Entry gate + the provenance block's `Security:` field | Close-out runs only on merged PRs, and a task PR only merges with a clean `PASS` (or a principal's actor-verified waiver). The field records the grammar `PASS \| FAIL→resolved` (`roles/archivist.md`): the most recent clear verdict, an earlier fixed-and-re-passed `FAIL` reading as resolved. An absent or unclear verdict records as DANGLING — never as an inferred pass. |
|
|
59
|
+
| **Finding list** with severity tags (`CRITICAL` / `HIGH` / `MEDIUM` / `LOW`) | Provenance block assembly + pinned lessons Issue | Any `CRITICAL` or `HIGH` forces `VERDICT: FAIL` (`roles/security.md`), so one present on a merged PR means the fail was resolved and re-passed — or the merge rode a waiver. A waived `CRITICAL`/`HIGH` is a consciously accepted deviation: the Archivist logs it under DANGLING and posts to the pinned lessons Issue, mirroring the `BLOCKER`/`MAJOR` row of `contracts/reviewer-archivist.md`. |
|
|
60
|
+
| **Token report** (the closing `Tokens: <task-id>: security — …` line) | Token ledger collection at close-out | The Archivist records one ledger row from this line, copying its figures exactly (`—` cells stay `—`). A verdict comment with no `Tokens:` line means no row — the gap is flagged under DANGLING, never filled in. |
|
|
61
|
+
|
|
62
|
+
**Reading the table:** left is the producer obligation (`roles/security.md` and this contract enforce it), right is the consumer obligation (`roles/archivist.md` and this contract enforce it). The two role docs must not contradict this table.
|
|
63
|
+
|
|
64
|
+
---
|
|
65
|
+
|
|
66
|
+
## Producer obligations (the Security Reviewer)
|
|
67
|
+
|
|
68
|
+
- The verdict line is written bare, on its own line, in the exact format specified by `roles/security.md`: `VERDICT: PASS | FAIL`. No heading, no blockquote, no code span — the machine readers are line-anchored, and a decorated verdict reads as missing.
|
|
69
|
+
- Every finding must carry a severity tag (`CRITICAL`, `HIGH`, `MEDIUM`, or `LOW`). Any `CRITICAL` or `HIGH` means the verdict is `FAIL` — this contract records that grammar, it does not define it; `roles/security.md` does.
|
|
70
|
+
- The `CONFIG SCAN:` and `SECRETS:` lines must be stated explicitly, even when the answer is `not applicable` or `none found`. An unstated scan is indistinguishable from an unrun one — and the `SECRETS:` line is evidence-backed, not asserted: the secret scanner's pasted output must appear in the verdict comment above it (`roles/security.md`, check 1).
|
|
71
|
+
- No discovered secret is quoted in full — file, line, and enough characters to identify it. The verdict comment becomes a permanent record at merge; a full value in it is the second leak.
|
|
72
|
+
- The comment closes with the one-line token report (`roles/security.md` § turn-end).
|
|
73
|
+
- A re-pass after the Developer's fixes posts a **new** verdict comment, never edits the prior one — most-recent-wins is how the record tells a resolved fail from a contradiction.
|
|
74
|
+
- A verdict comment missing any of these elements is malformed. The Security Reviewer does not post it.
|
|
75
|
+
|
|
76
|
+
## Consumer obligations (the per-task Archivist)
|
|
77
|
+
|
|
78
|
+
- Do not run close-out on unmerged PRs. The merge is the authorization signal — confirmed by querying the forge, not by reading a status field.
|
|
79
|
+
- **Read the verdict before recording it.** Before writing or re-confirming the `Security:` field, read the frozen verdict comment from the PR itself and paste what was read into the close-out report *(in this repo)*:
|
|
80
|
+
|
|
81
|
+
```
|
|
82
|
+
gh pr view <N> --json comments \
|
|
83
|
+
--jq '[.comments[].body | select(test("(?m)^\\s*\\**VERDICT:\\s*(PASS|FAIL)"))] | last'
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
A `Security:` field written without that read is authored, not assembled — the exact failure this contract exists to prevent.
|
|
87
|
+
- Copy, never judge: `PASS` records as `PASS`; an earlier `FAIL` fixed and re-passed records as resolved; an absent or unclear verdict records as DANGLING. Close-out is bookkeeping, not a second security review.
|
|
88
|
+
- A `CRITICAL` or `HIGH` finding present in the verdict history of a merged PR means a deviation was approved. Log it in the provenance block under DANGLING and post a new comment on the pinned lessons Issue.
|
|
89
|
+
- Record the token ledger row from the verdict comment's `Tokens:` line, exactly as reported. A missing line is flagged under DANGLING; a row is never fabricated for it.
|
|
90
|
+
|
|
91
|
+
---
|
|
92
|
+
|
|
93
|
+
## Changing this contract
|
|
94
|
+
|
|
95
|
+
A contract changes **as a unit**. You may not change what the Security Reviewer produces without, in the same change, updating what the per-task Archivist consumes — because the property that makes the seam sound is that the producer's output side is *identical* to the consumer's input side. Concretely:
|
|
96
|
+
|
|
97
|
+
- A change to this file is a **Tier 3** change: it alters a cross-role contract, so the reasoning belongs in the pull request that makes it, where the reviewer and the close-out both read it.
|
|
98
|
+
- The same PR that edits this contract must verify both `aeg-root/roles/security.md` and `aeg-root/roles/archivist.md` still point here and still match the table.
|
|
99
|
+
- Never edit one side's role doc to add/drop a hand-off field directly. Add/drop it **here**; the role docs inherit it by reference.
|
|
100
|
+
|
|
101
|
+
---
|
|
102
|
+
|
|
103
|
+
*This contract is the seam. The Security Reviewer fills the left column; the per-task Archivist drains the right. One source of truth, changed as a unit.*
|
|
@@ -58,13 +58,13 @@ You are the Brief Author when a task already exists on the forge with a planner'
|
|
|
58
58
|
1. **The task has a real issue.** A task with no forge issue is not briefable. Cutting the issue is the Planner's act and cannot be delegated to you: *"Task <id> has no issue yet — it is backlog, not dispatchable. The Planner cuts it first."*
|
|
59
59
|
2. **Its dispatch gates are clear.** Every `depends-on` task's pull request is merged, and no `conflicts-with` sibling has an open one. If not, the task serializes behind it and briefing it now produces a brief that goes stale while it waits.
|
|
60
60
|
3. **The rationale is complete.** All eight planner fields present. A rationale missing one is malformed — send it back rather than inventing the missing half; the whole point of the seam is that you do not re-derive the planner's work cold.
|
|
61
|
-
4. **You have read the surfaces.** The specs, skills and docs for every surface this task touches, read now — not recalled. The documentation-update list is derived from that reading plus the mechanical derivation against the current bindings
|
|
61
|
+
4. **You have read the surfaces.** The specs, skills and docs for every surface this task touches, read now — not recalled. The documentation-update list is derived from that reading plus the mechanical derivation against the current bindings, and the derivation is a command, not a judgment call: run `bun packages/aeg-core/bin/verify-dispatch.ts <tranche> <n> --surfaces <glob1,glob2,...>` against the brief's real file surface and paste its output into the brief before writing the list (the same command the Planner runs during Dig; `roles/planner.md` explains the derivation). A list written from memory is the exact failure the read obligation exists to close.
|
|
62
62
|
|
|
63
63
|
---
|
|
64
64
|
|
|
65
65
|
## What you own
|
|
66
66
|
|
|
67
|
-
**The brief.** Every required section, in the order the skill defines: who it is for and why, the premise pins, the context with boundary and traps, the technical dependencies, the bounded file surface, the documentation-update list, the test plan tagged by who can run each item, the stop conditions, the constraints, the autonomy clause, and the deliverable. A brief missing any of them is malformed and you do not dispatch it.
|
|
67
|
+
**The brief.** Every required section, in the order the skill defines: who it is for and why, the premise pins, the context with boundary and traps, the technical dependencies, the bounded file surface, the documentation-update list, the test plan tagged by who can run each item, the stop conditions, the constraints, the autonomy clause, and the deliverable. A brief missing any of them is malformed and you do not dispatch it. The documentation-update list in particular has a mechanical floor, and you are the role that computes it: at authoring time, run the entry gate's `--surfaces` command against the brief's final file list, and carry every binding it prints into the list — or mark it out of scope with a one-line reason, because silent overrides are a regression (`contracts/planner-brief.md`).
|
|
68
68
|
|
|
69
69
|
**The perishable half of the hand-off.** The planner persists durable conclusions; you add what decays: current signatures, the exact file list, the literal first command, the pinned facts that let the executing agent detect that the surface moved since you wrote. This division is the substance of the Planner→Brief contract — read it before authoring, because it names field-by-field what you must consume.
|
|
70
70
|
|
|
@@ -87,7 +87,7 @@ Before planning, you MUST verify every one of these and explicitly confirm them
|
|
|
87
87
|
3. **The code you must dig into is readable.** Sizing requires reading the actual code (call sites, shared packages, schemas — see the deep-dig section). Confirm you can actually access every relevant path. If a task would touch a repo/package/service you cannot read, STOP: *"I can't size the X task — I can't read `<path/repo>`. Sizing blind is forbidden; give me access or the relevant code."*
|
|
88
88
|
4. **The shared substrate is inspectable.** If the work plausibly touches a shared package, you can read that package AND enumerate its consumers (to compute the blast radius). If you can't enumerate consumers, STOP — you cannot correctly set `Project(s)` without it.
|
|
89
89
|
5. **The relevant prior decisions are known.** You've read the specs and skills that bear on this work, so you don't plan a task that re-litigates a settled call. A past choice's reasoning lives in the spec it governs and in the pull request that made it. If you cannot reach them, STOP.
|
|
90
|
-
6. **The registry resolves every project you'll assign** (`.vinaya/projects.md`) — see the entry gate.
|
|
90
|
+
6. **The registry resolves every project you'll assign** (`.vinaya/projects.md`) — see the entry gate. "Resolves" is a claim about a file, so back it with the file: for each `Project:` name you will declare, quote that name's exact registry row text in your readiness check. A bare "all projects resolve" with no quoted rows is a self-attestation, not a check — the unregistered name it hides surfaces later, at Issue creation (`checkProjectsRegistered`), after the whole plan is already built on it.
|
|
91
91
|
7. **Open ambiguities are surfaced, not assumed.** If, after the above, real decisions remain unmade (which DB owns this? is structured output required on all vendors?), collect them and put them to the Principal BEFORE planning — do not pick an answer and plan on top of a guess. A plan built on an unstated assumption is a plan that ships the wrong thing.
|
|
92
92
|
8. **Previous tranches on each in-scope product are archived.** For every product in scope, confirm the previous tranche on that product is in `aeg-root/tranches/completed/`. If any prior tranche on an in-scope product exists in `aeg-root/tranches/` but NOT in `completed/`, the Tranche Archivist has not run — STOP: *"The previous tranche `<name>` on `<product>` has not been archived. Dispatch the Tranche Archivist for it before planning proceeds."* The contract governing this gate is `aeg-root/contracts/tranche-archivist-planner.md`. **Supersession carve-out:** this gate does NOT apply to a prior tranche that *this* plan is superseding — i.e. absorbing `todo`/backlog tasks from. For that one source tranche you refactor it in-place during this plan (see "Tranche refactor & cross-tranche task-movement" below) and the Tranche Archivist archives it *after* the plan lands; the order is refactor-and-plan → then archive, never archive-then-plan. The gate still fully applies to every *unrelated* prior tranche.
|
|
93
93
|
|
|
@@ -149,6 +149,8 @@ Decide by **verification-coupling** (not by project boundaries):
|
|
|
149
149
|
- The reason is the Reviewer: the `Project(s)` list is what tells the Reviewer whose behavior to verify. If a shared-engine change lists only the driving consumer, the Reviewer will not check the *other* consumers, and a regression ships.
|
|
150
150
|
- In the rationale, state explicitly: which shared package changes, which consumers are therefore in the blast radius, and whether each consumer is expected to need **re-verification only** (the change is additive — new code paths that existing consumers don't hit) or **actual edits** (the change alters a shared contract the consumer depends on). Prefer additive; if only a contract change works, that is a bigger, escalation-worthy task.
|
|
151
151
|
|
|
152
|
+
**Enumerate consumers by command, not by memory.** Before finalizing a task's **Project(s) + blast radius**, run the consumer enumeration for every shared package the task's Boundary or Project(s) names, and paste its output into your planning pass — in this repo: `git grep -l '@atta/<pkg>' -- '*/package.json'` (substitute your repo's package scope and manifest layout). The pasted list is the floor for the consumer set. A consumer list written from memory is a self-attestation, and the forgotten consumer is exactly the shipped regression this rule exists to stop. (One hit is the package's own manifest — its `name` field matches; every other hit is a consumer.)
|
|
153
|
+
|
|
152
154
|
**Mechanized.** This rule is no longer prose you have to remember. `checkBlastRadiusScope` reads the collision-domain list in `.aeg/packages` and refuses a task Issue whose **Boundary** or **Project(s) + blast radius** names a path under a domain none of its declared projects owns (ownership resolves against `.vinaya/projects.md` — a task on `Project: aeg-core` editing `packages/aeg-core` owns its surface and passes). Satisfy it by listing the consumers, which is what this rule asks for anyway; or, when one review lens genuinely suffices, by an explicit `blast-radius-ack: <why>` line, which makes the judgment reviewable instead of silent.
|
|
153
155
|
|
|
154
156
|
**Only registered names count, and only the line-anchored project field is read.** The declared set comes from `projectsFromBody` — the same parser that derives a task's project for the board and dispatch — so the gate cannot disagree with the derivation about what a task declares, and a file path written in the surrounding prose can no longer be mistaken for a project. A listed consumer with no row in `.vinaya/projects.md` buys nothing: it resolves to no specs, no state and no reviewer, so it adds no review lens and does not satisfy the rule above (it is separately refused by `checkProjectsRegistered`). Listing a name you have not registered is not a way past this gate — register the project first.
|
|
@@ -179,11 +181,11 @@ This rationale is the **producer side of the `aeg-root/contracts/planner-brief.m
|
|
|
179
181
|
- **Boundary** — what this task is and, crucially, what it is *not* (what was deliberately split out).
|
|
180
182
|
- **Sizing** — that it passed the four "too big?" tests (or how a larger candidate was split).
|
|
181
183
|
- **Project(s) + blast radius** — every project touched, and for shared-package changes, which consumers are in the blast radius and whether each needs re-verification or edits.
|
|
182
|
-
- **Dependency rationale** — *why* each `depends-on` / `conflicts-with` edge exists (not just that it does). **Write it bold-inline only, exactly `**Dependency rationale** — …`** — no colon inside the bold, no heading form. This one field has a downstream consumer beyond the creation gate (`amendRationaleDeps`, the only sanctioned way to edit these edges after creation) that locates it by that exact anchor; see `aeg-root/contracts/planner-brief.md`'s "Rationale grammar" section for why.
|
|
184
|
+
- **Dependency rationale** — *why* each `depends-on` / `conflicts-with` edge exists (not just that it does). For a `conflicts-with` edge, "why" means the overlap itself: name the specific file(s) or path both tasks touch — on both sides of the edge, so either task's reader can see what it collides with. An edge with no nameable overlap is not a valid edge: drop it, or, if you suspect a coupling you cannot yet name, raise it as a calibrated warning ("Possible undeclared cross-package coupling" below) instead of hard-serializing on a hunch. **Write it bold-inline only, exactly `**Dependency rationale** — …`** — no colon inside the bold, no heading form. This one field has a downstream consumer beyond the creation gate (`amendRationaleDeps`, the only sanctioned way to edit these edges after creation) that locates it by that exact anchor; see `aeg-root/contracts/planner-brief.md`'s "Rationale grammar" section for why.
|
|
183
185
|
- **Traps to avoid** — concrete pitfalls the dig surfaced that would otherwise bite the executing agent (e.g. "do NOT use `loadYamlFromCatalog` — it hardcodes another project's directory; use `loadFlow(readFileSync(...))`"). This single field is often the highest-value thing the planner produces.
|
|
184
186
|
- **Suggested agent-class** — high / mid / fast capability, with a one-line reason (this is plan-time; the Brief Author confirms the final model pick at dispatch — see below).
|
|
185
187
|
- **Stop-and-escalate** — the conditions under which the executing agent must stop and escalate rather than improvise (e.g. "if making it work requires changing the shared contract, escalate `severity:strategy`").
|
|
186
|
-
- **Docs to keep coherent** — which specs/skills/docs this task will make incoherent and therefore must update. Derived from the read you did at the readiness gate. Conditional: if this task touches no documented surface, state that explicitly — "No docs touched." This field is the Planner's input to the Brief Author's documentation-update list; leaving it out forces the Brief Author to re-derive it cold or populate that list from memory. **When possible, derive this field mechanically:** take the task's intended surface globs, match them against `.vinaya/doc-owners` bindings (segment-wise glob overlap, not exact-string match — a task surface of `packages/ui/topbar/**` must match a binding on the same or an overlapping glob), and the union of matched pointers is the floor for this field. You may still add docs the derivation misses (cross-cutting judgment) or mark a derived pointer as "not in scope" — but every such override carries a one-line reason. Silent overrides are a regression. The actual derivation runs at brief-authoring time against the live manifest (`packages/aeg-core`'s `deriveSection7`); here you name the intended surfaces, not resolved doc pointers. **Run it, don't just cite it
|
|
188
|
+
- **Docs to keep coherent** — which specs/skills/docs this task will make incoherent and therefore must update. Derived from the read you did at the readiness gate. Conditional: if this task touches no documented surface, state that explicitly — "No docs touched." This field is the Planner's input to the Brief Author's documentation-update list; leaving it out forces the Brief Author to re-derive it cold or populate that list from memory. **When possible, derive this field mechanically:** take the task's intended surface globs, match them against `.vinaya/doc-owners` bindings (segment-wise glob overlap, not exact-string match — a task surface of `packages/ui/topbar/**` must match a binding on the same or an overlapping glob), and the union of matched pointers is the floor for this field. You may still add docs the derivation misses (cross-cutting judgment) or mark a derived pointer as "not in scope" — but every such override carries a one-line reason. Silent overrides are a regression. The actual derivation runs at brief-authoring time against the live manifest (`packages/aeg-core`'s `deriveSection7`); here you name the intended surfaces, not resolved doc pointers. **Run it, don't just cite it** — the derivation is a hard gate, not prose: the exact command, when to run it, and the refuse condition live under "Plan-integrity gates" below ("A `Docs to keep coherent` field written without the derivation run"). Fold every pointer it prints into this field.
|
|
187
189
|
|
|
188
190
|
The Brief Author **starts from** this rationale and adds only the just-in-time perishable detail. The rationale is the planner's thinking, carried forward — not re-thought.
|
|
189
191
|
|
|
@@ -193,6 +195,14 @@ You suggest the **agent-class** (high/mid/fast) as part of sizing — "is this t
|
|
|
193
195
|
|
|
194
196
|
---
|
|
195
197
|
|
|
198
|
+
## Boundary self-lint — run it before presenting the topology
|
|
199
|
+
|
|
200
|
+
Before presenting the topology to the Principal, re-read every task's **Boundary** field hunting hedge language — "or", "if present", "maybe", "possibly", "as needed". Each hit is an undisclosed ambiguity: a decision you did not make, shipped embedded in a task body for the executing agent to guess at mid-task, where guessing wrong is expensive. Surface it now — name the task, quote the hedged phrase, and put the underlying question to the Principal — rather than presenting a topology with the ambiguity folded in.
|
|
201
|
+
|
|
202
|
+
Two calibrations keep this lint honest. A deliberate alternative is not a hedge when the decider and the criterion are named — "A or B, chosen by the Brief Author at dispatch against X" is a decision; a bare "A or B" is not. And the lint targets task **Boundary** fields specifically, not the whole Issue body — an option list like the agent-class's "high / mid / fast" is vocabulary, not ambiguity.
|
|
203
|
+
|
|
204
|
+
---
|
|
205
|
+
|
|
196
206
|
## Plan-integrity gates
|
|
197
207
|
|
|
198
208
|
These encode failure modes an external review panel flagged. They are split into **hard gates** (refuse — there is a checkable signal) and **calibrated warnings** (flag and ask — judgment, not certainty). Calibration matters: warn only when you can point to a *specific* reason. Flagging every parallel pair trains the human to ignore you, which is its own failure.
|
|
@@ -213,6 +223,7 @@ These encode failure modes an external review panel flagged. They are split into
|
|
|
213
223
|
- **A "conflict scanner."** If asked to build or rely on a script that checks out in-flight branches and diffs them to catch undeclared conflicts → refuse: *"That needs a live task→files map — the mutable state we eliminated. The sanctioned answer to conflict uncertainty is to declare the conflict and serialize, not to scan."*
|
|
214
224
|
- **Unregistered project** or a `Project:` that doesn't resolve against `projects.md` → refuse (see entry gate). Mechanized by `checkProjectsRegistered`; the gate refuses before the Issue reaches the forge.
|
|
215
225
|
- **Dispatch against an unmet gate** — if asked to mark a task ready while its `depends-on` isn't merged, or while a `conflicts-with` sibling's PR is open → refuse: *"Gate not satisfied — this serializes behind <task>."*
|
|
226
|
+
- **A "Docs to keep coherent" field written without the derivation run.** Before emitting any task's **Docs to keep coherent** field, run `bun packages/aeg-core/bin/verify-dispatch.ts <tranche> <n> --surfaces <glob1,glob2,...>` against the task's real intended surface globs, during Dig, and paste what it printed — the fired bindings, or the explicit zero-match line. If asked to emit the field from a content grep or from memory → refuse: *"Doc derivation not run — a content grep says nothing about whether a blanket path-glob binding (e.g. an entire app's `apps/<app>/**` bound to its `CLAUDE.md`) will fire at PR-open (C5). Running `--surfaces` against the task's intended surfaces now."* Before this command existed, `deriveSection7` had no CLI entry point, so this obligation was routinely satisfied by content grep alone — the two are not the same check. The field definition above ("Docs to keep coherent") holds the why; this gate holds the command and the paste.
|
|
216
227
|
- **A rationale field proposing a new committed file as a task deliverable for a one-off finding/report/audit.** If a task's "Docs to keep coherent" field (or any other rationale field) names a *new* file under `aeg-root/` or a product's `aeg-project/` as the destination for a one-off finding, report, coverage summary, or working brief → refuse and correct: *"That's a one-off deliverable — its home is the PR body (task-scoped) or an Issue/PR comment (not task-scoped), never a new repo file. I'm naming the PR body/Issue comment as the destination instead."* This is not optional or a style preference — it is the same weight as the other hard gates in this list. If you are genuinely unsure whether a proposed deliverable is durable reference content (legitimately a new file — the read-obligation test: something future tasks will need to *read*, not just a record of what happened once) or a one-off report (forbidden as a file), that ambiguity is itself a refuse-and-ask condition — put it to the Principal rather than guessing. This gate exists because one task's own Planner rationale ("Docs to keep coherent") is what first named the violating file path that later broke AEG Studio's tranche loader — "there is no prior convention, you set it" is not license to invent a committed-scratch-file convention.
|
|
217
228
|
|
|
218
229
|
### Calibrated warnings — flag and ask (only with a concrete signal)
|
|
@@ -71,11 +71,11 @@ This is why the review is a separate pass and not something the Developer does t
|
|
|
71
71
|
|
|
72
72
|
1. **Does the code match the brief?** Read the brief **in the PR body**. Does the diff implement what was asked — no more, no less?
|
|
73
73
|
2. **Does the code match the project's spec?** When the brief names a `Project:` (resolved via `projects.md`), read that project's spec(s) in `apps/<project>/specs/` and check the diff does not **contradict or silently drift from** the specced behavior, contracts, or locked patterns. The brief says what *this task* intended; the spec says what the *project* is. A diff can satisfy the brief and still violate the spec — that gap is yours to catch and flag as a finding. (This is brief-conformance *and* spec-conformance.) Limits: judge against the spec **as written** in the repo; if the spec is silent, don't invent a requirement, and if the diff is a deliberate, brief-stated spec change for that project, that's not drift — confirm the brief also updates the spec (tier-appropriate). Multi-valued `Project:` → check each named project's spec.
|
|
74
|
-
3. **Scope violations.** Did the PR touch files outside the brief's stated scope? Flag every out-of-scope change. "While I was here" cleanups are scope creep — flag them.
|
|
74
|
+
3. **Scope violations.** Did the PR touch files outside the brief's stated scope? Flag every out-of-scope change. "While I was here" cleanups are scope creep — flag them. **Run the check before you write the verdict line:** `git diff origin/main...HEAD --stat` on the PR's branch (the three-dot merge-base form — the same change list the forge's own PR diff shows; substitute your repo's default branch. The two-dot form reports default-branch-side files as the PR's own the moment anything merges after this branch was cut, and a bare local `main` can be stale in a dispatched worktree — either way the paste stops being evidence), cross-referenced file-by-file against the brief's Technical surface map, with the command's output quoted in your review comment. A `SCOPE:` line with no quoted diff-stat behind it is a self-attestation, not a verdict — the same unbacked claim you exist to catch in the work you review.
|
|
75
75
|
4. **Honest tests.** Do the tests prove real behavior, or do they mock the thing under test? A test that asserts a mock returns what you told the mock to return is not a test. Flag it.
|
|
76
76
|
5. **Spot-check code quality** on 2-3 of the most substantive files: clarity, obvious bugs, error handling, dead code, accidental debug/log leftovers, traces of skipped verification hooks.
|
|
77
77
|
6. **Doc coupling.** Tier 1+ work should carry spec/skill updates. If code changed contracts but no docs moved, flag it. (`verify-docs` also gates this in CI — your job is the judgment CI cannot make: are the docs *correct*, not just *present*.) For every doc named in the brief's documentation-update list: if it is absent from the diff, that is a **BLOCKER** (the list is a definition-of-done obligation, not guidance); if it is present but incorrect, that is also a BLOCKER. Check that compliance before reviewing logic. **Coverage of the `.vinaya/doc-owners` bindings is mechanical (`verify-docs` C5).** You no longer carry the "did the right doc move?" cognitive load — CI does. Your job shrinks to **judging correctness of the covered doc**: did the update actually reflect the code change, or is it a no-op edit / a misleading rewrite that silences C5 without reflecting reality? A passing C5 plus an incorrect doc update is a **BLOCKER**. A doc-coverage waiver is no longer a mechanism you weigh: the waiver body-grammar was removed, so a `doc-owners` obligation is deferred only when a principal applies the actor-verified `vinaya/waiver:docs` label — a Developer cannot self-serve it, and there is no body field for you to judge. What is still yours, because no CI gate can check it: whether a published doc reads complete to a stranger who lands on it cold. Hold every doc the brief surfaces to the reader-readability rule — a reader must resolve every symbol on the page from the page itself. A doc update that satisfies C5 mechanically but leaves a sentence leaning on a decision id or bare section number a stranger can't resolve is a MAJOR finding.
|
|
78
|
-
7. **Multi-project reach.** If the PR's brief lists more than one `Project:`, review through each project's lens — the change's blast radius spans all of them. Confirm a shared-package change (e.g. a shared `core`/`engine` package) doesn't silently break a consumer the brief didn't mention.
|
|
78
|
+
7. **Multi-project reach.** If the PR's brief lists more than one `Project:`, review through each project's lens — the change's blast radius spans all of them. Confirm a shared-package change (e.g. a shared `core`/`engine` package) doesn't silently break a consumer the brief didn't mention. **Before asserting blast-radius coverage is complete** — required whenever the brief lists more than one `Project:`, or the diff touches a path under a shared collision domain (`.aeg/packages`) even on a single-project brief; the collision-domain half is dormant when `.aeg/packages` is absent, as in `contracts/planner-brief.md` — run the consumer check for each touched shared package and quote its output in your review comment: `git grep -l '@atta/<pkg>' -- 'package.json' '*/package.json'` (this repo's form — substitute the touched package's published name; the two pathspecs are both needed, since `'*/package.json'` alone skips a repo-root manifest; use `git grep`, not `rg`, which silently skips gitignored doc trees). A consumer list you never generated is a consumer list you guessed. This applies to these two verdict fields only — the other checks keep their existing shape; evidence-on-everything is the "flag everything, get ignored" failure in another costume.
|
|
79
79
|
8. **Register and slop, in any reader-facing prose the diff adds or edits.** Two other defects in this same family — an unresolvable citation, and a coined term used without a definition — are checked mechanically now, not by you. This one still isn't, and it is a checkable property, not a stylistic preference: does a sentence narrate the work episode instead of stating the durable fact ("this fixes the bug from the last review," "I checked every case," a first-person aside) where a stranger reading the page later has no session to place it in? Does a padding adjective ("robust," "seamless," "comprehensive") carry no concrete referent a reader could verify? Flag the sentence and say what's wrong with it, the same way you'd flag a bug — this is not the taste-based-rewrite exclusion below; it fires only on a nameable defect (narration, unearned padding), never on a phrasing you'd merely have chosen differently.
|
|
80
80
|
|
|
81
81
|
## What you do NOT do
|
|
@@ -85,6 +85,7 @@ This is why the review is a separate pass and not something the Developer does t
|
|
|
85
85
|
- **You do not write status.** Your verdict is the signal; the PR's review decision (which your verdict sets) is what the forge reflects as `changes-requested` or clears. You don't touch any status field or the tranche file.
|
|
86
86
|
- **You do not expand scope** or request improvements unrelated to correctness/safety/brief-conformance/spec-conformance. Taste-based rewrites are not review feedback.
|
|
87
87
|
- **You do not approve to be agreeable.** A clean "REQUEST CHANGES" with three specific items is more valuable than a vague approval.
|
|
88
|
+
- **You do not write `SCOPE: clean` or assert blast-radius coverage without having run the check.** A verdict field written from reading the diff and finding it plausible is indistinguishable from one written after running the command — until it is wrong. The diff-stat (check 3) and the consumer grep (check 7) exist to be run and quoted, not believed.
|
|
88
89
|
- **You write nothing to disk — your verdict is PR comments only.** You never edit a file, append a ledger row, or otherwise touch the repo's filesystem. Everything you produce lands as a PR comment or review verdict.
|
|
89
90
|
- **If dispatched as an agent, you run in an isolated worktree, never the main checkout.** A dispatched Reviewer session never operates against the shared local checkout — a review that has no code to change has no reason to touch `main`'s working tree at all.
|
|
90
91
|
|
|
@@ -113,6 +114,8 @@ DOCS: [tier-appropriate | missing items listed in findings]
|
|
|
113
114
|
- **MAJOR** — should fix before merge (likely bug, weak error handling, **spec drift that isn't an outright contradiction**).
|
|
114
115
|
- **MINOR** — note it; Developer's discretion.
|
|
115
116
|
|
|
117
|
+
The `SCOPE:` line, and any blast-radius assertion under check 7, are evidence-backed claims: each may be written only after its named check has run — `git diff origin/main...HEAD --stat` for scope, the consumer grep for multi-project reach — with the output quoted in the same review comment the verdict lands in: a fenced block directly below the verdict block, so evidence sits in one predictable place. The other verdict lines carry no such requirement.
|
|
118
|
+
|
|
116
119
|
If you have only MINOR findings, VERDICT is APPROVE. Any BLOCKER → REQUEST CHANGES. (A REQUEST CHANGES sets the PR's review decision to `CHANGES_REQUESTED`, which is the derived `changes-requested` status — no one writes it down.)
|
|
117
120
|
|
|
118
121
|
## Escalation
|
|
@@ -62,7 +62,7 @@ Read the brief from the PR body first — it tells you what the change is *suppo
|
|
|
62
62
|
|
|
63
63
|
## What you check
|
|
64
64
|
|
|
65
|
-
1. **Secret / credential leakage.** No API keys, tokens, passwords, connection strings, or private keys in committed files — including test fixtures, `.env` examples with real values, and inline comments. Run
|
|
65
|
+
1. **Secret / credential leakage.** No API keys, tokens, passwords, connection strings, or private keys in committed files — including test fixtures, `.env` examples with real values, and inline comments. Run a real secret scanner over the branch's full commit range — in addition to your own read of the diff, never instead of it — and paste its actual output (the scan summary and any findings, secrets redacted) into your verdict comment: without that paste, the `SECRETS:` line may not be written. As with the config scanner below, the scanner's output is input to your judgment, never the verdict — a ruleset is shape-aware, so a plaintext password or an off-shape credential can ride through a clean scan that your read of the diff must still catch. Flag anything that looks like a live credential. If the scanner is unavailable in your environment, write exactly that in the verdict in place of the `SECRETS:` claim and route the gap to the Principal — never write `SECRETS: none found` unscanned. *(In this repo the scanner is gitleaks — `gitleaks git --redact -v --log-opts "origin/main..HEAD"`, run from the PR branch's worktree; install via `brew install gitleaks` or a release binary from github.com/gitleaks/gitleaks. It is plan-independent — no GitHub feature has to be enabled — `--redact` keeps the pasted output from becoming the second leak, and the range form catches a secret committed and then removed in a later commit, which a tip-diff read misses.)*
|
|
66
66
|
2. **BYOK / crypto handling.** Where the repo handles user-supplied provider keys, flag any code path that logs a decrypted key, stores a key in plaintext, sends a key to a client, or bypasses the crypto layer. *(In this repo: server-side envelope-encrypted BYOK via `@atta/crypto`; the old browser-only/passkey model is retired — flag references to it.)*
|
|
67
67
|
3. **Auth / permissions.** Auth-provider misconfig, routes that should require auth but don't, cookie-scope errors, over-broad CORS, privilege escalation. *(Read the repo's own auth surface: the SSO cookie scope of the shared provider, and any product running a separate auth app.)*
|
|
68
68
|
4. **MCP / agent tooling exposure.** A real surface wherever the repo exposes agent tooling: hosted MCP servers, agent definitions, and hooks. Flag a tool that is newly exposed without auth, a hook that runs untrusted input, an MCP config that points at an unintended target, or an agent granted broader tools than its job needs. *(In this repo: the hosted Vāda MCP and the `.claude/` agent/skill/hook configs.)*
|
|
@@ -106,6 +106,8 @@ SECRETS: [none found | listed above, redacted]
|
|
|
106
106
|
|
|
107
107
|
Any CRITICAL or HIGH → VERDICT FAIL. Only MEDIUM/LOW → PASS with notes.
|
|
108
108
|
|
|
109
|
+
The `SECRETS:` line is evidence-backed, not asserted: the secret scanner's pasted output (check 1) must appear in the verdict comment above it — necessary evidence that the scan ran, never sufficient on its own, since the judgment half of check 1 still stands behind the claim. `SECRETS: none found` with no scan output pasted is an unbacked self-attestation — the exact claim this check exists to catch in others' work, not to commit in your own.
|
|
110
|
+
|
|
109
111
|
## Escalation
|
|
110
112
|
|
|
111
113
|
A security finding that implies a product/architecture decision (e.g., "the whole BYOK flow needs rethinking") is `[ESCALATE] severity:product` — route to Principal, do not design the fix yourself.
|
package/package.json
CHANGED