Skip to content

fix(spec,core): give pluralToSingular a schema-free home on /meta-spelling - #12006

Merged
os-support-ai merged 3 commits into
mainfrom
claude/issue-11503-pluraltosingular-schema-free
Aug 25, 2026
Merged

fix(spec,core): give pluralToSingular a schema-free home on /meta-spelling#12006
os-support-ai merged 3 commits into
mainfrom
claude/issue-11503-pluraltosingular-schema-free

Conversation

@os-support-ai

Copy link
Copy Markdown
Collaborator

Fixes#11503

What

Restores the #10096 standing invariant for @objectstack/core's plural→singular store-key fold. The maintainer ruling on #10096 (2026-08-20), quoted verbatim and untranslated:

浏览器可达的 spec 导出面必须 schema-free。

@objectstack/core — on every browser consumer's eager graph through @objectstack/client — carried the one value import that violated it:

import { pluralToSingular } from '@objectstack/spec/shared';

Disposition 2 was measured first (triage's claim constraint), and the answer is no

Does core need the map at module scope at all, or can the read move behind the call?

  • pluralToSingular is called inside canonicalMetadataServiceType, a synchronous exported fold consumed on the store-key path of all three shipped IMetadataService implementations (createMemoryMetadata in core, MetadataManager, MetadataFacade) plus the spec-side contract double.
  • In ESM, a static import is linked whether or not the call runs — "behind the call" removes nothing from a bundler's eager graph. The only deferral that moves bytes is dynamic import(), which would force the whole register/read contract async: a contract-shape change, and a consumer-side workaround of exactly the kind rule 5 forbids.
  • Copying the map into core is forbidden by the MetadataFacade answers three registerget round-trip cases differently from every other shipped IMetadataService #7378 row-2 ruling (the map has ONE owner; a copy is per-implementation folk normalization).

So the module-scope value import is genuinely needed, and the fix is disposition 1: give the symbol a schema-free home.

Disposition 1 — widen ./meta-spelling (the sanctioned #10096 shape)

Per the claim's clause-② declaration: this PR does widen a spec export surface, and the widened entry is the sanctioned shape under #10096./meta-spelling is already the ruling's reference pattern and is already declared (with externals: []) in browser-reachable-entries.json, so no ledger promotion was needed; the existing gate now proves the widened surface too.

  • packages/spec/src/meta-spelling/manifest-collection-spelling.ts (new): the declaration of PLURAL_TO_SINGULAR / SINGULAR_TO_PLURAL / pluralToSingular / singularToPlural, moved verbatim, importing nothing.
  • packages/spec/src/shared/metadata-collection.zod.ts: re-exports the four symbols — /shared keeps its published surface, every existing consumer (kernel authoring lint, conversions, rest, runtime, objectql, …) is untouched.
  • packages/spec/src/meta-spelling/index.ts: exports the four symbols; module doc now documents the entry as two deliberately distinct spelling contracts. Per #7894 lands five new public exports in @objectstack/spec/shared — should the three predicate helpers stay internal? #8424 there is no merge: PLURAL_TO_SINGULAR (manifest-collection keys; no fields/seeds/external_catalogs/translations) and META_URL_TO_SINGULAR (URL path segments) stay distinct symbols, and the new pin test asserts both the re-export identity and the distinctness.
  • packages/core/src/metadata-service-contract.ts: the import flips to @objectstack/spec/meta-spelling; comment block rewritten to carry the reason.
  • packages/spec/scripts/build-meta-url-spelling.ts: the generator reads PLURAL_TO_SINGULAR from its new home (same one owner, same derivation).
  • scripts/check-stack-collection-maps.mjs: the PLURAL_TO_SINGULAR site pin follows the declaration to its new file (--self-test green).
  • Consumer vitest configs need no edits: since packages/runtime's vitest alias list omits @objectstack/spec/cloud, so any runtime test that loads a compiled artifact dies with ENOTDIR #9457 every aliasing config resolves @objectstack/spec/<ns> through one anchored regex, and meta-spelling matches it.

⛔ The spec root barrel was not touched (seat-ledger hazard: root-entry export changes can flip downstream d.ts synthesized specifiers). Verified below via the full-workspace typecheck.

Disposition 3 (mechanizing the principle as a gate) already landed as check:browser-reachable-entries (#10199) and is out of this card's scope; this PR only rides the existing gate.

Acceptance evidence (node-side, per the card)

  • Built @objectstack/coredist/ carries no value import of @objectstack/spec/shared; positive control: @objectstack/spec/meta-spelling present. (Readings in the os-dev-report on the issue.)
  • Built dist/meta-spelling/index.mjs (4.8 KB): pluralToSingular present, zod-machinery probe 0 hits; positive control: same probe on dist/shared/index.mjs hits.
  • check:browser-reachable-entries — "2 declared browser-reachable entries link no zod; 44 bundle(s) scanned, 40 zod link(s) seen elsewhere (instrument calibrated)".
  • check:meta-url-spelling — "meta-url-spelling data is current (34 spellings, 27 registry-declared types; manifest/derived agreement holds)."
  • check:stack-collection-maps — "7 enumerations reconciled against 31 declared collections (16 waiver rows, each with a reason)."

Test and typecheck readings (head 43986ca)

  • @objectstack/spec full suite (vitest, maxWorkers=2): first lap at 4f94bf0 — "Test Files 1 failed | 423 passed | 1 skipped (425) / Tests 1 failed | 11275 passed | 1 skipped (11277)"; the one red was scripts/export-origins.test.ts demanding the widened surface be recorded. gen:export-origins regenerated the two shards (that commit is 43986ca, JSON-only), and the failing file re-ran green on that tree: "Test Files 1 passed (1) / Tests 22 passed (22)". The shard JSONs feed no other test.
  • @objectstack/core full suite at 43986ca: "Test Files 38 passed (38) / Tests 939 passed (939)".
  • Full-workspace build at 43986ca: turbo "Tasks: 70 successful, 70 total".
  • Full-workspace typecheck at 43986ca (the specifier-drift check): turbo "Tasks: 129 successful, 129 total" — no downstream d.ts specifier blow-up; core's own emitted d.ts references @objectstack/spec/meta-spelling and contains zero spec/shared occurrences.
  • Reverse-verification of the new pin test (source-level mutation, trap-restored): baseline green → /shared mutated to a forked pluralToSingular (mutation confirmed on disk, marker grep = 1) → pin RED on exactly the identity assertion → restored (marker grep = 0) → green.
  • check:type-check-debt (re-measure on the built workspace), check:test-source-alias, check:cross-package-test-inputs, check:nul-bytes, changeset gates and the rest of the dispatch-gates derivation: readings in the os-dev-report comment on @objectstack/core imports pluralToSingular from @objectstack/spec/shared, putting the schema-bearing entry on every browser consumer eager graph #11503.

Refs

#10096 (standing ruling), #8424 (the two maps are deliberately distinct), #7378 (row 2: one owner), objectui#5359 / objectui#5324 (consumer measurements).


Generated by Claude Code

…lling (#11503)
Restores the #10096 standing invariant for core's store-key fold: the defineStack manifest-collection vocabulary moves to a leaf module on the /meta-spelling entry graph, /shared re-exports it, and core imports the fold from /meta-spelling instead of /shared.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K93P8PbH7kVjsAXkqzH1zY
…gins (#11503)
gen:export-origins — the four manifest-collection symbols land on the meta-spelling shard and shared's origins repoint to the moved declaration.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K93P8PbH7kVjsAXkqzH1zY
@github-actions

github-actionsBot commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/core, @objectstack/spec, touching 15 documentable anchor(s). ⚠️5 changed file(s) yielded no anchor (packages/core/src/metadata-service-contract.ts, packages/spec/README.md, packages/spec/api-surface/meta-spelling.json, …), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

29 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 1e79aa4f812708d5d097ca33d9b7040e02b958c6.

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

What this run could not see
  • 5 changed file(s) yielded no anchor (packages/core/src/metadata-service-contract.ts, packages/spec/README.md, packages/spec/api-surface/meta-spelling.json, …) — pages documenting those are invisible to this run
  • 24 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 45 of 222 client-bound route-ledger rows — the other 177 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
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.

Coarse fallback — 132 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 1e79aa4f812708d5d097ca33d9b7040e02b958c6packageMentionDocs.

Which tree this was computed on

This run read content/docs from 51930e80e7dd07e329c38524a4d357b4ab283e51 — the merge of head 59cfccf50862cca92c6fb7300890207ca4db1325 into base 1e79aa4f812708d5d097ca33d9b7040e02b958c6, 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 51930e80e7dd07e329c38524a4d357b4ab283e51 && git checkout 51930e80e7dd07e329c38524a4d357b4ab283e51
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 1e79aa4f812708d5d097ca33d9b7040e02b958c6 59cfccf50862cca92c6fb7300890207ca4db1325 && git checkout -B drift-repro 1e79aa4f812708d5d097ca33d9b7040e02b958c6 && git merge --no-ff 59cfccf50862cca92c6fb7300890207ca4db1325
node scripts/docs-audit/affected-docs.mjs --json 1e79aa4f812708d5d097ca33d9b7040e02b958c6

⚠️ 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 1e79aa4f812708d5d097ca33d9b7040e02b958c6 → 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 tests tooling labels Aug 25, 2026
#11503)
gen:api-surface — touched: meta-spelling.json; check:api-surface and check:generated green.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K93P8PbH7kVjsAXkqzH1zY
@os-support-ai
os-support-ai marked this pull request as ready for review August 25, 2026 06:56
@os-support-ai
os-support-ai added this pull request to the merge queueAug 25, 2026
Merged via the queue into main with commit f658793Aug 25, 2026
33 checks passed
@os-support-ai
os-support-ai deleted the claude/issue-11503-pluraltosingular-schema-free branch August 25, 2026 07:19
os-support-ai pushed a commit that referenced this pull request Aug 25, 2026
…os-regen deferral)
os-regen-merge.sh sequence: merge committed first, then the full gen chain on
the merged source. check:generated --fix regenerated the 4 provably stale
artifacts; the audit-door entries (this PR) and the #12001/#12006 sibling
entries coexist in the regenerated set.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01K93P8PbH7kVjsAXkqzH1zY
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentationImprovements or additions to documentationsize/mteststooling

Projects

None yet

Development

Successfully merging this pull request may close these issues.

@objectstack/core imports pluralToSingular from @objectstack/spec/shared, putting the schema-bearing entry on every browser consumer eager graph

2 participants

@os-support-ai@claude