Skip to content

A reader for the clause-② gate: assert the dual carrier, and make a missing declaration loud - #13944

Merged
os-sam merged 3 commits into
mainfrom
claude/issue-13922-clause2-carrier-enforcement
Aug 31, 2026
Merged

A reader for the clause-② gate: assert the dual carrier, and make a missing declaration loud#13944
os-sam merged 3 commits into
mainfrom
claude/issue-13922-clause2-carrier-enforcement

Conversation

@claude

@claudeclaudeBot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Fixes#13922
Fixes#12409
Part of #13914

Family dispatch, code half only. One new checker in the existing check:pm-* gate family gives the clause-② enqueue gate a reader a seat can point at one pair: the dual carrier is asserted by mechanism, and a limb nobody can read is reported as a missing reading rather than passing for a decision.


⚠️ Premise correction — read this first

#13922 says the dual-carrier rule "has no enforcement at all" and that "there is no check anywhere that a needs:contract-review on one carrier implies it on the other". That is not true, and the correction shaped this PR.

h31ContractReviewCarrierSplit in scripts/pm/check-half-states.mjs is exactly that check. It compares both carriers, in both directions, names the offending PR, and it has a standing caller — .github/workflows/half-state-patrol.yml, four times a day. Measured on this branch: the patrol's last scheduled run before the card was filed was 2026-08-31T13:42:41Z, conclusion success.

Building a second carrier comparison as though H31 did not exist would have produced two predicates that can disagree about the same pair. So this PR imports H31's label constant and H31's delivery relation instead of restating either, and closes only the parts H31 genuinely cannot reach. Those parts are real, and all three were re-measured on the live board today:

  1. Agreement on ABSENCE is H31's silent case, by construction. It fires on a split; a pair carrying the gate on neither carrier is, to it, clean. That is precisely the "all three limbs silent at once" shape The clause-② gate has three limbs and all three were silent on PR #13910 at once — the dual-carrier label rule has no enforcement, and 4 of 6 measured pairs are out of sync #13922 measured on PR fix(rest): a data engine that cannot be RESOLVED no longer answers 403 FORBIDDEN (#13476) #13910 / card [finding] after the #13279 repair, an UNRESOLVABLE data engine still answers 403 FORBIDDEN — the last surviving GRANTS-LOST disguise at the package door #13476.
  2. Nothing read limb ② at all. Grep over scripts/ and .github/workflows/ finds the Clause-② token in a label description and in prose — never in a predicate. The Clause-②: yes | no machine spelling is missing from the claim comment on 2 of 3 measured cards — the enqueue gate's predicate reads it there, and it is not there #13914 measured the consequence: 2 of 3 cards in one review round carried the declaration as prose or in the PR body, and a card with no declaration is indistinguishable from a card that declared no.
  3. H31's delivery channel trims. Its report is one row family among ~38 rendered into one pinned anchor issue. Measured on the 13:42Z run body: 231 findings, 74 rendered, "157 further row(s) omitted to fit GitHub's issue-body limit." A gate row competes for space with board-hygiene rows, and the anchor then reads clean on the gate.

premise_still_valid is therefore partly false and the surviving defect is narrower and sharper than the card states. It is stated in full in the new file's header so the next reader is not told the original story.


What landed

scripts/pm/check-clause2-carriers.mjs — answers one question and prints only its own rows, so nothing it finds can be crowded out by another mechanism's inventory.

rowwhat it saysmember
C1the two carriers of one pair disagree — both directions, each with its own consequence#13922 · #12409
C2the declaration limb has NO READING: absent, misplaced (fixed spelling on the thread but not in the claim carrier), or malformed#13914 remedy 2
C3a declared Clause-②: yes with the gate on neither carrier — the fail-open a carrier comparison is structurally blind to#13922

Modes, and why each exit code is what it is:

  • default sweep — report-only, exit 0 on a complete sweep whatever it found; 2 when any pair was UNJUDGED (an unread carrier is not a bare carrier, an unread thread is not an absent declaration); 3 PREREQUISITE NOT MET, the constant imported from check-half-states.mjs so the family has one code for it.
  • --pair PR-NUMBER — a predicate about that pair, so it may answer adversely where the sweep may not: exit 4, deliberately not 3, so a seat reading the status can never turn a refusal into a clearance. A pre-arm tool, ⛔ not a merge gate.
  • --self-test — offline, 64 cases. This is what lint.yml runs.

Single-sourced, not restated: CONTRACT_REVIEW_LABEL, prDeliversCard, labelNames, CLAIM_COMMENT_MARKER, governingClaim, proxyRearmPlan and EXIT_PREREQUISITE_NOT_MET all come from check-half-states.mjs. This checker and H31 cannot come to disagree about which PR delivers which card.

It writes nothing, ever. Hanging or clearing a review gate from a checker would be issuing the review verdict — 自查放行. Same call H31 makes, for the same reason.


Measured, on the live board

The sweep, run on this branch's head against objectstack-ai/objectstack at 2026-08-31T18:0xZ: 25 card/PR pairs derived from 27 open PRs, 25 findings, 0 UNJUDGED (exit 0).

None of these was repaired here. The deliverable is the mechanism; the rows are the PM round's input.

Zone-2 assumptions, tested rather than inherited:


Two things recorded rather than acted on

1. The two directions of a split are not ranked, because the two standing sources rank them oppositely. H31's header calls the card-bare direction "the more dangerous half" (an ungated card enqueues past a live gate). #13922 calls the PR-bare direction the fail-open ("it is the carrier the enqueue gate reads"). Both are sound about their own consumer. This checker states each direction's consequence and ranks neither — picking one would print an adjudication as a derivation. Which carrier the enqueue gate actually reads is a protocol question for the maintainer.

2. What this implies for .claude/skills/pm-dispatch/references/contract-review.md — recorded here, no edit landed. That file is held by pending human-merge PR #13746 and is outside this dispatch's surface. Two of its sentences are affected:

Neither sentence is edited by this PR.


Scope, held


Verification

Derived on this PR's own final head — node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack, at this PR's final head 850a759 (4 paths vs merge base 0f63965ea; the union is byte-identical to the one derived at d06df85, and it was re-run in full on 850a759 with the same result): 33 families + 2 convention-triggered obligations = 35 commands, all run serially under scripts/pm/os-verify-lock.sh.

32 of 35 exit 0. Including both convention obligations an added gate script incurs — node scripts/pm/bare-root-worklist.mjs --self-test and pnpm check:pm-dispatch-gates — plus check:self-test-wired, check:entry-guard, check:parse-guard, check:watch-hint-literal, check:pm-label-desc-cap and check:required-contexts.

The other 3 are NOT MEASURED, not red — each says so in its own verdict text, and each needs a full package build this diff cannot affect (it touches no package source):

gateexitits own words
check-test-completeness.mjs3"PREREQUISITE NOT MET … the local reading for this gate is NOT MEASURED. ⛔ It is not a red, and there is nothing here to fix."
check:dual-build-cjs-loads3"PREREQUISITE NOT MET — this gate reads built output, and some package has no dist/. Run pnpm build first. ⛔ This is NOT a pass: nothing was measured."
check:type-check-debt1its --self-test and coverage read pass; --re-measure refuses without the built closure, because measuring there "would silently measure a DIFFERENT WORLD"

Also run, because this checker imports it: pnpm check:pm-half-states1826 cases pass, so the imported surface is intact.

Every exit code was exercised live, in the direction predicted before the run:

runexitreading
--pair 13944 (this PR)0its three pairs — #13922, #12409, #13914 — all declare Clause-②: no in the fixed spelling and both carriers agree. Three green lines, the clean control.
--pair 138474the C3 row on card #13700. A verdict about that pair, not about the environment.
--pair 13944, earlier attempt3a transient HTTP 502 on the PR listing, on a route where curl returned 200 throughout. It refused with "0 pair(s) had been read … NOT a reading of a clean board" rather than reporting green — which is why the listing now carries the same single retry the per-pair reads do (third commit).

Live evidence beyond the self-test: the sweep output above, plus an independent second read of the label sets on #13929/#13407, #13927/#13632 and #13847/#13700 confirming every C1 and C3 row.

Authored in session session_01Msg17tAHJ3jVTYFgHydCm2 (durable copy of the attribution, because a body edit normalises the footer's session URL away).

No changeset: this diff publishes nothing — scripts/pm/**, a workflow, and a script registration in the private root manifest. skip-changeset applies and is attached.


Generated by Claude Code


Generated by Claude Code

`needs:contract-review` is a dual-carrier gate by the maintainer's ruling of
2026-08-22 (「两边都挂好」), and its content limb is a fixed machine spelling
in the card's claim comment. Neither had a reader that a seat could point at
one pair: H31 in `check-half-states.mjs` compares the two carriers, but it is
silent when the gate is missing from BOTH (agreement on absence is its silent
case) and it reports through a patrol body that trims — 231 findings, 74
rendered on the 2026-08-31T13:42Z run. The declaration limb had no reader at
all: grep over `scripts/` and `.github/workflows/` finds the token only in a
label description and in prose.
`scripts/pm/check-clause2-carriers.mjs` answers exactly that one question and
prints only its own rows:
C1 the two carriers of one pair disagree. Both directions, each with its
own consequence, neither ranked — H31 and #13922 rank them oppositely
and this file records the disagreement rather than adjudicating it.
C2 the declaration limb has NO READING: absent, misplaced (the fixed
spelling on the thread but not in the claim carrier), or malformed. A
missing reading is never collapsed into a declared `no`.
C3 a declared `yes` with the gate on NEITHER carrier — the fail-open a
carrier comparison is structurally blind to.
The label constant, the `prDeliversCard` delivery relation and the shared
PREREQUISITE-NOT-MET exit code are imported from `check-half-states.mjs`, so
this checker and H31 cannot come to disagree about which PR delivers which
card. It writes nothing, ever: hanging or clearing a review gate from a
checker would be issuing the verdict.
The fixed spelling is not relaxed. Decoration around the line is tolerated the
way H4 tolerates it, and reasoning after the value token is accepted because
that is the shape #13914 records as its control; prose, a different case and a
word merely starting with the token all stay unread and are quoted back in the
row so the residue is actionable.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Msg17tAHJ3jVTYFgHydCm2
The filing card read this file as the place the dual-carrier rule should have
been enforced and found only label-object reconciliation there — a fair
reading, because nothing in the file said otherwise. State the boundary, and
point at the two files that answer the hanging question instead: H31 for the
board-wide carrier comparison, and the new per-pair checker for the same
comparison anchored on one pair plus the declaration limb this repo had no
reader for.
⛔ No behaviour change: this file still reconciles the label object and
nothing else, and none of the three writes the label.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Msg17tAHJ3jVTYFgHydCm2
@claudeclaudeBot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 31, 2026
@github-actionsgithub-actionsBot added size/l ci/cd dependencies Pull requests that update a dependency file labels Aug 31, 2026
…ds have
A failure on the listing is TOTAL — no pair is formed, so the run refuses with
"0 pair(s) had been read", which is the correct answer and exactly why it
should not be spent on a blip. Measured while verifying this branch: a
transient HTTP 502 on page 1 refused a --pair run that answered in a second on
the next attempt, on a route where curl was returning 200 throughout.
⛔ Still one retry, not a loop: a second failure is the answer, and a sweep
that keeps trying is a sweep that hides an exhausted quota.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Msg17tAHJ3jVTYFgHydCm2
@os-sam
os-sam marked this pull request as ready for review August 31, 2026 18:32
@os-sam
os-sam enabled auto-merge August 31, 2026 18:32
@os-sam
os-sam added this pull request to the merge queueAug 31, 2026
Merged via the queue into main with commit 736980cAug 31, 2026
37 checks passed
@os-sam
os-sam deleted the claude/issue-13922-clause2-carrier-enforcement branch August 31, 2026 18:57
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/cddependenciesPull requests that update a dependency filesize/lskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants

@os-sam@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { // Add copy buttons to all
 blocks
(function() {
function addCopyButtons() {
document.querySelectorAll('pre code').forEach(function(codeBlock) {
if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;
codeBlock.parentElement.setAttribute('data-copy-added', 'true');
var btn = document.createElement('button');
btn.textContent = 'Copy';
btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';
btn.onmouseover = function() { this.style.opacity = '1'; };
btn.onmouseout = function() { this.style.opacity = '0.7'; };
btn.onclick = function() {
navigator.clipboard.writeText(codeBlock.textContent).then(function() {
btn.textContent = 'Copied!';
setTimeout(function() { btn.textContent = 'Copy'; }, 1500);
});
};
codeBlock.parentElement.style.position = 'relative';
codeBlock.parentElement.appendChild(btn);
});
}
addCopyButtons();
// Re-run on dynamic content
var observer = new MutationObserver(addCopyButtons);
observer.observe(document.body, { childList: true, subtree: true });
})();
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
A reader for the clause-② gate: assert the dual carrier, and make a missing declaration loud by claude[bot] · Pull Request #13944 · objectstack-ai/objectstack · GitHub
Skip to content

A reader for the clause-② gate: assert the dual carrier, and make a missing declaration loud - #13944

Merged
os-sam merged 3 commits into
mainfrom
claude/issue-13922-clause2-carrier-enforcement
Aug 31, 2026
Merged

A reader for the clause-② gate: assert the dual carrier, and make a missing declaration loud#13944
os-sam merged 3 commits into
mainfrom
claude/issue-13922-clause2-carrier-enforcement

Conversation

@claude

@claudeclaudeBot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Fixes#13922
Fixes#12409
Part of #13914

Family dispatch, code half only. One new checker in the existing check:pm-* gate family gives the clause-② enqueue gate a reader a seat can point at one pair: the dual carrier is asserted by mechanism, and a limb nobody can read is reported as a missing reading rather than passing for a decision.


⚠️ Premise correction — read this first

#13922 says the dual-carrier rule "has no enforcement at all" and that "there is no check anywhere that a needs:contract-review on one carrier implies it on the other". That is not true, and the correction shaped this PR.

h31ContractReviewCarrierSplit in scripts/pm/check-half-states.mjs is exactly that check. It compares both carriers, in both directions, names the offending PR, and it has a standing caller — .github/workflows/half-state-patrol.yml, four times a day. Measured on this branch: the patrol's last scheduled run before the card was filed was 2026-08-31T13:42:41Z, conclusion success.

Building a second carrier comparison as though H31 did not exist would have produced two predicates that can disagree about the same pair. So this PR imports H31's label constant and H31's delivery relation instead of restating either, and closes only the parts H31 genuinely cannot reach. Those parts are real, and all three were re-measured on the live board today:

  1. Agreement on ABSENCE is H31's silent case, by construction. It fires on a split; a pair carrying the gate on neither carrier is, to it, clean. That is precisely the "all three limbs silent at once" shape The clause-② gate has three limbs and all three were silent on PR #13910 at once — the dual-carrier label rule has no enforcement, and 4 of 6 measured pairs are out of sync #13922 measured on PR fix(rest): a data engine that cannot be RESOLVED no longer answers 403 FORBIDDEN (#13476) #13910 / card [finding] after the #13279 repair, an UNRESOLVABLE data engine still answers 403 FORBIDDEN — the last surviving GRANTS-LOST disguise at the package door #13476.
  2. Nothing read limb ② at all. Grep over scripts/ and .github/workflows/ finds the Clause-② token in a label description and in prose — never in a predicate. The Clause-②: yes | no machine spelling is missing from the claim comment on 2 of 3 measured cards — the enqueue gate's predicate reads it there, and it is not there #13914 measured the consequence: 2 of 3 cards in one review round carried the declaration as prose or in the PR body, and a card with no declaration is indistinguishable from a card that declared no.
  3. H31's delivery channel trims. Its report is one row family among ~38 rendered into one pinned anchor issue. Measured on the 13:42Z run body: 231 findings, 74 rendered, "157 further row(s) omitted to fit GitHub's issue-body limit." A gate row competes for space with board-hygiene rows, and the anchor then reads clean on the gate.

premise_still_valid is therefore partly false and the surviving defect is narrower and sharper than the card states. It is stated in full in the new file's header so the next reader is not told the original story.


What landed

scripts/pm/check-clause2-carriers.mjs — answers one question and prints only its own rows, so nothing it finds can be crowded out by another mechanism's inventory.

rowwhat it saysmember
C1the two carriers of one pair disagree — both directions, each with its own consequence#13922 · #12409
C2the declaration limb has NO READING: absent, misplaced (fixed spelling on the thread but not in the claim carrier), or malformed#13914 remedy 2
C3a declared Clause-②: yes with the gate on neither carrier — the fail-open a carrier comparison is structurally blind to#13922

Modes, and why each exit code is what it is:

  • default sweep — report-only, exit 0 on a complete sweep whatever it found; 2 when any pair was UNJUDGED (an unread carrier is not a bare carrier, an unread thread is not an absent declaration); 3 PREREQUISITE NOT MET, the constant imported from check-half-states.mjs so the family has one code for it.
  • --pair PR-NUMBER — a predicate about that pair, so it may answer adversely where the sweep may not: exit 4, deliberately not 3, so a seat reading the status can never turn a refusal into a clearance. A pre-arm tool, ⛔ not a merge gate.
  • --self-test — offline, 64 cases. This is what lint.yml runs.

Single-sourced, not restated: CONTRACT_REVIEW_LABEL, prDeliversCard, labelNames, CLAIM_COMMENT_MARKER, governingClaim, proxyRearmPlan and EXIT_PREREQUISITE_NOT_MET all come from check-half-states.mjs. This checker and H31 cannot come to disagree about which PR delivers which card.

It writes nothing, ever. Hanging or clearing a review gate from a checker would be issuing the review verdict — 自查放行. Same call H31 makes, for the same reason.


Measured, on the live board

The sweep, run on this branch's head against objectstack-ai/objectstack at 2026-08-31T18:0xZ: 25 card/PR pairs derived from 27 open PRs, 25 findings, 0 UNJUDGED (exit 0).

None of these was repaired here. The deliverable is the mechanism; the rows are the PM round's input.

Zone-2 assumptions, tested rather than inherited:


Two things recorded rather than acted on

1. The two directions of a split are not ranked, because the two standing sources rank them oppositely. H31's header calls the card-bare direction "the more dangerous half" (an ungated card enqueues past a live gate). #13922 calls the PR-bare direction the fail-open ("it is the carrier the enqueue gate reads"). Both are sound about their own consumer. This checker states each direction's consequence and ranks neither — picking one would print an adjudication as a derivation. Which carrier the enqueue gate actually reads is a protocol question for the maintainer.

2. What this implies for .claude/skills/pm-dispatch/references/contract-review.md — recorded here, no edit landed. That file is held by pending human-merge PR #13746 and is outside this dispatch's surface. Two of its sentences are affected:

Neither sentence is edited by this PR.


Scope, held


Verification

Derived on this PR's own final head — node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack, at this PR's final head 850a759 (4 paths vs merge base 0f63965ea; the union is byte-identical to the one derived at d06df85, and it was re-run in full on 850a759 with the same result): 33 families + 2 convention-triggered obligations = 35 commands, all run serially under scripts/pm/os-verify-lock.sh.

32 of 35 exit 0. Including both convention obligations an added gate script incurs — node scripts/pm/bare-root-worklist.mjs --self-test and pnpm check:pm-dispatch-gates — plus check:self-test-wired, check:entry-guard, check:parse-guard, check:watch-hint-literal, check:pm-label-desc-cap and check:required-contexts.

The other 3 are NOT MEASURED, not red — each says so in its own verdict text, and each needs a full package build this diff cannot affect (it touches no package source):

gateexitits own words
check-test-completeness.mjs3"PREREQUISITE NOT MET … the local reading for this gate is NOT MEASURED. ⛔ It is not a red, and there is nothing here to fix."
check:dual-build-cjs-loads3"PREREQUISITE NOT MET — this gate reads built output, and some package has no dist/. Run pnpm build first. ⛔ This is NOT a pass: nothing was measured."
check:type-check-debt1its --self-test and coverage read pass; --re-measure refuses without the built closure, because measuring there "would silently measure a DIFFERENT WORLD"

Also run, because this checker imports it: pnpm check:pm-half-states1826 cases pass, so the imported surface is intact.

Every exit code was exercised live, in the direction predicted before the run:

runexitreading
--pair 13944 (this PR)0its three pairs — #13922, #12409, #13914 — all declare Clause-②: no in the fixed spelling and both carriers agree. Three green lines, the clean control.
--pair 138474the C3 row on card #13700. A verdict about that pair, not about the environment.
--pair 13944, earlier attempt3a transient HTTP 502 on the PR listing, on a route where curl returned 200 throughout. It refused with "0 pair(s) had been read … NOT a reading of a clean board" rather than reporting green — which is why the listing now carries the same single retry the per-pair reads do (third commit).

Live evidence beyond the self-test: the sweep output above, plus an independent second read of the label sets on #13929/#13407, #13927/#13632 and #13847/#13700 confirming every C1 and C3 row.

Authored in session session_01Msg17tAHJ3jVTYFgHydCm2 (durable copy of the attribution, because a body edit normalises the footer's session URL away).

No changeset: this diff publishes nothing — scripts/pm/**, a workflow, and a script registration in the private root manifest. skip-changeset applies and is attached.


Generated by Claude Code


Generated by Claude Code

`needs:contract-review` is a dual-carrier gate by the maintainer's ruling of
2026-08-22 (「两边都挂好」), and its content limb is a fixed machine spelling
in the card's claim comment. Neither had a reader that a seat could point at
one pair: H31 in `check-half-states.mjs` compares the two carriers, but it is
silent when the gate is missing from BOTH (agreement on absence is its silent
case) and it reports through a patrol body that trims — 231 findings, 74
rendered on the 2026-08-31T13:42Z run. The declaration limb had no reader at
all: grep over `scripts/` and `.github/workflows/` finds the token only in a
label description and in prose.
`scripts/pm/check-clause2-carriers.mjs` answers exactly that one question and
prints only its own rows:
C1 the two carriers of one pair disagree. Both directions, each with its
own consequence, neither ranked — H31 and #13922 rank them oppositely
and this file records the disagreement rather than adjudicating it.
C2 the declaration limb has NO READING: absent, misplaced (the fixed
spelling on the thread but not in the claim carrier), or malformed. A
missing reading is never collapsed into a declared `no`.
C3 a declared `yes` with the gate on NEITHER carrier — the fail-open a
carrier comparison is structurally blind to.
The label constant, the `prDeliversCard` delivery relation and the shared
PREREQUISITE-NOT-MET exit code are imported from `check-half-states.mjs`, so
this checker and H31 cannot come to disagree about which PR delivers which
card. It writes nothing, ever: hanging or clearing a review gate from a
checker would be issuing the verdict.
The fixed spelling is not relaxed. Decoration around the line is tolerated the
way H4 tolerates it, and reasoning after the value token is accepted because
that is the shape #13914 records as its control; prose, a different case and a
word merely starting with the token all stay unread and are quoted back in the
row so the residue is actionable.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Msg17tAHJ3jVTYFgHydCm2
The filing card read this file as the place the dual-carrier rule should have
been enforced and found only label-object reconciliation there — a fair
reading, because nothing in the file said otherwise. State the boundary, and
point at the two files that answer the hanging question instead: H31 for the
board-wide carrier comparison, and the new per-pair checker for the same
comparison anchored on one pair plus the declaration limb this repo had no
reader for.
⛔ No behaviour change: this file still reconciles the label object and
nothing else, and none of the three writes the label.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Msg17tAHJ3jVTYFgHydCm2
@claudeclaudeBot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 31, 2026
@github-actionsgithub-actionsBot added size/l ci/cd dependencies Pull requests that update a dependency file labels Aug 31, 2026
…ds have
A failure on the listing is TOTAL — no pair is formed, so the run refuses with
"0 pair(s) had been read", which is the correct answer and exactly why it
should not be spent on a blip. Measured while verifying this branch: a
transient HTTP 502 on page 1 refused a --pair run that answered in a second on
the next attempt, on a route where curl was returning 200 throughout.
⛔ Still one retry, not a loop: a second failure is the answer, and a sweep
that keeps trying is a sweep that hides an exhausted quota.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Msg17tAHJ3jVTYFgHydCm2
@os-sam
os-sam marked this pull request as ready for review August 31, 2026 18:32
@os-sam
os-sam enabled auto-merge August 31, 2026 18:32
@os-sam
os-sam added this pull request to the merge queueAug 31, 2026
Merged via the queue into main with commit 736980cAug 31, 2026
37 checks passed
@os-sam
os-sam deleted the claude/issue-13922-clause2-carrier-enforcement branch August 31, 2026 18:57
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/cddependenciesPull requests that update a dependency filesize/lskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants

@os-sam@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { // Force GitHub README to respect dark mode (function() { var style = document.createElement('style'); style.textContent = ' .markdown-body { color-scheme: dark light; } .markdown-body pre { background: #161b22 !important; } .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; } .markdown-body table th, .markdown-body table td { border-color: #30363d !important; } .markdown-body img { background: #0d1117; } .markdown-body blockquote { border-left-color: #8b949e; } .markdown-body hr { border-color: #30363d; } '; document.head.appendChild(style); })(); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' A reader for the clause-② gate: assert the dual carrier, and make a missing declaration loud by claude[bot] · Pull Request #13944 · objectstack-ai/objectstack · GitHub
Skip to content

A reader for the clause-② gate: assert the dual carrier, and make a missing declaration loud - #13944

Merged
os-sam merged 3 commits into
mainfrom
claude/issue-13922-clause2-carrier-enforcement
Aug 31, 2026
Merged

A reader for the clause-② gate: assert the dual carrier, and make a missing declaration loud#13944
os-sam merged 3 commits into
mainfrom
claude/issue-13922-clause2-carrier-enforcement

Conversation

@claude

@claudeclaudeBot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Fixes#13922
Fixes#12409
Part of #13914

Family dispatch, code half only. One new checker in the existing check:pm-* gate family gives the clause-② enqueue gate a reader a seat can point at one pair: the dual carrier is asserted by mechanism, and a limb nobody can read is reported as a missing reading rather than passing for a decision.


⚠️ Premise correction — read this first

#13922 says the dual-carrier rule "has no enforcement at all" and that "there is no check anywhere that a needs:contract-review on one carrier implies it on the other". That is not true, and the correction shaped this PR.

h31ContractReviewCarrierSplit in scripts/pm/check-half-states.mjs is exactly that check. It compares both carriers, in both directions, names the offending PR, and it has a standing caller — .github/workflows/half-state-patrol.yml, four times a day. Measured on this branch: the patrol's last scheduled run before the card was filed was 2026-08-31T13:42:41Z, conclusion success.

Building a second carrier comparison as though H31 did not exist would have produced two predicates that can disagree about the same pair. So this PR imports H31's label constant and H31's delivery relation instead of restating either, and closes only the parts H31 genuinely cannot reach. Those parts are real, and all three were re-measured on the live board today:

  1. Agreement on ABSENCE is H31's silent case, by construction. It fires on a split; a pair carrying the gate on neither carrier is, to it, clean. That is precisely the "all three limbs silent at once" shape The clause-② gate has three limbs and all three were silent on PR #13910 at once — the dual-carrier label rule has no enforcement, and 4 of 6 measured pairs are out of sync #13922 measured on PR fix(rest): a data engine that cannot be RESOLVED no longer answers 403 FORBIDDEN (#13476) #13910 / card [finding] after the #13279 repair, an UNRESOLVABLE data engine still answers 403 FORBIDDEN — the last surviving GRANTS-LOST disguise at the package door #13476.
  2. Nothing read limb ② at all. Grep over scripts/ and .github/workflows/ finds the Clause-② token in a label description and in prose — never in a predicate. The Clause-②: yes | no machine spelling is missing from the claim comment on 2 of 3 measured cards — the enqueue gate's predicate reads it there, and it is not there #13914 measured the consequence: 2 of 3 cards in one review round carried the declaration as prose or in the PR body, and a card with no declaration is indistinguishable from a card that declared no.
  3. H31's delivery channel trims. Its report is one row family among ~38 rendered into one pinned anchor issue. Measured on the 13:42Z run body: 231 findings, 74 rendered, "157 further row(s) omitted to fit GitHub's issue-body limit." A gate row competes for space with board-hygiene rows, and the anchor then reads clean on the gate.

premise_still_valid is therefore partly false and the surviving defect is narrower and sharper than the card states. It is stated in full in the new file's header so the next reader is not told the original story.


What landed

scripts/pm/check-clause2-carriers.mjs — answers one question and prints only its own rows, so nothing it finds can be crowded out by another mechanism's inventory.

rowwhat it saysmember
C1the two carriers of one pair disagree — both directions, each with its own consequence#13922 · #12409
C2the declaration limb has NO READING: absent, misplaced (fixed spelling on the thread but not in the claim carrier), or malformed#13914 remedy 2
C3a declared Clause-②: yes with the gate on neither carrier — the fail-open a carrier comparison is structurally blind to#13922

Modes, and why each exit code is what it is:

  • default sweep — report-only, exit 0 on a complete sweep whatever it found; 2 when any pair was UNJUDGED (an unread carrier is not a bare carrier, an unread thread is not an absent declaration); 3 PREREQUISITE NOT MET, the constant imported from check-half-states.mjs so the family has one code for it.
  • --pair PR-NUMBER — a predicate about that pair, so it may answer adversely where the sweep may not: exit 4, deliberately not 3, so a seat reading the status can never turn a refusal into a clearance. A pre-arm tool, ⛔ not a merge gate.
  • --self-test — offline, 64 cases. This is what lint.yml runs.

Single-sourced, not restated: CONTRACT_REVIEW_LABEL, prDeliversCard, labelNames, CLAIM_COMMENT_MARKER, governingClaim, proxyRearmPlan and EXIT_PREREQUISITE_NOT_MET all come from check-half-states.mjs. This checker and H31 cannot come to disagree about which PR delivers which card.

It writes nothing, ever. Hanging or clearing a review gate from a checker would be issuing the review verdict — 自查放行. Same call H31 makes, for the same reason.


Measured, on the live board

The sweep, run on this branch's head against objectstack-ai/objectstack at 2026-08-31T18:0xZ: 25 card/PR pairs derived from 27 open PRs, 25 findings, 0 UNJUDGED (exit 0).

None of these was repaired here. The deliverable is the mechanism; the rows are the PM round's input.

Zone-2 assumptions, tested rather than inherited:


Two things recorded rather than acted on

1. The two directions of a split are not ranked, because the two standing sources rank them oppositely. H31's header calls the card-bare direction "the more dangerous half" (an ungated card enqueues past a live gate). #13922 calls the PR-bare direction the fail-open ("it is the carrier the enqueue gate reads"). Both are sound about their own consumer. This checker states each direction's consequence and ranks neither — picking one would print an adjudication as a derivation. Which carrier the enqueue gate actually reads is a protocol question for the maintainer.

2. What this implies for .claude/skills/pm-dispatch/references/contract-review.md — recorded here, no edit landed. That file is held by pending human-merge PR #13746 and is outside this dispatch's surface. Two of its sentences are affected:

Neither sentence is edited by this PR.


Scope, held


Verification

Derived on this PR's own final head — node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack, at this PR's final head 850a759 (4 paths vs merge base 0f63965ea; the union is byte-identical to the one derived at d06df85, and it was re-run in full on 850a759 with the same result): 33 families + 2 convention-triggered obligations = 35 commands, all run serially under scripts/pm/os-verify-lock.sh.

32 of 35 exit 0. Including both convention obligations an added gate script incurs — node scripts/pm/bare-root-worklist.mjs --self-test and pnpm check:pm-dispatch-gates — plus check:self-test-wired, check:entry-guard, check:parse-guard, check:watch-hint-literal, check:pm-label-desc-cap and check:required-contexts.

The other 3 are NOT MEASURED, not red — each says so in its own verdict text, and each needs a full package build this diff cannot affect (it touches no package source):

gateexitits own words
check-test-completeness.mjs3"PREREQUISITE NOT MET … the local reading for this gate is NOT MEASURED. ⛔ It is not a red, and there is nothing here to fix."
check:dual-build-cjs-loads3"PREREQUISITE NOT MET — this gate reads built output, and some package has no dist/. Run pnpm build first. ⛔ This is NOT a pass: nothing was measured."
check:type-check-debt1its --self-test and coverage read pass; --re-measure refuses without the built closure, because measuring there "would silently measure a DIFFERENT WORLD"

Also run, because this checker imports it: pnpm check:pm-half-states1826 cases pass, so the imported surface is intact.

Every exit code was exercised live, in the direction predicted before the run:

runexitreading
--pair 13944 (this PR)0its three pairs — #13922, #12409, #13914 — all declare Clause-②: no in the fixed spelling and both carriers agree. Three green lines, the clean control.
--pair 138474the C3 row on card #13700. A verdict about that pair, not about the environment.
--pair 13944, earlier attempt3a transient HTTP 502 on the PR listing, on a route where curl returned 200 throughout. It refused with "0 pair(s) had been read … NOT a reading of a clean board" rather than reporting green — which is why the listing now carries the same single retry the per-pair reads do (third commit).

Live evidence beyond the self-test: the sweep output above, plus an independent second read of the label sets on #13929/#13407, #13927/#13632 and #13847/#13700 confirming every C1 and C3 row.

Authored in session session_01Msg17tAHJ3jVTYFgHydCm2 (durable copy of the attribution, because a body edit normalises the footer's session URL away).

No changeset: this diff publishes nothing — scripts/pm/**, a workflow, and a script registration in the private root manifest. skip-changeset applies and is attached.


Generated by Claude Code


Generated by Claude Code

`needs:contract-review` is a dual-carrier gate by the maintainer's ruling of
2026-08-22 (「两边都挂好」), and its content limb is a fixed machine spelling
in the card's claim comment. Neither had a reader that a seat could point at
one pair: H31 in `check-half-states.mjs` compares the two carriers, but it is
silent when the gate is missing from BOTH (agreement on absence is its silent
case) and it reports through a patrol body that trims — 231 findings, 74
rendered on the 2026-08-31T13:42Z run. The declaration limb had no reader at
all: grep over `scripts/` and `.github/workflows/` finds the token only in a
label description and in prose.
`scripts/pm/check-clause2-carriers.mjs` answers exactly that one question and
prints only its own rows:
C1 the two carriers of one pair disagree. Both directions, each with its
own consequence, neither ranked — H31 and #13922 rank them oppositely
and this file records the disagreement rather than adjudicating it.
C2 the declaration limb has NO READING: absent, misplaced (the fixed
spelling on the thread but not in the claim carrier), or malformed. A
missing reading is never collapsed into a declared `no`.
C3 a declared `yes` with the gate on NEITHER carrier — the fail-open a
carrier comparison is structurally blind to.
The label constant, the `prDeliversCard` delivery relation and the shared
PREREQUISITE-NOT-MET exit code are imported from `check-half-states.mjs`, so
this checker and H31 cannot come to disagree about which PR delivers which
card. It writes nothing, ever: hanging or clearing a review gate from a
checker would be issuing the verdict.
The fixed spelling is not relaxed. Decoration around the line is tolerated the
way H4 tolerates it, and reasoning after the value token is accepted because
that is the shape #13914 records as its control; prose, a different case and a
word merely starting with the token all stay unread and are quoted back in the
row so the residue is actionable.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Msg17tAHJ3jVTYFgHydCm2
The filing card read this file as the place the dual-carrier rule should have
been enforced and found only label-object reconciliation there — a fair
reading, because nothing in the file said otherwise. State the boundary, and
point at the two files that answer the hanging question instead: H31 for the
board-wide carrier comparison, and the new per-pair checker for the same
comparison anchored on one pair plus the declaration limb this repo had no
reader for.
⛔ No behaviour change: this file still reconciles the label object and
nothing else, and none of the three writes the label.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Msg17tAHJ3jVTYFgHydCm2
@claudeclaudeBot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 31, 2026
@github-actionsgithub-actionsBot added size/l ci/cd dependencies Pull requests that update a dependency file labels Aug 31, 2026
…ds have
A failure on the listing is TOTAL — no pair is formed, so the run refuses with
"0 pair(s) had been read", which is the correct answer and exactly why it
should not be spent on a blip. Measured while verifying this branch: a
transient HTTP 502 on page 1 refused a --pair run that answered in a second on
the next attempt, on a route where curl was returning 200 throughout.
⛔ Still one retry, not a loop: a second failure is the answer, and a sweep
that keeps trying is a sweep that hides an exhausted quota.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Msg17tAHJ3jVTYFgHydCm2
@os-sam
os-sam marked this pull request as ready for review August 31, 2026 18:32
@os-sam
os-sam enabled auto-merge August 31, 2026 18:32
@os-sam
os-sam added this pull request to the merge queueAug 31, 2026
Merged via the queue into main with commit 736980cAug 31, 2026
37 checks passed
@os-sam
os-sam deleted the claude/issue-13922-clause2-carrier-enforcement branch August 31, 2026 18:57
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/cddependenciesPull requests that update a dependency filesize/lskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants

@os-sam@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { // Highlight search terms from Google/DuckDuckGo/Bing referrer (function() { var ref = document.referrer; var terms = []; if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) { var url = new URL(ref); var q = url.searchParams.get('q') || url.searchParams.get('p'); if (q) { terms = q.split(/\s+/).filter(function(t) { return t.length > 2; }); } } if (terms.length === 0) return; var style = document.createElement('style'); style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }'; document.head.appendChild(style); function highlight(node) { if (node.nodeType === 3) { // text node var text = node.textContent; var found = false; terms.forEach(function(term) { var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\]\\]/g, '\\') + ')', 'gi'); if (regex.test(text)) { found = true; var frag = document.createDocumentFragment(); var parts = text.split(regex); parts.forEach(function(part, i) { if (i % 2 === 0) { frag.appendChild(document.createTextNode(part)); } else { var span = document.createElement('span'); span.className = 'userscript-highlight'; span.textContent = part; frag.appendChild(span); } }); node.parentNode.replaceChild(frag, node); } }); } else if (node.nodeType === 1 && node.childNodes) { // element var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT']; if (!skipTags.includes(node.tagName)) { Array.from(node.childNodes).forEach(highlight); } } } highlight(document.body); // Re-highlight on dynamic content var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1 || node.nodeType === 3) highlight(node); }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' A reader for the clause-② gate: assert the dual carrier, and make a missing declaration loud by claude[bot] · Pull Request #13944 · objectstack-ai/objectstack · GitHub
Skip to content

A reader for the clause-② gate: assert the dual carrier, and make a missing declaration loud - #13944

Merged
os-sam merged 3 commits into
mainfrom
claude/issue-13922-clause2-carrier-enforcement
Aug 31, 2026
Merged

A reader for the clause-② gate: assert the dual carrier, and make a missing declaration loud#13944
os-sam merged 3 commits into
mainfrom
claude/issue-13922-clause2-carrier-enforcement

Conversation

@claude

@claudeclaudeBot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Fixes#13922
Fixes#12409
Part of #13914

Family dispatch, code half only. One new checker in the existing check:pm-* gate family gives the clause-② enqueue gate a reader a seat can point at one pair: the dual carrier is asserted by mechanism, and a limb nobody can read is reported as a missing reading rather than passing for a decision.


⚠️ Premise correction — read this first

#13922 says the dual-carrier rule "has no enforcement at all" and that "there is no check anywhere that a needs:contract-review on one carrier implies it on the other". That is not true, and the correction shaped this PR.

h31ContractReviewCarrierSplit in scripts/pm/check-half-states.mjs is exactly that check. It compares both carriers, in both directions, names the offending PR, and it has a standing caller — .github/workflows/half-state-patrol.yml, four times a day. Measured on this branch: the patrol's last scheduled run before the card was filed was 2026-08-31T13:42:41Z, conclusion success.

Building a second carrier comparison as though H31 did not exist would have produced two predicates that can disagree about the same pair. So this PR imports H31's label constant and H31's delivery relation instead of restating either, and closes only the parts H31 genuinely cannot reach. Those parts are real, and all three were re-measured on the live board today:

  1. Agreement on ABSENCE is H31's silent case, by construction. It fires on a split; a pair carrying the gate on neither carrier is, to it, clean. That is precisely the "all three limbs silent at once" shape The clause-② gate has three limbs and all three were silent on PR #13910 at once — the dual-carrier label rule has no enforcement, and 4 of 6 measured pairs are out of sync #13922 measured on PR fix(rest): a data engine that cannot be RESOLVED no longer answers 403 FORBIDDEN (#13476) #13910 / card [finding] after the #13279 repair, an UNRESOLVABLE data engine still answers 403 FORBIDDEN — the last surviving GRANTS-LOST disguise at the package door #13476.
  2. Nothing read limb ② at all. Grep over scripts/ and .github/workflows/ finds the Clause-② token in a label description and in prose — never in a predicate. The Clause-②: yes | no machine spelling is missing from the claim comment on 2 of 3 measured cards — the enqueue gate's predicate reads it there, and it is not there #13914 measured the consequence: 2 of 3 cards in one review round carried the declaration as prose or in the PR body, and a card with no declaration is indistinguishable from a card that declared no.
  3. H31's delivery channel trims. Its report is one row family among ~38 rendered into one pinned anchor issue. Measured on the 13:42Z run body: 231 findings, 74 rendered, "157 further row(s) omitted to fit GitHub's issue-body limit." A gate row competes for space with board-hygiene rows, and the anchor then reads clean on the gate.

premise_still_valid is therefore partly false and the surviving defect is narrower and sharper than the card states. It is stated in full in the new file's header so the next reader is not told the original story.


What landed

scripts/pm/check-clause2-carriers.mjs — answers one question and prints only its own rows, so nothing it finds can be crowded out by another mechanism's inventory.

rowwhat it saysmember
C1the two carriers of one pair disagree — both directions, each with its own consequence#13922 · #12409
C2the declaration limb has NO READING: absent, misplaced (fixed spelling on the thread but not in the claim carrier), or malformed#13914 remedy 2
C3a declared Clause-②: yes with the gate on neither carrier — the fail-open a carrier comparison is structurally blind to#13922

Modes, and why each exit code is what it is:

  • default sweep — report-only, exit 0 on a complete sweep whatever it found; 2 when any pair was UNJUDGED (an unread carrier is not a bare carrier, an unread thread is not an absent declaration); 3 PREREQUISITE NOT MET, the constant imported from check-half-states.mjs so the family has one code for it.
  • --pair PR-NUMBER — a predicate about that pair, so it may answer adversely where the sweep may not: exit 4, deliberately not 3, so a seat reading the status can never turn a refusal into a clearance. A pre-arm tool, ⛔ not a merge gate.
  • --self-test — offline, 64 cases. This is what lint.yml runs.

Single-sourced, not restated: CONTRACT_REVIEW_LABEL, prDeliversCard, labelNames, CLAIM_COMMENT_MARKER, governingClaim, proxyRearmPlan and EXIT_PREREQUISITE_NOT_MET all come from check-half-states.mjs. This checker and H31 cannot come to disagree about which PR delivers which card.

It writes nothing, ever. Hanging or clearing a review gate from a checker would be issuing the review verdict — 自查放行. Same call H31 makes, for the same reason.


Measured, on the live board

The sweep, run on this branch's head against objectstack-ai/objectstack at 2026-08-31T18:0xZ: 25 card/PR pairs derived from 27 open PRs, 25 findings, 0 UNJUDGED (exit 0).

None of these was repaired here. The deliverable is the mechanism; the rows are the PM round's input.

Zone-2 assumptions, tested rather than inherited:


Two things recorded rather than acted on

1. The two directions of a split are not ranked, because the two standing sources rank them oppositely. H31's header calls the card-bare direction "the more dangerous half" (an ungated card enqueues past a live gate). #13922 calls the PR-bare direction the fail-open ("it is the carrier the enqueue gate reads"). Both are sound about their own consumer. This checker states each direction's consequence and ranks neither — picking one would print an adjudication as a derivation. Which carrier the enqueue gate actually reads is a protocol question for the maintainer.

2. What this implies for .claude/skills/pm-dispatch/references/contract-review.md — recorded here, no edit landed. That file is held by pending human-merge PR #13746 and is outside this dispatch's surface. Two of its sentences are affected:

Neither sentence is edited by this PR.


Scope, held


Verification

Derived on this PR's own final head — node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack, at this PR's final head 850a759 (4 paths vs merge base 0f63965ea; the union is byte-identical to the one derived at d06df85, and it was re-run in full on 850a759 with the same result): 33 families + 2 convention-triggered obligations = 35 commands, all run serially under scripts/pm/os-verify-lock.sh.

32 of 35 exit 0. Including both convention obligations an added gate script incurs — node scripts/pm/bare-root-worklist.mjs --self-test and pnpm check:pm-dispatch-gates — plus check:self-test-wired, check:entry-guard, check:parse-guard, check:watch-hint-literal, check:pm-label-desc-cap and check:required-contexts.

The other 3 are NOT MEASURED, not red — each says so in its own verdict text, and each needs a full package build this diff cannot affect (it touches no package source):

gateexitits own words
check-test-completeness.mjs3"PREREQUISITE NOT MET … the local reading for this gate is NOT MEASURED. ⛔ It is not a red, and there is nothing here to fix."
check:dual-build-cjs-loads3"PREREQUISITE NOT MET — this gate reads built output, and some package has no dist/. Run pnpm build first. ⛔ This is NOT a pass: nothing was measured."
check:type-check-debt1its --self-test and coverage read pass; --re-measure refuses without the built closure, because measuring there "would silently measure a DIFFERENT WORLD"

Also run, because this checker imports it: pnpm check:pm-half-states1826 cases pass, so the imported surface is intact.

Every exit code was exercised live, in the direction predicted before the run:

runexitreading
--pair 13944 (this PR)0its three pairs — #13922, #12409, #13914 — all declare Clause-②: no in the fixed spelling and both carriers agree. Three green lines, the clean control.
--pair 138474the C3 row on card #13700. A verdict about that pair, not about the environment.
--pair 13944, earlier attempt3a transient HTTP 502 on the PR listing, on a route where curl returned 200 throughout. It refused with "0 pair(s) had been read … NOT a reading of a clean board" rather than reporting green — which is why the listing now carries the same single retry the per-pair reads do (third commit).

Live evidence beyond the self-test: the sweep output above, plus an independent second read of the label sets on #13929/#13407, #13927/#13632 and #13847/#13700 confirming every C1 and C3 row.

Authored in session session_01Msg17tAHJ3jVTYFgHydCm2 (durable copy of the attribution, because a body edit normalises the footer's session URL away).

No changeset: this diff publishes nothing — scripts/pm/**, a workflow, and a script registration in the private root manifest. skip-changeset applies and is attached.


Generated by Claude Code


Generated by Claude Code

`needs:contract-review` is a dual-carrier gate by the maintainer's ruling of
2026-08-22 (「两边都挂好」), and its content limb is a fixed machine spelling
in the card's claim comment. Neither had a reader that a seat could point at
one pair: H31 in `check-half-states.mjs` compares the two carriers, but it is
silent when the gate is missing from BOTH (agreement on absence is its silent
case) and it reports through a patrol body that trims — 231 findings, 74
rendered on the 2026-08-31T13:42Z run. The declaration limb had no reader at
all: grep over `scripts/` and `.github/workflows/` finds the token only in a
label description and in prose.
`scripts/pm/check-clause2-carriers.mjs` answers exactly that one question and
prints only its own rows:
C1 the two carriers of one pair disagree. Both directions, each with its
own consequence, neither ranked — H31 and #13922 rank them oppositely
and this file records the disagreement rather than adjudicating it.
C2 the declaration limb has NO READING: absent, misplaced (the fixed
spelling on the thread but not in the claim carrier), or malformed. A
missing reading is never collapsed into a declared `no`.
C3 a declared `yes` with the gate on NEITHER carrier — the fail-open a
carrier comparison is structurally blind to.
The label constant, the `prDeliversCard` delivery relation and the shared
PREREQUISITE-NOT-MET exit code are imported from `check-half-states.mjs`, so
this checker and H31 cannot come to disagree about which PR delivers which
card. It writes nothing, ever: hanging or clearing a review gate from a
checker would be issuing the verdict.
The fixed spelling is not relaxed. Decoration around the line is tolerated the
way H4 tolerates it, and reasoning after the value token is accepted because
that is the shape #13914 records as its control; prose, a different case and a
word merely starting with the token all stay unread and are quoted back in the
row so the residue is actionable.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Msg17tAHJ3jVTYFgHydCm2
The filing card read this file as the place the dual-carrier rule should have
been enforced and found only label-object reconciliation there — a fair
reading, because nothing in the file said otherwise. State the boundary, and
point at the two files that answer the hanging question instead: H31 for the
board-wide carrier comparison, and the new per-pair checker for the same
comparison anchored on one pair plus the declaration limb this repo had no
reader for.
⛔ No behaviour change: this file still reconciles the label object and
nothing else, and none of the three writes the label.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Msg17tAHJ3jVTYFgHydCm2
@claudeclaudeBot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 31, 2026
@github-actionsgithub-actionsBot added size/l ci/cd dependencies Pull requests that update a dependency file labels Aug 31, 2026
…ds have
A failure on the listing is TOTAL — no pair is formed, so the run refuses with
"0 pair(s) had been read", which is the correct answer and exactly why it
should not be spent on a blip. Measured while verifying this branch: a
transient HTTP 502 on page 1 refused a --pair run that answered in a second on
the next attempt, on a route where curl was returning 200 throughout.
⛔ Still one retry, not a loop: a second failure is the answer, and a sweep
that keeps trying is a sweep that hides an exhausted quota.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Msg17tAHJ3jVTYFgHydCm2
@os-sam
os-sam marked this pull request as ready for review August 31, 2026 18:32
@os-sam
os-sam enabled auto-merge August 31, 2026 18:32
@os-sam
os-sam added this pull request to the merge queueAug 31, 2026
Merged via the queue into main with commit 736980cAug 31, 2026
37 checks passed
@os-sam
os-sam deleted the claude/issue-13922-clause2-carrier-enforcement branch August 31, 2026 18:57
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/cddependenciesPull requests that update a dependency filesize/lskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants

@os-sam@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { // Strip utm_, fbclid, gclid, etc. from all links on page (function() { var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content', 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid', 'ref', 'ref_src', 'source', 'medium', 'campaign']; function cleanUrl(url) { try { var u = new URL(url, window.location.origin); var changed = false; trackingParams.forEach(function(p) { if (u.searchParams.has(p)) { u.searchParams.delete(p); changed = true; } }); return changed ? u.toString() : url; } catch (e) { return url; } } function cleanLinks() { document.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } cleanLinks(); var observer = new MutationObserver(function(mutations) { mutations.forEach(function(m) { m.addedNodes.forEach(function(node) { if (node.nodeType === 1) { if (node.tagName === 'A') cleanLinks(); node.querySelectorAll('a[href]').forEach(function(a) { var clean = cleanUrl(a.href); if (clean !== a.href) a.href = clean; }); } }); }); }); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + ' A reader for the clause-② gate: assert the dual carrier, and make a missing declaration loud by claude[bot] · Pull Request #13944 · objectstack-ai/objectstack · GitHub
Skip to content

A reader for the clause-② gate: assert the dual carrier, and make a missing declaration loud - #13944

Merged
os-sam merged 3 commits into
mainfrom
claude/issue-13922-clause2-carrier-enforcement
Aug 31, 2026
Merged

A reader for the clause-② gate: assert the dual carrier, and make a missing declaration loud#13944
os-sam merged 3 commits into
mainfrom
claude/issue-13922-clause2-carrier-enforcement

Conversation

@claude

@claudeclaudeBot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Fixes#13922
Fixes#12409
Part of #13914

Family dispatch, code half only. One new checker in the existing check:pm-* gate family gives the clause-② enqueue gate a reader a seat can point at one pair: the dual carrier is asserted by mechanism, and a limb nobody can read is reported as a missing reading rather than passing for a decision.


⚠️ Premise correction — read this first

#13922 says the dual-carrier rule "has no enforcement at all" and that "there is no check anywhere that a needs:contract-review on one carrier implies it on the other". That is not true, and the correction shaped this PR.

h31ContractReviewCarrierSplit in scripts/pm/check-half-states.mjs is exactly that check. It compares both carriers, in both directions, names the offending PR, and it has a standing caller — .github/workflows/half-state-patrol.yml, four times a day. Measured on this branch: the patrol's last scheduled run before the card was filed was 2026-08-31T13:42:41Z, conclusion success.

Building a second carrier comparison as though H31 did not exist would have produced two predicates that can disagree about the same pair. So this PR imports H31's label constant and H31's delivery relation instead of restating either, and closes only the parts H31 genuinely cannot reach. Those parts are real, and all three were re-measured on the live board today:

  1. Agreement on ABSENCE is H31's silent case, by construction. It fires on a split; a pair carrying the gate on neither carrier is, to it, clean. That is precisely the "all three limbs silent at once" shape The clause-② gate has three limbs and all three were silent on PR #13910 at once — the dual-carrier label rule has no enforcement, and 4 of 6 measured pairs are out of sync #13922 measured on PR fix(rest): a data engine that cannot be RESOLVED no longer answers 403 FORBIDDEN (#13476) #13910 / card [finding] after the #13279 repair, an UNRESOLVABLE data engine still answers 403 FORBIDDEN — the last surviving GRANTS-LOST disguise at the package door #13476.
  2. Nothing read limb ② at all. Grep over scripts/ and .github/workflows/ finds the Clause-② token in a label description and in prose — never in a predicate. The Clause-②: yes | no machine spelling is missing from the claim comment on 2 of 3 measured cards — the enqueue gate's predicate reads it there, and it is not there #13914 measured the consequence: 2 of 3 cards in one review round carried the declaration as prose or in the PR body, and a card with no declaration is indistinguishable from a card that declared no.
  3. H31's delivery channel trims. Its report is one row family among ~38 rendered into one pinned anchor issue. Measured on the 13:42Z run body: 231 findings, 74 rendered, "157 further row(s) omitted to fit GitHub's issue-body limit." A gate row competes for space with board-hygiene rows, and the anchor then reads clean on the gate.

premise_still_valid is therefore partly false and the surviving defect is narrower and sharper than the card states. It is stated in full in the new file's header so the next reader is not told the original story.


What landed

scripts/pm/check-clause2-carriers.mjs — answers one question and prints only its own rows, so nothing it finds can be crowded out by another mechanism's inventory.

rowwhat it saysmember
C1the two carriers of one pair disagree — both directions, each with its own consequence#13922 · #12409
C2the declaration limb has NO READING: absent, misplaced (fixed spelling on the thread but not in the claim carrier), or malformed#13914 remedy 2
C3a declared Clause-②: yes with the gate on neither carrier — the fail-open a carrier comparison is structurally blind to#13922

Modes, and why each exit code is what it is:

  • default sweep — report-only, exit 0 on a complete sweep whatever it found; 2 when any pair was UNJUDGED (an unread carrier is not a bare carrier, an unread thread is not an absent declaration); 3 PREREQUISITE NOT MET, the constant imported from check-half-states.mjs so the family has one code for it.
  • --pair PR-NUMBER — a predicate about that pair, so it may answer adversely where the sweep may not: exit 4, deliberately not 3, so a seat reading the status can never turn a refusal into a clearance. A pre-arm tool, ⛔ not a merge gate.
  • --self-test — offline, 64 cases. This is what lint.yml runs.

Single-sourced, not restated: CONTRACT_REVIEW_LABEL, prDeliversCard, labelNames, CLAIM_COMMENT_MARKER, governingClaim, proxyRearmPlan and EXIT_PREREQUISITE_NOT_MET all come from check-half-states.mjs. This checker and H31 cannot come to disagree about which PR delivers which card.

It writes nothing, ever. Hanging or clearing a review gate from a checker would be issuing the review verdict — 自查放行. Same call H31 makes, for the same reason.


Measured, on the live board

The sweep, run on this branch's head against objectstack-ai/objectstack at 2026-08-31T18:0xZ: 25 card/PR pairs derived from 27 open PRs, 25 findings, 0 UNJUDGED (exit 0).

None of these was repaired here. The deliverable is the mechanism; the rows are the PM round's input.

Zone-2 assumptions, tested rather than inherited:


Two things recorded rather than acted on

1. The two directions of a split are not ranked, because the two standing sources rank them oppositely. H31's header calls the card-bare direction "the more dangerous half" (an ungated card enqueues past a live gate). #13922 calls the PR-bare direction the fail-open ("it is the carrier the enqueue gate reads"). Both are sound about their own consumer. This checker states each direction's consequence and ranks neither — picking one would print an adjudication as a derivation. Which carrier the enqueue gate actually reads is a protocol question for the maintainer.

2. What this implies for .claude/skills/pm-dispatch/references/contract-review.md — recorded here, no edit landed. That file is held by pending human-merge PR #13746 and is outside this dispatch's surface. Two of its sentences are affected:

Neither sentence is edited by this PR.


Scope, held


Verification

Derived on this PR's own final head — node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack, at this PR's final head 850a759 (4 paths vs merge base 0f63965ea; the union is byte-identical to the one derived at d06df85, and it was re-run in full on 850a759 with the same result): 33 families + 2 convention-triggered obligations = 35 commands, all run serially under scripts/pm/os-verify-lock.sh.

32 of 35 exit 0. Including both convention obligations an added gate script incurs — node scripts/pm/bare-root-worklist.mjs --self-test and pnpm check:pm-dispatch-gates — plus check:self-test-wired, check:entry-guard, check:parse-guard, check:watch-hint-literal, check:pm-label-desc-cap and check:required-contexts.

The other 3 are NOT MEASURED, not red — each says so in its own verdict text, and each needs a full package build this diff cannot affect (it touches no package source):

gateexitits own words
check-test-completeness.mjs3"PREREQUISITE NOT MET … the local reading for this gate is NOT MEASURED. ⛔ It is not a red, and there is nothing here to fix."
check:dual-build-cjs-loads3"PREREQUISITE NOT MET — this gate reads built output, and some package has no dist/. Run pnpm build first. ⛔ This is NOT a pass: nothing was measured."
check:type-check-debt1its --self-test and coverage read pass; --re-measure refuses without the built closure, because measuring there "would silently measure a DIFFERENT WORLD"

Also run, because this checker imports it: pnpm check:pm-half-states1826 cases pass, so the imported surface is intact.

Every exit code was exercised live, in the direction predicted before the run:

runexitreading
--pair 13944 (this PR)0its three pairs — #13922, #12409, #13914 — all declare Clause-②: no in the fixed spelling and both carriers agree. Three green lines, the clean control.
--pair 138474the C3 row on card #13700. A verdict about that pair, not about the environment.
--pair 13944, earlier attempt3a transient HTTP 502 on the PR listing, on a route where curl returned 200 throughout. It refused with "0 pair(s) had been read … NOT a reading of a clean board" rather than reporting green — which is why the listing now carries the same single retry the per-pair reads do (third commit).

Live evidence beyond the self-test: the sweep output above, plus an independent second read of the label sets on #13929/#13407, #13927/#13632 and #13847/#13700 confirming every C1 and C3 row.

Authored in session session_01Msg17tAHJ3jVTYFgHydCm2 (durable copy of the attribution, because a body edit normalises the footer's session URL away).

No changeset: this diff publishes nothing — scripts/pm/**, a workflow, and a script registration in the private root manifest. skip-changeset applies and is attached.


Generated by Claude Code


Generated by Claude Code

`needs:contract-review` is a dual-carrier gate by the maintainer's ruling of
2026-08-22 (「两边都挂好」), and its content limb is a fixed machine spelling
in the card's claim comment. Neither had a reader that a seat could point at
one pair: H31 in `check-half-states.mjs` compares the two carriers, but it is
silent when the gate is missing from BOTH (agreement on absence is its silent
case) and it reports through a patrol body that trims — 231 findings, 74
rendered on the 2026-08-31T13:42Z run. The declaration limb had no reader at
all: grep over `scripts/` and `.github/workflows/` finds the token only in a
label description and in prose.
`scripts/pm/check-clause2-carriers.mjs` answers exactly that one question and
prints only its own rows:
C1 the two carriers of one pair disagree. Both directions, each with its
own consequence, neither ranked — H31 and #13922 rank them oppositely
and this file records the disagreement rather than adjudicating it.
C2 the declaration limb has NO READING: absent, misplaced (the fixed
spelling on the thread but not in the claim carrier), or malformed. A
missing reading is never collapsed into a declared `no`.
C3 a declared `yes` with the gate on NEITHER carrier — the fail-open a
carrier comparison is structurally blind to.
The label constant, the `prDeliversCard` delivery relation and the shared
PREREQUISITE-NOT-MET exit code are imported from `check-half-states.mjs`, so
this checker and H31 cannot come to disagree about which PR delivers which
card. It writes nothing, ever: hanging or clearing a review gate from a
checker would be issuing the verdict.
The fixed spelling is not relaxed. Decoration around the line is tolerated the
way H4 tolerates it, and reasoning after the value token is accepted because
that is the shape #13914 records as its control; prose, a different case and a
word merely starting with the token all stay unread and are quoted back in the
row so the residue is actionable.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Msg17tAHJ3jVTYFgHydCm2
The filing card read this file as the place the dual-carrier rule should have
been enforced and found only label-object reconciliation there — a fair
reading, because nothing in the file said otherwise. State the boundary, and
point at the two files that answer the hanging question instead: H31 for the
board-wide carrier comparison, and the new per-pair checker for the same
comparison anchored on one pair plus the declaration limb this repo had no
reader for.
⛔ No behaviour change: this file still reconciles the label object and
nothing else, and none of the three writes the label.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Msg17tAHJ3jVTYFgHydCm2
@claudeclaudeBot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 31, 2026
@github-actionsgithub-actionsBot added size/l ci/cd dependencies Pull requests that update a dependency file labels Aug 31, 2026
…ds have
A failure on the listing is TOTAL — no pair is formed, so the run refuses with
"0 pair(s) had been read", which is the correct answer and exactly why it
should not be spent on a blip. Measured while verifying this branch: a
transient HTTP 502 on page 1 refused a --pair run that answered in a second on
the next attempt, on a route where curl was returning 200 throughout.
⛔ Still one retry, not a loop: a second failure is the answer, and a sweep
that keeps trying is a sweep that hides an exhausted quota.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Msg17tAHJ3jVTYFgHydCm2
@os-sam
os-sam marked this pull request as ready for review August 31, 2026 18:32
@os-sam
os-sam enabled auto-merge August 31, 2026 18:32
@os-sam
os-sam added this pull request to the merge queueAug 31, 2026
Merged via the queue into main with commit 736980cAug 31, 2026
37 checks passed
@os-sam
os-sam deleted the claude/issue-13922-clause2-carrier-enforcement branch August 31, 2026 18:57
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/cddependenciesPull requests that update a dependency filesize/lskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants

@os-sam@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { // Auto-enable theater mode on YouTube (function() { function tryTheater() { var btn = document.querySelector('button[aria-label="Theater mode"], ytd-player #player button[title="Theater mode"]'); if (btn && !btn.classList.contains('activated')) { btn.click(); } } // Try immediately tryTheater(); // Try after navigation (SPA) var lastUrl = location.href; setInterval(function() { if (location.href !== lastUrl) { lastUrl = location.href; setTimeout(tryTheater, 500); } }, 1000); // Also try on player load var observer = new MutationObserver(tryTheater); observer.observe(document.body, { childList: true, subtree: true }); })(); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' A reader for the clause-② gate: assert the dual carrier, and make a missing declaration loud by claude[bot] · Pull Request #13944 · objectstack-ai/objectstack · GitHub
Skip to content

A reader for the clause-② gate: assert the dual carrier, and make a missing declaration loud - #13944

Merged
os-sam merged 3 commits into
mainfrom
claude/issue-13922-clause2-carrier-enforcement
Aug 31, 2026
Merged

A reader for the clause-② gate: assert the dual carrier, and make a missing declaration loud#13944
os-sam merged 3 commits into
mainfrom
claude/issue-13922-clause2-carrier-enforcement

Conversation

@claude

@claudeclaudeBot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Fixes#13922
Fixes#12409
Part of #13914

Family dispatch, code half only. One new checker in the existing check:pm-* gate family gives the clause-② enqueue gate a reader a seat can point at one pair: the dual carrier is asserted by mechanism, and a limb nobody can read is reported as a missing reading rather than passing for a decision.


⚠️ Premise correction — read this first

#13922 says the dual-carrier rule "has no enforcement at all" and that "there is no check anywhere that a needs:contract-review on one carrier implies it on the other". That is not true, and the correction shaped this PR.

h31ContractReviewCarrierSplit in scripts/pm/check-half-states.mjs is exactly that check. It compares both carriers, in both directions, names the offending PR, and it has a standing caller — .github/workflows/half-state-patrol.yml, four times a day. Measured on this branch: the patrol's last scheduled run before the card was filed was 2026-08-31T13:42:41Z, conclusion success.

Building a second carrier comparison as though H31 did not exist would have produced two predicates that can disagree about the same pair. So this PR imports H31's label constant and H31's delivery relation instead of restating either, and closes only the parts H31 genuinely cannot reach. Those parts are real, and all three were re-measured on the live board today:

  1. Agreement on ABSENCE is H31's silent case, by construction. It fires on a split; a pair carrying the gate on neither carrier is, to it, clean. That is precisely the "all three limbs silent at once" shape The clause-② gate has three limbs and all three were silent on PR #13910 at once — the dual-carrier label rule has no enforcement, and 4 of 6 measured pairs are out of sync #13922 measured on PR fix(rest): a data engine that cannot be RESOLVED no longer answers 403 FORBIDDEN (#13476) #13910 / card [finding] after the #13279 repair, an UNRESOLVABLE data engine still answers 403 FORBIDDEN — the last surviving GRANTS-LOST disguise at the package door #13476.
  2. Nothing read limb ② at all. Grep over scripts/ and .github/workflows/ finds the Clause-② token in a label description and in prose — never in a predicate. The Clause-②: yes | no machine spelling is missing from the claim comment on 2 of 3 measured cards — the enqueue gate's predicate reads it there, and it is not there #13914 measured the consequence: 2 of 3 cards in one review round carried the declaration as prose or in the PR body, and a card with no declaration is indistinguishable from a card that declared no.
  3. H31's delivery channel trims. Its report is one row family among ~38 rendered into one pinned anchor issue. Measured on the 13:42Z run body: 231 findings, 74 rendered, "157 further row(s) omitted to fit GitHub's issue-body limit." A gate row competes for space with board-hygiene rows, and the anchor then reads clean on the gate.

premise_still_valid is therefore partly false and the surviving defect is narrower and sharper than the card states. It is stated in full in the new file's header so the next reader is not told the original story.


What landed

scripts/pm/check-clause2-carriers.mjs — answers one question and prints only its own rows, so nothing it finds can be crowded out by another mechanism's inventory.

rowwhat it saysmember
C1the two carriers of one pair disagree — both directions, each with its own consequence#13922 · #12409
C2the declaration limb has NO READING: absent, misplaced (fixed spelling on the thread but not in the claim carrier), or malformed#13914 remedy 2
C3a declared Clause-②: yes with the gate on neither carrier — the fail-open a carrier comparison is structurally blind to#13922

Modes, and why each exit code is what it is:

  • default sweep — report-only, exit 0 on a complete sweep whatever it found; 2 when any pair was UNJUDGED (an unread carrier is not a bare carrier, an unread thread is not an absent declaration); 3 PREREQUISITE NOT MET, the constant imported from check-half-states.mjs so the family has one code for it.
  • --pair PR-NUMBER — a predicate about that pair, so it may answer adversely where the sweep may not: exit 4, deliberately not 3, so a seat reading the status can never turn a refusal into a clearance. A pre-arm tool, ⛔ not a merge gate.
  • --self-test — offline, 64 cases. This is what lint.yml runs.

Single-sourced, not restated: CONTRACT_REVIEW_LABEL, prDeliversCard, labelNames, CLAIM_COMMENT_MARKER, governingClaim, proxyRearmPlan and EXIT_PREREQUISITE_NOT_MET all come from check-half-states.mjs. This checker and H31 cannot come to disagree about which PR delivers which card.

It writes nothing, ever. Hanging or clearing a review gate from a checker would be issuing the review verdict — 自查放行. Same call H31 makes, for the same reason.


Measured, on the live board

The sweep, run on this branch's head against objectstack-ai/objectstack at 2026-08-31T18:0xZ: 25 card/PR pairs derived from 27 open PRs, 25 findings, 0 UNJUDGED (exit 0).

None of these was repaired here. The deliverable is the mechanism; the rows are the PM round's input.

Zone-2 assumptions, tested rather than inherited:


Two things recorded rather than acted on

1. The two directions of a split are not ranked, because the two standing sources rank them oppositely. H31's header calls the card-bare direction "the more dangerous half" (an ungated card enqueues past a live gate). #13922 calls the PR-bare direction the fail-open ("it is the carrier the enqueue gate reads"). Both are sound about their own consumer. This checker states each direction's consequence and ranks neither — picking one would print an adjudication as a derivation. Which carrier the enqueue gate actually reads is a protocol question for the maintainer.

2. What this implies for .claude/skills/pm-dispatch/references/contract-review.md — recorded here, no edit landed. That file is held by pending human-merge PR #13746 and is outside this dispatch's surface. Two of its sentences are affected:

Neither sentence is edited by this PR.


Scope, held


Verification

Derived on this PR's own final head — node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack, at this PR's final head 850a759 (4 paths vs merge base 0f63965ea; the union is byte-identical to the one derived at d06df85, and it was re-run in full on 850a759 with the same result): 33 families + 2 convention-triggered obligations = 35 commands, all run serially under scripts/pm/os-verify-lock.sh.

32 of 35 exit 0. Including both convention obligations an added gate script incurs — node scripts/pm/bare-root-worklist.mjs --self-test and pnpm check:pm-dispatch-gates — plus check:self-test-wired, check:entry-guard, check:parse-guard, check:watch-hint-literal, check:pm-label-desc-cap and check:required-contexts.

The other 3 are NOT MEASURED, not red — each says so in its own verdict text, and each needs a full package build this diff cannot affect (it touches no package source):

gateexitits own words
check-test-completeness.mjs3"PREREQUISITE NOT MET … the local reading for this gate is NOT MEASURED. ⛔ It is not a red, and there is nothing here to fix."
check:dual-build-cjs-loads3"PREREQUISITE NOT MET — this gate reads built output, and some package has no dist/. Run pnpm build first. ⛔ This is NOT a pass: nothing was measured."
check:type-check-debt1its --self-test and coverage read pass; --re-measure refuses without the built closure, because measuring there "would silently measure a DIFFERENT WORLD"

Also run, because this checker imports it: pnpm check:pm-half-states1826 cases pass, so the imported surface is intact.

Every exit code was exercised live, in the direction predicted before the run:

runexitreading
--pair 13944 (this PR)0its three pairs — #13922, #12409, #13914 — all declare Clause-②: no in the fixed spelling and both carriers agree. Three green lines, the clean control.
--pair 138474the C3 row on card #13700. A verdict about that pair, not about the environment.
--pair 13944, earlier attempt3a transient HTTP 502 on the PR listing, on a route where curl returned 200 throughout. It refused with "0 pair(s) had been read … NOT a reading of a clean board" rather than reporting green — which is why the listing now carries the same single retry the per-pair reads do (third commit).

Live evidence beyond the self-test: the sweep output above, plus an independent second read of the label sets on #13929/#13407, #13927/#13632 and #13847/#13700 confirming every C1 and C3 row.

Authored in session session_01Msg17tAHJ3jVTYFgHydCm2 (durable copy of the attribution, because a body edit normalises the footer's session URL away).

No changeset: this diff publishes nothing — scripts/pm/**, a workflow, and a script registration in the private root manifest. skip-changeset applies and is attached.


Generated by Claude Code


Generated by Claude Code

`needs:contract-review` is a dual-carrier gate by the maintainer's ruling of
2026-08-22 (「两边都挂好」), and its content limb is a fixed machine spelling
in the card's claim comment. Neither had a reader that a seat could point at
one pair: H31 in `check-half-states.mjs` compares the two carriers, but it is
silent when the gate is missing from BOTH (agreement on absence is its silent
case) and it reports through a patrol body that trims — 231 findings, 74
rendered on the 2026-08-31T13:42Z run. The declaration limb had no reader at
all: grep over `scripts/` and `.github/workflows/` finds the token only in a
label description and in prose.
`scripts/pm/check-clause2-carriers.mjs` answers exactly that one question and
prints only its own rows:
C1 the two carriers of one pair disagree. Both directions, each with its
own consequence, neither ranked — H31 and #13922 rank them oppositely
and this file records the disagreement rather than adjudicating it.
C2 the declaration limb has NO READING: absent, misplaced (the fixed
spelling on the thread but not in the claim carrier), or malformed. A
missing reading is never collapsed into a declared `no`.
C3 a declared `yes` with the gate on NEITHER carrier — the fail-open a
carrier comparison is structurally blind to.
The label constant, the `prDeliversCard` delivery relation and the shared
PREREQUISITE-NOT-MET exit code are imported from `check-half-states.mjs`, so
this checker and H31 cannot come to disagree about which PR delivers which
card. It writes nothing, ever: hanging or clearing a review gate from a
checker would be issuing the verdict.
The fixed spelling is not relaxed. Decoration around the line is tolerated the
way H4 tolerates it, and reasoning after the value token is accepted because
that is the shape #13914 records as its control; prose, a different case and a
word merely starting with the token all stay unread and are quoted back in the
row so the residue is actionable.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Msg17tAHJ3jVTYFgHydCm2
The filing card read this file as the place the dual-carrier rule should have
been enforced and found only label-object reconciliation there — a fair
reading, because nothing in the file said otherwise. State the boundary, and
point at the two files that answer the hanging question instead: H31 for the
board-wide carrier comparison, and the new per-pair checker for the same
comparison anchored on one pair plus the declaration limb this repo had no
reader for.
⛔ No behaviour change: this file still reconciles the label object and
nothing else, and none of the three writes the label.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Msg17tAHJ3jVTYFgHydCm2
@claudeclaudeBot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 31, 2026
@github-actionsgithub-actionsBot added size/l ci/cd dependencies Pull requests that update a dependency file labels Aug 31, 2026
…ds have
A failure on the listing is TOTAL — no pair is formed, so the run refuses with
"0 pair(s) had been read", which is the correct answer and exactly why it
should not be spent on a blip. Measured while verifying this branch: a
transient HTTP 502 on page 1 refused a --pair run that answered in a second on
the next attempt, on a route where curl was returning 200 throughout.
⛔ Still one retry, not a loop: a second failure is the answer, and a sweep
that keeps trying is a sweep that hides an exhausted quota.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Msg17tAHJ3jVTYFgHydCm2
@os-sam
os-sam marked this pull request as ready for review August 31, 2026 18:32
@os-sam
os-sam enabled auto-merge August 31, 2026 18:32
@os-sam
os-sam added this pull request to the merge queueAug 31, 2026
Merged via the queue into main with commit 736980cAug 31, 2026
37 checks passed
@os-sam
os-sam deleted the claude/issue-13922-clause2-carrier-enforcement branch August 31, 2026 18:57
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/cddependenciesPull requests that update a dependency filesize/lskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants

@os-sam@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { // Remove or un-stick sticky/fixed headers that block content (function() { function unstick() { document.querySelectorAll('header, nav, [role="banner"], .header, .navbar, .sticky, .fixed-top, [style*="position: fixed"], [style*="position:sticky"]').forEach(function(el) { if (el.style.position === 'fixed' || el.style.position === 'sticky' || getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') { el.style.position = 'static'; el.style.top = 'auto'; el.style.zIndex = 'auto'; } }); } unstick(); var observer = new MutationObserver(unstick); observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] }); })(); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + ' A reader for the clause-② gate: assert the dual carrier, and make a missing declaration loud by claude[bot] · Pull Request #13944 · objectstack-ai/objectstack · GitHub
Skip to content

A reader for the clause-② gate: assert the dual carrier, and make a missing declaration loud - #13944

Merged
os-sam merged 3 commits into
mainfrom
claude/issue-13922-clause2-carrier-enforcement
Aug 31, 2026
Merged

A reader for the clause-② gate: assert the dual carrier, and make a missing declaration loud#13944
os-sam merged 3 commits into
mainfrom
claude/issue-13922-clause2-carrier-enforcement

Conversation

@claude

@claudeclaudeBot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Fixes#13922
Fixes#12409
Part of #13914

Family dispatch, code half only. One new checker in the existing check:pm-* gate family gives the clause-② enqueue gate a reader a seat can point at one pair: the dual carrier is asserted by mechanism, and a limb nobody can read is reported as a missing reading rather than passing for a decision.


⚠️ Premise correction — read this first

#13922 says the dual-carrier rule "has no enforcement at all" and that "there is no check anywhere that a needs:contract-review on one carrier implies it on the other". That is not true, and the correction shaped this PR.

h31ContractReviewCarrierSplit in scripts/pm/check-half-states.mjs is exactly that check. It compares both carriers, in both directions, names the offending PR, and it has a standing caller — .github/workflows/half-state-patrol.yml, four times a day. Measured on this branch: the patrol's last scheduled run before the card was filed was 2026-08-31T13:42:41Z, conclusion success.

Building a second carrier comparison as though H31 did not exist would have produced two predicates that can disagree about the same pair. So this PR imports H31's label constant and H31's delivery relation instead of restating either, and closes only the parts H31 genuinely cannot reach. Those parts are real, and all three were re-measured on the live board today:

  1. Agreement on ABSENCE is H31's silent case, by construction. It fires on a split; a pair carrying the gate on neither carrier is, to it, clean. That is precisely the "all three limbs silent at once" shape The clause-② gate has three limbs and all three were silent on PR #13910 at once — the dual-carrier label rule has no enforcement, and 4 of 6 measured pairs are out of sync #13922 measured on PR fix(rest): a data engine that cannot be RESOLVED no longer answers 403 FORBIDDEN (#13476) #13910 / card [finding] after the #13279 repair, an UNRESOLVABLE data engine still answers 403 FORBIDDEN — the last surviving GRANTS-LOST disguise at the package door #13476.
  2. Nothing read limb ② at all. Grep over scripts/ and .github/workflows/ finds the Clause-② token in a label description and in prose — never in a predicate. The Clause-②: yes | no machine spelling is missing from the claim comment on 2 of 3 measured cards — the enqueue gate's predicate reads it there, and it is not there #13914 measured the consequence: 2 of 3 cards in one review round carried the declaration as prose or in the PR body, and a card with no declaration is indistinguishable from a card that declared no.
  3. H31's delivery channel trims. Its report is one row family among ~38 rendered into one pinned anchor issue. Measured on the 13:42Z run body: 231 findings, 74 rendered, "157 further row(s) omitted to fit GitHub's issue-body limit." A gate row competes for space with board-hygiene rows, and the anchor then reads clean on the gate.

premise_still_valid is therefore partly false and the surviving defect is narrower and sharper than the card states. It is stated in full in the new file's header so the next reader is not told the original story.


What landed

scripts/pm/check-clause2-carriers.mjs — answers one question and prints only its own rows, so nothing it finds can be crowded out by another mechanism's inventory.

rowwhat it saysmember
C1the two carriers of one pair disagree — both directions, each with its own consequence#13922 · #12409
C2the declaration limb has NO READING: absent, misplaced (fixed spelling on the thread but not in the claim carrier), or malformed#13914 remedy 2
C3a declared Clause-②: yes with the gate on neither carrier — the fail-open a carrier comparison is structurally blind to#13922

Modes, and why each exit code is what it is:

  • default sweep — report-only, exit 0 on a complete sweep whatever it found; 2 when any pair was UNJUDGED (an unread carrier is not a bare carrier, an unread thread is not an absent declaration); 3 PREREQUISITE NOT MET, the constant imported from check-half-states.mjs so the family has one code for it.
  • --pair PR-NUMBER — a predicate about that pair, so it may answer adversely where the sweep may not: exit 4, deliberately not 3, so a seat reading the status can never turn a refusal into a clearance. A pre-arm tool, ⛔ not a merge gate.
  • --self-test — offline, 64 cases. This is what lint.yml runs.

Single-sourced, not restated: CONTRACT_REVIEW_LABEL, prDeliversCard, labelNames, CLAIM_COMMENT_MARKER, governingClaim, proxyRearmPlan and EXIT_PREREQUISITE_NOT_MET all come from check-half-states.mjs. This checker and H31 cannot come to disagree about which PR delivers which card.

It writes nothing, ever. Hanging or clearing a review gate from a checker would be issuing the review verdict — 自查放行. Same call H31 makes, for the same reason.


Measured, on the live board

The sweep, run on this branch's head against objectstack-ai/objectstack at 2026-08-31T18:0xZ: 25 card/PR pairs derived from 27 open PRs, 25 findings, 0 UNJUDGED (exit 0).

None of these was repaired here. The deliverable is the mechanism; the rows are the PM round's input.

Zone-2 assumptions, tested rather than inherited:


Two things recorded rather than acted on

1. The two directions of a split are not ranked, because the two standing sources rank them oppositely. H31's header calls the card-bare direction "the more dangerous half" (an ungated card enqueues past a live gate). #13922 calls the PR-bare direction the fail-open ("it is the carrier the enqueue gate reads"). Both are sound about their own consumer. This checker states each direction's consequence and ranks neither — picking one would print an adjudication as a derivation. Which carrier the enqueue gate actually reads is a protocol question for the maintainer.

2. What this implies for .claude/skills/pm-dispatch/references/contract-review.md — recorded here, no edit landed. That file is held by pending human-merge PR #13746 and is outside this dispatch's surface. Two of its sentences are affected:

Neither sentence is edited by this PR.


Scope, held


Verification

Derived on this PR's own final head — node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack, at this PR's final head 850a759 (4 paths vs merge base 0f63965ea; the union is byte-identical to the one derived at d06df85, and it was re-run in full on 850a759 with the same result): 33 families + 2 convention-triggered obligations = 35 commands, all run serially under scripts/pm/os-verify-lock.sh.

32 of 35 exit 0. Including both convention obligations an added gate script incurs — node scripts/pm/bare-root-worklist.mjs --self-test and pnpm check:pm-dispatch-gates — plus check:self-test-wired, check:entry-guard, check:parse-guard, check:watch-hint-literal, check:pm-label-desc-cap and check:required-contexts.

The other 3 are NOT MEASURED, not red — each says so in its own verdict text, and each needs a full package build this diff cannot affect (it touches no package source):

gateexitits own words
check-test-completeness.mjs3"PREREQUISITE NOT MET … the local reading for this gate is NOT MEASURED. ⛔ It is not a red, and there is nothing here to fix."
check:dual-build-cjs-loads3"PREREQUISITE NOT MET — this gate reads built output, and some package has no dist/. Run pnpm build first. ⛔ This is NOT a pass: nothing was measured."
check:type-check-debt1its --self-test and coverage read pass; --re-measure refuses without the built closure, because measuring there "would silently measure a DIFFERENT WORLD"

Also run, because this checker imports it: pnpm check:pm-half-states1826 cases pass, so the imported surface is intact.

Every exit code was exercised live, in the direction predicted before the run:

runexitreading
--pair 13944 (this PR)0its three pairs — #13922, #12409, #13914 — all declare Clause-②: no in the fixed spelling and both carriers agree. Three green lines, the clean control.
--pair 138474the C3 row on card #13700. A verdict about that pair, not about the environment.
--pair 13944, earlier attempt3a transient HTTP 502 on the PR listing, on a route where curl returned 200 throughout. It refused with "0 pair(s) had been read … NOT a reading of a clean board" rather than reporting green — which is why the listing now carries the same single retry the per-pair reads do (third commit).

Live evidence beyond the self-test: the sweep output above, plus an independent second read of the label sets on #13929/#13407, #13927/#13632 and #13847/#13700 confirming every C1 and C3 row.

Authored in session session_01Msg17tAHJ3jVTYFgHydCm2 (durable copy of the attribution, because a body edit normalises the footer's session URL away).

No changeset: this diff publishes nothing — scripts/pm/**, a workflow, and a script registration in the private root manifest. skip-changeset applies and is attached.


Generated by Claude Code


Generated by Claude Code

`needs:contract-review` is a dual-carrier gate by the maintainer's ruling of
2026-08-22 (「两边都挂好」), and its content limb is a fixed machine spelling
in the card's claim comment. Neither had a reader that a seat could point at
one pair: H31 in `check-half-states.mjs` compares the two carriers, but it is
silent when the gate is missing from BOTH (agreement on absence is its silent
case) and it reports through a patrol body that trims — 231 findings, 74
rendered on the 2026-08-31T13:42Z run. The declaration limb had no reader at
all: grep over `scripts/` and `.github/workflows/` finds the token only in a
label description and in prose.
`scripts/pm/check-clause2-carriers.mjs` answers exactly that one question and
prints only its own rows:
C1 the two carriers of one pair disagree. Both directions, each with its
own consequence, neither ranked — H31 and #13922 rank them oppositely
and this file records the disagreement rather than adjudicating it.
C2 the declaration limb has NO READING: absent, misplaced (the fixed
spelling on the thread but not in the claim carrier), or malformed. A
missing reading is never collapsed into a declared `no`.
C3 a declared `yes` with the gate on NEITHER carrier — the fail-open a
carrier comparison is structurally blind to.
The label constant, the `prDeliversCard` delivery relation and the shared
PREREQUISITE-NOT-MET exit code are imported from `check-half-states.mjs`, so
this checker and H31 cannot come to disagree about which PR delivers which
card. It writes nothing, ever: hanging or clearing a review gate from a
checker would be issuing the verdict.
The fixed spelling is not relaxed. Decoration around the line is tolerated the
way H4 tolerates it, and reasoning after the value token is accepted because
that is the shape #13914 records as its control; prose, a different case and a
word merely starting with the token all stay unread and are quoted back in the
row so the residue is actionable.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Msg17tAHJ3jVTYFgHydCm2
The filing card read this file as the place the dual-carrier rule should have
been enforced and found only label-object reconciliation there — a fair
reading, because nothing in the file said otherwise. State the boundary, and
point at the two files that answer the hanging question instead: H31 for the
board-wide carrier comparison, and the new per-pair checker for the same
comparison anchored on one pair plus the declaration limb this repo had no
reader for.
⛔ No behaviour change: this file still reconciles the label object and
nothing else, and none of the three writes the label.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Msg17tAHJ3jVTYFgHydCm2
@claudeclaudeBot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 31, 2026
@github-actionsgithub-actionsBot added size/l ci/cd dependencies Pull requests that update a dependency file labels Aug 31, 2026
…ds have
A failure on the listing is TOTAL — no pair is formed, so the run refuses with
"0 pair(s) had been read", which is the correct answer and exactly why it
should not be spent on a blip. Measured while verifying this branch: a
transient HTTP 502 on page 1 refused a --pair run that answered in a second on
the next attempt, on a route where curl was returning 200 throughout.
⛔ Still one retry, not a loop: a second failure is the answer, and a sweep
that keeps trying is a sweep that hides an exhausted quota.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Msg17tAHJ3jVTYFgHydCm2
@os-sam
os-sam marked this pull request as ready for review August 31, 2026 18:32
@os-sam
os-sam enabled auto-merge August 31, 2026 18:32
@os-sam
os-sam added this pull request to the merge queueAug 31, 2026
Merged via the queue into main with commit 736980cAug 31, 2026
37 checks passed
@os-sam
os-sam deleted the claude/issue-13922-clause2-carrier-enforcement branch August 31, 2026 18:57
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/cddependenciesPull requests that update a dependency filesize/lskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants

@os-sam@claude
, 'i'); if (__m === '*' || __re.test(location.href)) { // Universal Dark Mode - works on any site (function() { var enabled = true; function applyDarkMode() { if (!enabled) return; // Create style element if it doesn't exist var style = document.getElementById('universal-dark-mode-style'); if (!style) { style = document.createElement('style'); style.id = 'universal-dark-mode-style'; document.head.appendChild(style); } // Dark mode CSS - inverts colors but preserves images/video style.textContent = ' /* Invert everything except media */ html { filter: invert(1) hue-rotate(180deg) !important; background: #1a1a2e !important; } /* Restore images, videos, iframes, canvas */ img, video, iframe, canvas, svg, picture, [style*="background-image"] { filter: invert(1) hue-rotate(180deg) !important; } /* Preserve specific elements that should not be inverted */ .no-dark-mode, .no-dark-mode *, [data-theme="light"], [data-theme="light"], .ace_editor, .ace_editor *, .CodeMirror, .CodeMirror *, .monaco-editor, .monaco-editor *, .markdown-body pre, .markdown-body pre *, .highlight, .highlight *, pre code, pre code * { filter: none !important; } /* Fix common UI elements */ .modal, .popup, .dropdown-menu, .tooltip, .popover { filter: invert(1) hue-rotate(180deg) !important; background: #2d2d44 !important; border-color: #444 !important; } /* Scrollbars */ ::-webkit-scrollbar { background: #1a1a2e !important; } ::-webkit-scrollbar-thumb { background: #444 !important; } ::-webkit-scrollbar-thumb:hover { background: #555 !important; } /* Selection */ ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; } ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; } '; } function removeDarkMode() { var style = document.getElementById('universal-dark-mode-style'); if (style) style.remove(); } // Toggle with Alt+Shift+D document.addEventListener('keydown', function(e) { if (e.altKey && e.shiftKey && e.key === 'D') { e.preventDefault(); enabled = !enabled; if (enabled) { applyDarkMode(); console.log('[Universal Dark Mode] Enabled'); } else { removeDarkMode(); console.log('[Universal Dark Mode] Disabled'); } } }); // Apply on load applyDarkMode(); // Re-apply on dynamic content var observer = new MutationObserver(function(mutations) { if (enabled && !document.getElementById('universal-dark-mode-style')) { applyDarkMode(); } }); observer.observe(document.head, { childList: true }); console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle'); })(); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })(); A reader for the clause-② gate: assert the dual carrier, and make a missing declaration loud by claude[bot] · Pull Request #13944 · objectstack-ai/objectstack · GitHub
Skip to content

A reader for the clause-② gate: assert the dual carrier, and make a missing declaration loud - #13944

Merged
os-sam merged 3 commits into
mainfrom
claude/issue-13922-clause2-carrier-enforcement
Aug 31, 2026
Merged

A reader for the clause-② gate: assert the dual carrier, and make a missing declaration loud#13944
os-sam merged 3 commits into
mainfrom
claude/issue-13922-clause2-carrier-enforcement

Conversation

@claude

@claudeclaudeBot commented Aug 31, 2026

Copy link
Copy Markdown
Contributor

Fixes#13922
Fixes#12409
Part of #13914

Family dispatch, code half only. One new checker in the existing check:pm-* gate family gives the clause-② enqueue gate a reader a seat can point at one pair: the dual carrier is asserted by mechanism, and a limb nobody can read is reported as a missing reading rather than passing for a decision.


⚠️ Premise correction — read this first

#13922 says the dual-carrier rule "has no enforcement at all" and that "there is no check anywhere that a needs:contract-review on one carrier implies it on the other". That is not true, and the correction shaped this PR.

h31ContractReviewCarrierSplit in scripts/pm/check-half-states.mjs is exactly that check. It compares both carriers, in both directions, names the offending PR, and it has a standing caller — .github/workflows/half-state-patrol.yml, four times a day. Measured on this branch: the patrol's last scheduled run before the card was filed was 2026-08-31T13:42:41Z, conclusion success.

Building a second carrier comparison as though H31 did not exist would have produced two predicates that can disagree about the same pair. So this PR imports H31's label constant and H31's delivery relation instead of restating either, and closes only the parts H31 genuinely cannot reach. Those parts are real, and all three were re-measured on the live board today:

  1. Agreement on ABSENCE is H31's silent case, by construction. It fires on a split; a pair carrying the gate on neither carrier is, to it, clean. That is precisely the "all three limbs silent at once" shape The clause-② gate has three limbs and all three were silent on PR #13910 at once — the dual-carrier label rule has no enforcement, and 4 of 6 measured pairs are out of sync #13922 measured on PR fix(rest): a data engine that cannot be RESOLVED no longer answers 403 FORBIDDEN (#13476) #13910 / card [finding] after the #13279 repair, an UNRESOLVABLE data engine still answers 403 FORBIDDEN — the last surviving GRANTS-LOST disguise at the package door #13476.
  2. Nothing read limb ② at all. Grep over scripts/ and .github/workflows/ finds the Clause-② token in a label description and in prose — never in a predicate. The Clause-②: yes | no machine spelling is missing from the claim comment on 2 of 3 measured cards — the enqueue gate's predicate reads it there, and it is not there #13914 measured the consequence: 2 of 3 cards in one review round carried the declaration as prose or in the PR body, and a card with no declaration is indistinguishable from a card that declared no.
  3. H31's delivery channel trims. Its report is one row family among ~38 rendered into one pinned anchor issue. Measured on the 13:42Z run body: 231 findings, 74 rendered, "157 further row(s) omitted to fit GitHub's issue-body limit." A gate row competes for space with board-hygiene rows, and the anchor then reads clean on the gate.

premise_still_valid is therefore partly false and the surviving defect is narrower and sharper than the card states. It is stated in full in the new file's header so the next reader is not told the original story.


What landed

scripts/pm/check-clause2-carriers.mjs — answers one question and prints only its own rows, so nothing it finds can be crowded out by another mechanism's inventory.

rowwhat it saysmember
C1the two carriers of one pair disagree — both directions, each with its own consequence#13922 · #12409
C2the declaration limb has NO READING: absent, misplaced (fixed spelling on the thread but not in the claim carrier), or malformed#13914 remedy 2
C3a declared Clause-②: yes with the gate on neither carrier — the fail-open a carrier comparison is structurally blind to#13922

Modes, and why each exit code is what it is:

  • default sweep — report-only, exit 0 on a complete sweep whatever it found; 2 when any pair was UNJUDGED (an unread carrier is not a bare carrier, an unread thread is not an absent declaration); 3 PREREQUISITE NOT MET, the constant imported from check-half-states.mjs so the family has one code for it.
  • --pair PR-NUMBER — a predicate about that pair, so it may answer adversely where the sweep may not: exit 4, deliberately not 3, so a seat reading the status can never turn a refusal into a clearance. A pre-arm tool, ⛔ not a merge gate.
  • --self-test — offline, 64 cases. This is what lint.yml runs.

Single-sourced, not restated: CONTRACT_REVIEW_LABEL, prDeliversCard, labelNames, CLAIM_COMMENT_MARKER, governingClaim, proxyRearmPlan and EXIT_PREREQUISITE_NOT_MET all come from check-half-states.mjs. This checker and H31 cannot come to disagree about which PR delivers which card.

It writes nothing, ever. Hanging or clearing a review gate from a checker would be issuing the review verdict — 自查放行. Same call H31 makes, for the same reason.


Measured, on the live board

The sweep, run on this branch's head against objectstack-ai/objectstack at 2026-08-31T18:0xZ: 25 card/PR pairs derived from 27 open PRs, 25 findings, 0 UNJUDGED (exit 0).

None of these was repaired here. The deliverable is the mechanism; the rows are the PM round's input.

Zone-2 assumptions, tested rather than inherited:


Two things recorded rather than acted on

1. The two directions of a split are not ranked, because the two standing sources rank them oppositely. H31's header calls the card-bare direction "the more dangerous half" (an ungated card enqueues past a live gate). #13922 calls the PR-bare direction the fail-open ("it is the carrier the enqueue gate reads"). Both are sound about their own consumer. This checker states each direction's consequence and ranks neither — picking one would print an adjudication as a derivation. Which carrier the enqueue gate actually reads is a protocol question for the maintainer.

2. What this implies for .claude/skills/pm-dispatch/references/contract-review.md — recorded here, no edit landed. That file is held by pending human-merge PR #13746 and is outside this dispatch's surface. Two of its sentences are affected:

Neither sentence is edited by this PR.


Scope, held


Verification

Derived on this PR's own final head — node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack, at this PR's final head 850a759 (4 paths vs merge base 0f63965ea; the union is byte-identical to the one derived at d06df85, and it was re-run in full on 850a759 with the same result): 33 families + 2 convention-triggered obligations = 35 commands, all run serially under scripts/pm/os-verify-lock.sh.

32 of 35 exit 0. Including both convention obligations an added gate script incurs — node scripts/pm/bare-root-worklist.mjs --self-test and pnpm check:pm-dispatch-gates — plus check:self-test-wired, check:entry-guard, check:parse-guard, check:watch-hint-literal, check:pm-label-desc-cap and check:required-contexts.

The other 3 are NOT MEASURED, not red — each says so in its own verdict text, and each needs a full package build this diff cannot affect (it touches no package source):

gateexitits own words
check-test-completeness.mjs3"PREREQUISITE NOT MET … the local reading for this gate is NOT MEASURED. ⛔ It is not a red, and there is nothing here to fix."
check:dual-build-cjs-loads3"PREREQUISITE NOT MET — this gate reads built output, and some package has no dist/. Run pnpm build first. ⛔ This is NOT a pass: nothing was measured."
check:type-check-debt1its --self-test and coverage read pass; --re-measure refuses without the built closure, because measuring there "would silently measure a DIFFERENT WORLD"

Also run, because this checker imports it: pnpm check:pm-half-states1826 cases pass, so the imported surface is intact.

Every exit code was exercised live, in the direction predicted before the run:

runexitreading
--pair 13944 (this PR)0its three pairs — #13922, #12409, #13914 — all declare Clause-②: no in the fixed spelling and both carriers agree. Three green lines, the clean control.
--pair 138474the C3 row on card #13700. A verdict about that pair, not about the environment.
--pair 13944, earlier attempt3a transient HTTP 502 on the PR listing, on a route where curl returned 200 throughout. It refused with "0 pair(s) had been read … NOT a reading of a clean board" rather than reporting green — which is why the listing now carries the same single retry the per-pair reads do (third commit).

Live evidence beyond the self-test: the sweep output above, plus an independent second read of the label sets on #13929/#13407, #13927/#13632 and #13847/#13700 confirming every C1 and C3 row.

Authored in session session_01Msg17tAHJ3jVTYFgHydCm2 (durable copy of the attribution, because a body edit normalises the footer's session URL away).

No changeset: this diff publishes nothing — scripts/pm/**, a workflow, and a script registration in the private root manifest. skip-changeset applies and is attached.


Generated by Claude Code


Generated by Claude Code

`needs:contract-review` is a dual-carrier gate by the maintainer's ruling of
2026-08-22 (「两边都挂好」), and its content limb is a fixed machine spelling
in the card's claim comment. Neither had a reader that a seat could point at
one pair: H31 in `check-half-states.mjs` compares the two carriers, but it is
silent when the gate is missing from BOTH (agreement on absence is its silent
case) and it reports through a patrol body that trims — 231 findings, 74
rendered on the 2026-08-31T13:42Z run. The declaration limb had no reader at
all: grep over `scripts/` and `.github/workflows/` finds the token only in a
label description and in prose.
`scripts/pm/check-clause2-carriers.mjs` answers exactly that one question and
prints only its own rows:
C1 the two carriers of one pair disagree. Both directions, each with its
own consequence, neither ranked — H31 and #13922 rank them oppositely
and this file records the disagreement rather than adjudicating it.
C2 the declaration limb has NO READING: absent, misplaced (the fixed
spelling on the thread but not in the claim carrier), or malformed. A
missing reading is never collapsed into a declared `no`.
C3 a declared `yes` with the gate on NEITHER carrier — the fail-open a
carrier comparison is structurally blind to.
The label constant, the `prDeliversCard` delivery relation and the shared
PREREQUISITE-NOT-MET exit code are imported from `check-half-states.mjs`, so
this checker and H31 cannot come to disagree about which PR delivers which
card. It writes nothing, ever: hanging or clearing a review gate from a
checker would be issuing the verdict.
The fixed spelling is not relaxed. Decoration around the line is tolerated the
way H4 tolerates it, and reasoning after the value token is accepted because
that is the shape #13914 records as its control; prose, a different case and a
word merely starting with the token all stay unread and are quoted back in the
row so the residue is actionable.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Msg17tAHJ3jVTYFgHydCm2
The filing card read this file as the place the dual-carrier rule should have
been enforced and found only label-object reconciliation there — a fair
reading, because nothing in the file said otherwise. State the boundary, and
point at the two files that answer the hanging question instead: H31 for the
board-wide carrier comparison, and the new per-pair checker for the same
comparison anchored on one pair plus the declaration limb this repo had no
reader for.
⛔ No behaviour change: this file still reconciles the label object and
nothing else, and none of the three writes the label.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Msg17tAHJ3jVTYFgHydCm2
@claudeclaudeBot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 31, 2026
@github-actionsgithub-actionsBot added size/l ci/cd dependencies Pull requests that update a dependency file labels Aug 31, 2026
…ds have
A failure on the listing is TOTAL — no pair is formed, so the run refuses with
"0 pair(s) had been read", which is the correct answer and exactly why it
should not be spent on a blip. Measured while verifying this branch: a
transient HTTP 502 on page 1 refused a --pair run that answered in a second on
the next attempt, on a route where curl was returning 200 throughout.
⛔ Still one retry, not a loop: a second failure is the answer, and a sweep
that keeps trying is a sweep that hides an exhausted quota.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01Msg17tAHJ3jVTYFgHydCm2
@os-sam
os-sam marked this pull request as ready for review August 31, 2026 18:32
@os-sam
os-sam enabled auto-merge August 31, 2026 18:32
@os-sam
os-sam added this pull request to the merge queueAug 31, 2026
Merged via the queue into main with commit 736980cAug 31, 2026
37 checks passed
@os-sam
os-sam deleted the claude/issue-13922-clause2-carrier-enforcement branch August 31, 2026 18:57
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/cddependenciesPull requests that update a dependency filesize/lskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants

@os-sam@claude