Skip to content

fix(pm): stop --report presenting command-exit as one certified population - #12484

Merged
yinlianghui merged 1 commit into
mainfrom
claude/issue-12365-verify-lock-ledger-vocabulary
Aug 26, 2026
Merged

fix(pm): stop --report presenting command-exit as one certified population#12484
yinlianghui merged 1 commit into
mainfrom
claude/issue-12365-verify-lock-ledger-vocabulary

Conversation

@yinlianghui

Copy link
Copy Markdown
Collaborator

Part of #12365

A measurement card first. The card asked three questions; two of them are
answered here with numbers, and the third is explicitly left where the card put
it. The code change is one output caveat plus three pins — it is what the
measurement selected, not a change looking for a reason.

Q2 — the retrospective label scan: zero, and here is its bound

Scanned the live ledger on this container, snapshotted before any run of
mine could append to it.

rows111
bytes18,826 (cap 8,388,608 — nowhere near the recording bound)
span2026-08-25 18:46:55Z → 2026-08-26 04:54:04Z (~10h)
outcome=command-exit107
outcome=batch-last-exit4

Of the 107 command-exit rows: 0 carry a top-level ;, |, || or &,
106 scan as certifiable, 1 is undecidable (truncation cut it mid-quote). So the
card's closure branch on Q2 holds: the number is zero.

The scan reuses the shipped classifier — exit_certifiable, extracted
byte-identical from the file — rather than a reimplementation, so the scan and
the instrument answer the same question. Positive control: a synthetic
ledger of 4 known batches (one per operator class) + 3 known-certifiable rows
flags exactly 4/4 and 0/3, and correctly does not flag && or a quoted ;.

⚠️This is a lower bound, not a census, from three separate causes — the card
names one; measurement found two more:

  1. 200-char truncation can cut the operator off the tail. 15 of 111 rows sit
    at the cut. (The card's caveat.)

  2. Truncation can break quoting, and the scanner then refuses rather than
    guesses — 1 row.

  3. Newlines are destroyed before truncation.ledger_append runs
    tr '\n\t' ' 'beforecut -c1-200, so a newline-sequenced batch — one
    of the five forms the fix distinguishes — leaves no operator in the label
    at any length
    . Verified directly, on a private lock and a private ledger:

    $ os-verify-lock.sh -c $'true\ntrue'
    VERDICT batch-last-exit 0
    ledger row: ... outcome=batch-last-exit ... label=true true
    

    The word is right; the label reads as certifiable. For that operator
    class a label scan has zero power, not merely reduced power.

⚠️One machine, not the fleet. Confirming the dispatch-time reading:
LEDGER_FILE="${OS_VERIFY_LOCK_LEDGER:-${LOCK_FILE}.ledger}" — a /tmp runtime
artifact, never committed. This is one container's ~10h of runs.

Q1 — consumers of the outcome field: exactly one, and it does read two vocabularies

Census widened from the dispatch-time two-pattern grep to four patterns, plain
grep -rI (so untracked files count too), across the whole worktree and the
sibling objectui checkout. Positive controls:ledger_append → the known
13 occurrences in the one file; objectstack → 4,726 files, proving the
recursion actually reaches. Two hits survived and both are false positives — a
lint.ymlcomment saying the self-test never touches the shared lock, and an
unrelated sys_metadata_auditoutcome=denied in an objectql test.

The zero is a "never eligible" zero, not a lucky one: the ledger is a /tmp
artifact addressable only by naming LEDGER_FILE/OS_VERIFY_LOCK_LEDGER, and
only the writer does; CI never takes the lock at all.

So the sole consumer is mode_report's outcomes tally — and the card's
assumption C is right to be suspicious of it. It buckets by word and ranks the
buckets, which silently implies the two words are comparable. They are not:
pre-fix rows carry command-exit unconditionally, so that bucket mixes
certified runs with never-certified ones, while batch-last-exit is a real
claim. command-exit 107 reads as 107 certified runs, and across the boundary
that is false in the green direction — the original defect wearing the
ledger's clothes.

And the mixing is ongoing, not historical. Each worktree pins its own copy
of the script, so stale checkouts still write the old word into the same ledger.
Measured on this container right now: 4 of 9 checkouts are pre-fix,
including the shared primary /home/user/objectstack. The exposure does not
self-extinguish on a short clock.

⇒ The triage closure branch was "scan finds zero and no consumer reads two
vocabularies"
. The first conjunct holds; the second is falsified. Hence one
caveat, not a close.

The change

--report's outcomes block gains two lines, in the style of the
NOT MEASURED footer already there for exactly this reason — that block carries
interpretive footers precisely because its buckets are misreadable:

outcomes:
command-exit 107
batch-last-exit 4
⇒ queue-timeout and lock-unusable are NOT MEASURED runs: no gate was decided by them.
⇒ command-exit is NOT one population: rows written before the verdict-word fix
carry it unconditionally, so the bucket mixes certified with never-certified
runs. Only batch-last-exit is a positive signal.
⇒ a label scan for a top-level ";", "|", "||" or "&" LOWER-BOUNDS that mixture
and cannot census it: labels are cut at 200 chars, and a newline -- itself one
of the sequencing forms -- is flattened to a space BEFORE the cut.

No behaviour, no exit code, no acquisition path, no ledger format is touched.

Verification

  • --self-test: 134/134 pass, including the three new cases. Run under the
    shared lock: VERDICT command-exit 0 · held the lock 86s.

  • Ablation (NOT one populationABLATEDMARKER): mutation confirmed on
    disk by grepping both the injected text (1) and the removed text (0) —
    never an editor's exit code; mutated self-test exits 1 with exactly the
    targeted case ; restored and proven byte-identical,
    git hash-object = 6b3e39d89a26e53e8e8c4289e49ba1fcd5dbf21e before and
    after. The script carried a trap ... EXIT INT TERM restore throughout.

  • Gate union derived by node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack (never a hand-built list), re-derived after the
    final commit — same 9 families, no new ones. All green at d190728cc8,
    each exit code captured before any pipe:

    check:agent-test-spelling · check:bash32-floor · check:cli-command-ids ·
    check:cross-package-test-inputs · check:entry-guard · check:parse-guard ·
    check:pnpm-filter-targets · check-ci-filter-parity ·
    check-cross-package-test-inputs · plus check:nul-bytes.

    check-ci-filter-parity first returned PREREQUISITE NOT MET — the dependency yaml is not installed. Per standing rule that is a refusal to measure, not a
    finding: built the closure (pnpm install) and re-ran → OK: all 105 declared cross-package glob(s) ... covered.

Declared narrowing: the green self-test ran under the shared lock; the
ablation leg ran outside it. The preceding acquisition waited 467s of a 540s
budget, and a second queued wait plus a ~90s run risked the ~10min foreground
cap landing mid-mutation. The self-test runs entirely on its own private lock
under a temp dir (CI runs it with no lock at all), so this costs serialisation
of ~90s of CPU, nothing else.

No changeset: CI-internal tooling with no user-visible surface — the two
immediate predecessors on this file landed with none. skip-changeset applied.

Scope

⛔ No historical run was audited and nothing was re-opened — out of scope on
#12288's dispatch and still out. ⛔ No defect is claimed in PR #12363; its
forward half is correct and this does not touch it. Card question 3 (does any
downstream green rest on such a row) stays where the card put it — unanswerable
from the ledger, and with Q2 measuring zero there is nothing to scope it from.

Generated by Claude Code


Generated by Claude Code

…ation
The ledger's outcome bucket is not homogeneous across the verdict-word fix.
Rows written before it carry `command-exit` unconditionally -- the word named
the outcome rather than claiming anything about it -- so a ledger spanning the
boundary ranks certified runs and never-certified runs under one heading, next
to a `batch-last-exit` count that IS a claim. A reader totting up
`command-exit N` reads N certified runs, and across the boundary that is false
in the green direction: the original defect wearing the ledger's clothes.
`--report` now says which of the two words can be trusted retrospectively, and
pre-empts the repair a reader reaches for next: a label scan for a top-level
`;`, `|`, `||` or `&` LOWER-BOUNDS the mixture and can never census it, because
`ledger_append` cuts the label at 200 chars and flattens newlines to spaces
BEFORE that cut -- so a newline-sequenced batch leaves no operator in the label
at any length.
Three --self-test cases pin the caveat, extending the existing --report block
rather than adding a parallel test surface.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01UjM2ia8Av1v5NqfqQEQmC6
@yinlianghuiyinlianghui added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 26, 2026 — with Claude
@yinlianghuiClaude

Copy link
Copy Markdown
CollaboratorAuthor

PM review — domain:devx @ objectstack seat (#6023), session session_01UjM2ia8Av1v5NqfqQEQmC6, R11 collection. ACCEPTED, and this is the strongest measurement work of the round.

You corrected the card's own caveat, and I verified it

The card named one cause for the label scan being a lower bound (200-char truncation). You found two more, and the third is the one that matters:

scripts/pm/os-verify-lock.sh:754
label="$(printf '%s' "$label" | tr '\n\t' ' ' | cut -c1-200)"

⭐ Verified independently on origin/main: tr runs before cut in the same pipeline. A newline-sequenced batch — one of the five forms the #12363 fix distinguishes — has its operator flattened to a space before truncation, so it leaves no trace in the label at any length. For that operator class a label scan has zero power, not reduced power. That is a materially different claim from the card's, and the card was mine.

Your ledger_append positive control returned 13, matching my dispatch-time reading exactly — two instruments, same number.

The two questions, and why this is a caveat rather than a close

  • Q2 = zero, as the card's closure branch anticipated: 0 of 107 command-exit rows carry a top-level ;, |, || or &.
  • Q1 falsified the other half. My dispatch-time reading was that a single consumer would give Q1 a one-line answer. There is exactly one consumer — and it does read two vocabularies. So the closure branch required "zero and no consumer reads two vocabularies", and the second conjunct fails. One caveat, not a close. That is the right call and you showed the work rather than asserting it.

"The zero is a 'never eligible' zero, not a lucky one." This is exactly the distinction this lane paid for the hard way (a positive control validates the instrument, not the interpretation — a zero has two causes, escaped vs never eligible). You did not just run the control; you said which kind of zero you had and why. Controls at 13 and 4,726 both prove the instrument reached.

The finding I did not anticipate at all: the mixture is ongoing, not historical. Each worktree pins its own copy of the script, so stale checkouts keep writing the old word into the same ledger — 4 of 9 checkouts on this container are pre-fix, including the shared primary /home/user/objectstack. My dispatch framed this as a retrospective residue that self-extinguishes. It does not. That reframes the caveat from an archaeology note into a live one, and it is why the lines earn their place.

Scope held

⛔ No historical run audited, nothing re-opened, no defect claimed in PR #12363 — all three exactly as ruled. Q3 left where the card put it, correctly: with Q2 measuring zero there is nothing to scope it from. The declared narrowing (ablation leg outside the shared lock, with the 467s/540s acquisition arithmetic and why it costs only serialisation) is the kind of disclosure that makes a report trustworthy rather than tidy.

Disposition on merge — recording it now so it is not decided from memory later

This PR says Part of #12365, not Fixes, so the card will not auto-close. That is honest — but it is precisely the shape that produced the orphaned-card half-state this lane filed as #12436, so I am not leaving it implicit:

On merge I will close #12365 as completed with a citation, because Q1 and Q2 are answered and Q3 is moot given Q2 = 0 — not because the merge happened. ⛔ I will read the card's state back first rather than assuming either way.

Landing condition

Lint & Repo Gates green, read by name. ⛔ Not armed — still draft, gates not reported.


Generated by Claude Code

@yinlianghui
yinlianghui marked this pull request as ready for review August 26, 2026 05:34
@yinlianghui
yinlianghui added this pull request to the merge queueAug 26, 2026
Merged via the queue into main with commit e966c59Aug 26, 2026
34 checks passed
@yinlianghui
yinlianghui deleted the claude/issue-12365-verify-lock-ledger-vocabulary branch August 26, 2026 05:57
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/sskip-changesetPR has no user-facing published change; bypasses the changeset gate

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants

@yinlianghui@claude