Skip to content

fix(devx): measure-self-test-floor honours a per-row probe budget and reads definitions from masked source (#15573, #15574) - #15758

Merged
baozhoutao merged 1 commit into
mainfrom
claude/issue-15573-15574-self-test-floor-budget-and-masked-defs
Sep 5, 2026
Merged

fix(devx): measure-self-test-floor honours a per-row probe budget and reads definitions from masked source (#15573, #15574)#15758
baozhoutao merged 1 commit into
mainfrom
claude/issue-15573-15574-self-test-floor-budget-and-masked-defs

Conversation

@claude

@claude claude Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor

Fixes #15573
Fixes #15574

Two defects of one instrument, scripts/measure-self-test-floor.mjs, folded into one PR because they edit the same file. Both are the same shape: a limit of the INSTRUMENT recorded as a property of a FILE.

#15573 — a per-row budget, not a bigger default

probeEarlyReturn spawned both runs with a 120 s budget, which is ~3.3x under scripts/pm/dispatch-gates.mjs's own --self-test. The baseline is killed, the mutated run is never spawned, and the row reads NOT MEASURED — killed by SIGTERM.

The default stays 120 s — 180 of the 181 rows fit it, and raising it for all of them makes every sweep slower to serve one member. Instead ENTRY_BY_HAND gains a second row spelling:

'scripts/x.mjs': 'selfTest'                          // the entry NAME alone
'scripts/x.mjs': { entry: 'selfTest', timeoutMs }    // the name, plus what THIS member needs

main() passes timeoutMs through; nothing else may. The one carrier measured on the card carries a measured budget with the reading it came from, in the ledger's own comment convention.

The shrink is now a number, not a silence. A run killed by this tool's own budget publishes timedOut: true, and the sweep prints that population as its own line — printed even when it is zero, because a zero that is printed is a reading and a line that appears only when non-zero is not.

timedOut is read from spawnSync's error.code === 'ETIMEDOUT', never from the signal. Measured: a timed-out spawnSync returns { status: null, signal: 'SIGTERM', error: { code: 'ETIMEDOUT' } } — and the signal half of that is not exclusive to it. A foreground wall-clock cap kills children in exactly the same shape, so reading every SIGTERM as this budget would book rows against a number nobody set, in the direction that OVERSTATES how much of the survey this tool shrank. The SIGTERM verdict text itself is unchanged: it is the safe direction and stays so.

The count line does NOT collide with the axis suspended on #13874

Checked rather than assumed. #13874 is closed (2026-09-04, option 2 landed); the axis suspended there is its option 3, and it belongs to a different instrument — the envelope-caller census in packages/client/src/envelope-caller-census.test.ts, whose prohibition is that its count not move. The line added here counts rows of this census. Measured, with a control:

grep -cE 'analytics\.(query|meta|explain)|automation\.trigger' scripts/measure-self-test-floor.mjs   ->  0 (exit 1)
CONTROL, same grep: packages/client/src/envelope-caller-census.test.ts                               ->  matches

The zero is a reading, not a dead probe. The count line stays in.

#15574selfTestDefs reads masked text, and the payload change IS the fix

selfTestDefs read RAW source, so a name written inside a fixture string counted as a definition of the file quoting it. It now reads the same maskCommentsAndLiterals text injectEarlyReturn has read since #14963, so both halves of "where is the definition" answer from one text.

The --json defs diff, for the one row it moves:

  {
    "file": "scripts/pm/dispatch-gates.mjs",
    "floor": "NONE",
    "defs": [
-     "selfTest",
      "selfTestOnlyCallables",
      "maskSelfTests",
      "selfTestCaseLines",
-     "fixtureSelfTest"
+     "selfTest"
    ]
  }

fixtureSelfTest is a name in a fixture array; nothing can call it. (selfTest moves position because the first raw match was a docblock sentence; the surviving entry is the real definition.) That payload change is the repair, not a side effect of it#14963's byte-identical constraint was that card's, not this one's.

⚠️ The card's numbers were stale and are re-derived here, not quoted. The census is 181 rows today, not 178, and this row held five raw defs, not four (selfTestCaseLines landed since cf6b67164). The mechanism survived; the counts did not.

Census-wide, old reading vs new, through the file's own population():

rows compared: 181
rows whose defs differ under masking: 1 | rows whose ambiguity changes: 0

no entry, no verdict and no row moves. That row was hand-read before and stays hand-read, for the three real definitions that remain — and the static census output is byte-identical before and after (diff exit 0).

The pins, and what they are pinned in

⚠️ This script deliberately ships no --self-test flag — its controls run inline on every invocation, precisely so they cannot become unrun (the inline route check-self-test-wired.mjs records, and that gate is green here). So both pins live in runControls(), which is this file's self-test and is stronger than a flag: node scripts/measure-self-test-floor.mjs --self-test runs them, and so does every other invocation.

The definition pin carries decoys whose names DIFFER from the real ones. The existing anchor fixture spells every decoy selfTest, which is right for an anchor (it takes ONE match; the question is WHICH) but cannot pin a collector — a Set of names collapses the decoys into the real name, and the wrong answer and the right answer become the same list. Only a decoy with its own name can be seen to be absent. Both collected shapes are covered (a function and an arrow inside a fixture string, plus a function inside a comment), and the other direction is pinned too: with the literal delimiters removed the same collector DOES collect them, so the verdict reads the MASK rather than a fixture whose decoys were never collectable.

The budget pin is one fixture probed under two budgets, nothing else different — killed under the smaller, DEFEATED under one that fits. It sleeps rather than spins: a control that runs on every invocation may not take a core with it on a shared box.

A ledger-integrity control reads every shipped row and refuses an unknown field, because a misspelled timeoutMs is not a smaller budget — it is NO budget, i.e. the default, i.e. the SIGTERM this field exists to end.

Verification

The single-row probe went through population() -> probePlan() -> probeEarlyReturn(), the same path main() takes (a throwaway, not committed): --probe takes no row selector, and sweeping all 181 rows twice does not fit a foreground turn.

leg budget result
dispatch-gates row, as shipped 900 000 ms (ledger) HELDbaselineExit=0 mutatedExit=1 mutatedBytes=200, head ✗ dispatch-gates self-test: selfTest() returned without reaching its verdict, · 434.3 s wall
ABLATION: timeoutMs removed from the row 120 000 ms (default) NOT MEASURED — killed by SIGTERM, timedOut: true · 120.2 s wall
ABLATION: masking removed from selfTestDefs controls FAIL, exit 1: DEFINITION CONTROL FAILED: ... got ["commentSelfTest","fixtureSelfTest","selfTest","fixtureSelfTestLater","runSelfTestTwice"], expected ["selfTest","runSelfTestTwice"]

Both ablations were run after the implementation was committed, trap-guarded with absolute paths, each mutation proved on disk by counting the removed and injected text (one attempt was caught by that guard as a zero-hit edit and its reading discarded, not re-used), and each restore proved by git hash-object equal to the HEAD blob and git diff HEAD empty.

The heavy legs ran through scripts/pm/os-verify-lock.sh (OS_VERIFY_LOCK_SLOT=issue-15573): VERDICT command-exit 0 · held the lock 436s and VERDICT command-exit 0 · held the lock 123s. Those are shared-box seconds, as the lock itself says.

⚠️ One reading I had to discard. The first probe read baseline run failed (exit 1). That was my worktree's node_modules symlink, not this tree and not this change: dispatch-gates' self-test runs git check-ignore, which refuses fatal: pathspec 'node_modules/...' is beyond a symbolic link. With a real directory the baseline exits 0. Recorded because the same shape reads exactly like #15515, which the card warns masks this one.

Gates

node scripts/pm/dispatch-gates.mjs --changed --commands --repo objectstack-ai/objectstack (exit 0) derived 26 commands; all 26 were run. 20 exit 0. The other six are NOT MEASURED — this box has no root install, each named:

  • typescriptcheck-reference-carrier-shape.mjs (and its --self-test), check:driver-memory-census (exit 3, PREREQUISITE NOT MET)
  • yamlcheck-ci-filter-parity.mjs, check-closing-keyword-parity.mjs (and its --self-test) (exit 3, PREREQUISITE NOT MET)
  • @typescript-eslint/parsercheck-comment-mask-corpus.mjs (exit 1, a bare ERR_MODULE_NOT_FOUND rather than the tidy exit 3 — same class, different spelling)

⚠️ The derivation's own warning is passed on rather than swallowed: this tree is 5 commits behind origin/main, and 15 files the families are derived FROM changed across that range. The family list above is therefore a reading about this tree; CI's is authoritative.

pnpm check:nul-bytes exit 0, plus a direct control-byte scan of the changed file (zero hits). node scripts/pm/check-governed-merges.mjs --test scripts/measure-self-test-floor.mjs exit 0 — ✅ NOT governed.

scripts/pm/dispatch-gates.mjs --self-test itself ran green on this branch at eb52f996 (exit 0, 1,415 cases) — it is the probe's own baseline above.

Changeset

skip-changeset, applied. AGENTS.md: "A bug fix in a released package takes a patch changeset — never none, and ⛔ never skip-changeset: that label is for a diff that publishes nothing from any released package." This diff is one repo-internal instrument under scripts/; nothing is published from any package.

🤖 Generated with Claude Code

https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk


Generated by Claude Code

…sked source

measure-self-test-floor carries two defects of its own instrument, both in
scripts/measure-self-test-floor.mjs.

#15573 -- the probe spawns every run with a 120 s budget, which is ~3.3x under
scripts/pm/dispatch-gates.mjs's own --self-test (1,415 cases, 6m39.690s
measured). The baseline is killed, the mutated run is never spawned, and the row
reads NOT MEASURED `killed by SIGTERM`: a limit of the INSTRUMENT recorded as a
property of the FILE. The repair is a per-member budget, not a bigger default:
probeEarlyReturn's default stays 120 s for the 180 rows that fit it, and
ENTRY_BY_HAND gains a second row spelling, { entry, timeoutMs }, carrying a
MEASURED budget with the reading it came from. main() passes it through.

The shrink is also now a number rather than a silence: a run killed by this
tool's own budget publishes `timedOut: true` (read from spawnSync's
error.code === 'ETIMEDOUT', never from the signal, which a foreground wall-clock
cap raises identically), and the sweep prints that population as its own line --
printed even when zero, because a zero that is printed is a reading.

#15574 -- selfTestDefs read RAW source, so a name written inside a fixture
string counted as a definition of the file quoting it. It now reads the same
maskCommentsAndLiterals text injectEarlyReturn has read since #14963, so both
halves of "where is the definition" answer from one text. Measured over the
census: exactly 1 row's defs differ (dispatch-gates.mjs loses the phantom
fixtureSelfTest) and 0 rows change ambiguity, so no entry, no verdict and no row
moves. The --json payload change IS the fix.

Both are pinned in the controls, which run inline on every invocation: a fixture
whose decoys are definition-shaped text inside a string and inside a comment
(collected once their delimiters are removed, so the pin reads the mask), and
one slow fixture probed under two budgets -- killed under the smaller, DEFEATED
under one that fits.

Fixes #15573
Fixes #15574

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_012zGPuVVX3deAx9LdjK8jCk
@claude

claude Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor Author

ACCEPT — PR #15758 (head eb52f9962, scripts/measure-self-test-floor.mjs +324/−33) reviewed against the GitHub three-dot diff, not the report.

Both rulings are implemented as spelled: selfTestDefs reads maskCommentsAndLiterals(src); probeEarlyReturn's default stays 120 s and the ledger row shape gains timeoutMs via readLedgerRow (string / null rows unchanged; the record form { entry, timeoutMs }), with the dispatch-gates.mjs row budgeted at 900_000 from the recorded 900 s reading; timedOut: true is published only on a budget kill (budgetExpired keys off ETIMEDOUT). The controls are inline on every invocation — this script ships no --self-test flag by design — and they pin both directions with fixture-validity guards (comment decoy, literal decoy, arrow decoy, real definition survives; SLOW_GATE killed at 250 ms / DEFEATED at 20 s; probePlan and ledger-row invariants). The --json defs change for the one row (fixtureSelfTest dropped) is the fix and is stated. #13874 collision measured as none (its suspended axis lives in packages/client's census, zero spellings in this file).

Measured by this seat: branch blob run against the main tree (static census, controls inline) → EXIT=0 (a failing control exits 1); git merge-tree --write-tree origin/main <branch> → EXIT=0; governed test per the report ✅ NOT governed. NOT MEASURED here: the 434 s locked probe and both ablation legs (the report's readings — HELD … 434.3s, budget removed → killed by SIGTERM … timedOut: true … 120.2s, masking removed → DEFINITION CONTROL FAILED, restores by blob hash 151c963d0… — are the record); the typescript / yaml / @typescript-eslint/parser gates by name.

Open question (the unfiled finding): A — filed by this seat as #15759 (--probe has no row selector; evidence taken through a private copy of main()'s decision). The branch is 5 commits behind main; CI's family list is authoritative and merge-tree is clean, so no rebase is asked.

Flip + arm when both required jobs report success on eb52f9962. On landing #15573 and #15574 close; frees measure-self-test-floor.mjs#15594.


Generated by Claude Code

@claude

claude Bot commented Sep 5, 2026

Copy link
Copy Markdown
Contributor Author

Flipped ready + auto-merge enabled (06:45Z, method: MERGE). Required jobs on eb52f9962: Lint & Repo Gates = success, TypeScript Type Check = success; git merge-tree --write-tree origin/main <branch> → EXIT=0 against the current main. Watched; on landing #15573 and #15574 close.


Generated by Claude Code

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

Labels

size/m skip-changeset PR has no user-facing published change; bypasses the changeset gate

Projects

None yet

2 participants