ci: make the projection fan-out HOLD mechanically answerable - #820
Merged
Conversation
jason931225
force-pushed
the
fix/lane-fanout-sccache-and-slice
branch
from
August 19, 2026 04:49
fa93203 to
33baaec
Compare
jason931225
force-pushed
the
ci/hold-release-conditions
branch
2 times, most recently
from
August 19, 2026 04:55
f78ef75 to
88d29bb
Compare
PRODUCT.md holds Company, Person, Employment and PayRun projection
fan-out until "each has an explicit owning port and a proven
single-writer boundary". Every clause of that is already enforced, and
enforced well:
- canonical_contract.rs proves each ObjectKey names an owner crate and
at least one table it alone may write, sized against ObjectKey::ALL
so a seventh key cannot slip through unowned;
- gate_detects_violation.rs derives its owned-table set from the same
ALL and proves the static gate catches a second writer, including
deliberately misspelled evasions;
- topology.canonical_enforcement refuses at runtime to claim
enforcement over zero tables.
This adds NO enforcement. Duplicating any of the above would be the
mistake, not the fix.
What was missing is composition. The release condition is prose and its
evidence is spread over six files whose naming conventions disagree: the
registry spells a key PayRun, the suite on disk is
pay_run_port_as_runtime_role.rs, and the CI map calls that same suite
payroll-adapter-postgres-pay-run-port-as-runtime-role-pg. Deciding
whether the hold may lift therefore meant re-deriving three transforms
by hand, and that derivation is genuinely error-prone: a find(1) under
the canonical adapter's test directory returns five of six suites and
misses PayRun entirely, because PayRun's owner is the payroll adapter.
That wrong turn reads as "PayRun is barely covered" when it has the
largest owned-table set of the six and seventeen tests.
So: one command, three transforms applied consistently, evidence cited.
It reports each object's owner, owned tables, the suite proving its
boundary, and whether that suite runs in the workflow PostgreSQL job.
Measured on this tree: every leg met for all six canonical objects.
Fails closed, verified by mutation rather than asserted:
- un-wiring PayRun's suite from the workflow postgres job
-> RED, naming that suite;
- naming an object in the hold that is not an ObjectKey
-> RED, because a hold over a non-existent object can never be
evaluated, let alone released;
- emptying an object's owned-table set
-> RED ("owns no table, so no writer rule applies to it");
- deleting the hold bullet -> throws, rather than passing over an
empty subject list.
The hold's subject is parsed from the bullet, not hardcoded, so editing
the bullet moves the check with it and the prose cannot drift away from
the registry it describes.
Releasing the hold stays a separate authority decision. This reports
evidence; it does not confer it.
Also fixes the tree walker: readdirSync(withFileTypes) reports a
symlinked directory as neither file nor directory, so the naive
isDirectory() walk skipped it and under-reported proofs.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
jason931225
force-pushed
the
ci/hold-release-conditions
branch
from
August 19, 2026 05:34
88d29bb to
8f0b523
Compare
github-merge-queue
Bot
removed this pull request from the merge queue due to failed status checks
Aug 19, 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.
What
docs/current/PRODUCT.md:38holds Company, Person, Employment and PayRun projection fan-out until "each has an explicit owning port and a proven single-writer boundary." This adds one command that decides whether that condition currently holds, and cites the evidence.What this is NOT
It adds no enforcement. Every clause of the hold is already enforced, and enforced well —
canonical_contract.rs(owner + owned tables, sized againstObjectKey::ALL),gate_detects_violation.rs(static gate catches a second writer, incl. misspelled evasions, table set derived from the sameALL), andtopology.canonical_enforcement(refuses to claim enforcement over zero tables). Duplicating any of that would be the mistake, not the fix.The actual friction
The release condition is prose; its evidence spans six files whose naming conventions disagree:
PayRunpay_run_port_as_runtime_role.rspayroll-adapter-postgres-pay-run-port-as-runtime-role-pgAnswering the question meant re-deriving three transforms by hand. That derivation is error-prone in a way that matters: a
findunder the canonical adapter's test dir returns five of six suites and misses PayRun, whose owner is the payroll adapter — which reads as "PayRun is barely covered" when it owns the largest table set of the six and has 17 tests. That wrong turn is why this exists, and it is pinned by a test.Fails closed — verified by mutation, not asserted
ObjectKeyThe hold's subject is parsed from the bullet rather than hardcoded, so editing the bullet moves the check with it.
Authority
Releasing the hold stays a separate authority decision. The command reports evidence; it does not confer it. The PRODUCT.md edit adds a pointer to the verifier and says so explicitly — it does not change the hold's status.
Also
Fixes the tree walker:
readdirSync(withFileTypes)reports a symlinked directory as neither file nor directory, so the naiveisDirectory()walk skipped it and under-reported proofs.Verification
13/13 gate sweep green in the worktree, both reachability scanners green (
60 suites; dark 0),executed-tests-baselinegreen.Base
Stacked on #818, which carries
tools/ci/gate-sweep.json. Registering there rather than inpackage.jsonavoids re-introducing an&&link that #819 exists to remove.🤖 Generated with Claude Code