test(service-settings): scan every carrier extension in the shared-predicate ratchet - #18269
Merged
claude[bot] merged 2 commits intoSep 15, 2026
Merged
Conversation
…edicate ratchet
The walk read `.ts` alone, so a code table in a `.json` file was never opened.
Measured on a specimen the card describes: 249 ISO 3166-1 alpha-2 codes as a
JSON array in `src/`, imported by the door's CALLER with
`with { type: 'json' }` and judging `iso_3166_alpha2` at the `validatePatch`
refusal. It shipped entirely green — ratchet 7/7, `settings-service.test.ts`
139/139, `tsc --noEmit` exit 0, `tsup` exit 0 — while a second membership
definition decided the `value_domain` FieldError on every alpha-2 save
(dropping one code from the JSON turned two behavioural cases red, so it was
live code).
Admit `.mts`, `.cts`, `.js`, `.mjs`, `.cjs` and `.json` beside `.ts`, and spell
the test exclusion per stem so it covers each. The existing ARRAY shape catches
a JSON array of quoted codes once the file is opened; nothing else had to move.
Add the scope half the shape scans never had: no runtime source may relatively
name a module the walk does not open, so "scanned" and "reachable" cannot drift
apart the way the extension list and the toolchain just did.
Census re-run over the widened set, comments masked: 1 hit over 2,503 tracked
non-test carriers under a `src` root (2,495 at `.ts` alone) and 1 over all
3,583 tracked carriers — `packages/spec/src/shared/value-domain.zod.ts`, the
shared table itself, unchanged from the number the header carried.
Claude-Session: https://claude.ai/code/session_01URLHobLUJB9K1ABV6ofdjj
Co-authored-by: Claude <noreply@anthropic.com>
…on-carrier Claude-Session: https://claude.ai/code/session_01URLHobLUJB9K1ABV6ofdjj Co-authored-by: Claude <noreply@anthropic.com>
Contributor
📓 Docs Drift CheckNothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs. What this run could not see
Coarse fallback — 0 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): |
claude
Bot
deleted the
claude/issue-15610-shared-predicate-ratchet-json-carrier
branch
September 15, 2026 06:15
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #15610
Clause-②: no
The shared-predicate ratchet walked
.tsfiles only, so a membership table carried in a.jsonfile was never opened. This admits every carrier extension the toolchain followsand adds the scope half the shape scans never had.
The green-while-broken state, reproduced first
The card's specimen, rebuilt and measured on this branch's base before anything was
changed: 249 ISO 3166-1 alpha-2 codes as a JSON array at
packages/services/service-settings/src/zz-alpha2.json, imported by the door's CALLER(
import ALPHA2 from './zz-alpha2.json' with { type: 'json' }insettings-service.ts)and judging
iso_3166_alpha2itself at thevalidatePatchrefusal, falling through tofirstRejectedDomainMemberfor the other two domains.value-domains.shared-predicate.pin.test.tssettings-service.test.tstsc --noEmittsupbuildIt was live code, not decoration: deleting
CHfrom the JSON turned exactly 2 of 139behavioural cases red — the two that write an accepted country through the save path — so
the second definition really was deciding the
value_domainFieldError.What the strengthened ratchet does to it
Same specimen, same command, on this PR's commit:
Nothing but the file's extension was missing — the existing
ARRAYshape catches a JSONarray of quoted codes on its current spelling once the walk opens the file.
Each admitted carrier measured separately
One carrier on disk at a time, because a single run with several aborts at the first
assertion and names only one file:
src/zz-alpha2.mtszz-alpha2.ctszz-alpha2.jszz-alpha2.mjszz-alpha2.cjszz-alpha2.jsonzz-alpha2.md(control — not a carrier)zz-alpha2.test.ts(control — evidence, not enforcement).mtsand.ctsare the two the card records as never separately measured. They aremeasured here, and so are the three JS spellings: the argument is about what the bundler
opens, not about which language the table is typed in. The two controls are what keep the
admission list a LIST — a walk that reported on prose or on test evidence would be a
different and worse instrument.
Which of the three blind spots this closes, and which it does not
runtimeSources()reading*.tsonly — CLOSED. The walk admits.ts .mts .cts .js .mjs .cjs .json, and the test exclusion is spelled per stem so it covers each. Sixseparate red measurements above.
not in the door's shape. No caller-side whitelist is possible:
settings-service.tslegitimately imports dozens of modules, so there is no one-specifier assertion to make
against it. What is closable is the SCOPE of the scans that do cover callers, and that
is the eighth assertion this PR adds — no runtime source may relatively name a module
the walk does not open, so a carrier one directory above
src/cannot hide the way the.jsoncarrier hid behind an extension. A table reached by a BARE package specifierstays outside both pins, for the door and the caller alike; it was already in the
NOT-covered list and stays there.
.json—CLOSED for a carrier inside this package, which is the measured specimen; NOT a general
close. The density scan still sees only what the walk opens, so the table is now found
where it LIVES rather than where it is consulted. A caller consulting a table that lives
outside this package remains uncovered, and a table in a shape no scan knows — the
object-key map, now including its JSON spelling
{ "AD": 1, … }— is still thedocumented three-character-separator class. The header says both.
"the finding is closed".
Census re-run
The header carried a false-positive census, and a widened scan that moves that number owes
an explanation. It does not move. Measured on this branch at
de98ec29a, comments maskedwith the repo's shared mask, over git-tracked non-test files:
DENSESPACEDARRAY.tsonly, under asrcrootsrcrootThe one hit is
packages/spec/src/shared/value-domain.zod.tsin all three scopes — theshared table itself, which is the one place the definition belongs. Widening the extension
list admitted 8 more files under
srcroots and 1,080 more across the repository and hitnone of them.
Verification
Run on
de98ec29a, after mergingorigin/main:pnpm --filter @objectstack/service-settings test— 33 files, 579 tests, all passedpnpm --filter @objectstack/service-settings exec tsc --noEmit— exit 0pnpm --filter @objectstack/service-settings build(tsup +check-dts-emitted) — exit 0pnpm --filter '@objectstack/service-settings^...' build(dependency closure) — exit 0pnpm lint(eslint . --no-inline-config, the repo-wide style authority) — exit 0scripts/pm/dispatch-gates.mjs --commandsand reconciled with--ran: 55 derived · 53 run and green · 2 NOT MEASURED · 0 UNRUN. The two arecheck:dual-build-cjs-loadsandcheck:type-check-debt, both exit 3 = PREREQUISITENOT MET — each reads built output for 50+ packages and refuses to measure without a
full
pnpm build, which CI performs. Exit 3 is recorded as not-measured, never as a pass.HEADby blob hash, with theworking tree proven clean afterwards; no probe file is committed.
Release surface
No changeset: nothing this package publishes moves.
filesis["dist", "README.md", "CHANGELOG.md"]; the diff is one*.test.tsfile, whichtsupdoes not emit. Measuredafter a build — the symbols this PR adds return zero hits under
dist/, while the positivecontrol (
firstRejectedDomainMember,knownValueDomain) is present indist/index.jsanddist/index.cjs, so the grep discriminates.skip-changesetapplied.Contract-surface reachability (the ② derivation)
Derived from the DELIVERED diff by reachability from the published entry, not from the word
exportand not from a grep ofdist/index.js. The published entry isexports["."] -> ./dist/index.{js,d.ts}, built from thesrc/index.tsbarrel. The staticimport closure of that barrel is 29 files. The delivered file is not one of them; the
positive control
src/value-domains.tsis in the closure, and a second test file(
src/value-domains.test.ts) is outside it exactly as the delivered one is. The diff addsno schema key, no closed-set member, no published export and no registry entry. Declared
noabove.Acceptance notes
.tsfiles repo-wide (49srcroots)". That count does not reproduce on this tree bythe method stated here (2,495 non-test
.tsfiles under 78srcroots, same singlehit). The difference is one of scope definition and of the tree each was taken against,
not a disagreement about the finding — the header now carries numbers a reader can
re-derive with the mask this repository ships.
ARRAY/SPACED/DENSEassertions sit inside aforloop, so atree with several offending carriers reddens naming only the first. That is ordinary
vitest shape, not a defect, and it is why the per-carrier table above was measured one
file at a time. Whoever next edits this pin is the reader who needs to know.
Generated by Claude Code