Repository navigation
[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
Activity
Claim: PM seat
domain:devx @ objectstack(#6023), sessionsession_012zGPuVVX3deAx9LdjK8jCk, dispatching to anos-devsubagent on branchclaude/issue-15324-probe-held-needs-bytes(S). Assignee field was empty at the read immediately before this write; main at607534f0b(#15327 landed the #14842 ENTRY_BY_HAND row in the same file).
Generated by Claude Code
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
ACCEPT → PR #15372 (head
cc3fc2def)Reviewed on the branch (
origin/main...claude/issue-15324-probe-held-needs-bytes), not the self-report:- One file, +78/−7.
probeEarlyReturn's verdict is three-valued:DEFEATEDon exit 0;HELDonly when the mutated run also printed a non-blank line (mutatedSpoke, an additive row field);ACCIDENTfor a silent non-zero exit — tallied and listed apart in--probewith a trailing note. The card's option A as ruled; B (matching refusal wording) deliberately not taken — that coupling is what No canonical way to ask whether a self-test has a verdict handshake — the repair landed in three spellings and measure-self-test-floor reports none of them #14968 exists to remove.classifyFlooruntouched; no--jsonkey renamed. A third inline control fixture (the measured accident shape,process.exit(runSelfTest() === 0 ? 0 : 1)over a handshake-less body) assertsACCIDENTon every invocation, with two assertions pinning the discriminator from both sides. - Run by this seat on a detached review worktree:
--probe --jsonexit 0; a plain--jsonrun exit 0 (the inline controls, incl. the new one, pass); floor tallies 162 / 7 / 1 on the branch base — identical to the dev's base reading, so the floor classification is untouched.⚠️ My worktree is UNINSTALLED, so the audits file's mutated run dies on module resolution and PRINTS a stack (mutatedBytes 950,mutatedSpoke true) → it readsHELDthere and 34 rows readNOT MEASURED; the dev's installed-tree reading (ACCIDENT,mutatedBytes 0; whole-population verdict change = exactly that one file;HELD 165 → 164 + ACCIDENT 1) is the measurement of record and the ablation (verdict reverted to exit-code-only → the new positive control redsan exit code is not a handshake, restore proven by hash) stands. - Observation for the follow-ups, not a blocker: a crash stack "speaks" too, and a row whose
baselineExitis non-zero still gets a HELD/ACCIDENT verdict — an uninstalled tree can read HELD by accident.baselineExit !== 0 ⇒ NOT MEASUREDand "spoke with a recognised refusal" are the next refinements; the second is No canonical way to ask whether a self-test has a verdict handshake — the repair landed in three spellings and measure-self-test-floor reports none of them #14968's column, the helper-spelling coverage gap is [finding] measure-self-test-floor's --probe has zero coverage of the helper handshake spelling — its only carrier is an ENTRY_BY_HAND null #15371 (gradeddomain:devx+tooling+pm:queue), the ternary recogniser is measure-self-test-floor: PRODUCES_FAILURE does not recognise a ternaryprocess.exit(cond ? 0 : 1), so a sound roster floor classifies NONE #15339 (next on this file). - CI on
cc3fc2def: 18 success / 11 skipped / 3 in progress, 0 red;skip-changeset+size/s;Fixes #15324in the PR body.
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
Fixesand this seat stripspm:dispatched+ assignee, then dispatches #15339 on this file.
Generated by Claude Code
- One file, +78/−7.
LANDED — PR #15372 merged 14:00:25Z (card closed via
Fixes)Probed on re-fetched
main(5b2ad1b41, local checkout fast-forwarded): theACCIDENTverdict is in the file (12 mentions);--probe --jsonexit 0, floor tallies 163 / 6 / 1 — unchanged, 170 rows.⚠️ On this seat's checkout the audits census file readsHELDwithbaselineExit 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
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
Found while classifying
scripts/audits/14744-before-update-per-row-value-census.mjsfor #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()inscripts/measure-self-test-floor.mjsdecides its verdict on the mutated run's exit code and nothing else: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: zeroSELF_TEST_VERDICT, zeroselfTestReachedVerdict, zerorequireReachedVerdict. Its dispatch is:An early return makes
runSelfTest()yieldundefined;undefined === 0is false; the process exits 1 printing nothing. The instrument's own row records exactly that and still calls it HELD (measured on103677652,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":""}}mutatedBytes0 and an emptymutatedHeadare the tell, and they are already in the row — the verdict just does not read them. Compare the same run'sscripts/check-osv-exemptions.mjs:mutatedBytes206,mutatedHeadnaming 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 (
selfTestmutated 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--probesweep is not evidence that the files in it have handshakes, and #13798's completion picture cannot be read off it.Options
HELDonly whenmutatedExit !== 0ANDmutatedBytes > 0; the exit-1-silent case becomes a third verdict (SILENT REFUSAL/NOT A HANDSHAKE). Cheap, mechanical, and reads a field the row already carries. Cost: files that legitimately refuse silently — if any exist — move out of HELD and need reading.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