RFC-015 P1-S2: consolidation protection parity, marker inheritance, playbook advisory#625
Merged
Conversation
…laybook advisory Routes every consolidation path through the shared protection resolver, inherits the playbook marker into digests on both the legacy and clerk paths, prefers a marked member as dedupe canonical, and emits a playbook_advisory on the dry-run, legacy apply and clerk apply envelopes (REQ-9). Suite tests/test-rfc-015-p1-s2-consolidation.mjs is 29/30 and registered in the substrate CI job. t4b_abort_message_discriminates is knowingly red: its EISDIR fixture throws at em-consolidate.mjs:808 (module-init loadIndex) before the resolver runs, so the process dies with a raw stack trace instead of the structured abort envelope. Fixing that needs a production change this frozen plan does not authorize; filed separately. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This was referenced Jul 26, 2026
Routing the legacy dry-run and apply paths through resolveProtectionOrAbort made class-0 `pinned` protection re-skip rows the pre-lock row filter had already admitted. main had no pinned gating on those paths at all, so two suites that pass on main went red: test-em-consolidate.mjs --include-pinned became a no-op (7/8) test-store-write-concurrency S3c test 46, a row pinned during lock-wait (45/46) liftPinnedGate() strips only the `pinned` reason, only from the two protectedIds maps those paths feed to partitionProtectionSkips. It is a no-op in the default case because the row filter already excludes pinned rows before clustering. protection.mjs, the clerk path and the fold paths are untouched, and every other protection class including playbook-referenced passes through unchanged. test-em-consolidate 8/8, test-store-write-concurrency 46/46, test-rfc-015-p1-s2-consolidation 29/30 (t4b unchanged), test-rfc-009-p4-apply 30/30, test-fold-superseded 11/11, test-fold-all-projects 8/8. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
t4b_abort_message_discriminates exposes a real product gap, not a bad test: em-consolidate.mjs:808 loads the index at module-init with no guard, so the EISDIR fixture throws in relevance.mjs:100 before resolveProtectionOrAbort runs and the process dies with a raw stack trace and empty stdout. Filed as #626. Closing it is a production change outside this slice's frozen plan. The leg's fixture and all four assertions are kept verbatim behind a single T4B_PENDING_626 constant. The runner prints a distinct PEND line naming the issue and tallies honestly as "29/29 pass, 1 pending" rather than claiming 30/30. Re-enabling once #626 lands is flipping that constant to false. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
lantiscooperdev
approved these changes
Jul 27, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Implements RFC-015 Phase 1 slice 2 by mechanical transcription of the frozen, review-ACCEPTed plan
docs/plans/rfc-015-p1-s2.md.What lands
--fold,--all-projects— now derives protected ids through the single sharedresolveProtectionOrAborthelper instead of per-path ad-hoc logic. The clerk path's barecatch {}aroundcomputeProtectedIdsis replaced with a real failure path that aborts and releases the lock.playbook: truewhen any member carries it, on both the legacy and clerk paths, through the frontmatter serializer, the index row builder, and the clerk merge builder.buildConsolidationAdvisory()inscripts/lib/playbook-registration.mjs, wired into all three envelopes: dry-run, legacy apply, and clerk apply. It names the causing declaration and the recovery path when a cluster is skipped by class-e protection, and flags marker inheritance when a marked-but-undeclared episode is folded.substratejob.Verification
CI green across all three workflows (
Tests,Plugin + schema validation,RFC registry validation) ond660624.tests/test-rfc-015-p1-s2-consolidation.mjstests/test-em-consolidate.mjstests/test-store-write-concurrency.mjstests/test-rfc-009-p4-apply.mjstests/test-fold-superseded.mjstests/test-fold-all-projects.mjstests/test-ci-suite-registration.mjsWorkflow YAML re-parsed after the CI edit (
yaml.safe_loadresolves all six jobs), since a YAML-invalid workflow edit is how #613 broke.Two things CI caught after this PR was opened
1.
--include-pinnedregression (fixed in9ed38da). Centralizing protection added gating to the legacy dry-run and apply paths, wheremainhad none. Class-0pinnedprotection then re-skipped rows the pre-lock row filter had already admitted, so--include-pinnedsilently became a no-op and a pinned-during-lock-wait race stopped folding. Two suites that pass onmainwent red:test-em-consolidate.mjs(7/8) andtest-store-write-concurrency.mjstest 46 (45/46). Verified against a detached worktree atmainto confirm the regression was ours.liftPinnedGate()strips only thepinnedreason, only from the twoprotectedIdsmaps those newly-gated paths feed topartitionProtectionSkips. It is a no-op in the default case because the row filter already excludes pinned rows before clustering.protection.mjs, the clerk path and the fold paths are untouched, and every other protection class includingplaybook-referencedpasses through unchanged.2.
t4b_abort_message_discriminatesmarked pending (d660624). It exposes a real product gap rather than a bad assertion:scripts/em-consolidate.mjs:808loads the index at module-init with no guard, so the EISDIR fixture throws insiderelevance.mjs:100beforeresolveProtectionOrAbortruns and the process dies with a raw stack trace and empty stdout. Filed as #626. Closing it is a production change outside this frozen plan, so the leg's fixture and all four assertions are kept verbatim behind a singleT4B_PENDING_626constant; the runner prints a distinctPENDline naming the issue and tallies honestly as29/29 pass, 1 pendingrather than claiming 30/30. Re-enabling is flipping that constant tofalse.Review
Adversarial review by an independent GLM-5.2 seat, two rounds, every finding backed by a runtime probe rather than code reading.
Round 1 returned ACCEPT-WITH-MOD with one P2: the clerk apply envelope emitted neither
playbook_advisorynorprotection_skips, leaving REQ-9 ("both apply paths") half-met. Round 1 also independently proved by probe that the clerk marker chain is correct despite its suite leg passing vacuously, that canonical selection is stable across insertion order, that the abort path releases the lock with no double-release, and thatbuildConsolidationAdvisorynever throws on malformed input.The P2 was fixed by implementing Listing L11's clerk-envelope prose, and
t7b_clerk_marker_inheritwas de-vacuumed with a state-D fixture that actually reaches the clerk merge path. Round 2 returned ACCEPT with no findings above NIT, confirming content parity with the legacy envelope, correct negative polarity (both keys omitted, not null), nocanonical-supersededleak intoprotection_skips, and that the rewrittent7bwould fail against the pre-fix envelope.Note that the review rounds predate the two CI-caught items above, which were found by CI rather than by review.
Filed alongside
loadIndexgap behind the pendingt4b.🤖 Generated with Claude Code