Skip to content

feat(spec): schema-free /meta-spelling entry + sideEffects: false with measured purity (#10096, #10031) - #10201

Merged
qq9340100 merged 2 commits into
mainfrom
claude/issue-10096-schema-free-meta-spelling
Aug 20, 2026
Merged

feat(spec): schema-free /meta-spelling entry + sideEffects: false with measured purity (#10096, #10031)#10201
qq9340100 merged 2 commits into
mainfrom
claude/issue-10096-schema-free-meta-spelling

Conversation

@claude

@claudeclaudeBot commented Aug 20, 2026

Copy link
Copy Markdown
Contributor

Fixes#10096
Fixes#10031

Family landing per the maintainer ruling recorded 2026-08-20T09:17Z on #10096 (one authoritative copy; #10031's ruling comment defers to it). Two per-member commits so each member is independently verifiable. Unblocks objectui#5359.

What landed (per ruling clause)

  1. Schema-free fine-grained entry @objectstack/spec/meta-spelling (@objectstack/spec/shared costs a consumer 60.1 KB gzipped to import one string fold — the /meta spelling contract has no fine-grained export #10096, commit 1) — the /meta/:type spelling contract: META_URL_TO_SINGULAR, canonicalMetaUrlType, metaUrlSpellingRefusal, unrecognisedMetaTypeRefusal. The map is materialized at build time by gen:meta-url-spelling into src/meta-spelling/meta-url-data.generated.ts (registered with the merge driver and the check:generated GATED ledger), so the entry's module graph is two pure modules — zero imports, zero zod content in dist/meta-spelling/index.mjs (verified by grep and by a fresh-process evaluation probe: PURE, 1 ms). /shared keeps all four symbols as re-exports of the one declaration (additive; reference-identity pinned in the test suite).
    • Derivation-compatible: objectui/scripts/check-spec-symbol-derivation.mjs treats any @objectstack/spec/* import as sanctioned derivation (isSpecModule prefix match) and enumerates export names from package.jsonexports entries carrying types conditions — the new entry has both conditions, so its symbols enter the guard's name set like every existing entry's (verified by reading the guard's resolution logic; objectui itself untouched).
  2. sideEffects: false + PURE-annotated schema construction (spec: importing one function from a @objectstack/spec subpath pulls its whole module graph — measured +237 KB minified, nothing tree-shakes #10031, commit 2) — purity proven by measurement (below). The annotation is a tsup esbuild onLoad transform (lazySchema / strictObject / defineForm call sites), not a source sweep — measured: esbuild preserves input annotations in output but does not re-emit marks made via its pure option, so the annotation must exist at parse time; injecting at load keeps ~1700 call sites out of the diff and edits no source file, so packages/spec/src/data/object.zod.ts and packages/spec/src/automation/flow.zod.ts (held by in-flight sibling claims spec: widen the #8778 scope-pin annotation on the audit-stamping declaration to name the cloud#1395 ruling and its three sanctioned consumers #10110/bug(service-automation): a flow's declared errorHandling.strategy: 'retry' silently stops applying once the run pauses — the resume path has no retry loop and the continuation carries no attempt state #9705) are untouched.
  3. assertMetaUrlSpellingsAgree() moved to a build-time checkcheck:meta-url-spelling re-derives the three-limb union from the live PLURAL_TO_SINGULAR + DEFAULT_METADATA_TYPE_REGISTRY on every CI lap (new step in lint.yml's required typecheck job) and fails on disagreement or staleness. Moved, not dropped. Both failure directions ablation-verified (mutation confirmed on disk by grep before each run, restore confirmed after): a mutated artifact fails with the staleness prescription; a mutated PLURAL_TO_SINGULAR entry fails with the assertion's own "One spelling may not name two types" message. The registry-quantified vitest invariants additionally pin the agreement and the generated data's freshness as attributable tests.
  4. Standing principle written into the package docspackages/spec/README.md (new "Export surfaces" section) and the src/index.ts module doc, ruling text quoted verbatim, untranslated. Mechanizing it as a gate is filed unassigned as Mechanize the schema-free-export principle as a gate: a declared browser-reachable spec entry must link no zod in its module graph #10199.

Measurements

esbuild --bundle --minify --format=esm --platform=browser --external:fs; probes resolve the workspace spec through the exports map. Before = origin/main @cc21aad8e built in a pristine worktree; after = this branch @a9be45a86. (--external:fs: pg-connection-string, reached through the datasource schemas, requires fs; a browser build shims it — that weight is already on record as #9771.) KB below = 1000 bytes.

probebefore min / gzafter min / gz
canonicalMetaUrlType via /shared, alone578.4 / 136.8 KB431.9 / 90.2 KB
canonicalMetaUrlType via /meta-spelling, alone1.11 KB / 0.53 KB
/ui + /kernel baseline graph1439.6 / 388.2 KB1288.9 / 349.7 KB
… + the one fold via /shared (marginal)+246.9 / +69.7 KB+104.8 / +25.2 KB
… + the one fold via /meta-spelling (marginal)+1.11 / +0.37 KB
import * as spec (root)1433.8 / 409.1 KB1389.3 / 396.0 KB
… + postureEnforcesWall via /security (marginal)+261.5 / +82.4 KB+177.8 / +56.6 KB
/ui + postureEnforcesWall via /security (marginal)+261.5 / +82.4 KB+177.8 / +56.7 KB
postureEnforcesWall via /security, alone588.7 / 147.6 KB505.0 / 121.8 KB

Module-scope purity measurement (#10031, ruling clause 2)

Method: per-entry fresh-process probe of the built .mjs (diff of Object.getOwnPropertyNames(globalThis), process.env, console output, process listeners) + a module-scope call-statement scan of src/**.

  • Source scan: exactly one module-scope call statement existed in the published graph — assertMetaUrlSpellingsAgree() in shared/metadata-url-spelling.ts — and this PR moves it to the build-time gate. (The *-type-assertions.ts probes are compile-only files referenced by no entry.)
  • Runtime probe, origin/main dist: 15 of 16 entries report exactly two new globals — __zod_globalConfig, __zod_globalRegistry — which are zod core's own globalThis-keyed initialization, not a spec module's write; contracts evaluates with zero observations. zod itself ships "sideEffects": false, so the library already declares that initialization droppable-when-unreachable.
  • Spec-side registry writes: none — no z.globalRegistry / z.config() / .register() call at module scope anywhere in src/**; .meta() registrations sit inside deferred lazySchema factories and are read back only through the same schema object.
  • Runtime probe, this branch's dist: unchanged for the schema entries (zod core globals only); meta-spelling probes PURE with zero observations.

No impure spec module surfaced, so the ruling's hard-stop condition was not met and sideEffects: false (not an allowlist) is the measured declaration.

Verification at head (all quoted from each gate's own verdict line, run at a9be45a)

  • check:generated aggregate: "✓ All 14 generated artifacts are up to date" — including check:meta-url-spelling ("35 spellings, 27 registry-declared types; manifest/derived agreement holds"), check:api-surface, check:export-origins, check:docs, check:authorable-surface, check:test-typecheck.
  • pnpm --filter @objectstack/spec test: "Test Files 414 passed (414) · Tests 11043 passed (11043)" (full suite, includes the new identity/freshness pins).
  • pnpm --filter @objectstack/spec typecheck: exit 0 (src + scripts + test programs).
  • Workspace closure: turbo build "Tasks: 70 successful, 70 total".
  • Path-derived gate families (dispatch-gates on the actual merge-base diff, no hand-fed paths): adr-0087-registration, changeset-no-major, empty-changeset, changeset-gate-self-tests, changeset-fixed, cross-package-test-inputs, dev-prereqs ("✓ @objectstack/spec built from the sources on disk"), osv-exemptions, required-contexts, shard-attestation, node-version, objectui-changeset, override-consistency, release-page-status, slot-lookup, spec-parsed-alias, type-source-resolution, workflow-status-functions, affected-docs, doc-formula-expressions, empty-state, liveness, strictness-ledger, variant-docs, merge-driver, nul-bytes — all green.
  • Test-file convention gates: type-check-coverage ("OK — 64/77 … 13 in the DEBT ledger"), type-check-debt --re-measure ("33 ledger entr(ies) re-measured … none above its recorded number"), engine-double-contract ("OK — 329 pinned"), where-matcher ("264 matcher(s) … correctly"), query-options-erasure ("ratchet holds") — all green.
  • Consumer-facing type check: a probe compiling canonicalMetaUrlType through the exports map under nodenext resolves the fresh .d.mts (reverse-verified: a wrong-type assignment goes red with TS2322).

Fences respected

Out-of-scope findings filed: #10199 (mechanize the principle as a gate — the ruling's welcomed follow-up), #10200 (residual retention after PURE marking).


Generated by Claude Code

…greement moves to a build-time gate (#10096)
The /meta URL-spelling contract (META_URL_TO_SINGULAR, canonicalMetaUrlType,
metaUrlSpellingRefusal, unrecognisedMetaTypeRefusal) gets its own entry whose
module graph is two pure modules — no zod, no registry closure. The map is
materialized at build time by gen:meta-url-spelling into
src/meta-spelling/meta-url-data.generated.ts (merge-driver registered,
check:generated GATED); check:meta-url-spelling re-derives the three-limb
union per CI lap and is the build-time enforcement home of the former
module-load assertMetaUrlSpellingsAgree() (maintainer ruling 2026-08-20:
moved, never dropped). /shared keeps all four symbols as re-exports of the
one declaration. Standing principle written into README.md and the root
module doc. Measured: the one fold costs 1110 B min / 530 B gz via the new
entry vs 578.4 KB min / 136.8 KB gz via /shared on origin/main.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016gcKVsiywU9CcS96S5t9qD
…on pure in emitted bundles (#10031)
Module-scope evaluation purity measured, not assumed: per-entry fresh-process
probe of every built entry (globalThis / process.env / console / listeners) —
the only observation is zod core's own globalThis-keyed __zod_globalConfig /
__zod_globalRegistry init (zod itself ships sideEffects:false); no spec module
writes a registry or mutates a global at module scope, and the one former
module-scope effect (the metadata-url-spelling agreement assertion) moved to
the build-time gate in the previous commit. A tsup esbuild onLoad transform
injects PURE annotations before lazySchema/strictObject/defineForm call sites
at parse time (esbuild preserves input annotations but does not re-emit
'pure'-option marks — measured), so consumer bundlers can drop unreached
schema consts; no source file is edited, keeping the two files held by
sibling claims untouched. Measured: the root + /security one-predicate
marginal falls from +261.5 KB min / +82.4 KB gz to +177.8 KB / +56.6 KB,
identical in both graph directions, reproducing the card's delta shrinking.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016gcKVsiywU9CcS96S5t9qD
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec, touching 29 documentable anchor(s).

24 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: node scripts/docs-audit/affected-docs.mjs --json 04096f17e14dccd9c8c9f1e5e924e1fc4dd99398.

4 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails.

What this run could not see
  • 6 changed file(s) yielded no anchor (packages/spec/README.md, packages/spec/api-surface/meta-spelling.json, packages/spec/export-origins/meta-spelling.json, …) — pages documenting those are invisible to this run
  • 1 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 45 of 221 client-bound route-ledger rows — the other 176 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run: node scripts/docs-audit/affected-docs.mjs --bridge-coverage

Coarse fallback — 115 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 04096f17e14dccd9c8c9f1e5e924e1fc4dd99398packageMentionDocs.

Which tree this was computed on

This run read content/docs from 9e7879f97ff071b39021b371e6022daa87cf633e — the merge of head a9be45a86f421a2a59c7d391217c60e64c80cf44 into base 04096f17e14dccd9c8c9f1e5e924e1fc4dd99398, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 9e7879f97ff071b39021b371e6022daa87cf633e && git checkout 9e7879f97ff071b39021b371e6022daa87cf633e
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 04096f17e14dccd9c8c9f1e5e924e1fc4dd99398 a9be45a86f421a2a59c7d391217c60e64c80cf44 && git checkout -B drift-repro 04096f17e14dccd9c8c9f1e5e924e1fc4dd99398 && git merge --no-ff a9be45a86f421a2a59c7d391217c60e64c80cf44
node scripts/docs-audit/affected-docs.mjs --json 04096f17e14dccd9c8c9f1e5e924e1fc4dd99398

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 04096f17e14dccd9c8c9f1e5e924e1fc4dd99398 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@github-actionsgithub-actionsBot added documentation Improvements or additions to documentation ci/cd dependencies Pull requests that update a dependency file tests tooling labels Aug 20, 2026
@qq9340100
qq9340100 marked this pull request as ready for review August 20, 2026 13:53
@qq9340100
qq9340100 enabled auto-merge August 20, 2026 13:53
@qq9340100
qq9340100 added this pull request to the merge queueAug 20, 2026
Merged via the queue into main with commit 923c424Aug 20, 2026
27 checks passed
@qq9340100
qq9340100 deleted the claude/issue-10096-schema-free-meta-spelling branch August 20, 2026 14:31
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci/cddependenciesPull requests that update a dependency filedocumentationImprovements or additions to documentationsize/xlteststooling

Projects

None yet

2 participants

@qq9340100@claude