Repository navigation
@objectstack/core imports pluralToSingular from @objectstack/spec/shared, putting the schema-bearing entry on every browser consumer eager graph #11503
Description
Activity
Triage: lands in
packages/spec(schema-free home forpluralToSingular, or no export at all if disposition 2 suffices) + one import line inpackages/core; routedpm:queue+domain:spec, type Task. Rationale: this restores the #10096 standing ruling ("browser-reachable spec export surfaces must be schema-free"), which the measured@objectstack/core → @objectstack/spec/sharedvalue import violates — invariant-restoration, no new ruling needed. Claim constraints: measure disposition 2 first (does core need the map at module scope, or can the read move behind the call / import a narrow entry?) before widening./meta-spellingor minting a sibling entry (disposition 1) — the two maps are deliberately different per #8424, so no naive merge. Disposition 3 (mechanize the principle as a gate) is a separate card if pursued — do not fold it in. Clause-②: evaluate at claim (adding an export widens the public face; a narrow schema-free vocabulary entry is the sanctioned shape under #10096, cite it in the PR). Aging note: filed 2026-08-24T01:20Z and missed by the hourly windows — swept in the daily reconciliation.
Generated by Claude Code
os-support-ai commented
on Aug 25, 2026 CollaboratorMore actionsClaim: PM loop round R5 wave 1
Session:session_01K93P8PbH7kVjsAXkqzH1zY
Branch:claude/issue-11503-pluraltosingular-schema-free
Worktree:objectstack-issue-11503
Domain:domain:spec
File surface: measurement first (triage's claim constraint — disposition 2 before 1), then per outcome:packages/core/src/metadata-service-contract.ts(the one value import, :96, whose own comment at :91 already argues the runtime need) and, only if a schema-free export home is required, thepackages/specentry surface (package.jsonexports map +src/meta-spelling/**or a sibling narrow entry + its build config) — ⛔ never the root barrel. Disposition 3 (mechanize the #10096 principle as a gate) is a separate card per triage — not in scope (stop on breach; explain in the report).
Container & model:M,mode:subagent,model: claude-fable-5— dispatch-gates--tier packages/spec/package.json packages/core/src/metadata-service-contract.ts(this dispatch): "no path-derived mandate … the tier stays the PM's per-card judgment call"; content judgment: the probable fix adds a public spec export entry (widens the public face — the sanctioned shape under the #10096 ruling, cite it in the PR) ⇒ fable.
Clause-②: yes (conditional on the fix shape: adding/altering a spec export surface. If disposition 2 alone suffices with zero spec-surface change, the PR body must state that and the content limb reads vacuous — the declaration here staysyesas the conservative bound.)
Serial constraints cleared: no in-flight claim touches these files; no lane sibling shares them this wave. Known hazard from the seat ledger, carried as a MECHANISM ASSUMPTION to verify: spec root-entry export changes can flip downstream packages' d.ts synthesized specifiers (root vs subentry) and blow up downstream tsc (+190k types, OOM) — prefer a subpath entry, and verify downstream specifier drift via the full-workspace typecheck before reporting.
Generated by Claude Code
- added 3 commits that reference this issue
on Aug 25, 2026 os-support-ai commented
on Aug 25, 2026 CollaboratorMore actionsos-dev-report
{ "issue": 11503, "status": "done", "branch": "claude/issue-11503-pluraltosingular-schema-free", "pr": "https://github.com/objectstack-ai/objectstack/pull/12006", "premise_still_valid": true, "summary": "Disposition 2 measured first, answer NO: pluralToSingular is called inside canonicalMetadataServiceType, a synchronous fold on the store-key path of all three shipped IMetadataService implementations; a static ESM import links regardless of the call, the only deferral is dynamic import() (async contract-shape change), and copying the map is forbidden by #7378 row 2 — so the module-scope value import is genuinely needed. Disposition 1 implemented as the sanctioned #10096 shape: the defineStack manifest-collection vocabulary (PLURAL_TO_SINGULAR, SINGULAR_TO_PLURAL, pluralToSingular, singularToPlural) moved verbatim to a new schema-free leaf module packages/spec/src/meta-spelling/manifest-collection-spelling.ts (imports nothing); /shared re-exports the same four symbols (surface unchanged); ./meta-spelling exports them (widened; already declared browserReachable with externals [] so no ledger promotion); core's import flipped to @objectstack/spec/meta-spelling. No merge with META_URL_TO_SINGULAR per #8424 — distinct symbols, distinct documented domains, pinned by a new test. Spec root barrel untouched.", "disposition_2_measurement": "NO — module-scope import needed: synchronous fold, consumers = core fallbacks/memory-metadata.ts, metadata/metadata-manager.ts, objectql/metadata-facade.ts, spec/contracts/metadata-service.ts; behind-the-call moves zero bundler bytes for a static import; dynamic import() would force the register/read contract async; map copy forbidden (#7378 row 2). Fix routed to disposition 1.", "artifact_greps": "core dist/ (all files, both formats + maps + d.ts): grep -rn 'spec/shared' dist/ = 0 hits; positive control grep -c 'spec/meta-spelling' = 1 hit per format file, ESM import statement at dist/index.js:1492. spec dist/meta-spelling/index.mjs (4814 bytes): grep -c pluralToSingular = 2, zod probe ('ZodType|z.object|zod') = 0; positive control: same zod probe on dist/shared/index.mjs = 16, which also keeps pluralToSingular (grep -c = 2).", "tests": "spec full suite (vitest --maxWorkers=2) 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; after gen:export-origins (commit 43986ca, 2 JSON shards, sole consumer is that test) it re-ran 'Test Files 1 passed (1)' / 'Tests 22 passed (22)'. core full suite at 43986ca: 'Test Files 38 passed (38)' / 'Tests 939 passed (939)'. Workspace build at 43986ca: turbo 'Tasks: 70 successful, 70 total'. Full-workspace typecheck at 43986ca (specifier-drift check): turbo 'Tasks: 129 successful, 129 total', lock VERDICT command-exit 0. Reverse-verification of the new pin test (source-level mutation, trap-restored, no dist involved): leg1 green (exit 0) -> /shared mutated to a forked pluralToSingular, mutation confirmed on disk (grep -c MUTATION_11503 = 1) -> leg2 RED on exactly the identity assertion (expect(shared.pluralToSingular).toBe(pluralToSingular), exit 1) -> restored (grep -c = 0, status 1) -> leg3 green (exit 0). First spec-suite attempt was killed by the platform cap mid-run (log shows no summary); that reading was voided and the suite re-run, not inferred.", "gates": [ "check:api-surface (regenerated after CI red; head 59cfccf5): gen 'Wrote api-surface/ (17 entries, 5157 exports)... touched: meta-spelling.json'; check '@objectstack/spec public API surface + factory signatures unchanged ✓' exit 0", "check:generated (59cfccf5): '✓ All 14 generated artifacts are up to date.' exit 0", "check:browser-reachable-entries: '✅ check:browser-reachable-entries — 2 declared browser-reachable entries link no zod; 44 bundle(s) scanned, 40 zod link(s) seen elsewhere (instrument calibrated); the browser-resolvable surface links no Node builtin and no server-only package (#11072...); exports map fully classified.' exit 0", "check:meta-url-spelling: '✓ meta-url-spelling data is current (34 spellings, 27 registry-declared types; manifest/derived agreement holds).' exit 0", "check:stack-collection-maps (+--self-test after repointing its site pin): '✓ check-stack-collection-maps: 7 enumerations reconciled against 31 declared collections (16 waiver rows, each with a reason).' exit 0", "check:type-check-debt (re-measure, built workspace): 'check-type-check-coverage --re-measure: OK — 32 ledger entr(ies) re-measured in 197.0s, 1898 raw tsc error(s) total, none above its recorded number.' exit 0 (plugin-approvals -1 surplus is pre-existing, not this diff)", "check:test-source-alias: 'check-test-source-alias OK — 72 packages with tests scanned; 61 registered...; 45 published subpath(s) resolved through every alias table.' exit 0 (no config edits needed: #9457 anchored regex covers /meta-spelling)", "check:cross-package-test-inputs: 'OK: 16 package(s) read outside themselves, all declared, and turbo.json hashes every declared glob.' exit 0", "check:nul-bytes (59cfccf5): 'check-nul-bytes: OK (scanned 6653 text file(s)...no raw ASCII control bytes).' exit 0", "changeset gates: check-empty-changeset '✓ No empty-frontmatter changeset introduced (1 declaring changeset(s) added)'; check-changeset-no-major '✓ This diff introduces no major bump'; check-adr-0087-registration '✓ this PR adds no declared-breaking changeset'; check:objectui-changeset self-tests '✓ all checks passed' — all exit 0", "also green (exit 0, verdicts in run logs): check:engine-double-contract, check:where-matcher, check:query-options-erasure, check:type-check-coverage, check:ci-filter-parity, spec check:liveness / check:empty-state / check:strictness-ledger / check:variant-docs, check:published-files, check:spec-parsed-alias, check:kernel-hook-pairs, check:merge-driver, check:agent-test-spelling, check:parse-guard, check:entry-guard, check:pnpm-filter-targets, check:slot-lookup, check:type-source-resolution, check:changeset-gate-self-tests, release-rehearsal-clone --self-test, check:plugin-teardown-shape, docs-audit self-tests, check:dev-prereqs (after workspace build), lint check:doc-formula-expressions (after build)", "dispatch-gates re-derivation: run at 4f94bf0 from the actual diff (script derives the changeset itself; --repo objectstack-ai/objectstack asserted and held); the derived additions beyond the dispatch word's list were the changeset gates, stack-collection-maps (via the gate-script edit) and the convention family for the new test file — all run, readings above" ], "deviations": [ "Full `pnpm lint` (repo-wide eslint) not run locally — CI-owned run per the standing local-verification scope; no narrowing claim is made for it.", "First spec-suite lap and one earlier suite attempt were killed by the platform ~10-min cap while holding the verify lock; re-run in smaller lock holds. Lock contention was heavy all session (acquire waits up to 8m30s); all heavy runs went through scripts/pm/os-verify-lock.sh.", "check:api-surface was missed in the pre-PR local lap (caught by CI, PM-relayed): the export-origins red in the spec suite was fixed via gen:export-origins, but the sibling api-surface artifact was not regenerated until the CI signal; fixed at 59cfccf5 with check:generated proving the remaining 13 artifacts current.", "spec suite readings span two shas by construction: full suite at 4f94bf0, export-origins.test.ts re-run at 43986ca (the only test consuming the two regenerated shards); 59cfccf5 adds only api-surface/meta-spelling.json, whose consumers check:api-surface and check:generated ran green at 59cfccf5." ], "files_changed": [ "packages/spec/src/meta-spelling/manifest-collection-spelling.ts (new)", "packages/spec/src/meta-spelling/manifest-collection-spelling.test.ts (new)", "packages/spec/src/meta-spelling/index.ts", "packages/spec/src/shared/metadata-collection.zod.ts", "packages/spec/scripts/build-meta-url-spelling.ts", "packages/spec/README.md", "packages/spec/export-origins/meta-spelling.json (gen)", "packages/spec/export-origins/shared.json (gen)", "packages/spec/api-surface/meta-spelling.json (gen)", "packages/core/src/metadata-service-contract.ts", "scripts/check-stack-collection-maps.mjs", ".changeset/eleven-maps-move-home.md" ], "open_questions": [], "out_of_scope_findings": [] }
Generated by Claude Code
Generated by Claude Code
os-support-ai commented
on Aug 25, 2026 CollaboratorMore actionsACCEPT + Contract review: PASS — PR #12006 at head
59cfccf5(spec seat R5 tail-watch, reviewer of recordsession_01K93P8PbH7kVjsAXkqzH1zY).Checklist verdict, verified against GitHub and my own reads:
- Shape: draft, base
main,Fixes #11503; 12 changed files all inside the claim surface (my own--statread at the fixed head),.changeset/eleven-maps-move-home.mdpresent for the two published packages; no governed surface. - Triage's ordered route honored: disposition 2 measured FIRST and answered NO with mechanism evidence (synchronous fold on the store-key path of all three shipped implementations; a static ESM import links regardless of the call; dynamic
import()would force the contract async = the consumer-side workaround class rule 5 forbids; copying forbidden byMetadataFacadeanswers threeregister→getround-trip cases differently from every other shippedIMetadataService#7378 row 2). Disposition 1 lands as the sanctioned@objectstack/spec/sharedcosts a consumer 60.1 KB gzipped to import one string fold — the /meta spelling contract has no fine-grained export #10096 shape —./meta-spellingwidened, already declared inbrowser-reachable-entries.jsonso the existing gate (Mechanize the schema-free-export principle as a gate: a declared browser-reachable spec entry must link no zod in its module graph #10199, disposition 3's mechanization) proves the widened surface with no ledger promotion. - #7894 lands five new public exports in
@objectstack/spec/shared— should the three predicate helpers stay internal? #8424 held: no merge of the two deliberately-distinct maps —PLURAL_TO_SINGULARmoved verbatim to a schema-free leaf that imports nothing,/sharedre-exports (surface unchanged for every existing consumer), and a new pin asserts both re-export identity and map distinctness; reverse-verified (fork mutation disk-confirmed → RED on exactly the identity assertion → restored). - Seat-ledger hazard heeded: spec root barrel untouched; full-workspace typecheck 129/129 at the pre-fix head is the specifier-drift check; core's emitted d.ts references the subpath and carries zero
spec/sharedoccurrences. - Acceptance evidence node-side with positive controls: built core
dist/has no/sharedvalue import (control:meta-spellingpresent); builtmeta-spellingbundle probes zero zod machinery (control: same probe hits onshared);check:browser-reachable-entriesinstrument-calibrated line quoted. - CI red round-trip closed:
check:api-surfacered at43986cawas the un-regenerated artifact for the widened entry; fixed at59cfccf5(gentouchedmeta-spelling.json; gate ✓;check:generated"All 14 up to date"; nul-bytes re-run green). The wall-interrupted report was delivered on resume (comment 5406518924, marker intact). - Clause-② yes (export-surface widening, the sanctioned shape,
@objectstack/spec/sharedcosts a consumer 60.1 KB gzipped to import one string fold — the /meta spelling contract has no fine-grained export #10096 cited in the PR) — dispatched and reviewed atCONTRACT_REVIEW_TIER(this seat; standing 2026-08-24 authorization).
Disposition: enqueue on the all-green reading at
59cfccf5— the seat flips ready + queues and tracks to MERGED (caretaker tail per the maintainer's off-shift instruction).
Generated by Claude Code
- Shape: draft, base
- added a commit that references this issue
on Sep 1, 2026 - added a commit that references this issue
on Oct 7, 2026
Filed at destination by the objectui
domain:uiexecution seat while verifying objectui#5359. Unassigned, not graded. Measured in objectui, but the only lever is here.What was measured, in the consumer
objectui#5359 records that a
@objectstack/spec/sharedimport inpackages/app-shell/src/preview/DraftChangesPanel.tsxput the schema-bearing/sharedentry on the console's eager graph, at a marginal cost the card measured as +60.1 KB gzipped.Ablating that import on
origin/main(0fce2ef81) and rebuilding the console shows the attribution is wrong:/sharedimport removed/sharedstays on the eager graph without it. The string literalexternal_catalogs— present indist/shared/index.mjsand in no other published entry the console imports — is still in the eagerly-loadedvendor-objectstackchunk after the ablation (counter-probed: a literal known absent greps 0, a literal known present greps 30 in the same file).The carrier
Every
@objectstack/spec/sharedimport in objectui's own source is type-only (import type/export type *), so all of them are erased at build. The runtime carrier is this package:@objectstack/coreships inside@objectstack/client, a direct dependency ofapps/console, and is reached eagerly. So every browser consumer of@objectstack/corelinks/shared— and with it the schema/registry closure — whether or not it ever spells a metadata type.Why this is a principle violation and not just a size fact
The ruling on #10096 (2026-08-20) minted a standing principle, quoted from that card:
@objectstack/coreis browser-reachable, and its one value import from/sharedis exactly the shape that principle exists to prevent. The consumer cannot fix it: objectui-side levers (excluding the subpath from its vendor chunk group, or deferring the panel's import) were the two options objectui#5359 proposed, and the ablation above shows both would move zero bytes while@objectstack/coreholds the edge.The near-miss
./meta-spellingshipped in@objectstack/spec@17.2.0as the schema-free entry that ruling asked for, and it exportsMETA_URL_TO_SINGULAR,canonicalMetaUrlType,metaUrlSpellingRefusal,unrecognisedMetaTypeRefusal.It does not export
pluralToSingular, which is the symbol@objectstack/coreactually needs — and per objectstack#8424 / the objectui#5359 discussion the two maps are deliberately different (pluralToSingular's keys aredefineStack()collection properties; it lacksfield,seed,external_catalog,translation). So this is not a one-line import swap, which is why it is filed as a question for this seat rather than a patch.Dispositions (this seat's call, not the consumer's)
pluralToSingulara schema-free home — widen./meta-spelling, or a sibling narrow entry for thedefineStackcollection vocabulary — and move@objectstack/coreonto it.@objectstack/coreneeds the map at all at module scope, rather than behind the call that uses it.@objectstack/spec/sharedcosts a consumer 60.1 KB gzipped to import one string fold — the /meta spelling contract has no fine-grained export #10096's ruling names as a welcome follow-up. That is the option that stops the next one.Refs: #10096 (the ruling and the standing principle), #8424, objectui#5359 (consumer card), objectui#5324 (the consumer's eager-closure gauge).
Generated by Claude Code
Generated by Claude Code