fix(scripts,runtime): the code-helper stamp shape reaches the object-literal position - #13477
Conversation
📓 Docs Drift CheckThis PR changes 1 package(s): 8 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 2 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 23 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 4ca02264b67122689856bca706e7e300b2f636ea && git checkout 4ca02264b67122689856bca706e7e300b2f636ea
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin b172e2e3b7072f28bb6e8a883feac057159f2cd2 4a207492153e7369bfb172636eebdc64225cf7ba && git checkout -B drift-repro b172e2e3b7072f28bb6e8a883feac057159f2cd2 && git merge --no-ff 4a207492153e7369bfb172636eebdc64225cf7ba
node scripts/docs-audit/affected-docs.mjs --json b172e2e3b7072f28bb6e8a883feac057159f2cd2
|
Fixes #13233
Widens the code-carrying-helper stamp shape to the object-literal position, lands the verdict rows that widening owes, and answers the card's declared blocker — the undischargeable
unresolvedfindings — with a mechanism rather than a suppression.The gap
codehelperwas anchored on an assignment,.code = ident. The reasoning it implements — the identifier is a PARAMETER, so the literals live at the CALL SITES — belongs to the helper, not to=, and it holds just as well for the helper that builds an object literal:Nothing fired.
objlitneeds a quote,objlitconstneeds a SCREAMING_SNAKE identifier (the conventional parameter name iscode),objlittemplateneeds backticks. So there was no site and no unresolved entry — the one way the gate's own "reported, never dropped" bound can fail without anything saying so.The re-census, measured on this head — not quoted
The card's table was taken before the class-method closure merged, so it was re-derived here through the gate's own
deriveSitesoverpackages/**non-test source, not through a separate instrument.The grade-bearing row is the last one and it holds: of the 33 SCREAMING_SNAKE values reached, 33 are already registered, so zero real wire codes were hiding behind this blindness. Closing it prevents a future defect; it fixed no present one.
The helper count is a definition difference, not drift: the card counts 14 as 13 named + 1 anonymous, and an anonymous helper can produce neither a site nor an unresolved (every call scan is built from the declaration's name), so 13 is the actionable figure and the named counts agree exactly.
What the rows are
All 29 are one genre: ADR-0114 D2
FieldErrorCodemembers (required,invalid_type,min_value, …) across four validation helpers. The evidence is stronger than a reading of the call sites — each helper'scodeparameter is typed to that closedz.enum, so the vocabulary is compiler-enforced against a catalog that is not the ledger's, and it lands atApiError.details.fields[].code, never aterror.code. ADR-0112 D6 draws exactly this line. Declaredforeign-vocabulary,door: 'none'.The blocker, and how it is answered
An
unresolvedentry is pushed unconditionally and no row discharged it, so each of the five would have been a red gate with no verdict available. Areason: 'helper'entry is now dischargeable by a row in a second declared list,UNRESOLVED_CODE_HELPERS, carrying a door, a verdict and its evidence — reconciled in both directions, so a row whose helper the scan stops reporting goes stale and reds.The restriction is the whole argument, and it is what keeps this a widening rather than an exemption: only
reason: 'helper'. The other two reasons name a remedy the author can carry out (constant→ resolve it;reassigned→ spell itconst), so a row there would buy an exemption from work that is possible. A helper has no such remedy — its callers may live in another package, pass a vocabulary declared out of the gate's population, or pass a genuine runtime value. Relative toorigin/mainthe gate sees strictly more: before this change none of the five produced a site or an unresolved, because no shape reached them.The five, classified: an SMS one-time password (
deliverPhoneOtp), a YAML fence body (block), the kebab diagnostic vocabulary already declared out of both gates (Parser#error), a helper whose parameter is typed to the closedErrorCodeunion so the compiler enforces it (sendError, 0 in-file callers), and the dispatcher door itself, which authors no code but re-stamps one off a caught value (http-dispatcher.error).Precision this position does not have — recorded, not smoothed over
This falsifies the dispatch's framing that the unresolved findings are irreducible error codes. Two of the thirteen helpers reached carry no error code at all:
deliverPhoneOtp(phone, code)stamps an SMS one-time password into a notification-template payload, andcheck-yaml-examples.block(code, decl)stamps a raw documentation fence body. They are not irreducible — they are out of population..code = identneeds a property namedcodeon a value being mutated, which is nearly always an error;{ code }is simply how any record carries any field calledcode. And no sibling-key test separates them, which was measured rather than assumed: the obvious candidate — require the stamping literal to carry amessagesibling, the envelope shape — fails on the largest true positive.record-validator'sfailstamps{ field, code, def, constraint, messageKey, options, value }with nomessageat all and alone contributes 15 of the 29 rows;share-links.sendErrstamps{ code }and nothing else. That narrowing would drop 25 of the 29 rows to remove 2 false positives. So both are classified, not filtered: a predicate drawn around the cases one finds inconvenient is an exemption wearing a predicate's clothes.How the shape earns "object literal"
The regex is a candidate generator; two structural guards decide. The innermost enclosing bracket at the
codetoken must be a{— computed once per file by a quote-aware pass (enclosingOpeners) — and the identifier must be a parameter of the enclosing declaration. The bracket guard is what separates an object literal from the argument listf(a, code, b)and the array[a, code], three positions written identically. This is the over-match that made the measuring round discard a regex instrument, and it is real: without the guard the naive anchor reports 19 helpers where the guarded shape reports 13.Bounded in-place fix, named here because it is not the card's defect
--self-testiterates thesamplesmap, notSHAPES, and nothing held the two equal — so a shape added without a sample is simply never pinned, while the run prints a confident "N shapes OK" with N one too small. It was live:objlithelperlanded unpinned and the count read 8 of 9. One assertion now holdsSHAPES⊆samples. Same defect class as the card (a published list whose price is that an unrecognised member reports nothing, silently), mechanical, same gate, no new verification surface.Verification
Union re-run at final head
4a207492.Both legs of the gate — a CI step is
--self-test &&the check, and running one is not running the other:Baseline on
origin/mainwasOK — 22 … all classified, so the widening is +29 sites, all with rows, and the discharge list clears all five helpers.Ablations — each mutation proven on disk by hash before the run, each restore proven byte-identical to its
HEADblob afterwards, withgit diff HEADempty:Parser#errordischarge row →1 finding(s) [unresolved-constant] packages/sdui-parser/src/parse.ts: the code helper 'error(code)' (objlithelper) …. The discharge is not vacuous.max_scale) →1 finding(s) [unclassified-site] … stamps unregistered code 'max_scale' (objlithelper) …. The rows are load-bearing.code passed through an argument list was read as an object-literal stamp, and the real tree grows exactly the two argument-list false helpers (containsJsx,findBareAnyincheck-skill-examples.ts) that the census correction predicted. Restored, self-test green again at 9 shapes + 217 assertions.Other gates, exit codes captured before any pipe:
check:doc-authoring(red first — issue ids inwhy:string prose; ids stripped, ADR ids kept, now green with the sibling-package baseline held),check:nul-bytes,check:error-code-casing,check:self-test-wired,check:pm-dispatch-gates,bare-root-worklist --self-test,check:cross-package-test-inputs,check:entry-guard,check:parse-guard,check:published-files,check:test-source-alias,check:type-source-resolution,check:watch-hint-literal,check:pnpm-filter-targets,check:agent-test-spelling,check:bash32-floor,check:cli-command-ids,check:slot-lookup,check:page-declaration-shape,check:logger-receiver-detach,check-ci-filter-parity,check-comment-mask-adoption,check-keyed-text-bounds,check-plugin-teardown-shape,check-shard-attestation,check-undeclared-dep-imports, the changeset family, andrelease-rehearsal-clone --self-test— all green.pnpm --filter @objectstack/runtime typecheckclean, and--listFilesconfirms the edited file is in that program rather than assumed to be.vitest run src/error-envelope.conformance.test.ts src/package-door-error-parity.test.ts src/http-dispatcher.error-leak.test.ts— 64 passed.pnpm exec turbo run build --filter=@objectstack/runtime— 30/30. Full-repopnpm lintran and passed, so no narrowing is claimed.NOT MEASURED, reported as such rather than as green:
check-test-completeness,check-half-statesandcheck:dual-build-cjs-loadseach exit 3, their own declared "nothing was swept" code — they need a CI test run or a fullpnpm buildthis branch does not perform.Scope note for the reviewer
The dispatch declared the file surface as
scripts/check-dispatcher-error-vocabulary.mjsalone, while the card body, the triage comment and the claim comment's own ruling all require the 29 verdict rows — which can only live inpackages/runtime/src/dispatcher-error-vocabulary.ts, registered in advance as "a rider on the same change, not a re-routing". The rider was taken after checking that no in-flight PR touches either file (all 14 open non-release PRs enumerated, zero hits), so the serialization the surface line exists to protect is not at risk. Flagged rather than assumed.dispatcher-error-vocabulary.tsis not re-exported from the package index, so no published surface is widened and no runtime behaviour moves — the gate's population does.Generated by Claude Code