Skip to content

Mechanize the schema-free-export principle as a gate: a declared browser-reachable spec entry must link no zod in its module graph #10199

Description

@qq9340100

The 2026-08-20 ruling on #10096 minted a standing principle for @objectstack/spec export design (recorded verbatim on that card):

浏览器可达的 spec 导出面必须 schema-free。 A @objectstack/spec export surface that browser/client consumers reach must carry vocabulary — maps, folds, enums, pure predicates — without linking the zod schema/validation machinery.

The implementing PR wrote the principle into the package docs (packages/spec/README.md "Export surfaces", src/index.ts module doc) and shipped the reference pattern (@objectstack/spec/meta-spelling, derived at build time by gen:meta-url-spelling). The ruling explicitly named mechanizing the principle as a welcome follow-up card, not a requirement of that landing — this is that card, filed unassigned.

What a gate would check

For each entry the spec seat declares browser-reachable (a declared list, starting with ./meta-spelling), assert its built module graph links no zod machinery. Two candidate instruments, cheapest first:

  1. Static scan of the built entry bundle — each entry is a self-contained bundle (dist/<entry>/index.mjs), so an inlined zod runtime marker in the file is a one-line detection (the @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 landing verified this manually: dist/meta-spelling/index.mjs has zero imports and zero zod content). Cheap, runs post-build, no bundler needed.
  2. esbuild probe — bundle a synthetic entry importing the subpath with a metafile and assert no zod input appears. Heavier but measures the consumer-visible truth (what the exports map resolves to), not the build layout.

Either way the declared-browser-reachable list is the contract half: an entry not on the list is unjudged, and adding an entry to the exports map without classifying it should fail the gate's reconciliation (the check:generated ledger pattern).

Why it should exist

The principle currently binds as prose. The measured failure shape it guards against (#10096: one string fold costing +246.9 KB minified / +69.7 KB gz through /shared; #10031: +261.5 KB minified for one pure predicate through /security, both on main @cc21aad8e) was invisible to every existing gate — objectui#5324 records that the consumer's own budget check reported PASS on the PR that added the bytes.

Metadata

Metadata

Assignees

Type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions