You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Commit e0a4efa
Browse filesBrowse the repository at this point in the historyBrowse files
fix(pm): bind H31's carrier pair through the closing keyword
H31 compares the two carriers of the `needs:contract-review` gate, and it
took `prDeliversCard` -- the wide body-first/branch-fallback delivery
relation -- as its population. That relation counts a `Part of #N` line,
which declares MEMBERSHIP in what #N tracks rather than a delivery of it, so
an epic tracker whose sub-PRs are gated correctly drew a row saying the gate
was missing from the card half of a dual carrier. The row's remedy text is
action-shaped: acting on it hangs the gate on a card nothing will ever close,
where the stroke that clears gates never arrives.
The comparison now runs over PRs bound by a closing keyword -- the binding
that makes the landing reach the card, which is what the clearing stroke
rides. The shared relation is untouched (its docblock forbids narrowing it),
so H8, H31, H35 and H53 still agree about which PR delivers which card.
Weaker bindings are DECLINED, never silently dropped: a weak-bound delivering
PR whose carrier differs from the card's still produces a row that names both
carriers and the binding it read, states that it declined, and prescribes no
write. Agreement on a weak binding stays silent, as before.
Claude-Session: https://claude.ai/code/session_01HZfg2AwVX191qCizp88gQr
Co-authored-by: Claude <noreply@anthropic.com>
Copy file name to clipboardExpand all lines: scripts/pm/check-half-states.mjs
+85-1Lines changed: 85 additions & 1 deletion
Original file line number
Diff line number
Diff line change
@@ -25927,7 +25927,8 @@ async function selfTest() {
25927
25927
t('H31: a PR row whose labels could not be read is not judged as bare', h31ContractReviewCarrierSplit(gateCard([CONTRACT_REVIEW_LABEL]), [{ ...bare, labels: undefined }]), null);
25928
25928
t('H31: …and one readable bare PR alongside it still fires', typeof h31ContractReviewCarrierSplit(gateCard([CONTRACT_REVIEW_LABEL]), [{ ...bare, labels: undefined }, gatePr(11845, [])]), 'string');
25929
25929
// The delivery relation is H8's, shared rather than re-derived.
25930
-
t('H31: the branch-name fallback delivers a body-silent PR', typeof h31ContractReviewCarrierSplit(gateCard([CONTRACT_REVIEW_LABEL]), [{ ...bare, body: '' }]), 'string');
25930
+
t('H31: the branch-name fallback still produces a row for a body-silent PR', typeof h31ContractReviewCarrierSplit(gateCard([CONTRACT_REVIEW_LABEL]), [{ ...bare, body: '' }]), 'string');
25931
+
t('H31: …but it is the DECLINED one — a body that declared nothing does not close the card (#18229)', says(h31ContractReviewCarrierSplit(gateCard([CONTRACT_REVIEW_LABEL]), [{ ...bare, body: '' }]), 'DECLINES to judge'), true);
25931
25932
t('H31: …and H8 reads the same PR as delivering the same card', prDeliversCard({ ...bare, body: '' }, '11427'), true);
25932
25933
t('H31: a CLOSED card is out of scope', h31ContractReviewCarrierSplit(gateCard([CONTRACT_REVIEW_LABEL], { state: 'closed' }), [bare]), null);
25933
25934
t('H31: a missing issue does not crash', h31ContractReviewCarrierSplit(undefined, [bare]), null);
@@ -25949,6 +25950,89 @@ async function selfTest() {
25949
25950
// all — the shape this row deliberately does NOT report.
// The binding predicate itself, both directions on ONE body.
25981
+
t('#18229: a closing keyword closes the card it names', bindingClosesCard(pr18212(), '18202'), true);
25982
+
t('#18229: a `Part of` line on the SAME body does not', bindingClosesCard(pr18212(), '14122'), false);
25983
+
t('#18229: nor an inline `Part of`', bindingClosesCard({ number: 1, body: 'see part of #7918 above' }, '7918'), false);
25984
+
t('#18229: nor the branch-name fallback', bindingClosesCard({ number: 1, body: '', head: { ref: 'claude/issue-9834-x' } }, '9834'), false);
25985
+
t('#18229: a keyword outranks an inline `Part of` for the SAME card, exactly as GitHub does', bindingClosesCard({ number: 1, body: 'Fixes #7918\n\nsee part of #7918 above' }, '7918'), true);
25986
+
t('#18229: a PR bound to no card at all closes nothing', bindingClosesCard({ number: 1, body: 'no declaration' }, '9999'), false);
25987
+
t('#18229: a missing PR does not crash', bindingClosesCard(undefined, '1'), false);
25988
+
// ⛔ The SHARED relation is untouched — the narrowing is H31's own filter.
25989
+
t('#18229: ⛔ `prDeliversCard` is NOT narrowed — the tracker is still a delivery to H8/H35/H53', prDeliversCard(pr18212(), '14122'), true);
25990
+
t('#18229: …and the evidence kind H8 prints is unchanged', deliveryEvidence(pr18212(), '14122'), 'part-of');
25991
+
25992
+
// The PIN: the tracker's row no longer prescribes a write.
0 commit comments