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 c337079
Browse filesBrowse the repository at this point in the historyBrowse files
fix(scripts/pm): H59 reads state_reason so a deliberately REOPENED card is not called a platform failure (#19427)
Fixes#19177
Clause-②: no
H59's FALSE OPEN row asserted a cause the payload it already holds
contradicts: for a card that
is `open` carrying `state_reason: reopened`, it printed the sentence
naming a platform failure.
The discriminator rides the same open-listing row the sweep already has,
so the arm costs zero
requests. Region H59 only; `SELF_TEST_BATTERY_FLOOR` untouched (8). Net
**+29 lines** (29 added,
0 removed, 1 file) against the PM's ceiling of +30.
## Before / after, on the filing card's live shape
Probe: a merged PR binding a closing keyword to a card that is open with
`state_reason: reopened`.
Both quotes are fenced so no keyword in this body binds anything.
Before (identical bytes for `reopened` and for `null` — the row is blind
to the field):
```
FALSE OPEN: merged PR #9936 binds `Fixes#9874` in its body, and the card is STILL OPEN after
that merge — GitHub did not perform the closure the PR declared. ...
```
After, for `state_reason: reopened` only:
```
FALSE OPEN (reopened): merged PR #9936 binds `Fixes#9874` in its body and the card is open
carrying `state_reason: reopened` — it was CLOSED at some point and REOPENED since, a deliberate
act and ⛔ never the platform failure the plain row names. ⚠️ This row buys no timeline, so it
does not PLACE that reopen against the merge: whether the declared closure fired and was
reversed, or never fired on an already-reopened card, is unread here — what the payload settles
is that a REOPEN happened, and that is the act to read. Remedy, and it belongs to the SEAT THAT
OWNS the card: read the reopen's own reason — the comment that made it, or the decision-box
label it carries — and ⛔ do not close a card somebody reopened on purpose. ⛔ This row closes
nothing and writes no label.
```
`null`, `completed` and `not_planned` keep today's sentence, byte for
byte.
## The disposition, on the four axes
The card named two dispositions and picked neither: **suppress** the row
on `reopened`, or **keep
it and change the sentence**. This PR keeps it and changes the sentence.
- **实际业务需求** — measured, not assumed. The live instance cost a full
investigation on two
consecutive sweeps and could be cleared by no legal act on the card. The
population is live
today: scanning the same `state=open` listing this row already holds,
objectstack#17147 and
objectui#7844 / objectui#6596 carry `state_reason: reopened` right now
(920 open issues read
across the two boards in this act). So the case is real. And the audit
still wants the row: a
merged PR whose body declares a closure that is not in force reads as
done to everyone who
finds it, whichever way the card got back to open. Suppression deletes
that from the board.
- **项目长远合理性** — H59's whole architecture is "narrow before you buy,
count every narrowing,
⛔ never call an unjudged thing clean" (its own clause prints "Rows are a
LOWER BOUND").
Suppression would add the one silent drop in this row that no counter
reports. The defect is a
sentence asserting a cause its data does not establish; the fix belongs
at the producer of that
sentence, not in a consumer that learns to ignore the row.
- **防 AI 写错** — the decisive axis. This row's reader is an AI seat on
patrol, and today's
sentence sends it to look for a platform failure that did not happen
**and** hands it a remedy
("close it if the work landed") that is wrong for a deliberate reopen: a
seat that follows it
closes a card somebody reopened on purpose. Suppression removes the
false instruction but also
removes the true information, and a verifier that silently degrades is
worse than no verifier.
The new arm removes the false accusation, declares the limit of what it
read, and redirects the
remedy at the reopen's own reason — declared, loud, and correct.
- **创业阶段不扩散需求** — one field read, one branch in an existing sentence. ⛔
No new request,
no new constant, no new row, no new battery, no new count key. This is
the narrowest arm that
fixes the defect, and it is a correction of an existing sentence rather
than an expansion.
No axis conflict: axis 4 mildly prefers the smaller change and the
chosen arm is not larger than
suppression on any measure. **Suppression is refused on axes 1 and 3.**
⚠️ One honesty limit, and it is stated on the row rather than papered
over: `state_reason:
reopened` proves the card was closed and reopened; it does ⛔ not prove
WHICH closure, so it
cannot rule out "GitHub never fired on an already-reopened card".
Placing the reopen against the
merge needs a timeline page, and direction (b) is the half that costs no
request — buying one
here would change what this arm is. So the row reports what the payload
settles and names what it
did not read, which is the same discipline direction (a) already applies
to its band leg.
## The fixture extension
The direction (b) fixture `card59Open` is a plain object literal, ⛔ not
a helper, so the cases
build the variants with a one-line factory over a spread of it and the
literal is left untouched
— every existing (b) assertion still reads the plain arm. Nine cases
added beside the existing
ones (⛔ never at `selfTest()`'s tail): the platform-failure sentence
absent, the field quoted,
the closed-and-reopened reading, the no-timeline declaration, both
halves of the remedy, the row
still firing, the angle-bracket pin, and one case holding `null` /
`undefined` / `completed` /
`not_planned` on today's sentence.
## Verification
`node scripts/pm/check-half-states.mjs --self-test` — **5118 cases at
base a0e62e6, 5127 after**,
exit 0 both times. Batteries unchanged, `SELF_TEST_BATTERY_FLOOR` still
8.
**Reverse verification (ablation), run from the committed state through
`scripts/ablation-replace.mjs`** so the mutation is proven on disk and
the restore is proven
against HEAD. Predicted direction before running: 转红. Mutation: the new
predicate neutered to
`if (false && ...)`.
```
ablation-replace: ok mutation landed: anchor 1 -> 0, blob 0fe677f -> afec5bdfc7fe
✗ check-half-states self-test: 7 of 5127 case(s) failed.
ablation-replace: ok restored: blob == HEAD (0fe677f) and `git diff HEAD` is empty
```
7 of the 9 new cases go red. The 2 survivors are exactly the 2 that are
⛔ not about the reopened
sentence — the angle-bracket pin, and the control case that holds every
OTHER `state_reason` on
today's sentence, which reads the plain arm either way. That asymmetry
is the evidence the cases
bind to this predicate rather than to the row in general.
**Derived gate union** — `node scripts/pm/dispatch-gates.mjs --commands
--repo
objectstack-ai/objectstack` at head `30caf75`, no paths passed (the tool
derives its own change
set: 1 path, 29 changed lines, merge base a0e62e6). It printed 41
commands; **40 ran green in the
foreground, each exit captured before any pipe**; `pnpm
check:pm-dispatch-gates` ran detached with
its own exit captured. Reconciled with `--ran`. The tool's own NOT
MEASURED classes (8 families
taking a value from the workflow, 1 CI-measured-only, 10 pending a
changeset that this card does
not write, 46 artifact rosters, 11 wide-population families, 1
path-scheduled CI job) are CI's
and are named as unmeasured here rather than read as clearances.
`node scripts/pm/check-governed-merges.mjs --pr N` — `scripts/pm/**` is
⛔ not a governed surface.
## Acceptance notes
Off-path observations from this region, ⛔ not filed and ⛔ not fixed
here:
- The census clause (`h59LinkageClause`) counts a reopened row as an
ordinary declared-closure row
and does not break the two arms out. Nothing it prints is false — it
says a declared target
still on the open listing is a FALSE OPEN row, which stays true — so
this is an enhancement,
⛔ not one of the three filable classes. It would also need a new
`SWEEP_COUNT_KEYS` entry and
the clause's own pins, well past this card's ceiling. 承接者: whoever next
widens H59's census.
- The claim's line pointers for the H22 lit control read
`:25702`–`:25718`; at a0e62e6 those
fixtures are at `:25902`–`:25918`, and a fifth spelling of the field
lives at `:29968` in H24's
battery. All five are self-test fixtures, so the file had no production
reader of `state_reason`
before this PR. Drift in a dispatch pointer, ⛔ not a defect in the file.
承接者: none.
---
_Generated by [Claude
Code](https://claude.ai/code/session_017ETYWqMQD4qMtZzAGovWNi)_
Co-authored-by: Claude <noreply@anthropic.com>
t('H59 (b) reopened: ⛔ the platform-failure sentence is NOT printed at all', row59bReopened.includes('GitHub did not perform the closure'), false);
34803
+
t('H59 (b) reopened: …it quotes the field it read off the payload it already held', row59bReopened.includes('`state_reason: reopened`'), true);
34804
+
t('H59 (b) reopened: it says the card was closed and REOPENED since, a deliberate act', row59bReopened.includes('CLOSED at some point and REOPENED since'), true);
34805
+
t('H59 (b) reopened: ⛔ it refuses to PLACE the reopen against the merge, buying no timeline', row59bReopened.includes('buys no timeline'), true);
34806
+
t('H59 (b) reopened: the remedy points at the REOPEN\'s own reason', row59bReopened.includes('read the reopen\'s own reason'), true);
34807
+
t('H59 (b) reopened: …and ⛔ warns off closing a card somebody reopened on purpose', row59bReopened.includes('do not close a card somebody reopened on purpose'), true);
34808
+
t('H59 (b) reopened: the row still FIRES — this changed the SENTENCE, ⛔ not the population', row59bReopened.startsWith('FALSE OPEN (reopened):'), true);
34809
+
t('H59 (b) reopened: ⛔ no angle-bracket placeholder reaches this one either', /[<>]/.test(row59bReopened), false);
34810
+
t('H59 (b) reopened: ⛔ every OTHER state_reason keeps today\'s sentence, null included', [null, undefined, 'completed', 'not_planned'].every((r) => row59bBy(r).includes('GitHub did not perform the closure')), true);
34811
+
34783
34812
// The two directions are DISTINCT sentences — neither restates the other.
34784
34813
t('H59 directions: the two rows do not share an opening', row59a.slice(0, 20) === row59b.slice(0, 20), false);
0 commit comments