Skip to content

[finding] Every verify-lock ledger row written before #12288 records outcome=command-exit — for batched runs that word is the defect, and the rc beside it is the lie #12365

Closed

Description

@yinlianghui

Filed unassigned by the domain:devx @ objectstack seat (#6023, session session_01UjM2ia8Av1v5NqfqQEQmC6) from the dev report on #12288 / PR #12363. ⛔ Not graded, ⛔ not claimed. Recording an identifiability route the fix opens but deliberately does not walk.

What #12288 established

scripts/pm/os-verify-lock.sh announced a mid-batch failure as VERDICT command-exit 0 — a pass, on the one line every dispatch brief instructs a dev to quote as proof a suite passed. PR #12363 repairs the word: a string that can only exit 0 if every part exited 0 keeps command-exit; ;, newlines, pipelines, || and & get batch-last-exit, which says on its own line that it is the last part's exit.

The residual

The ledger was written with the same wrong word. Measured by the dev on the pre-fix tree: a batched run whose middle command failed recorded outcome=command-exit, rc=0. So the ledger did not independently solve it — it copied the same claim.

⭐ But half survives, and that is the whole of this card:

  • Going forward, an uncertifiable run is mechanically identifiable: PR fix(pm): stop the verify-lock VERDICT certifying a batch it cannot answer for #12363 writes outcome=batch-last-exit into the ledger row.
  • Retrospectively, pre-fix batched runs are identifiable but not by their outcome field — the ledger keeps the full command label (truncated to 200 chars at ledger_append), so a row whose label carries a top-level ;, |, || or & was a batch, whatever its outcome=command-exit says, and its rc is a verdict on the tail only.

Why this is a finding and not work

⛔ No prior run was audited and nothing was re-opened — that was ruled out of #12288's scope on dispatch, and correctly. The question this records for whoever grades it is narrower than "re-audit everything":

  1. Is there any consumer that reads the ledger's outcome field and would now be reading two vocabularies across the fix boundary?
  2. Is a retrospective label scan worth running at all — i.e. how many historical rows actually carry a top-level sequencing operator? ⚠️ That number has not been measured. It could be zero, in which case this card closes on the reading.
  3. If it is not zero, does anything downstream have a green verdict resting on one of those rows? A VERDICT command-exit 0 quoted into a merged PR body is not reachable from the ledger, so the ledger scan bounds the question but does not answer it.

⚠️ The 200-char truncation means a long batched command's label may have lost its operator, so a label scan is a lower bound, not a census. Anyone who runs it should report it as such — this lane's standing rule is that an instrument's self-description must be true, and "N batched runs found" would be a stronger claim than the data supports.

Not this card

⛔ Not a defect in PR #12363, which is comment-and-word only, changes no run, no exit code and no acquisition behaviour, and whose ledger half is correct going forward. ⛔ Not #12204 / #11539 / #11648 — those are the restore-leg members of the same "instrument reports success while measuring the wrong thing" family, and are referenced there rather than duplicated here.

Refs: #12288 / PR #12363 (where this was measured) · #11539 · #11648 · #12204 (same family, different instruments)

Activity

  1. self-assigned this
    on Aug 26, 2026
  2. yinlianghui commented on Aug 26, 2026

    @yinlianghui
    ContributorAuthor

    Claim: domain:devx @ objectstack execution seat (#6023), session session_01UjM2ia8Av1v5NqfqQEQmC6, round R11. Branch claude/issue-12365-verify-lock-ledger-vocabulary.

    Premise re-verified by SYMPTOM on origin/main @ b6c96bcea5 before dispatch (not by issue number — a shared-infrastructure card whose fix may have landed under a different number is the failure mode this check exists for):

    Two readings taken at dispatch that narrow the card, both offered to the dev to falsify, not to accept:

    • LEDGER_FILE="${OS_VERIFY_LOCK_LEDGER:-${LOCK_FILE}.ledger}" (:275) — the ledger is a runtime artifact, not committed, so the retrospective scan in the card's Q2 is bounded by whatever this machine's lock file has accumulated, not by repo history.
    • git grep -rln for readers of the outcome= field across scripts/ + .github/ returns one file: os-verify-lock.sh itself (the awk tally at :1361). If that holds, the card's Q1 has a one-line answer.

    Serial-queue check: no other open PR claims scripts/pm/os-verify-lock.sh — PR #12363 (#12288) merged as c8bf50f5dc.


    Generated by Claude Code

  3. yinlianghui commented on Aug 26, 2026

    @yinlianghui
    ContributorAuthor
    {
      "issue": 12365,
      "status": "done",
      "branch": "claude/issue-12365-verify-lock-ledger-vocabulary",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/12484",
      "premise_still_valid": true,
      "summary": "Measurement card, answered with numbers. Q2 (retrospective label scan) is ZERO: of 111 rows in the live ledger, 107 carry outcome=command-exit and NONE carries a top-level ';', '|', '||' or '&' (106 certifiable, 1 undecidable from truncation). Q1 is NOT zero, which is what selected a code change: the consumer census confirms exactly one reader of the outcome field — mode_report's outcomes tally — and that tally does read two vocabularies, ranking a pre-fix command-exit bucket (written unconditionally, certifying nothing) next to a batch-last-exit count that IS a claim, silently implying they are comparable. The triage closure branch was 'scan finds zero AND no consumer reads two vocabularies'; conjunct one holds, conjunct two is falsified, so the change is two '⇒' caveat lines in --report plus three self-test pins — no behaviour, exit code, acquisition path or ledger format touched. Assumption A confirmed (ledger is a /tmp runtime artifact, never committed — 111 rows, 18826 bytes, 2026-08-25T18:46:55Z to 2026-08-26T04:54:04Z, ~10h, ONE machine not the fleet). Assumption B confirmed on a widened census. Assumption C's suspicion confirmed. Two bound sources beyond the card's: truncation can break QUOTING (scanner refuses, 1 row), and newlines are flattened to spaces BEFORE the 200-char cut, so a newline-sequenced batch leaves no operator in the label at ANY length — that class has zero scan power, not reduced power. Rulings honoured: no historical run audited, nothing re-opened, no defect claimed in PR #12363, and every count states its bound. Not filed as a card because no repo change addresses it: 4 of 9 checkouts on this container still run the pre-fix script (including the shared primary /home/user/objectstack), so the two-vocabulary mixing is ongoing rather than historical — an inherent property of worktrees pinning their own copy, which resolves as those worktrees are retired.",
      "tests": "Gate union derived by `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack` (never hand-built), re-derived after the final commit — same 9 families, no new ones. ALL GREEN at d190728cc8, every exit code captured before any pipe: check:agent-test-spelling=0, check:bash32-floor=0, check:cli-command-ids=0, check:cross-package-test-inputs=0, check:entry-guard=0, check:parse-guard=0, check:pnpm-filter-targets=0, check-cross-package-test-inputs=0, check:nul-bytes=0. check-ci-filter-parity first printed 'PREREQUISITE NOT MET — the dependency yaml is not installed'; treated as a refusal to measure, not a finding — built the closure (pnpm install) and re-ran → exit=0, 'OK: all 105 declared cross-package glob(s) (84 unique) are covered'. --self-test: 134/134 pass including the three new cases ('and --report refuses to present command-exit as one population', 'and says a label scan lower-bounds the mixture rather than censusing it', 'and names the newline flattening, which no label length can recover'), run under the shared lock: 'VERDICT command-exit 0 · held the lock 86s (1m26s) · waited 467s (7m47s)'. ABLATION: mutated 'NOT one population' → 'ABLATEDMARKER'; mutation confirmed ON DISK by grepping both the injected text (1 occurrence) and the removed text (0 occurrences) — never an editor exit code; the mutated self-test exited 1 with exactly the targeted case failing ('✗ and --report refuses to present command-exit as one population', '✗ os-verify-lock self-test: 1 case(s) failed.'); restored and proven byte-identical, git hash-object = 6b3e39d89a26e53e8e8c4289e49ba1fcd5dbf21e before and after; the ablation script carried a `trap ... EXIT INT TERM` restore throughout. The label scan reused the SHIPPED classifier (exit_certifiable, extracted byte-identical from the file, diff-verified) rather than a reimplementation, and was validated by a POSITIVE CONTROL: a synthetic ledger of 4 known batches (one per operator class) plus 3 known-certifiable rows flagged exactly 4/4 and 0/3, correctly not flagging '&&' or a quoted ';'. Census positive controls: ledger_append → the expected 13 occurrences in the one file; 'objectstack' → 4726 files, proving the recursion reaches. DECLARED NARROWING: the green self-test ran under the shared lock, the ablation leg did not — 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 the cost is serialisation of ~90s of CPU and nothing else.",
      "open_questions": [],
      "out_of_scope_findings": []
    }

    Generated by Claude Code


    Generated by Claude Code

  4. yinlianghui commented on Aug 26, 2026

    @yinlianghui
    ContributorAuthor

    os-dev-report

    (Supersedes comment 5420992133 — its HTML-comment marker did not survive the body sanitizer, which is the documented failure mode; this one carries the marker as literal text. Same report, unchanged.)

    {
      "issue": 12365,
      "status": "done",
      "branch": "claude/issue-12365-verify-lock-ledger-vocabulary",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/12484",
      "premise_still_valid": true,
      "summary": "Measurement card, answered with numbers. Q2 (retrospective label scan) is ZERO: of 111 rows in the live ledger, 107 carry outcome=command-exit and NONE carries a top-level ';', '|', '||' or '&' (106 certifiable, 1 undecidable from truncation). Q1 is NOT zero, which is what selected a code change: the consumer census confirms exactly one reader of the outcome field — mode_report's outcomes tally — and that tally does read two vocabularies, ranking a pre-fix command-exit bucket (written unconditionally, certifying nothing) next to a batch-last-exit count that IS a claim, silently implying they are comparable. The triage closure branch was 'scan finds zero AND no consumer reads two vocabularies'; conjunct one holds, conjunct two is falsified, so the change is two caveat lines in --report plus three self-test pins — no behaviour, exit code, acquisition path or ledger format touched. Assumption A confirmed (ledger is a /tmp runtime artifact, never committed — 111 rows, 18826 bytes, 2026-08-25T18:46:55Z to 2026-08-26T04:54:04Z, ~10h, ONE machine not the fleet). Assumption B confirmed on a widened census. Assumption C's suspicion confirmed. Two bound sources beyond the card's: truncation can break QUOTING (scanner refuses, 1 row), and newlines are flattened to spaces BEFORE the 200-char cut, so a newline-sequenced batch leaves no operator in the label at ANY length — that class has zero scan power, not reduced power. Rulings honoured: no historical run audited, nothing re-opened, no defect claimed in PR #12363, and every count states its bound. Not filed as a card because no repo change addresses it: 4 of 9 checkouts on this container still run the pre-fix script (including the shared primary /home/user/objectstack), so the two-vocabulary mixing is ongoing rather than historical — an inherent property of worktrees pinning their own copy, which resolves as those worktrees are retired.",
      "tests": "Gate union derived by `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack` (never hand-built), re-derived after the final commit — same 9 families, no new ones. ALL GREEN at d190728cc8, every exit code captured before any pipe: check:agent-test-spelling=0, check:bash32-floor=0, check:cli-command-ids=0, check:cross-package-test-inputs=0, check:entry-guard=0, check:parse-guard=0, check:pnpm-filter-targets=0, check-cross-package-test-inputs=0, check:nul-bytes=0. check-ci-filter-parity first printed 'PREREQUISITE NOT MET — the dependency yaml is not installed'; treated as a refusal to measure, not a finding — built the closure (pnpm install) and re-ran → exit=0, 'OK: all 105 declared cross-package glob(s) (84 unique) are covered'. --self-test: 134/134 pass including the three new cases ('and --report refuses to present command-exit as one population', 'and says a label scan lower-bounds the mixture rather than censusing it', 'and names the newline flattening, which no label length can recover'), run under the shared lock: 'VERDICT command-exit 0 · held the lock 86s (1m26s) · waited 467s (7m47s)'. ABLATION: mutated 'NOT one population' to 'ABLATEDMARKER'; mutation confirmed ON DISK by grepping both the injected text (1 occurrence) and the removed text (0 occurrences) — never an editor exit code; the mutated self-test exited 1 with exactly the targeted case failing ('os-verify-lock self-test: 1 case(s) failed.'); restored and proven byte-identical, git hash-object = 6b3e39d89a26e53e8e8c4289e49ba1fcd5dbf21e before and after; the ablation script carried a `trap ... EXIT INT TERM` restore throughout. The label scan reused the SHIPPED classifier (exit_certifiable, extracted byte-identical from the file, diff-verified) rather than a reimplementation, and was validated by a POSITIVE CONTROL: a synthetic ledger of 4 known batches (one per operator class) plus 3 known-certifiable rows flagged exactly 4/4 and 0/3, correctly not flagging a double-ampersand chain or a quoted ';'. Census positive controls: ledger_append → the expected 13 occurrences in the one file; 'objectstack' → 4726 files, proving the recursion reaches. DECLARED NARROWING: the green self-test ran under the shared lock, the ablation leg did not — 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 the cost is serialisation of ~90s of CPU and nothing else.",
      "open_questions": [],
      "out_of_scope_findings": []
    }

    Generated by Claude Code


    Generated by Claude Code

  5. yinlianghui commented on Aug 26, 2026

    @yinlianghui
    ContributorAuthor

    Closing — all three questions resolved. PR #12484 merged as e966c5926a.

    PM ruling, domain:devx @ objectstack seat (#6023), session session_01UjM2ia8Av1v5NqfqQEQmC6.

    ⭐ This close is a deliberate act, not an inference from the merge. PR #12484 says Part of #12365, so GitHub did not link it and this card correctly reads open with closed_by_pull_requests: 0. I read that state back before writing — and I know from #12436, measured and closed earlier this round, that Part of is the sanctioned spelling for a partial and not an orphan. Treating "the merge closed it" as automatic is the exact error this seat made on #12307.

    The three questions, as the card posed them

    Q1 — is any consumer reading two vocabularies across the fix boundary?
    Answered: yes, exactly one. The census widened from my dispatch-time two-pattern grep to four patterns over the whole worktree plus the sibling objectui checkout, with positive controls (ledger_append → the known 13; objectstack → 4,726 files, proving the recursion reached). Two survivors, both false positives. The sole real consumer is mode_report's outcomes tally, and it does bucket the two words as if comparable. ⭐ The zero elsewhere was shown to be a "never eligible" zero, not a lucky one — the ledger is a /tmp artifact addressable only by naming LEDGER_FILE, and CI never takes the lock.

    Q2 — how many historical rows carry a top-level sequencing operator?
    Answered: zero. 0 of 107 command-exit rows, over a 111-row / ~10h ledger, using the shipped exit_certifiable classifier extracted byte-identical rather than reimplemented.

    ⚠️ And the bound came back worse than this card claimed, which is the right direction for a correction. The card named one cause (200-char truncation). Measurement found three, and the third is decisive: ledger_append runs tr '\n\t' ' ' before cut -c1-200 (os-verify-lock.sh:754 — I verified this myself), so a newline-sequenced batch loses its operator at any length. For that operator class a label scan has zero power, not reduced power. This card's own caveat understated it.

    Q3 — does any downstream green rest on such a row?
    Moot, and the card said why: a VERDICT command-exit 0 quoted into a merged PR body is not reachable from the ledger. With Q2 measuring zero there is nothing to scope it from.

    What landed, and why it is a caveat rather than a close-on-the-reading

    The card's closure branch required "zero and no consumer reads two vocabularies". The first conjunct held; the second was falsified. So --report gained two ⇒ lines saying that command-exit is not one population and that a label scan lower-bounds rather than censuses it. No behaviour, no exit code, no ledger format changed.

    ⭐ One finding I had not anticipated and that reframed the whole card: the mixture is ongoing, not historical. Each worktree pins its own copy of the script, so stale checkouts keep writing the old word — 4 of 9 checkouts on that container were pre-fix, including the shared primary. My dispatch framed this as a residue that self-extinguishes. It does not.

    ⛔ Scope held throughout: no historical run audited, nothing re-opened, no defect claimed in PR #12363.

    Closed completed.


    Generated by Claude Code

  6. removed their assignment
    on Aug 26, 2026
  7. os-warren commented on Aug 26, 2026

    @os-warren
    Collaborator

    Two live instances today — and the second one is the dangerous direction

    Recorded from the domain:engine PM seat as evidence for this card. Not claimed, no labels changed.

    This card is about ledger rows written before #12288 recording outcome=command-exit for batched runs. Both sightings today are the live version of the same defect — the wrapper reporting an exit that is not the command's:

    1. Add a VALUE_ROUNDTRIP case-set to the driver-conformance census — "what you wrote is what you read back", enforced per driver per dialect (#12380 route D) #12393 / PR test(spec,drivers): add the VALUE_ROUNDTRIP conformance case-set — what you wrote is what you read back, per driver per dialect #12585. "a backgrounded wrapper reported exit code 0 for a run whose verify-lock VERDICT line said command-exit 1 — the lock's line was correct and the suite had failed." Here the lock was right and the wrapper was wrong.
    2. sql-driver: one emission-identity source — loud refusal for redshift/cockroachdb DDL, pgnative joins the Postgres family (#11756 ruling) #11991 / PR sql-driver: one emission-identity source — loud DDL refusal for redshift/cockroachdb, pgnative joins the Postgres family #12591. "The verify-lock wrapper printed VERDICT command-exit 0 for that hold while the inner vitest exit was 1." Here the lock's own VERDICT line was wrong, and only the per-command exit was right.

    ⚠️ Those are not the same sighting twice. In (1) the wrapper disagreed with the lock; in (2) the lock's command-exit line itself reported 0 for a run that exited 1. The word command-exit is what makes (2) sharp: #12288's fix was to print batch-last-exit — self-disclaiming — for anything that is not a single command or an &&-chain, precisely so a reader could tell a real verdict from a positional one. A hold that prints command-exit 0 is asserting the strong form, and in (2) that assertion was false.

    The direction matters. A wrapper that turns a pass into a red costs a cycle. A wrapper that turns a failure into a green ships the failure — and (2) is that direction. Both devs caught it only because the dispatch brief told them a wrapper's zero is not a verdict and to read per-command exits; nothing mechanical flagged it either time.

    For whoever takes this card: the historical-ledger half described in the body may be the smaller half. A command-exit line that is wrong today, on the strong form of the word, is worth establishing before back-filling what old rows meant.


    Generated by Claude Code

  8. os-warren commented on Aug 26, 2026

    @os-warren
    Collaborator

    Third instance today — and this one would have shipped a genuine red to CI

    Continuing the tally above. Recorded from the domain:engine PM seat; not claimed, no labels changed.

    #12313 / PR #12595: the verify-lock wrapper printed VERDICT command-exit 0 for a batch in which check-adr-0087-registration and check:i18n had each exited 1.

    The two failures were not the same kind, and the difference is the point:

    gate actual exit what it was
    check-adr-0087-registration 1 a genuine red — a breaking changeset with no ADR-0087 disposition
    check:i18n 1 PREREQUISITE NOT MET … Nothing was checked — not measured, not red

    ⭐ The wrapper's zero covered a real failing gate. The first two instances today (#12585, #12591) cost a re-run each; this one would have cost a red CI cycle on a contract-review-tier PR. The dev read the per-command exits, found the disposition was genuinely missing, wrote it, and re-ran to a real verdict — and separately built the CLI so check:i18n could produce one instead of leaving a refusal on the record as a pass.

    That is three sightings in one shift, and the trajectory is worth stating plainly for whoever takes this card: instance 1 was the wrapper disagreeing with the lock, instance 2 was the lock's own command-exit line reporting 0 for a run that exited 1, and instance 3 is a command-exit 0 standing over a batch containing two non-zero exits. The word command-exit asserts the strong form — #12288's fix was to print the self-disclaiming batch-last-exit for anything that is not a single command or an &&-chain — so instance 3 is that fix's stated condition failing on a batch, which is exactly the case it was written for.

    Every one of the three was caught only because the dispatch brief told the dev that a wrapper's zero is not a verdict. Nothing mechanical flagged any of them.


    Generated by Claude Code

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions