Skip to content

[finding] bare-root-worklist pins a recorded spelling in hintCovers' terms only for SINGLE-hint rows — five DECLARED-NARROWER rows are re-measured by nothing #14233

Description

@claude

Measured while working #13813 (PR #14231). Filed unassigned, not graded.

What is unpinned

scripts/pm/bare-root-worklist.mjs argues, in its own docblock, that the whole licence for
recording "a precise live spelling exists" is that something re-measures the spelling:

a vocabulary value recording "a precise live spelling exists" whose spelling nothing
re-measures is the allowlist #10840 refused by name.

That pin is the SPELLINGS loop in --self-test, which asserts LIVE / PRECISE / COMPLETE for
each recorded spelling in hintCovers' own terms. It is a real pin and it works.

⭐ But SPELLINGS holds one hint per entry, and a row carries one spelling name. So a
gate whose population needs more than one hint cannot record a spelling at all, and its row is
pinned by nothing in this file. Five DECLARED-NARROWER rows are in exactly that state today:

row hints the gate declares
check:logger-receiver-detach SCAN_ROOTS packages 6 (three extensions x two roots)
check:logger-receiver-detach SCAN_ROOTS examples (same declaration)
check:logger-receiver-detach SCAN_ROOTS apps (same declaration)
check:dual-build-cjs-loads SCAN_ROOT packages 2
check:ratchet-remedy-authority SCRIPTS_DIR scripts 2 (one per admitted extension)

The verdict is not wrong on any of them, and the omission is deliberate and stated in each row.
What is missing is the re-measurement the file's own argument says the record depends on.

Why the gate-side self-tests do not close it

Each of those gates does pin its declaration from its own side, and each pin is deliberately
written without importing scripts/pm/dispatch-gates.mjs — because the import specifier would
itself be a path literal, handing the gate that module's declared population as if it were its own.
check-logger-receiver-detach.mjs records that refusal in as many words, and #13813's gate repeats
it.

⇒ Every one of those pins is therefore a local segment matcher, never hintCovers. They hold
"the declaration is a function of the constants the walk is a function of", which is the drift they
exist for. They cannot hold "and hintCovers still reaches these files."

The two claims come apart, and not hypothetically — the whole reason this card's parent row was
re-decided is that hintCovers moved under it twice (#12300, then #13448) while every local
assertion about the declared value stayed green. A regression in the other direction is the same
shape: scripts/*.mjs reaches 183 files today, and if a future change to judgedAsPattern sent it
back through collapseHint it would reach zero — collapseHint('scripts/*.mjs') still returns
scripts/.mjs today — with the gate's own self-test, check:watch-hint-literal and
check:declared-population-live all green. Only check:declared-population-live asks the
reachability question at all, and it is per-FAMILY: one live literal rescues the family, so a
partial death is invisible there too.

Shape of a remedy (not proposed as an implementation)

The narrow version is a SPELLINGS entry that holds a list of segment arrays rather than one,
with holds asked of the union — the pin loop then covers multi-hint rows unchanged. That is a
change to the pin harness in a governance file this lane does not write code in, which is why this
is recorded rather than fixed.

⚠️ What I am not claiming: I have not audited whether the other four rows' declarations are
still live in hintCovers' terms — only that nothing in the tree asserts it. The count says
nothing about which.


Generated by Claude Code

Activity

  1. huangyiirene commented on Sep 2, 2026

    @huangyiirene
    Collaborator

    Triage (R+89, triage seat, session session_019kDRpB7D2XzVzkaLp57T5D): graded → pm:queue · priority:p3 · domain:devx · type Task. finding removed (graded = off the finding box).

    Lands in: scripts/pm/bare-root-worklist.mjs — the SPELLINGS self-test harness (one hint per entry today). Rationale: the file's own docblock licenses a recorded spelling only when something re-measures it; five DECLARED-NARROWER rows have no re-measurement because the harness cannot hold a multi-hint spelling. Verdicts are not wrong today, so p3; the remedy is the narrow one the card sketches — let a SPELLINGS entry carry a list of segment arrays and ask holds of the union — and nothing else. ⛔ Not a gate weakening, not a new gate.

    Size/model suggestion: S mechanical (harness extension + the five rows), sonnet/opus; the pin loop is the proof — every added row must fail red when its declaration is edited away.


    Generated by Claude Code

  2. added theissue type on Sep 2, 2026
  3. claude commented on Sep 2, 2026

    @claude
    ContributorAuthor

    Claim: session session_01WLJQhde67SeTccsmnBVarV (domain:devx execution seat, seat post #6023) — R1 wave 8.

    • Branch: claude/issue-14233-spellings-multi-hint
    • Worktree: ../objectstack-14233 (dedicated per-task worktree, off origin/main)
    • Domain: domain:devx (scripts/pm/** — the bare-root worklist's own self-test harness, the triage's anchor)
    • File surface: scripts/pm/bare-root-worklist.mjs ONLY — (a) the SPELLINGS map: an entry may carry a LIST of segment arrays instead of one; (b) the pin loop in --self-test (LIVE / PRECISE / COMPLETE / NARROWING) asks holds of the union of the entry's hints; (c) the five DECLARED-NARROWER rows the card names (check:logger-receiver-detach SCAN_ROOTS packages / examples / apps, check:dual-build-cjs-loads SCAN_ROOT packages, check:ratchet-remedy-authority SCRIPTS_DIR scripts) gain a spelling: naming a new multi-hint entry, and the sentence in their why prose that records the one-hint omission is rewritten to match; (d) the docblock sentences that state the one-hint limitation. ⛔ No verdict changes; ⛔ no gate is weakened and none added; ⛔ no edit to any gate under scripts/check-*.mjs — their declarations are read, not written.
    • Premise re-verified on origin/main e5812fa43: SPELLINGS (bare-root-worklist.mjs:261–308) holds exactly one segments array per entry, and the pin loop (:1116–:1135) destructures { segments, claim, holds } and joins a single hint; the five rows (:319–:413) carry no spelling: and the :428 prose says so in as many words ("SPELLINGS holds ONE hint per entry and this population needs one per extension"). The three gates' declarations on the same ref: check-logger-receiver-detach.mjs:168–172 (six hints), check-dual-build-cjs-loads.mjs:271 (packages/**/package.json, packages/**/tsup.config.ts), check-ratchet-remedy-authority.mjs:182 (scripts/*.mjs, scripts/*.mts). Premise holds.
    • Container & model: subagent lane (S), sonnet — the triage's suggestion.
    • Clause ②: not reached — a self-test pin harness; no contract, no gate verdict changes.
    • Serial constraints: scripts/pm/** is excluded from 144 of 154 scripts/** self-tests exit 0 when an early return leaves them before their verdict — 137 printing nothing at all #13798 batch 1 by that claim's file surface; no other in-flight card of this seat edits bare-root-worklist.mjs. The proof the triage names is binding: every added multi-hint row must be shown to fail red when its declaration is edited away (temporary edit, quoted verdict, reverted).

    Generated by Claude Code

  4. added a commit that references this issue on Sep 2, 2026
    7f3db21
  5. claude commented on Sep 2, 2026

    @claude
    ContributorAuthor

    os-dev-report

    {
      "issue": 14233,
      "status": "done",
      "branch": "claude/issue-14233-spellings-multi-hint",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/14523",
      "premise_still_valid": true,
      "summary": "Extended SPELLINGS in scripts/pm/bare-root-worklist.mjs so an entry's `segments` may hold either one hint (a flat array, unchanged for the nine existing entries -- chosen as the smaller, more readable diff over normalising every entry to a list) or a LIST of segment arrays; the pin loop asks hintCovers and holds of the UNION via a new `hintsOf()` normaliser. Five entries added, matching each gate's own ROOT_DIR_WATCH_HINTS exactly: 'packages TypeScript source' (packages/**/*.ts, *.tsx, *.mts -- covered 5240/5796 root files), 'examples TypeScript source' (examples/**/*.ts -- 206/243, single-hint so it used the existing flat shape), 'apps TypeScript source' (apps/**/*.ts, *.tsx -- 29/41), 'dual-build manifests and configs' (packages/**/package.json, tsup.config.ts -- 94/5796, matching the row's own 74+20 split exactly, NOT repurposing the existing 'packages manifests' entry), 'scripts top-level script files' (scripts/*.mjs, *.mts -- 183/310, matching check-ratchet-remedy-authority's own corpusFiles() walk of 183 exactly). All five hints in each multi-hint entry were confirmed (by direct measurement during authoring, not pinned as a new mechanical check -- see open_questions) to share one root, satisfying Zone2.1's 'assert, don't assume' instruction via report evidence rather than a new runtime assertion. All five rows gained `spelling:` and corrected `why` prose; the check:ratchet-remedy-authority row's sentence stating 'SPELLINGS holds ONE hint per entry and this population needs one per extension' is now false and was rewritten to describe the new pin (the gate's own self-test pin and this file's pin now independently corroborate the same declaration). No verdict changed. No scripts/check-*.mjs gate was edited. RED-PROOFS (all reverted via `git checkout HEAD -- scripts/pm/bare-root-worklist.mjs`, each followed by an empty `git diff HEAD` before the next mutation): (1) 'packages TypeScript source' -- segments changed to nonexistent extensions -> 'the spelling recorded as \"packages TypeScript source\" is LIVE -- hintCovers reaches 0 tracked file(s) with it...' + a COMPLETE failure (2 failures, exit 1). (2) 'examples TypeScript source' -- same nonexistent-extension mutation -> LIVE failure, exit 1. (3) 'apps TypeScript source' -- `holds` narrowed to drop .tsx acceptance (LIVE/segments untouched) -> '...and it is PRECISE... OVER-NAMES 14: apps/docs/app/[lang]/blog/[[...slug]]/page.tsx ...', exit 1 -- a different failure mode (PRECISE, not LIVE) to demonstrate the pin catches drift beyond simple dead-hint. (4) 'dual-build manifests and configs' -- `holds` widened to also accept tsconfig.json (a file the hints don't reach) -> '...and COMPLETE over its own claim... MISSES 73: packages/adapters/hono/tsconfig.json ...', exit 1 -- COMPLETE failure mode. (5) 'scripts top-level script files' -- segments changed to nonexistent extensions -> LIVE + COMPLETE failures (MISSES 183), exit 1. Final self-test after all reverts: green, 78 `t()` assertions execute at runtime (up from 58 on unmodified main -- +4 per added entry x 5, confirmed via temporary instrumentation then reverted). PER-MEMBER LIVE DECISION: left out. Zone3 offered it as an option; the ruling's own words are 'the remedy is the narrow one the card sketches ... and nothing else', and a per-member pin is a genuine addition beyond 'ask holds of the union', not a restatement of it -- see open_questions for the tradeoff recorded instead of built.",
      "tests": "node scripts/pm/bare-root-worklist.mjs --self-test: green pre-merge and on merged head 6cc887260 -- 'OK self-test: 57 live row(s), 49 unreachable as spelled, 49 recorded verdict(s) -- none stale, none missing, none contradicted. 23 record(s) carry a spelling and every one of 14 distinct spelling(s) is pinned LIVE, PRECISE and COMPLETE...'. Five red-proofs (quoted in summary), each reverted and reconfirmed clean before the next. node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands scripts/pm/bare-root-worklist.mjs derived 14 commands (13 by path, 2 by change KIND) both pre- and post-merge (post-merge run showed no 'behind origin/main' warning, commit 6cc887260). All 14 run and re-run on the merged tree: node scripts/check-ci-filter-parity.mjs RAN-PASS (exit 0); node scripts/check-cross-package-test-inputs.mjs RAN-PASS (exit 0); node scripts/check-shard-attestation.mjs RAN-PASS (exit 0); node scripts/check-test-completeness.mjs NOT-MEASURED (exit 3, 'PREREQUISITE NOT MET -- this gate grades a saved turbo run test log' -- by design, CI supplies the log); node scripts/pm/bare-root-worklist.mjs --self-test RAN-PASS (above); pnpm check:agent-test-spelling RAN-PASS (exit 0, '0 violations'); pnpm check:bash32-floor RAN-PASS (exit 0); pnpm check:cli-command-ids RAN-PASS (exit 0, '315 command-id literal(s)...'); pnpm check:cross-package-test-inputs RAN-PASS (exit 0); pnpm check:entry-guard RAN-PASS (exit 0); pnpm check:parse-guard RAN-PASS (exit 0, resumed once via OS_VERIFY_LOCK_SLOT after an unrelated queue timeout on the merged-tree re-run, waited 4m23s then ran); pnpm check:pm-dispatch-gates RAN-PASS (exit 0 explicitly captured via `; echo EXITCODE=$?` on a second detached run -- 'dispatch-gates self-test: 1240 cases pass', EXITCODE=0; run detached both times per dispatch instructions, ~11 min each); pnpm check:pnpm-filter-targets RAN-PASS (exit 0); pnpm check:watch-hint-literal RAN-PASS (exit 0, '43 declaration(s) across 4 rostered name(s)'). Additionally per dispatch instructions: pnpm check:ratchet-remedy-authority RAN-PASS (exit 0, '183 scripts swept (scripts/*.{mjs,mts})' -- matches the new 'scripts top-level script files' spelling's 183 exactly, on both pre- and post-merge trees); pnpm check:declared-population-live RAN-PASS (exit 0, '157 of 201 famil(ies)... reaches this tree's 7928 tracked file(s)' post-merge, 7921 pre-merge); node scripts/check-self-test-wired.mjs RAN-PASS (exit 0, '163 script(s)... run by CI'). Plus: node scripts/check-nul-bytes.mjs RAN-PASS (exit 0, clean, 7921 files post-merge). Narrow ESLint scan on the one changed file (node ... eslint.js scripts/pm/bare-root-worklist.mjs --no-inline-config --format json, exit code captured before any pipe): 0 errors, 0 warnings, both pre- and post-merge -- population is the single file this diff touches (confirmed via --format json's own file count of 1); type-aware linting is not part of this repo's plain-JS eslint config for scripts/**, so this diff cannot move any judgment on an untouched file. All exit codes captured via redirect-then-$?, never through a pipe. Final commit: 6cc887260 (merge of origin/main into the branch; merge was clean, no conflicts, bare-root-worklist.mjs untouched by upstream commits since base e5812fa43). PR pushed and gates above all re-run on this exact sha.",
      "mcp_calls": 0,
      "open_questions": [
        {
          "question": "Should the pin loop add a per-member LIVE assertion for multi-hint entries (each hint independently reaches >=1 tracked file), so a union LIVE cannot let one dead hint hide behind a live sibling -- the partial-death shape the parent card (#13813-adjacent) described for check:declared-population-live?",
          "options": [
            "A: leave it out (what this PR does) -- the ruling's own words are 'the remedy is the narrow one the card sketches ... and nothing else', and a per-member pin is a genuine addition beyond 'ask holds of the union'. Every new entry's hints were individually measured non-empty during this PR's own authoring/verification (packages: 5215/8/17; apps: 15/14; dual-build: 74/20; scripts: 177/6), so the gap is theoretical today, not a live defect.",
            "B: add it inside the pin loop (still no new gate, no verdict change) -- directly closes the exact partial-death risk the parent card raised, at the cost of +1 t() assertion per multi-hint entry (a scope decision beyond the literal ruling text)."
          ],
          "recommendation": "A, because the ruling explicitly said 'and nothing else' and the risk is provably inert on every entry this PR adds; B is a reasonable next increment if a future row's hints diverge in liveness, but building it speculatively here would be exactly the kind of unrequested scope-add the file's own conventions (SHRINK-ONLY map, 'adding a row is still not a remedy for a stale one') warn against."
        }
      ],
      "out_of_scope_findings": []
    }

    Generated by Claude Code


    Generated by Claude Code

  6. claude commented on Sep 2, 2026

    @claude
    ContributorAuthor

    ACCEPT — #14233 landed. PR #14523 merged to main as fed4fa409 via the merge queue (draft flipped + auto-merge armed 10:31Z, added_to_merge_queue 10:31:17Z; ~30 min in the queue behind the 10:08Z batch).

    Close-out probes executed on origin/main (fresh fetch, throwaway worktree — not the shared checkout; origin/main IS fed4fa409):

    • scripts/pm/bare-root-worklist.mjs:366-373 — hintsOf(segments) normalises a SPELLINGS entry's segments to a LIST of segment arrays (flat array = one hint, list = several; distinguished by Array.isArray(segments[0])); spellingOf() at :377-380 joins the list with |.
    • Multi-hint entries now recorded: packages TypeScript source (:323-337, three hints *.ts / *.tsx / *.mts), apps TypeScript source (:338-346, two hints), dual-build manifests and configs (:347-…) — the populations the ruling named as "needs one hint per extension".
    • Control: the single-hint packages manifests entry still reads at :274-278 (flat segments, unchanged); --self-test pin loop :1163-1197 (every recorded spelling names a SPELLINGS entry).

    Merged diff = 1 file (scripts/pm/bare-root-worklist.mjs, +114/−21; skip-changeset).

    Unlocks (serial queue): jointly with PR #14520 (merged) and PR #14524 (fix in flight), the scripts/pm/* half of #13798 batch 2.

    pm:dispatched stripped in this same step (type / priority / domain:devx kept — ownership is not state).

    Seat: session_01WLJQhde67SeTccsmnBVarV (domain:devx, #6023).


    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

Type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions