fix: bind code-generation review receipts to the workspace source state (#629) - #646
fix: bind code-generation review receipts to the workspace source state (#629)#646iuryeng wants to merge 15 commits into
Conversation
e5d5c05 to
a1e4d67
Compare
|
Reviewed at a1e4d67. The mechanism is well built: the temp-index The problem is the binding's scope: one workspace-global fingerprint, anchored at time-of-stamp, bound to receipts that are per-unit and (on swarm) per-worktree. That combination refuses the normal multi-unit flow and deadlocks autonomous swarm settle. Findings below, most severe first; everything was verified against this branch's head, and the two flow-breaking items were reproduced with the PR's own tools. P1: the engine-driven multi-unit flow is falsely refused (reproduced)
Reproduced end to end with this branch's tools: two units seeded via the Bolt DAG, sequential code+review with nothing edited un-reviewed, single approve: Re-recording unit A's review against the final tree unblocks (a second approve passes), but that bakes in N-1 redundant re-reviews per batch on every multi-unit run, and it contradicts the issue's acceptance criterion: "Per-unit completion does not incorrectly invalidate unrelated units where attribution is available." Note the declared-artifact invalidation directly above the new filter already scopes per unit via the t245 cannot catch this: its guard scenarios run with no units doc, so the per-unit branch resolves P1: autonomous swarm cannot complete code-generation (settle deadlock, single-repo layout)The swarm path records receipts inside each Bolt worktree (SKILL.md: (A multi-repo intent escapes only by accident: P1: swarm finalize itself never checks the fingerprint
All three P1s share one root cause and one fix space. Two shapes that would resolve them together:
P2: fingerprint repo-boundary semantics
P2: documentationNo docs changes in the diff, while the repo's Documentation Policy and the issue's criterion ("Reviewer/audit documentation describes how source freshness is established") both require them in the same commit:
P2: mechanical
Verified green at this headt245 7/7 plus the reviewer-guard regression family (t111, t115, t205, t206, t68: 179 assertions, 0 failures), This is worth landing: the fingerprint idea is the right instrument for the gap the issue describes, and the implementation discipline (temp index, fail-open, off-switch symmetry) is exactly right. It needs the binding scoped to match how receipts are actually recorded (per unit, per worktree) before it can hold on the flows that matter most, which are the multi-unit and swarm runs. |
a1e4d67 to
965a161
Compare
…ipts (review response, awslabs#629/awslabs#646) Addresses apackeer's review of the source-fingerprint binding: a single workspace-global, time-of-stamp fingerprint compared per-receipt broke the normal multi-unit sequential flow and deadlocked autonomous swarm settle, and swarm finalize never checked the fingerprint at all. Reproduced all three P1s and both P2 findings against this branch's own tools before fixing them. - verifyReviewerPrecondition (aidlc-state.ts): track only the chronologically newest fingerprinted receipt during the event walk, then reconcile once against the current tree - a match keeps every collected receipt (fixes the false multi-unit refusal), a mismatch clears them all. A settled autonomous swarm is exempt from this reconciliation entirely (isSettledSwarmForArtifactGuard), since the main checkout's mismatch before finalize merges the code back is expected, not staleness. - reviewerReceiptError (aidlc-swarm.ts): finalize now compares a converged unit's terminal receipt fingerprint against workspaceSourceFingerprint recomputed scoped to that unit's own Bolt worktree, refusing the merge on a mismatch - the worktree-scoped check the settled-swarm exemption above depends on existing. - gitTreeFingerprint (aidlc-lib.ts): recurses into every initialized git submodule (gitlinks read off the temp index directly - git submodule status measured 1s+ per call on Windows) and excludes the aidlc/.aidlc/ .aidlc-worktrees/.aidlc-sensors workspace family at any depth via pathspec glob magic, not just the workspace root. - Docs updated (audit-format.md, 12-state-machine.md) to describe the fingerprint semantics and the AIDLC_SKIP_SOURCE_FRESHNESS off-switch. - t245 -> t247 (collision with a file landed on v2), extended with 8 new regression tests covering every fix above; coverage registry regenerated. - Rebased onto v2 (2.5.10 tip); this branch is now 2.5.11.
|
Thank you for the extremely thorough review — reproducing both P1s yourself before writing them up made this much easier to fix correctly. I reproduced all three P1s and both P2 findings independently using this branch's own tools, then implemented the second shape you sketched (per-receipt fingerprint scoped to the newest-stamp reconciliation + worktree-scoped swarm finalize validation + settled-swarm exemption), plus both P2 fixes and the docs. P1 #1 — multi-unit false refusal → fixed (last-receipt-wins)
P1 #2 — swarm settle deadlock → fixed (settled-swarm exemption)
P1 #3 — swarm finalize never checked the fingerprint → fixed
P2 — repo-boundary semantics → both fixed
P2 — docs → both fixed
P2 — mechanical → both fixed
Verified at this head
Really appreciate the depth here — the per-unit/per-worktree scoping mismatch was exactly right as the root cause, and pointing at the two concrete resolution shapes saved a lot of design time. |
965a161 to
2fc7010
Compare
…ipts (review response, awslabs#629/awslabs#646) Addresses apackeer's review of the source-fingerprint binding: a single workspace-global, time-of-stamp fingerprint compared per-receipt broke the normal multi-unit sequential flow and deadlocked autonomous swarm settle, and swarm finalize never checked the fingerprint at all. Reproduced all three P1s and both P2 findings against this branch's own tools before fixing them. - verifyReviewerPrecondition (aidlc-state.ts): track only the chronologically newest fingerprinted receipt during the event walk, then reconcile once against the current tree - a match keeps every collected receipt (fixes the false multi-unit refusal), a mismatch clears them all. A settled autonomous swarm is exempt from this reconciliation entirely (isSettledSwarmForArtifactGuard), since the main checkout's mismatch before finalize merges the code back is expected, not staleness. - reviewerReceiptError (aidlc-swarm.ts): finalize now compares a converged unit's terminal receipt fingerprint against workspaceSourceFingerprint recomputed scoped to that unit's own Bolt worktree, refusing the merge on a mismatch - the worktree-scoped check the settled-swarm exemption above depends on existing. - gitTreeFingerprint (aidlc-lib.ts): recurses into every initialized git submodule (gitlinks read off the temp index directly - git submodule status measured 1s+ per call on Windows) and excludes the aidlc/.aidlc/ .aidlc-worktrees/.aidlc-sensors workspace family at any depth via pathspec glob magic, not just the workspace root. - Docs updated (audit-format.md, 12-state-machine.md) to describe the fingerprint semantics and the AIDLC_SKIP_SOURCE_FRESHNESS off-switch. - t245 -> t247 (collision with a file landed on v2), extended with 8 new regression tests covering every fix above; coverage registry regenerated. - Rebased onto v2 (2.5.10 tip); this branch is now 2.5.11.
leandrodamascena
left a comment
There was a problem hiding this comment.
The source-freshness mechanism still has two paths that silently allow unreviewed source to satisfy the completion gate:
- A later receipt for one unit refreshes the workspace-global fingerprint while older receipts for modified units remain valid.
- Recursive
**/aidlc/**exclusions remove legitimate application source from the fingerprint.
Submodule paths subject to Git quoting are also skipped, and the swarm documentation incorrectly states that finalization merges application source.
I reproduced all three fingerprint bypasses against head 965a161e. The added tests pass because they do not cover these adversarial sequences and paths.
The PR is also currently conflicting with v2 in CHANGELOG.md and tests/unit/gen-coverage-registry.test.ts, with no CI checks reported.
| if (verdict !== "READY" && verdict !== "NOT-READY") continue; | ||
| const recordedFp = auditBlockField(e.block, "Source Fingerprint"); | ||
| if (recordedFp && stage.workspace_requires && !sourceFreshnessOff && !settledSwarm) { | ||
| newestFingerprintedFp = recordedFp; |
There was a problem hiding this comment.
[P1] Do not let the newest receipt refresh every unit’s source binding
This only proves that the workspace has not changed since the last review, not that each unit’s current source was reviewed.
Reproduction: review alpha, review beta, edit beta, then review alpha again. The new global fingerprint matches the workspace, while the old beta receipt remains in reviewedUnits, so approval succeeds with unreviewed beta changes. A receipt for an unknown unit can produce the same result.
Please use unit-scoped source bindings or require an explicit final workspace-wide review rather than allowing one receipt to validate all older per-unit receipts.
There was a problem hiding this comment.
Reproduced your exact scenario (and its inverse — re-reviewing an earlier unit after tampering a later one) and fixed at fe0810c: every consecutive fingerprint transition in a multi-receipt chain must now be a pure addition, not just the newest-matches-current check. See the summary comment above for the full explanation; both orderings are now covered by regression tests.
There was a problem hiding this comment.
Update, and it is a retraction of what I wrote here earlier: I'm removing fingerprintChainIsAdditionsOnly. Your finding stands — the newest-matches-current comparison alone does not prove each unit's current source was reviewed, and with the rule gone your exact sequence (review alpha, review beta, edit beta, re-review alpha) passes again. I'm not claiming otherwise, and I'd rather say that plainly than leave this thread reading as fixed.
The reason is that the rule I built for it is wrong in both directions, and I reproduced both at 6300ad3b before deciding.
It refuses work the protocol itself mandates. stage-protocol.md §12a requires recording a NOT-READY receipt, re-invoking the lead to fix the artifact in place, then re-reviewing. Fixing in place is an M transition, so the chain fails closed even though the reviewer inspected exactly the current tree:
newest == current: true
transition : "M\talpha.ts"
approve : rc 1 source-fingerprint mismatch
And it still does not prove review. A file written after alpha's review and read by nobody is a pure addition, so the chain check passes and approve returns rc 0. So the rule buys a false refusal of the normal rework loop and of any second unit that touches a shared file, without closing the hole it was built for. Two further failures are in the summary comment above: re-review can never clear the chain once a transition is in it, and the recorded-repo layout — which is the default, since sibling auto-discovery populates repos at intent birth — produces a composite hash that is not a diffable git object at all.
Where that leaves your finding:
- It is documented, not buried. The CHANGELOG bullet and
docs/reference/12-state-machine.mdclaimed the stronger property; both now state that a match proves only that the current tree is identical to the one the newest recorded review inspected, not that each unit was reviewed against its own code. The same paragraph goes inverifyReviewerPrecondition's comment block. - Your repro survives as a permanent test, inverted rather than deleted — same scenario, asserting the documented behaviour, citing this round. When attribution lands it goes red on purpose.
- It is genuinely fixed on one route already:
aidlc-swarm.tsfinalize compares each claimed unit's terminal receipt against its own Bolt worktree's fingerprint, which is the unit-scoped binding you asked for. It is the stage-level route where the hash is workspace-global and the receipts are per unit. - Your other finding here — the
:(glob)**/aidlc/**pathspec swallowing real source — stays closed, and this round tightened it further: the exclusion was still being applied inside every recorded sibling repo and inside submodules, where a directory of that name is application source. Fixed, with repros. - Follow-up for the real fix is [RFC]: per-unit attribution for code-generation review receipts #662. It needs a machine-readable manifest of the source paths each unit wrote;
code-summaryhas that in prose only and there is no such manifest incore/tools/orcore/hooks/today, which is why I don't think it fits in this PR.
This leans on #629's criterion being conditional — "ambiguous attribution fails closed or follows an explicitly documented policy" — and I've taken the documented-policy branch. That is a judgement call on a finding you raised, so if you'd rather I keep a fail-closed rule and fix the flows it breaks instead, or hold the PR until attribution exists, say so and I'll do that.
| "git", | ||
| [ | ||
| "-C", repoDir, "rm", "-r", "-q", "--cached", "--ignore-unmatch", "--", | ||
| ...AIDLC_WORKSPACE_DIR_NAMES.map((name) => `:(glob)**/${name}/**`), |
There was a problem hiding this comment.
[P1] Restrict exclusions to framework-owned paths
:(glob)**/aidlc/** excludes every directory named aidlc, including legitimate application source such as src/aidlc/engine.ts or services/compiler/aidlc/index.ts.
Changes under those paths leave the fingerprint unchanged and can pass approval without another review. Please anchor framework exclusions to their actual workspace locations instead of excluding these names recursively.
There was a problem hiding this comment.
Fixed at b4195e9 — restricted the any-depth exclusion to .aidlc-sensors only (the one name that genuinely isn't root-anchored); aidlc/.aidlc/.aidlc-worktrees are back to root-level-only. Verified with your exact examples: an src/aidlc/engine.ts-shaped path and the 2-level-nested services/compiler/aidlc/index.ts both correctly change the fingerprint now.
| if (!line.startsWith("160000 ")) continue; | ||
| const tabIdx = line.indexOf("\t"); | ||
| if (tabIdx === -1) continue; | ||
| const subPath = line.slice(tabIdx + 1).trim(); |
There was a problem hiding this comment.
[P2] Parse submodule paths without Git quoting
With the default core.quotePath behavior, git ls-files -s quotes and escapes non-ASCII paths. For a submodule such as vendor/cafe-é, subPath contains Git’s quoted representation, isGitRepoDir returns false, and edits inside the submodule are omitted from the fingerprint.
This is reproducible: an uncommitted edit inside that submodule leaves the workspace fingerprint unchanged. Please use git ls-files -s -z and parse NUL-delimited entries.
There was a problem hiding this comment.
Fixed at b4195e9 — gitlinks are now read via ls-files -s -z (NUL-delimited, quoting disabled) instead of -s. Verified with an accented submodule path (vendor/café-módulo): an uncommitted edit inside it now correctly changes the fingerprint.
| // not brick. Off-switch: AIDLC_SKIP_SOURCE_FRESHNESS=1. Settled autonomous | ||
| // swarm exempts this reconciliation entirely: receipts are stamped inside | ||
| // per-unit Bolt worktrees, so a workspace-global fingerprint of the MAIN | ||
| // checkout is the wrong comparison basis until finalize merges the code |
There was a problem hiding this comment.
[P2] Do not claim that swarm finalization merges application source
aidlc-swarm finalize invokes aidlc-bolt complete --merge, which merges state, audit, and runtime metadata only. It never invokes aidlc-worktree merge; aidlc-orchestrate.ts also explicitly states that generated source changes are not copied.
The new comments, documentation, and test names rely on a merge-back that does not happen. Please correct this description and avoid using it as lifecycle justification. The underlying missing source merge appears pre-existing and may require a separate fix.
There was a problem hiding this comment.
Fixed at fe0810c — this specific copy of the claim was missed in the first pass (my doc sweep only grepped docs/CHANGELOG/aidlc-swarm.ts, not this comment block); caught via this inline comment, thanks. Corrected to match the other three spots: finalize's own merge-back (aidlc-bolt complete --merge) carries only the AIDLC data, never application source. Agreed the missing source merge itself is a separate, pre-existing concern out of scope here.
… (review response, awslabs#629/awslabs#646) Addresses leandrodamascena's CHANGES_REQUESTED review of the source-freshness fix. Reproduced all three fingerprint bypasses and the docs inaccuracy against this branch's own tools before fixing any of them. - verifyReviewerPrecondition (aidlc-state.ts): the newest-receipt-matches- current check alone was not sufficient for a chain of 2+ receipts - unit A reviewed, A's file silently edited with no new review, unit B coded and reviewed (B's receipt stamps a fingerprint over the tree that already contains A's unreviewed edit) - passed even though nobody reviewed A's edit. Every consecutive fingerprint transition in a multi-receipt chain must now also be a pure addition (fingerprintChainIsAdditionsOnly, aidlc-lib.ts - git-diffs the content-addressed tree shas directly, no commits needed); any modified/deleted pre-existing path, or an unverifiable transition (multi-repo/submodule composite hash, a pruned tree object), discards the whole chain. - gitTreeFingerprint (aidlc-lib.ts): the any-depth exclusion added for the prior review's .aidlc-sensors finding was applied to all four workspace names, which silently dropped real application source that happened to live under a directory coincidentally named aidlc/.aidlc/.aidlc-worktrees. Only .aidlc-sensors is genuinely not root-anchored (a monorepo's per- package tsconfig can anchor it anywhere); the other three are excluded only at the workspace root again, where they are the only place they legitimately occur. - Submodule gitlinks are now read via `ls-files -s -z` instead of `-s`: without -z, git's default core.quotePath wraps a path containing a non-ASCII or otherwise "unusual" character in escaped double quotes, which never resolves to the real on-disk directory - the submodule was silently skipped and a reviewed-then-edited submodule at such a path shipped unreviewed. - Corrected three spots (12-state-machine.md, CHANGELOG.md, an aidlc-swarm.ts comment) that said finalize "merges the code back": aidlc-bolt complete --merge carries only the AIDLC data (state/audit/ runtime fragment); application source is never merged by finalize. - t244 extended with 3 new regression tests covering all three bypasses; docs and CHANGELOG updated to describe the additions-only chain check and the root-only/any-depth exclusion split.
|
Thank you for the deep dive — reproducing all three bypasses before writing them up made these very actionable. I reproduced all three, plus the docs inaccuracy, against this branch's own tools before fixing any of them, then fixed all four at Bypass 1 — a later receipt refreshing the global fingerprint while an earlier unit's edit went unreviewed → fixedConfirmed exactly as described: unit A reviewed, A's file edited with no new review, unit B coded and reviewed — B's receipt stamps a fingerprint over the tree that already contains A's unreviewed edit, so the newest-matches-current check alone passed even though nobody reviewed A's edit.
Bypass 2 — recursive
|
…te.ts (review response, awslabs#646) leandrodamascena's review had a 4th inline comment (aidlc-state.ts:1399, original commit 965a161) pointing at a copy of the "finalize merges the code back" claim inside verifyReviewerPrecondition's own comment block that the prior fix's grep sweep (scoped to docs/CHANGELOG/aidlc-swarm.ts) missed. Corrected to match the other three spots: finalize's own merge-back (aidlc-bolt complete --merge) carries only the AIDLC data, never application source. Also added a regression test reproducing the reviewer's own inline-comment scenario verbatim (review alpha, review beta, tamper beta, re-review alpha) - distinct from the already-covered later-unit-masks-earlier-unit case, this is earlier-unit-re-review-masks-later-unit's-tamper. Verified against the already-fixed code before adding as a permanent test: refuses correctly.
|
Follow-up: I'd only read the review summary when I replied above — just went through the 4 inline comments individually and verified each against
All fixed and verified at |
…references only Matches the codebase's established convention (issue/PR number only in "why this exists" comments, not the individual reviewer's username) - inconsistent with the single pre-existing exception elsewhere and not durable if the PR/review is ever reorganized. The awslabs#646 reference alone already gives full traceability to the review discussion.
|
Reviewed at The fingerprint mechanism itself is good work and I want to be clear that I think it should ship. The temp-index My concern is confined to the new P1: the chain rule refuses the reviewer rework loop the protocol itself mandates
Instrumented, single unit, nothing tampered: Note the newest fingerprint equals the current tree. Nothing is unreviewed; the reviewer looked at exactly this source. Only the diff shape between two receipts triggers the refusal. The same shape refuses a second unit whose work modifies a pre-existing shared file, which is ordinary integration wiring: P1: re-reviewing cannot recover, and the refusal names re-review as the fix
The only escapes are P1: the standard recorded-repo layout can never satisfy the chain checkTwo independent reasons, either fatal on its own:
This is not an edge case: sibling auto-discovery auto-populates P1:
|
…own root (2.5.12) The exclusion was applied relative to every fingerprinted repo dir, so in a multi-repo intent a `repo-a/aidlc/**` file - application source, since the record tree is a SIBLING of the repo dirs - was dropped from the fingerprint and could be edited after a review without invalidating the receipt. Same defect one level down, inside an initialized submodule. gitTreeFingerprint now takes a required carriesWorkspaceShell flag, true only for the dir the caller hands in (the workspace roof, or a Bolt worktree with its own record mirror) and false for every dir the function derives itself. The pathspecs are directory-anchored, so a root file named `aidlc` is source too. `.aidlc-worktrees` is dropped from the set: no code path creates it (worktrees are `.aidlc/worktrees/bolt-<slug>`, already covered), so it only removed real source from the binding. Reproduced each case before fixing (awslabs#646 review, awslabs#629).
…space-global policy (2.5.12) The rule refused the rework loop stage-protocol.md §12a mandates (NOT-READY, lead fixes the artifact in place, re-review - an `M` transition) and any second unit touching a pre-existing shared file; it could never be cleared by re-reviewing, since every fingerprinted receipt is appended unconditionally; it could never be satisfied by the recorded-repo layout, whose fingerprint is a sha256 composite rather than a diffable git object; and it still admitted a pure addition nobody reviewed. A workspace-global hash cannot attribute a change to a unit, and inferring attribution from the diff shape is wrong in both directions. The guard keeps what it can actually establish - the current tree equals the tree the newest recorded review inspected - and the limitation is now stated in the CHANGELOG, the state-machine reference, the guard's own comment block and two tests. Two user-facing refusal messages that claimed more than the guard knows are corrected in the same change. Reproduced all five findings before changing anything (awslabs#646 review, awslabs#629).
|
Thank you — and specifically for reproducing all five before writing them up. I reproduced each one independently at One thing up front, because it is the substance of this change and not a footnote: the chain rule exists because of @leandrodamascena's P1 on P1 — the rework loop the protocol mandates → confirmed, rule droppedReproduced The second shape refuses too — a second unit that touches a pre-existing shared file, which is ordinary integration wiring: P1 — re-review cannot recover, and the message names re-review → confirmed
P1 — the recorded-repo layout can never satisfy the chain check → confirmed, and worse than reportedBoth of your reasons reproduce. Reproducing the second one turned up something slightly stronger than "the roof is not a repo": even when the roof is a git repo, its object store is a different one, so two genuine tree shas from sibling repos still fail: Confirmed too that this is the default and not an edge case: P1 —
|
apackeer
left a comment
There was a problem hiding this comment.
Approving, reviewed at 3cbf1917. All five round-3 findings are genuinely fixed, and I verified each by re-running my own round-3 reproductions against this head (same scripts, same worktree, PR dist confirmed via the carriesWorkspaceShell symbol before trusting any verdict):
- §12a rework loop (NOT-READY, fix in place, re-review): approve now rc 0, no mismatch. Same for the second
M-shaped case, a second unit wiring into a pre-existing shared file. - Recorded-repo / composite deadlock: the clean two-unit run through a recorded repo now passes (rc 0), and the multi-repo and submodule-bearing variants pass their clean flows too. With
fingerprintChainIsAdditionsOnlygone (zero references left anywhere in the tree,dist/included), the roof-not-a-git-repo failure mode is gone with it. - No-recovery: with the chain rule removed there is no permanent refusal state left to recover from; the mismatch path's own instruction (fresh verdict OR revert) is now accurate, and the revert wording in
reviewerPreconditionErrormatches what the content-addressed fingerprint actually does - I re-verified tamper-then-revert restores rc 0. - Sibling-repo
aidlc/exclusion:repo-a/aidlc/**,repo-a/.aidlc/**, and a submodule's ownaidlc/all move the fingerprint now; the roof's shell still doesn't. ThecarriesWorkspaceShellparameter being required rather than defaulted is the right call - a new call site has to make the decision consciously. Dropping.aidlc-worktreesentirely checks out too: I grepped the tree and no code path ever creates that name (worktreePathis.aidlc/worktrees/bolt-<slug>), so it was pure fingerprint blindness. - Additions-only proves nothing: correctly not claimed as fixed. The inverted limitation tests are the honest way to carry this - the policy is pinned in code, cited to the review round, and turns red the day attribution lands. #662's framing (needs a machine-readable per-unit path manifest;
code-summaryis prose-only today) matches what I found in round 3.
Negative controls, since a fix that also deletes the guard would pass every deadlock repro: a post-review edit still refuses with source-fingerprint mismatch and reverting restores approval; Source Fingerprint is still stamped on REVIEW_COMPLETED (verified against this head's dist, not just the unit tests). The recorded-repo test's second act (edit inside repo-a after the last review, must refuse) covers the same concern in-suite.
Docs match the shipped behavior now. The CHANGELOG limitation bullet, the 12-state-machine.md §Source-freshness binding paragraph plus the rewritten REVIEW_COMPLETED row, and verifyReviewerPrecondition's comment block all say the same thing: source-state equality against the newest recorded review, not per-unit attribution. The two refusal-message corrections (reachable-false "was never reviewed", and the "at least one receipt" wording on the per-unit path) are both accurate against the code as it stands. Good catch on #663 - workspaceSourceFingerprint returning null for the whole workspace when one recorded repo is unusable is indeed pre-existing and worth its own issue rather than scope creep here.
Gates on my side (Linux, this head): bun run check (all 3 tsconfigs + biome) rc 0; bun scripts/package.ts --check green for all 5 harnesses; smoke+unit 178 files / 4414 assertions / 0 failed (t244 26/26, t68 green); integration t166 17/17. One caveat this approval rests on: the repo's CI has not actually executed on this head - the fork's workflow runs sit at action_required awaiting a maintainer's "Approve and run", so the green above is my local Linux run, not the v2 gate itself. Version 2.5.12 and the t244 filename are both still free on origin/v2 as of this review (v2 is at 2.5.11; tests/unit/t244-scope-matrix-doc-sync.test.ts from #596 merged since your push shares the number but not the filename - the suite keys on filenames, so no collision, but worth knowing it's there).
One optional nit, not blocking: docs/reference/12-state-machine.md:178 says "See the REVIEW_COMPLETED row above" but the row is at line 277, below the paragraph. "Below" (or dropping the direction) would be accurate.
This resolves everything I raised. As you said yourself, the laundering window @leandrodamascena flagged is deferred to #662 rather than closed, so his CHANGES_REQUESTED remains the review that has to clear - but from my side the mechanism, the documented policy, and the test evidence are all in a state I'd be happy to see merge.
|
@leandrodamascena — a status note rather than a nudge, in case you come back to this thread expecting the version you reviewed. The shape of the change moved in the direction you pushed for, and it would be unfair to leave you to reconstruct that from the diff. The rule you flagged as a bypass is gone — not defended, not patched. What ships instead is the narrower claim: source-state equality against the newest recorded review, with the limitation stated outright in the CHANGELOG and Your other three inline findings are fixed, each with a test: the I retracted the newest-wins argument in your thread when I pushed the fix, so this is a summary and not a second attempt at persuasion. @apackeer approved at |
…ipts (review response, awslabs#629/awslabs#646) Addresses apackeer's review of the source-fingerprint binding: a single workspace-global, time-of-stamp fingerprint compared per-receipt broke the normal multi-unit sequential flow and deadlocked autonomous swarm settle, and swarm finalize never checked the fingerprint at all. Reproduced all three P1s and both P2 findings against this branch's own tools before fixing them. - verifyReviewerPrecondition (aidlc-state.ts): track only the chronologically newest fingerprinted receipt during the event walk, then reconcile once against the current tree - a match keeps every collected receipt (fixes the false multi-unit refusal), a mismatch clears them all. A settled autonomous swarm is exempt from this reconciliation entirely (isSettledSwarmForArtifactGuard), since the main checkout's mismatch before finalize merges the code back is expected, not staleness. - reviewerReceiptError (aidlc-swarm.ts): finalize now compares a converged unit's terminal receipt fingerprint against workspaceSourceFingerprint recomputed scoped to that unit's own Bolt worktree, refusing the merge on a mismatch - the worktree-scoped check the settled-swarm exemption above depends on existing. - gitTreeFingerprint (aidlc-lib.ts): recurses into every initialized git submodule (gitlinks read off the temp index directly - git submodule status measured 1s+ per call on Windows) and excludes the aidlc/.aidlc/ .aidlc-worktrees/.aidlc-sensors workspace family at any depth via pathspec glob magic, not just the workspace root. - Docs updated (audit-format.md, 12-state-machine.md) to describe the fingerprint semantics and the AIDLC_SKIP_SOURCE_FRESHNESS off-switch. - t245 -> t247 (collision with a file landed on v2), extended with 8 new regression tests covering every fix above; coverage registry regenerated. - Rebased onto v2 (2.5.10 tip); this branch is now 2.5.11.
… (review response, awslabs#629/awslabs#646) Addresses leandrodamascena's CHANGES_REQUESTED review of the source-freshness fix. Reproduced all three fingerprint bypasses and the docs inaccuracy against this branch's own tools before fixing any of them. - verifyReviewerPrecondition (aidlc-state.ts): the newest-receipt-matches- current check alone was not sufficient for a chain of 2+ receipts - unit A reviewed, A's file silently edited with no new review, unit B coded and reviewed (B's receipt stamps a fingerprint over the tree that already contains A's unreviewed edit) - passed even though nobody reviewed A's edit. Every consecutive fingerprint transition in a multi-receipt chain must now also be a pure addition (fingerprintChainIsAdditionsOnly, aidlc-lib.ts - git-diffs the content-addressed tree shas directly, no commits needed); any modified/deleted pre-existing path, or an unverifiable transition (multi-repo/submodule composite hash, a pruned tree object), discards the whole chain. - gitTreeFingerprint (aidlc-lib.ts): the any-depth exclusion added for the prior review's .aidlc-sensors finding was applied to all four workspace names, which silently dropped real application source that happened to live under a directory coincidentally named aidlc/.aidlc/.aidlc-worktrees. Only .aidlc-sensors is genuinely not root-anchored (a monorepo's per- package tsconfig can anchor it anywhere); the other three are excluded only at the workspace root again, where they are the only place they legitimately occur. - Submodule gitlinks are now read via `ls-files -s -z` instead of `-s`: without -z, git's default core.quotePath wraps a path containing a non-ASCII or otherwise "unusual" character in escaped double quotes, which never resolves to the real on-disk directory - the submodule was silently skipped and a reviewed-then-edited submodule at such a path shipped unreviewed. - Corrected three spots (12-state-machine.md, CHANGELOG.md, an aidlc-swarm.ts comment) that said finalize "merges the code back": aidlc-bolt complete --merge carries only the AIDLC data (state/audit/ runtime fragment); application source is never merged by finalize. - t244 extended with 3 new regression tests covering all three bypasses; docs and CHANGELOG updated to describe the additions-only chain check and the root-only/any-depth exclusion split.
…te.ts (review response, awslabs#646) leandrodamascena's review had a 4th inline comment (aidlc-state.ts:1399, original commit 965a161) pointing at a copy of the "finalize merges the code back" claim inside verifyReviewerPrecondition's own comment block that the prior fix's grep sweep (scoped to docs/CHANGELOG/aidlc-swarm.ts) missed. Corrected to match the other three spots: finalize's own merge-back (aidlc-bolt complete --merge) carries only the AIDLC data, never application source. Also added a regression test reproducing the reviewer's own inline-comment scenario verbatim (review alpha, review beta, tamper beta, re-review alpha) - distinct from the already-covered later-unit-masks-earlier-unit case, this is earlier-unit-re-review-masks-later-unit's-tamper. Verified against the already-fixed code before adding as a permanent test: refuses correctly.
3cbf191 to
5d8896a
Compare
…references only Matches the codebase's established convention (issue/PR number only in "why this exists" comments, not the individual reviewer's username) - inconsistent with the single pre-existing exception elsewhere and not durable if the PR/review is ever reorganized. The awslabs#646 reference alone already gives full traceability to the review discussion.
…own root (2.5.12) The exclusion was applied relative to every fingerprinted repo dir, so in a multi-repo intent a `repo-a/aidlc/**` file - application source, since the record tree is a SIBLING of the repo dirs - was dropped from the fingerprint and could be edited after a review without invalidating the receipt. Same defect one level down, inside an initialized submodule. gitTreeFingerprint now takes a required carriesWorkspaceShell flag, true only for the dir the caller hands in (the workspace roof, or a Bolt worktree with its own record mirror) and false for every dir the function derives itself. The pathspecs are directory-anchored, so a root file named `aidlc` is source too. `.aidlc-worktrees` is dropped from the set: no code path creates it (worktrees are `.aidlc/worktrees/bolt-<slug>`, already covered), so it only removed real source from the binding. Reproduced each case before fixing (awslabs#646 review, awslabs#629).
…own root (2.5.12) The exclusion was applied relative to every fingerprinted repo dir, so in a multi-repo intent a `repo-a/aidlc/**` file - application source, since the record tree is a SIBLING of the repo dirs - was dropped from the fingerprint and could be edited after a review without invalidating the receipt. Same defect one level down, inside an initialized submodule. gitTreeFingerprint now takes a required carriesWorkspaceShell flag, true only for the dir the caller hands in (the workspace roof, or a Bolt worktree with its own record mirror) and false for every dir the function derives itself. The pathspecs are directory-anchored, so a root file named `aidlc` is source too. `.aidlc-worktrees` is dropped from the set: no code path creates it (worktrees are `.aidlc/worktrees/bolt-<slug>`, already covered), so it only removed real source from the binding. Reproduced each case before fixing (awslabs#646 review, awslabs#629).
…space-global policy (2.5.12) The rule refused the rework loop stage-protocol.md §12a mandates (NOT-READY, lead fixes the artifact in place, re-review - an `M` transition) and any second unit touching a pre-existing shared file; it could never be cleared by re-reviewing, since every fingerprinted receipt is appended unconditionally; it could never be satisfied by the recorded-repo layout, whose fingerprint is a sha256 composite rather than a diffable git object; and it still admitted a pure addition nobody reviewed. A workspace-global hash cannot attribute a change to a unit, and inferring attribution from the diff shape is wrong in both directions. The guard keeps what it can actually establish - the current tree equals the tree the newest recorded review inspected - and the limitation is now stated in the CHANGELOG, the state-machine reference, the guard's own comment block and two tests. Two user-facing refusal messages that claimed more than the guard knows are corrected in the same change. Reproduced all five findings before changing anything (awslabs#646 review, awslabs#629).
…lusion Two P2 findings from the awslabs#646 review, both in the fingerprint itself. A configured `clean` filter runs as content enters the index, so the tree sha hashed the FILTERED bytes rather than the bytes in the worktree: a lossy filter mapped two different worktrees onto one fingerprint, and the stage executes against the bytes the reviewer read. Fold a `--no-filters` hash of exactly the paths a clean driver touches into the fingerprint. The scan is gated on an attributes file actually assigning `filter=`, because `check-attr` measured +66% on this fingerprint's wall clock and it recomputes on every completion route; `hash-object` runs without `-w`, so no object enters the caller's store. The sensor-cache exclusion matched the leaf name at any depth, so an application tracking source under a directory named `.aidlc-sensors` could edit or delete it without moving the fingerprint. Match the path the engine actually writes instead: every writer resolves through sensorsDir -> docsRoot -> intentsDir -> workspaceRoot, so the cache is always `<anchor>/aidlc/spaces/<space>/intents[/<record>]/.aidlc-sensors/`. t259 gains the leaf-name reproduction (edit AND delete), the filter collapse with its revert control, and a row pinning that a repo which filters nothing keeps the bare tree sha it had before. The two rows that asserted the leaf-name exclusion are inverted in place, not removed.
|
All five are fixed, plus the Thank you for going back through this one. Items 4 and 5 are both my own code, from the two rounds where I was answering earlier review — and 5 is specifically the perf gate I introduced to avoid paying for
1 — the checkbox stopped being a standing permissionTwo changes, because one is not enough. Both halves are proven separately against the fixed engine:
The second is the one the first fix cannot reach, and it is the one that proves the route-level check is load-bearing. 3 — and why the receipt now says whyOmitting the field made a new receipt indistinguishable from a pre-#629 one, so it inherited that row's documented fail-open. New receipts record The swarm's own convergence check had the same 5 and 5b — reproduced with controlsYour
Performance, since the gate exists for it: alternating baseline and fix in the same window, five rounds each, 60-file repo — baseline median 243 ms, fix median 235 ms, −3.5%, within noise. The two added spawns are cached per repo. 4 — what I could and could not proveThe mechanism reproduces exactly as you describe: 8,000 indexed paths produce a 1.68 MB listing (you measured ~1.69) and a plain What I could not do is reproduce the end-to-end bypass on this machine. The spawn inside the fingerprint does not overrun where an identical standalone spawn does, and I did not determine why. So the Something I got wrong and removedI also added a line treating an untracked Gates
|
apackeer
left a comment
There was a problem hiding this comment.
Re-reviewed at 292db10f. The partial-approval ordering fix is sound, the route-level check is present on all four completion commands, and the new ls-files/attribute-source cases improve the mechanism. This still is not merge-ready: the source merge can validate one tree and merge another, and two of the claimed fail-closed fixes remain reproducibly fail-open.
P1: swarm source merge validates worktree bytes but merges the branch ref
workspaceSourceFingerprint includes staged, unstaged, and untracked worktree bytes (core/tools/aidlc-lib.ts:2611-2613), and the new merge guard recomputes that fingerprint over the Bolt worktree (core/tools/aidlc-worktree.ts:273-295). The actual squash/merge paths consume bolt-<slug>, not those worktree bytes (:337-338, :399-429).
Reproduced end to end with this head's tools:
- Commit unreviewed source X on
bolt-foo. - Revert the worktree to source B without committing.
- Record the review and converge/finalize against B.
- Run
aidlc-worktree merge --strategy squash.
The fingerprint guard passed, but main received X:
finalize_rc: 0
merge_reported_fingerprint_mismatch: false
landed: export const value = 1; // UNREVIEWED_BRANCH
The merge command eventually returned 1 only because cleanup noticed the dirty worktree, after the unreviewed commit had already landed (aidlc-worktree.ts:482-495, tagged [merge-succeeded:...]). The positive t259 swarm test creates uncommitted source and stops after metadata finalization (tests/unit/t259-source-freshness-receipts.test.ts:1106-1124), so it never checks which bytes the later source merge takes.
The convergence binding needs to name the exact Git object that will be merged, or source merge must require/snapshot the reviewed worktree state and merge that object. Comparing a dirty worktree and then merging a different branch ref cannot establish freshness.
P1: newly unbindable receipts still fail open while Git remains unavailable
aidlc-log now records Source Fingerprint: unbindable (core/tools/aidlc-log.ts:323-325), but the guard marks that receipt stale only when the current workspace becomes bindable (core/tools/aidlc-state.ts:1462-1469). If fingerprinting is still unavailable, the new receipt is accepted without proving anything.
I reproduced the original path with Git unavailable for both review and approval: the receipt contained unbindable, source was edited after review, and approve returned 0 with no mismatch. This is still a newly emitted receipt failing open. Only fieldless pre-#629 receipts need migration behavior; a new unbindable receipt should refuse until a binding can be established. The same conditional appears in swarm finalize and source merge (aidlc-swarm.ts:360-363, aidlc-worktree.ts:291-294).
P1: large filtered repositories still lose the raw-byte binding
The ls-files call now has a larger buffer, but the downstream check-attr call still uses the default buffer and turns every failure into an empty raw-binding set (core/tools/aidlc-lib.ts:2527-2532). The caller then returns the bare filtered tree SHA when that set is empty (:2691-2695).
Reproduced with 12,002 real files:
ls-files bytes: 2196122
check-attr status: null
check-attr error: ENOBUFS
fingerprints equal after byte-distinct clean-filtered edit: true
The new large-index test cannot catch this: its 8,000 synthetic entries exist only in the committed index, so the fingerprint's git add -A removes their absent worktree paths before the inner listing (tests/unit/t259-source-freshness-receipts.test.ts:475-501); the test also explicitly says it passes without the fix (:463-467). Every subprocess in the raw-binding phase needs sufficient output handling, and failures must propagate as null rather than [].
Related false negatives remain in the same gate: a process-only filter is ignored because driver detection checks only filter.<name>.clean (aidlc-lib.ts:2553-2559), and an ignored worktree .gitattributes file is read by Git but absent from the temporary index path list scanned at :2421-2425. Both shapes reproduce byte-distinct worktrees with identical fingerprints.
P1: the convergence fingerprint is recomputed instead of carrying the reviewed value
reviewerReceiptError verifies a receipt against currentFp but discards that value (core/tools/aidlc-swarm.ts:353-374). After the metadata merges, emitUnitConverged recomputes the fingerprint independently (:789-818, :425-451). An edit between those two reads is therefore stamped as the converged state even though it never matched the reviewer receipt. Carry the fingerprint that actually passed receipt validation into SWARM_UNIT_CONVERGED; do not take a second, authorizing snapshot.
The eventual source check has two further holes:
aidlc-worktree merge --intent Areads convergence rows from the active intent becauseassertConvergedSourceUnchangedcallsreadAllAuditShards(pd)without the command's--intent/--spaceselectors (core/tools/aidlc-worktree.ts:273-288). If B is active, A's row is invisible and the guard passes.- The check runs at
:346, before audit emission and all merge/rebase operations. The worktree/branch remains writable through that gap, and the rebase path performs fetch/rebase before the final ff merge (:379-456).
P2: true advance replays are no longer idempotent
Freshness runs before isReplay is evaluated (core/tools/aidlc-state.ts:1658-1673). After code-generation had genuinely advanced, I changed source for later work and replayed advance code-generation; instead of returning {"replay":true}, it refused with a source-fingerprint mismatch.
The crash-window state this fix targets is distinguishable: after approve writes [x] but before delegation, Current Stage has not moved to nextSlug, so it does not satisfy isReplay. A true replay can return before freshness validation without reopening the bypass.
P2: schema docs and regression coverage are incomplete
SWARM_UNIT_CONVERGED now emits Source Fingerprint, but the shipped audit registry still omits it (core/knowledge/aidlc-shared/audit-format.md:202), and the reference still says an uncomputable workspace keeps the previous behavior (docs/reference/12-state-machine.md:277,399). No committed test drives convergence through aidlc-worktree merge, persistent unbindable, partial-approval recovery, or true replay after later source work. Those are the paths carrying the new guarantees.
Verification
STAMP: tests/logs/2026-08-03T20-06-28Z
TRACES: /home/ubuntu/src/aidlc-workflows/.claude/worktrees/pr-646-rereview/tests/logs/2026-08-03T20-06-28Z/*.log (10 files)
SUMMARY: tests/logs/2026-08-03T20-06-28Z/summary.txt + failures.txt - Result: PASS; Failed files: 0
RESULT: unit . 10 pass/0 fail . reds: none . live vars set: none . invariant grep hits: 0 (path-excluded)
Focused unit slice: 255 assertions, 0 failures. bun run check, bun scripts/package.ts --check, git diff --check, base ancestry, and mergeability are clean. GitHub reports no checks on this head.
|
Thank you for the careful re-review and for spelling out the remaining failure modes so concretely. I addressed the requested scope in 0c38645, without taking on the separate per-unit attribution work from #662. What changed:
Regression coverage now includes the real large Validation completed locally:
|
|
Thanks, Iury. I re-reviewed current head I found two remaining merge-integrity blockers in the new immutable-source path:
Two smaller correctness cases should come with that update. A tracked symlink matched by a broad clean/process filter is included in raw-path scanning but rejected because snapshot materialization accepts only The existing tests cover submodule fingerprinting separately and an end-to-end ordinary filtered-file merge, so please add lifecycle coverage for the submodule snapshot, switch-on-finalize/switch-off-merge sequence, filtered symlink, and object retention. I am not reopening the per-unit attribution decision tracked in #662, and this branch does not need another rebase; it is already current with |
|
Thanks again for the precise reproductions and for confirming the paths already closed. I addressed the four remaining lifecycle cases in
Lifecycle coverage now exercises the dirty-submodule refusal, switch-on-finalize/switch-off-merge sequence, filtered symlink finalize+merge, external symlink-target isolation, retention through Local validation on the pushed head:
The new CI run and documentation run are currently |
|
Thanks, Iury. I re-reviewed current head The four lifecycle cases from my last comment are genuinely closed. I verified each with independent fixtures that go past the branch's own tests:
I found two blockers at this head, one of them a regression this branch introduces on a mainstream path:
One smaller correctness issue in the messaging: the recovery that the bypass refusal and the CHANGELOG advertise ("re-run review and finalize with source freshness enabled") is not actually reachable. A Housekeeping: Full smoke+unit at |
…te (2.5.6) PR awslabs#569's freshness guard invalidates receipts via audit events for declared record artifacts, but code-generation produces application source outside the record and workspace writes are deliberately invisible to the audit trail - so a post-review source edit left the receipt satisfying the completion guard for code nobody re-reviewed. `aidlc-log.ts review --verdict` now stamps a Source Fingerprint on REVIEW_COMPLETED for workspace_requires stages: a git-native content fingerprint (temp-index write-tree over tracked + untracked source, aidlc workspace tree excluded) per recorded repo. All four completion routes recompute and refuse a fingerprinted receipt whose source no longer matches. Legacy receipts and unbindable workspaces keep the previous fail-open behavior; off-switch AIDLC_SKIP_SOURCE_FRESHNESS=1. Closes awslabs#629
…ipts (review response, awslabs#629/awslabs#646) Addresses apackeer's review of the source-fingerprint binding: a single workspace-global, time-of-stamp fingerprint compared per-receipt broke the normal multi-unit sequential flow and deadlocked autonomous swarm settle, and swarm finalize never checked the fingerprint at all. Reproduced all three P1s and both P2 findings against this branch's own tools before fixing them. - verifyReviewerPrecondition (aidlc-state.ts): track only the chronologically newest fingerprinted receipt during the event walk, then reconcile once against the current tree - a match keeps every collected receipt (fixes the false multi-unit refusal), a mismatch clears them all. A settled autonomous swarm is exempt from this reconciliation entirely (isSettledSwarmForArtifactGuard), since the main checkout's mismatch before finalize merges the code back is expected, not staleness. - reviewerReceiptError (aidlc-swarm.ts): finalize now compares a converged unit's terminal receipt fingerprint against workspaceSourceFingerprint recomputed scoped to that unit's own Bolt worktree, refusing the merge on a mismatch - the worktree-scoped check the settled-swarm exemption above depends on existing. - gitTreeFingerprint (aidlc-lib.ts): recurses into every initialized git submodule (gitlinks read off the temp index directly - git submodule status measured 1s+ per call on Windows) and excludes the aidlc/.aidlc/ .aidlc-worktrees/.aidlc-sensors workspace family at any depth via pathspec glob magic, not just the workspace root. - Docs updated (audit-format.md, 12-state-machine.md) to describe the fingerprint semantics and the AIDLC_SKIP_SOURCE_FRESHNESS off-switch. - t245 -> t247 (collision with a file landed on v2), extended with 8 new regression tests covering every fix above; coverage registry regenerated. - Rebased onto v2 (2.5.10 tip); this branch is now 2.5.11.
… (review response, awslabs#629/awslabs#646) Addresses leandrodamascena's CHANGES_REQUESTED review of the source-freshness fix. Reproduced all three fingerprint bypasses and the docs inaccuracy against this branch's own tools before fixing any of them. - verifyReviewerPrecondition (aidlc-state.ts): the newest-receipt-matches- current check alone was not sufficient for a chain of 2+ receipts - unit A reviewed, A's file silently edited with no new review, unit B coded and reviewed (B's receipt stamps a fingerprint over the tree that already contains A's unreviewed edit) - passed even though nobody reviewed A's edit. Every consecutive fingerprint transition in a multi-receipt chain must now also be a pure addition (fingerprintChainIsAdditionsOnly, aidlc-lib.ts - git-diffs the content-addressed tree shas directly, no commits needed); any modified/deleted pre-existing path, or an unverifiable transition (multi-repo/submodule composite hash, a pruned tree object), discards the whole chain. - gitTreeFingerprint (aidlc-lib.ts): the any-depth exclusion added for the prior review's .aidlc-sensors finding was applied to all four workspace names, which silently dropped real application source that happened to live under a directory coincidentally named aidlc/.aidlc/.aidlc-worktrees. Only .aidlc-sensors is genuinely not root-anchored (a monorepo's per- package tsconfig can anchor it anywhere); the other three are excluded only at the workspace root again, where they are the only place they legitimately occur. - Submodule gitlinks are now read via `ls-files -s -z` instead of `-s`: without -z, git's default core.quotePath wraps a path containing a non-ASCII or otherwise "unusual" character in escaped double quotes, which never resolves to the real on-disk directory - the submodule was silently skipped and a reviewed-then-edited submodule at such a path shipped unreviewed. - Corrected three spots (12-state-machine.md, CHANGELOG.md, an aidlc-swarm.ts comment) that said finalize "merges the code back": aidlc-bolt complete --merge carries only the AIDLC data (state/audit/ runtime fragment); application source is never merged by finalize. - t244 extended with 3 new regression tests covering all three bypasses; docs and CHANGELOG updated to describe the additions-only chain check and the root-only/any-depth exclusion split.
…te.ts (review response, awslabs#646) leandrodamascena's review had a 4th inline comment (aidlc-state.ts:1399, original commit 965a161) pointing at a copy of the "finalize merges the code back" claim inside verifyReviewerPrecondition's own comment block that the prior fix's grep sweep (scoped to docs/CHANGELOG/aidlc-swarm.ts) missed. Corrected to match the other three spots: finalize's own merge-back (aidlc-bolt complete --merge) carries only the AIDLC data, never application source. Also added a regression test reproducing the reviewer's own inline-comment scenario verbatim (review alpha, review beta, tamper beta, re-review alpha) - distinct from the already-covered later-unit-masks-earlier-unit case, this is earlier-unit-re-review-masks-later-unit's-tamper. Verified against the already-fixed code before adding as a permanent test: refuses correctly.
…references only Matches the codebase's established convention (issue/PR number only in "why this exists" comments, not the individual reviewer's username) - inconsistent with the single pre-existing exception elsewhere and not durable if the PR/review is ever reorganized. The awslabs#646 reference alone already gives full traceability to the review discussion.
…own root (2.5.12) The exclusion was applied relative to every fingerprinted repo dir, so in a multi-repo intent a `repo-a/aidlc/**` file - application source, since the record tree is a SIBLING of the repo dirs - was dropped from the fingerprint and could be edited after a review without invalidating the receipt. Same defect one level down, inside an initialized submodule. gitTreeFingerprint now takes a required carriesWorkspaceShell flag, true only for the dir the caller hands in (the workspace roof, or a Bolt worktree with its own record mirror) and false for every dir the function derives itself. The pathspecs are directory-anchored, so a root file named `aidlc` is source too. `.aidlc-worktrees` is dropped from the set: no code path creates it (worktrees are `.aidlc/worktrees/bolt-<slug>`, already covered), so it only removed real source from the binding. Reproduced each case before fixing (awslabs#646 review, awslabs#629).
…space-global policy (2.5.12) The rule refused the rework loop stage-protocol.md §12a mandates (NOT-READY, lead fixes the artifact in place, re-review - an `M` transition) and any second unit touching a pre-existing shared file; it could never be cleared by re-reviewing, since every fingerprinted receipt is appended unconditionally; it could never be satisfied by the recorded-repo layout, whose fingerprint is a sha256 composite rather than a diffable git object; and it still admitted a pure addition nobody reviewed. A workspace-global hash cannot attribute a change to a unit, and inferring attribution from the diff shape is wrong in both directions. The guard keeps what it can actually establish - the current tree equals the tree the newest recorded review inspected - and the limitation is now stated in the CHANGELOG, the state-machine reference, the guard's own comment block and two tests. Two user-facing refusal messages that claimed more than the guard knows are corrected in the same change. Reproduced all five findings before changing anything (awslabs#646 review, awslabs#629).
…ires binding AIDLC_SKIP_SOURCE_FRESHNESS lived only inside two table cells, while its two siblings are documented in prose where a user looks for them; and stage-definition.md, which defines workspace_requires, listed only the artifact guard consequence of the flag.
…lided t244 slot While this branch was in review v2 merged awslabs#644 (2.5.25) and awslabs#645 (2.5.26), and earlier awslabs#596's t244-scope-matrix-doc-sync.test.ts, colliding on two fronts. Per the conflict-trap convention this branch, as the later one, rebases and re-bumps. - CHANGELOG heading renamed 2.5.12 -> 2.5.27 and placed above v2's 2.5.26 entry; aidlc-version.ts and the README badge follow. 2.5.27 is the first free slot above v2, and no open PR claims it. - t244-source-freshness-receipts.test.ts -> t259-source-freshness-receipts.test.ts. v2 now ships t244-scope-matrix-doc-sync.test.ts; two files sharing a t-number auto-merge cleanly while leaving `--filter t244` matching both forever, and nothing enforces t-number uniqueness. t248-t257 are claimed by open PRs and t258 shipped with awslabs#644, so t259 is the first free slot. The header comment, six describe() blocks, five mkdtemp prefixes and the gen-coverage-registry entry follow the filename. - Coverage registry and ratchet regenerated with bun tests/gen-coverage-registry.ts rather than hand-edited. No behavior change: every source, doc and test file is byte-identical to the reviewed head modulo the t244 -> t259 rename.
…lusion Two P2 findings from the awslabs#646 review, both in the fingerprint itself. A configured `clean` filter runs as content enters the index, so the tree sha hashed the FILTERED bytes rather than the bytes in the worktree: a lossy filter mapped two different worktrees onto one fingerprint, and the stage executes against the bytes the reviewer read. Fold a `--no-filters` hash of exactly the paths a clean driver touches into the fingerprint. The scan is gated on an attributes file actually assigning `filter=`, because `check-attr` measured +66% on this fingerprint's wall clock and it recomputes on every completion route; `hash-object` runs without `-w`, so no object enters the caller's store. The sensor-cache exclusion matched the leaf name at any depth, so an application tracking source under a directory named `.aidlc-sensors` could edit or delete it without moving the fingerprint. Match the path the engine actually writes instead: every writer resolves through sensorsDir -> docsRoot -> intentsDir -> workspaceRoot, so the cache is always `<anchor>/aidlc/spaces/<space>/intents[/<record>]/.aidlc-sensors/`. t259 gains the leaf-name reproduction (edit AND delete), the filter collapse with its revert control, and a row pinning that a repo which filters nothing keeps the bare tree sha it had before. The two rows that asserted the leaf-name exclusion are inverted in place, not removed.
Five findings from the latest review, all of which let unreviewed source reach a completion route. The completion checkbox stopped being a standing permission. `approve` validated Scope only after persisting `[x]`, so an aborted approval left a state file every completion route reads as "already done" and skips its guard; Scope is now validated before anything is written. That closes the window, but not one opened by a kill between the write and the route it delegates to, so source freshness now runs on every route into the transition - advance, its replay short-circuit, finalize and complete-workflow - while the artifact guard stays gated as before. A receipt that could not be fingerprinted recorded no field at all, which made it indistinguishable from a pre-awslabs#629 receipt and inherited that row's fail-open. New receipts record the reason instead, and the guard refuses when a receipt claims to be unbindable but the workspace can be fingerprinted now, or when a real fingerprint cannot be recomputed. A receipt with no field is still pre-awslabs#629 and still passes. Swarm convergence emitted a durable signal naming no source state, so nothing downstream could tell whether the bytes it was about to merge were the bytes that converged. The convergence row now carries the worktree's fingerprint and the source merge re-verifies against it. `git ls-files -s -z` ran on the default spawn buffer: a large index overran it and every submodule and clean-filtered path silently dropped out while the bare tree sha was still returned. It now has a sufficient buffer and fails closed. The gate deciding whether to run the precise `check-attr` scan missed three attribute sources - the default global path git reads with core.attributesFile unset, the target repo's own local config, and the info attributes of a linked worktree or submodule - and looked only for `filter=`, so git's built-in `ident` conversion collapsed worktrees unchecked. All four are covered, and the scan now asks for `ident` too.
f811ddf to
6399c02
Compare
|
Thank you for the precise reproduction and for narrowing the completed-stage fix to the correct seam. I reproduced the reported recovery failures and the bypass-message dead end before changing the implementation, then rebased and validated the result at What changed:
Local validation on the published head:
For transparency, the full smoke run on this Windows host is 1133 pass / 21 fail, all 21 in the existing GitHub now recognizes |
|
Addressed the latest review findings at
Verification on this head:
GitHub has not reported remote checks on this head yet, so I am not claiming remote CI green. |
|
The current head ( |
Summary
Implements #629: code-generation reviewer receipts now carry a fingerprint of the workspace source state the reviewer inspected, and every completion route refuses while the current source differs from the state the most recent recorded review saw. Source edited after the last review can no longer be shipped by that review's receipt. It does not establish per-unit attribution; see Known limitation below. Closes #629.
The mechanism follows the issue's framing ("does not bind that receipt to the source state"): a binding, not new audit events — workspace writes stay deliberately invisible to the audit trail, exactly as
aidlc-audit-logger.tsintends.Updated after three review rounds. The most recent one found five P1s in the
fingerprintChainIsAdditionsOnlychain rule that an earlier round had introduced; all five reproduce, the rule is removed, the sibling-repo exclusion is fixed, and the limitation that remains is stated rather than claimed away. See the review-response comments below for the point-by-point.Changes
1. Fingerprint (
aidlc-lib.ts—workspaceSourceFingerprint/gitTreeFingerprint): a git-native content fingerprint per repo —git write-treeover a temporary index (GIT_INDEX_FILE) seeded fromHEADwithgit add -Aapplied, so it covers tracked edits, untracked adds, and deletions without ever touching the real index or worktree. Content-addressed: reverting an edit restores the original fingerprint. Recurses into every initialized git submodule (detected off the temp index's own gitlink entries, read withls-files -zso a non-ASCII path is not lost to git's quoted-path escaping) and folds its own fingerprint into the parent's, so an uncommitted submodule edit is visible even though the gitlink itself doesn't move. Multi-repo: the intent's recorded repo set viarepoDir(), combined as a sha256 over sortedname=shalines; no recorded repos = the legacy single-repo default (projectDir).The
aidlc/.aidlcworkspace shell is excluded only at the top level of the directory that carries the shell — the workspace roof, or a Bolt worktree with its own record mirror. A directory of those names inside a fingerprinted repo or submodule is application source and is part of the fingerprint. The pathspecs are directory-anchored, so a root file namedaidlcis source too..aidlc-sensorsis excluded at any depth (a monorepo's per-package tsconfig can anchor the type-check sensor's cache anywhere). Git-ignored paths and uninitialized submodules are outside the binding.2. Stamping (
aidlc-log.ts review): a terminal--verdictfor aworkspace_requiresstage (code-generation, the only one) records the fingerprint as aSource Fingerprintfield on theREVIEW_COMPLETEDrow. A null fingerprint (not a git checkout) records no field.3. Stage-level guard (
aidlc-state.ts—verifyReviewerPrecondition): during receipt collection, only the chronologically newest fingerprinted receipt is tracked; after collection, ONE reconciliation against the recomputed current-tree fingerprint — a match keeps every collected receipt (a normal sequential multi-unit run, unit A reviewed then unit B coded and reviewed, is not falsely invalidated), a mismatch discards all of them (a workspace-global hash can't attribute which unit changed). A settled autonomous swarm (isSettledSwarmForArtifactGuard) is exempt from this reconciliation entirely — the main checkout's mismatch before finalize merges the AIDLC data back is expected, not a staleness signal. Runs on all four completion routes (approve/advance/finalize/complete-workflow). Fail-open where binding is impossible: legacy receipts without the field and unbindable workspaces keep today's behavior. Off-switch:AIDLC_SKIP_SOURCE_FRESHNESS=1.4. Swarm finalize guard (
aidlc-swarm.ts—reviewerReceiptError): for a converged, claimed unit, the terminal receipt'sSource Fingerprintis compared againstworkspaceSourceFingerprintrecomputed scoped to that unit's OWN Bolt worktree (not the main checkout) — a mismatch refuses the merge. This is the worktree-scoped half the settled-swarm exemption above depends on: the stage-level guard steps aside at settle because finalize does this check instead. It is also the one route with genuine per-unit attribution, since a Bolt worktree is a per-unit checkout.5. Docs:
audit-format.md'sREVIEW_COMPLETEDrow lists the new field;12-state-machine.md'sREVIEW_COMPLETEDandSWARM_UNIT_CONVERGEDrows describe the semantics, the limitation and the fail-open cases; the off-switch is documented in prose alongside its two siblings;stage-definition.md, which definesworkspace_requires, names the binding as a consequence of the flag.6. Tests (
tests/unit/t244-source-freshness-receipts.test.ts): fingerprint semantics in-process (deterministic, content-addressed, tracked-edit + untracked-add sensitive, null off-git, real index untouched, submodule-recursive including a non-ASCII path, and the exclusion scope — a directory namedaidlcinside a registered sibling repo or a submodule, and a root file of that name, must all move the fingerprint, while the roof's own shell must not and a nested.aidlc-sensorsmust not, including inside a registered repo); stamping + stage-level guard via the realaidlc-log/aidlc-stateCLI (passes while source matches, refuses after a post-review edit, off-switch restores the legacy pass, stripped-field legacy rows stay fail-open); the multi-unit policy (ordinary sequential run, the §12a rework loop, a shared-fileMtransition, and a recorded-repo workspace all pass; the two limitation cases assert the documented behaviour); swarm finalize via real Bolt worktrees.Known limitation (explicit policy)
The fingerprint is one workspace-global hash while receipts are per unit, so a match proves only that the current source tree is identical to the state the newest recorded review inspected — not that each unit was reviewed against its own code. Source written or changed before that newest review, including an earlier unit's file and including code no reviewer was shown, is inside the matching tree and passes; one fresh receipt re-validates every earlier receipt.
An earlier revision of this PR tried to recover attribution from the shape of the diff between consecutive receipts (
fingerprintChainIsAdditionsOnly: every transition had to be a pure addition). It was removed after being reproduced failing in both directions at once: it refused the rework loopstage-protocol.md§12a mandates (record NOT-READY, fix the artifact in place, re-review — anMtransition) and any second unit touching a pre-existing shared file; it could never be cleared by re-reviewing, since every fingerprinted receipt is appended unconditionally; it could never be satisfied by the default recorded-repo layout, whose fingerprint is a sha256 composite rather than a diffable git object; and it still admitted a pure addition nobody reviewed.Per-unit attribution needs a machine-readable manifest of the paths each unit wrote.
code-summarycarries that in prose only, and no such manifest exists incore/tools/orcore/hooks/today, which is why it is follow-up work rather than part of this PR. #629's acceptance criterion is conditional — "ambiguous attribution fails closed or follows an explicitly documented policy" — and this takes the documented-policy branch. The tradeoff is real: this ships a weaker guarantee than the earlier prose claimed, and the laundering window stays open on the stage-level route until attribution lands.Version note
This branch is 2.5.12, which is unreleased here (v2's tip is 2.5.11); the entry was rewritten in place rather than opening a second heading for one feature. The test file holds slot
t244, free onv2today. Both are contested by other open PRs — if either is taken first, I'll rebase, re-bump and rename per the AGENTS.md conflict-trap before merge.User experience
Before: produce a unit's source, record the architecture review, then edit
src/whatever.ts— approve/advance/finalize still complete, shipping source the reviewer never saw. After: the completion route refuses withRefusing to complete "code-generation": the workspace source no longer matches the state of the most recent recorded review (source-fingerprint mismatch) …and names both recoveries — record a fresh verdict, or revert the edit (content-addressed, so an undone change restores the original value). A normal multi-unit sequential run, the §12a rework loop, shared-file integration and a settled autonomous swarm are all unaffected; a swarm unit whose worktree source changed after its own review is refused at finalize with a worktree-scoped mismatch message.Checklist
If your change doesn't seem to apply, please leave them unchecked.
Test Plan
bun scripts/package.ts --check→ green for all 5 harnesses.bun run typecheck→ clean across all 3 tsconfigs;biome check --error-on-warnings→ clean.bun tests/gen-coverage-registry.ts) —--checkreports fresh, guards green, ratchet held.bun tests/run-tests.ts --unit --filter "t244|t115|t205|t206|t208|t186|t68|t134|t152|t166|t201|t221|t242|t174|t246|gen-coverage"→ the reviewer-guard, multi-repo and version-sync families.Acknowledgment
By submitting this pull request, I confirm that you can use, modify, copy, and redistribute this contribution, under the terms of the project license.