Uh oh!
There was an error while loading. Please reload this page.
A reader for the clause-② gate: assert the dual carrier, and make a missing declaration loud - #13944
Merged
Merged
Conversation
`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
…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
This was referenced Aug 31, 2026
os-sam
marked this pull request as ready for review
August 31, 2026 18:32
os-sam
enabled auto-merge
August 31, 2026 18:32
Uh oh!
There was an error while loading. Please reload this page.
This was referenced Aug 31, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for freeto join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.#13922 says the dual-carrier rule "has no enforcement at all" and that "there is no check anywhere that a
needs:contract-reviewon one carrier implies it on the other". That is not true, and the correction shaped this PR.h31ContractReviewCarrierSplitinscripts/pm/check-half-states.mjsis 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, conclusionsuccess.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:
scripts/and.github/workflows/finds theClause-②token in a label description and in prose — never in a predicate. TheClause-②: yes | nomachine 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 declaredno.premise_still_validis 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.absent,misplaced(fixed spelling on the thread but not in the claim carrier), ormalformedClause-②: yeswith the gate on neither carrier — the fail-open a carrier comparison is structurally blind toModes, and why each exit code is what it is:
0on a complete sweep whatever it found;2when any pair was UNJUDGED (an unread carrier is not a bare carrier, an unread thread is not an absent declaration);3PREREQUISITE NOT MET, the constant imported fromcheck-half-states.mjsso 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: exit4, deliberately not3, 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 whatlint.ymlruns.Single-sourced, not restated:
CONTRACT_REVIEW_LABEL,prDeliversCard,labelNames,CLAIM_COMMENT_MARKER,governingClaim,proxyRearmPlanandEXIT_PREREQUISITE_NOT_METall come fromcheck-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/objectstackat 2026-08-31T18:0xZ: 25 card/PR pairs derived from 27 open PRs, 25 findings, 0 UNJUDGED (exit 0).needs:contract-review, card rest/meta: getViewsByObject / GET /meta/view?object= omits a runtime-authored view CONTAINER — #7163/#7736 expansion fix does not cover this path #13407 does not. Verified independently by a second read of both label sets.FieldSchemaaccepts alookup/master_detailwith noreferencetarget, though its own TSDoc calls the key required #13632 (PR fix(spec): require a non-empty reference on lookup/master_detail fields #13927) and card serve 面保证 stored metadata 规范拼写:reference_to → reference 归一化 + spec 裁定 reference_field/referenceField 协议归属(ui#6837 半 1) #13700 (PR Add field-reference-to-alias conversion so stored reference_to serves as reference #13847) each declareClause-②: yesin the claim comment while neither carrier carries the gate label. PR fix(spec): require a non-empty reference on lookup/master_detail fields #13927 is not draft. Both claims say in their own words that the label was to be hung on both carriers in the same stroke as PR creation.NO READINGand 2MISPLACED. The two misplaced ones quote what was there instead, e.g. 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:### Clause ② — 'yes', and it is a FOURTH class, and card APERMISSION_DENIEDrefusal never reaches the dispatcher's throw-transparent exit, so it still drops the author'suserMessage— a second door, behind #7898's trigger file #13623:### Clause ② — declared 'yes' conservatively. APERMISSION_DENIEDrefusal never reaches the dispatcher's throw-transparent exit, so it still drops the author'suserMessage— a second door, behind #7898's trigger file #13623 is one of TheClause-②: yes | nomachine 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's own two measured misses, re-observed by mechanism.⛔ 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:
Part ofkeyword in the PR body naming its card, and every branch name carries the fallback too. Both channels agree on all seven.pull_request_read getreturned no labels field, and the web-payload grep spelling that works on issues reads zero on a labeled PR #12902's claim that PR-side labels cannot be written — not relied on either way. This PR writes no label from any channel, so the question does not arise for it. What was measured instead is the read side: repo-scoped REST reads and writes both answer 200 from this container, and the sweep read 27 PRs and 25 threads over it.check-half-states.mjsH31/H35 — the stated condition did not hold, and the answer is standalone anyway. Open PR fix(pm): mask the fixture builders only a self-test can reach #13930 changesscripts/pm/dispatch-gates.mjs, notcheck-half-states.mjs, so H31/H35 were not held. Standalone is still right, because the trimming measurement above says a new row family added to that report is a row family that can be omitted before a reader sees it. H31/H35 are untouched;dispatch-gates.mjsis untouched.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:--pairnow answers that question directly for one pair instead of inferring it, so the recovery rule could point at the checker rather than at an inference.check-clause2-carriers.mjs --pairbeside it would let the prose stop being the only executor.Neither sentence is edited by this PR.
Scope, held
Contract review: PASSmarker that matched 5 of 35 removals strictly and 26 loosely). This PR gives the declaration a reader; it gives the verdict none, deliberately. That residue is named in the wrap-up comment on [finding] The contract-review gate is being hung on ONE carrier — 34 of 36 lone clears had a PR that never carried it, which makes a strip undetectable #12409.Clause-②: yes | nomachine 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 isPart of, remedy 2 only. The claim-comment template in.claude/skills/pm-dispatch/SKILL.md(remedy 1) is not touched and the card stays open for it.Clause-②: yes/Clause-②: no. Decoration around the line is tolerated the way H4 tolerates it; reasoning after the value token is accepted because that is the shape TheClause-②: yes | nomachine 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 records as its control (os lintnever surfaces ADR-0087 conversion notices — it normalizes with noonConversionNoticesink, the #3782 parity gapos buildwas in #12297 "carriesClause-②: yeswith reasoning"). A reader that rejected it would have graded the card's own control as the defect — and it did, on four real claims, before that case was written.Clause-②: YES,Clause-②: nope,Clause-②: probably not, an empty value and the prose formClause ②: yesall stay unread, and are quoted back in the row so the residue is actionable.lint.ymlstep runs the self-test only, the same split ascheck:pm-half-statesandcheck:pm-governed-mergesbeside it.Verification
Derived on this PR's own final head —
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack, at this PR's final head850a759(4 paths vs merge base0f63965ea; the union is byte-identical to the one derived atd06df85, and it was re-run in full on850a759with the same result): 33 families + 2 convention-triggered obligations = 35 commands, all run serially underscripts/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-testandpnpm check:pm-dispatch-gates— pluscheck:self-test-wired,check:entry-guard,check:parse-guard,check:watch-hint-literal,check:pm-label-desc-capandcheck: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):
check-test-completeness.mjscheck:dual-build-cjs-loadspnpm buildfirst. ⛔ This is NOT a pass: nothing was measured."check:type-check-debt--self-testand coverage read pass;--re-measurerefuses without the built closure, because measuring there "would silently measure a DIFFERENT WORLD"Also run, because this checker imports it:
pnpm check:pm-half-states— 1826 cases pass, so the imported surface is intact.Every exit code was exercised live, in the direction predicted before the run:
--pair 13944(this PR)Clause-②: noin the fixed spelling and both carriers agree. Three green lines, the clean control.--pair 13847--pair 13944, earlier attemptHTTP 502on the PR listing, on a route wherecurlreturned 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-changesetapplies and is attached.Generated by Claude Code
Generated by Claude Code