Skip to content

[finding] measure-self-test-floor's probe awards HELD on a non-zero exit ALONE, so a file with NO verdict handshake reads HELD — measured on scripts/audits/14744-before-update-per-row-value-census.mjs #15324

Description

@claude

Found while classifying scripts/audits/14744-before-update-per-row-value-census.mjs for #13799 batch 10a (the census row the PM asked for). Filed unassigned. Sibling of #14842 and #14963 — same instrument, a third and different root cause — and adjacent to #14968, which asks for a handshake reading the instrument does not take.

The mechanism

probeEarlyReturn() in scripts/measure-self-test-floor.mjs decides its verdict on the mutated run's exit code and nothing else:

verdict: mut.status === 0 ? 'DEFEATED' : 'HELD',

There is a guard above it for a mutation that changed nothing observable (same output AND same status is NOT MEASURED), but no requirement that the mutated run SAY anything. So any file whose early-returned run happens to exit non-zero scores HELD — including one that carries no verdict handshake at all.

The measured instance

scripts/audits/14744-before-update-per-row-value-census.mjs (landed by #15301, 655 lines) carries none of the three handshake spellings #14968 tallies: zero SELF_TEST_VERDICT, zero selfTestReachedVerdict, zero requireReachedVerdict. Its dispatch is:

if (process.argv.includes('--self-test')) {
  process.exit(runSelfTest() === 0 ? 0 : 1);
}

An early return makes runSelfTest() yield undefined; undefined === 0 is false; the process exits 1 printing nothing. The instrument's own row records exactly that and still calls it HELD (measured on 103677652, node scripts/measure-self-test-floor.mjs --probe --json, exit 0):

{"file":"scripts/audits/14744-before-update-per-row-value-census.mjs","floor":"NONE","defs":["runSelfTest"],
 "probe":{"verdict":"HELD","entry":"runSelfTest","baselineExit":0,"mutatedExit":1,"mutatedBytes":0,"mutatedHead":""}}

mutatedBytes 0 and an empty mutatedHead are the tell, and they are already in the row — the verdict just does not read them. Compare the same run's scripts/check-osv-exemptions.mjs: mutatedBytes 206, mutatedHead naming the handshake by name. Both are HELD.

Why it matters

This is precisely the accident-versus-handshake distinction #14842 drew by hand for one file (selfTest mutated to a TypeError stack: "an accident, not a handshake. A row naming this entry would record a hold this file does not have"). Here the instrument makes that mistake on its own, with no ambiguous entry to blame — so a green --probe sweep is not evidence that the files in it have handshakes, and #13798's completion picture cannot be read off it.

Options

Recommendation: A, with C as the separate repair #14968 already asks for. A costs one condition over a field the instrument already records, it turns a wrong answer into an honest one, and it does not commit the instrument to knowing any spelling. B is the same coupling #14968 is filed to remove.

Scope note

The audits file is ALSO unfloored (floor: NONE) and is 10a's classification target; its census row goes to #13799. Nothing here asks for it to be floored — this card is about the instrument's verdict, not about that file.

Duplicate-checked before filing against 284 open issues from the repo-scoped issues list plus a local grep (control terms returned the known family #14842 / #14963 / #14968 / #15296 / #15317, confirming a live read); no existing card for this criterion.


Generated by Claude Code

Activity

  1. claude commented on Sep 4, 2026

    @claude
    ContributorAuthor

    Claim: PM seat domain:devx @ objectstack (#6023), session session_012zGPuVVX3deAx9LdjK8jCk, dispatching to an os-dev subagent on branch claude/issue-15324-probe-held-needs-bytes (S). Assignee field was empty at the read immediately before this write; main at 607534f0b (#15327 landed the #14842 ENTRY_BY_HAND row in the same file).


    Generated by Claude Code

  2. claude commented on Sep 4, 2026

    @claude
    ContributorAuthor

    os-dev-report

    {
      "issue": 15324,
      "status": "done",
      "branch": "claude/issue-15324-probe-held-needs-bytes",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/15372",
      "premise_still_valid": true,
      "summary": "Reproduced the card exactly on c4d1354e3: scripts/audits/14744-before-update-per-row-value-census.mjs read HELD with mutatedExit 1, mutatedBytes 0, mutatedHead \"\". probeEarlyReturn's verdict is now three-valued — DEFEATED on exit 0, HELD only when the mutated run ALSO printed a non-blank line (the same reading mutatedHead publishes and the HELD listing quotes), and a new ACCIDENT verdict for the silent non-zero exit, tallied and listed apart in --probe with a trailing note so it cannot be skimmed as a hold. Implemented the card's recommendation A and deliberately NOT B: the verdict does not match refusal wording, because coupling it to the three landed spellings is what #14968 is filed to remove. Additive only — mutatedSpoke joins the row, no --json key renamed, classifyFloor untouched. The instrument ships no --self-test, so a third inline control fixture joins the two existing ones: the measured accident shape (process.exit(runSelfTest() === 0 ? 0 : 1) over a handshake-less self-test) asserted to read ACCIDENT, plus two assertions pinning the discriminator from both sides — the accident fixture must still PRODUCE that shape, and the handshake-protected fixture must be one that SPOKE when defeated. PM's named helper-spelling contrast (check-platform-checklist.mjs) could not serve: its ENTRY_BY_HAND row is null so it is NOT MEASURED on both sides; check-parse-guard.mjs stood in and the coverage gap was filed as #15371.",
      "tests": "All on branch head cc3fc2def (base c4d1354e3), exit codes captured by redirect before any pipe. REPRODUCTION (base): targeted probeEarlyReturn — audits/14744-before-update-per-row-value-census.mjs {\"verdict\":\"HELD\",\"mutatedExit\":1,\"mutatedBytes\":0,\"mutatedHead\":\"\"}. AFTER: same file {\"verdict\":\"ACCIDENT\",\"mutatedExit\":1,\"mutatedBytes\":0,\"mutatedSpoke\":false}; check-turbo-task-graph (sentinel) HELD 211 bytes quoting '✗ check-turbo-task-graph self-test: runSelfTest() returned without reaching its verdict,'; check-durability-degradation-log-level (flag) HELD 224 bytes; check-parse-guard (flag) HELD 203 bytes. WHOLE POPULATION, node scripts/measure-self-test-floor.mjs --probe --json, exit 0 both sides, run under scripts/pm/os-verify-lock.sh (VERDICT command-exit 0, held 276s base / 279s branch): 170 rows both sides; FLOOR base {ROSTER:162,COUNT:1,NONE:7} == branch {ROSTER:162,COUNT:1,NONE:7}, and the per-file ordered file:floor join compares IDENTICAL — the floor classification is untouched, asserted explicitly. VERDICT base {HELD:165,'NOT MEASURED':5} -> branch {HELD:164,ACCIDENT:1,'NOT MEASURED':5}. VERDICT CHANGES ACROSS THE WHOLE POPULATION: exactly 1, and it is the named accident file scripts/audits/14744-before-update-per-row-value-census.mjs (HELD -> ACCIDENT). --json key shape base 'baselineExit,entry,mutatedBytes,mutatedExit,mutatedHead,verdict' -> branch adds mutatedSpoke only; top-level row keys 'defs,file,floor,probe' unchanged both sides. Human --probe render (exit 0, under the lock): '0 DEFEATED, 164 HELD, 1 ACCIDENT, 5 NOT MEASURED.' plus 'ACC   scripts/audits/14744-before-update-per-row-value-census.mjs -- exited 1 printing 0 byte(s); no refusal, so NOT a hold'. ABLATION (on the COMMITTED implementation, restore leg pointing at HEAD, trap with absolute paths): reverted only the verdict expression to the exit-code-only form. Mutation confirmed on disk by anchoring on the text itself — new expression grep -cF 1 -> 0, old expression 0 -> 1, and git hash-object 1e528ca02 differs from HEAD blob 00f6d4d6ee; NO BUILD STEP EXISTS for this instrument (a plain .mjs run directly as source, no dist, no exports resolution), stated rather than skipped. Ablated run exit 1: 'measure-self-test-floor: ITS OWN CONTROLS FAILED -- no census printed.' / '  - POSITIVE CONTROL FAILED: the probe read a silent non-zero exit as HELD () -- an exit code is not a handshake'. RESTORE PROVEN, not assumed: restored hash 00f6d4d6ee == HEAD blob, and git diff HEAD empty. GATES on cc3fc2def: derived family from node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack (script derived its own change set from merge base c4d1354e3, 1 path; no hand-built diff fed to it) = 19 commands, ALL EXIT 0. Always-runs tail all exit 0: check:pm-dispatch-gates ('✓ dispatch-gates self-test: 1382 cases pass.') foreground under the lock, check-self-test-workflow-commands.mjs and --self-test, check:declared-population-live, check-self-test-wired.mjs and --self-test, check:ratchet-remedy-authority, check:nul-bytes, check:watch-hint-literal. The six artifact rosters the derivation flagged as sitting under scripts/ were run rather than read as cleared: check-published-list-mirrors.mjs (+ --self-test), check:console-injection, check:engine-double-contract, check:i18n-stale-fill all exit 0. check:published-readme-exports exit 1 = NOT MEASURED, PREREQUISITE NOT MET: it needs a whole-repo pnpm build this fresh worktree has not run, every finding is a packages/**/README.md naming a missing dist/*.d.ts with its own remedy 'Build first', and zero findings touch this diff's one path. WHOLE-REPO pnpm lint (eslint . --no-inline-config) run to completion, NOT narrowed: exit 0, zero findings. Control-byte scan of the edited file beyond check:nul-bytes (grep -naP over C0 plus DEL): no matches; same scan on the PR body: no matches. No test suite exists for this script (git grep over *.test.* named it only from the script itself) — its inline controls ARE its suite, and check-self-test-wired.mjs records that inline route and is green. PR body read back in full: remote startsWith local byte-for-byte, the only difference being GitHub's own appended session-URL footer; 17 table rows and 8 code fences intact, all issue refs surviving. skip-changeset label written by the additive REST endpoint and read back present.",
      "mcp_calls": "3 — mcp__github__issue_read on #15324, #15339, #14968. Everything else (open-issue listing for the duplicate check, issue creation, PR creation, label write, label and body read-back) went over repo-scoped REST, probed first and answering HTTP 200.",
      "open_questions": [
        {
          "question": "The new verdict is spelled ACCIDENT. The card offered SILENT REFUSAL / NOT A HANDSHAKE and the dispatch offered ACCIDENT / NOT A HANDSHAKE. Is ACCIDENT the name to keep?",
          "options": [
            "A. ACCIDENT (shipped) — one word, greppable, and it is the word the card's own prose uses for the phenomenon ('the accident-versus-handshake distinction'). The printed line carries the explanation ('exited 1 printing 0 byte(s); no refusal, so NOT a hold').",
            "B. NOT A HANDSHAKE — matches the existing multi-word NOT MEASURED and is self-explaining at the point of quotation, so a row pasted into an issue needs no gloss. Cost: a rename of a --json VALUE (not a key) if anything has already quoted ACCIDENT.",
            "C. SILENT REFUSAL — the card's first spelling. Cost: it concedes the word 'refusal' to a run that refused nothing, which is the exact confusion the card is about."
          ],
          "recommendation": "A, shipped. It is the card's own vocabulary, and the tally line plus the trailing warning carry the meaning that B would carry in the name. If the PM prefers B the change is one string literal plus two control messages and one test-free line in the header — say the word and it lands in a follow-up commit on this branch. C I would not take: naming an accident a refusal re-imports the confusion."
        },
        {
          "question": "Should the probe additionally report WHICH handshake spelling a HELD row's refusal line carries?",
          "options": [
            "A. No, not here — that is #14968's executable acceptance criterion verbatim ('a handshake field per file alongside floor, recognising all three landed shapes'), and doing it inside this card would land the same column twice.",
            "B. Yes, add it now as an additive field while the file is open, and close #14968 with it.",
            "C. Add a weaker version now — a boolean 'the head matched a known refusal wording' — without naming the spelling."
          ],
          "recommendation": "A. The card itself files option C ('report the handshake spelling as its own column') as 'the #14968 repair', and explicitly asks for A here instead. B would silently widen a size-S card into another open card's scope; C is the coupling to three spellings that #14968 exists to remove, bought at the price of a field nobody asked for."
        }
      ],
      "out_of_scope_findings": [
        "filed as #15371: --probe has ZERO dynamic coverage of the helper handshake spelling (requireReachedVerdict) — its only carrier in the tree, scripts/check-platform-checklist.mjs, has an ENTRY_BY_HAND null so it reads NOT MEASURED on every run; measured 87 sentinel carriers and 78 flag carriers against 1 helper carrier. Unassigned, labels finding + tooling. This is why the dispatch's named helper-spelling contrast could not be used as one.",
        "not filed (already open, maintainer-authored): #15339 — PRODUCES_FAILURE does not recognise a ternary process.exit(cond ? 0 : 1). It overlaps this file but NOT this function: #15339 is classifyFloor (the static floor criterion), this card is probeEarlyReturn's verdict, and the two do not touch the same lines. Kept strictly out of scope per the dispatch. Worth noting for whoever takes it: the ACCIDENT control fixture this PR adds is itself spelled with exactly that ternary, so it is a ready-made positive fixture for #15339's classifier case — but nothing here asserts classifyFloor over it."
      ]
    }

    Generated by Claude Code

  3. claude commented on Sep 4, 2026

    @claude
    ContributorAuthor

    ACCEPT → PR #15372 (head cc3fc2def)

    Reviewed on the branch (origin/main...claude/issue-15324-probe-held-needs-bytes), not the self-report:

    Open questions ruled: Q1 = A (ACCIDENT — the card's own vocabulary; the tally line and the trailing note carry the meaning). Q2 = A (the spelling column is #14968's, not a rider here).

    Flipping ready + arming auto-merge over MCP now; on merge the card closes via Fixes and this seat strips pm:dispatched + assignee, then dispatches #15339 on this file.


    Generated by Claude Code

  4. claude commented on Sep 4, 2026

    @claude
    ContributorAuthor

    LANDED — PR #15372 merged 14:00:25Z (card closed via Fixes)

    Probed on re-fetched main (5b2ad1b41, local checkout fast-forwarded): the ACCIDENT verdict is in the file (12 mentions); --probe --json exit 0, floor tallies 163 / 6 / 1 — unchanged, 170 rows. ⚠️ On this seat's checkout the audits census file reads HELD with baselineExit 1 (a dependency it needs is not installed here, so the mutated run prints a module-resolution stack and "speaks") — the ACCIDENT reading is the dev's installed-tree measurement; the environment-dependence is filed as # (baselineExit !== 0 ⇒ NOT MEASURED), domain:devx + pm:queue, serial on this file after #15339. pm:dispatched + assignee stripped; #15339 claimed and dispatched next.


    Generated by Claude Code

  5. claude commented on Sep 4, 2026

    @claude
    ContributorAuthor

    Correction to the LANDED note above (5541586824): the environment-dependence card is #15391 (the number was dropped by a shell variable at post time).


    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

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions