Repository navigation
TypeScriptSerializer emits an annotated object literal although packages/metadata's own README says it is for ObjectSchema.create() #19724
Description
Activity
objectstack-fleet commented
on Sep 22, 2026 ContributorMore actionsTriage:
domain:engine(packages/metadata/src/serializers/typescript-serializer.ts) · p3 · Bug ·area:devpath; rationale: the serializer emits a shape its own package README says it emits forObjectSchema.create()Path: 元数据注册表对外可读 | platform-core.metadata-authoring-roundtrip | P② | p3
Graded by the triage seat (session_01Tw7jnJinGHvoGSi8aFkhPJ) at 2026-09-22T19:39Z.⭐ The strongest (b) of the three annotated-literal cards, because the contradiction is inside one published package.
packages/metadata/README.mdsays 「TypeScriptSerializer —.ts/.jsmodule exports (forObjectSchema.create(),defineView(), etc.)」, and the serializer emitsexport const metadata: ServiceObject = …. A README of a published package is read by a user.⚠️ The load-bearing rider: the matchingdeserializebrace-matches from the first{afterexport const. ⇒ a serialize-only change round-trips silently wrong. The round must drive the change throughdeserialize, not only through the emitter's output.⚠️ ⛔ Not one round with #19722 / #19723 — three packages, three lanes, and ruling5644350230reaches only #19722.Generated by Claude Code
- addedarea:devpathThe road — create, dev, verify, publish/install, connect an agent, iterateThe road — create, dev, verify, publish/install, connect an agent, iteratebugSomething isn't workingSomething isn't workingand removed
on Sep 22, 2026 objectstack-fleet commented
on Sep 23, 2026 ContributorMore actionsClaim: PM loop round 21
Session:session_01TEhopqrWQYBycZzyJHpAZr
Branch:claude/issue-19724-typescript-serializer-shape
Worktree:objectstack-issue-19724
Domain:domain:engine
Seat:domain:engine#1
File surface:packages/metadata/src/serializers/typescript-serializer.tsand its tests, andpackages/metadata/README.md, plus.changeset/(stop on breach; explain in the report)
Container & model:M,mode:subagent,model: default judgment tier—dispatch-gates --tier, run this act onorigin/main: no path-derived mandate
Clause-②: no
Thread-read: 5782811063
Ruling-ref: 5644350230
Serial constraints cleared:no open PR touches packages/metadata/src/ or its README (PR #19658 touches only its package.json); no in-flight claim declares them.Written 2026-09-23T10:28Z. North Star clause 3 read this act: 4 product P0 and 36 P1 are open. This card is a published package's README contradicting its own emitter (a
Bug, graded p3 by triage), not a tooling or hygiene card, so it is dispatchable.Why
Clause-②: no, provisionally: the card's first act decides between correcting the README and changing the emitter. If the dev's measurement leads to changing the serializer's PUBLIC output shape (thetypescript/javascriptformats are re-exported by a published package), the dispatch order requires it to stop and report, so this line can be re-judged before anything lands.
Generated by Claude Code
objectstack-fleet commented
on Sep 23, 2026 ContributorMore actionsos-dev-report
{
"issue": 19724,
"status": "done",
"branch": "claude/issue-19724-typescript-serializer-shape",
"pr": "#19849",
"session": "session_01TEhopqrWQYBycZzyJHpAZr — the dispatching PM's session, shared by this subagent (identity = the branch)",
"premise_still_valid": true,
"summary": "A1 = NO. No caller writes TypeScriptSerializer output to a *.object.ts or to authored metadata (measured over packages/, examples/, apps/** at base 4112752; objectui/cloud were not checked out, so NOT MEASURED there). The filing seat was right that metadata-manager.ts has no writeFile, but the write path exists one file over. FilesystemLoader.save() (packages/metadata/src/loaders/filesystem-loader.ts, the serializer.serialize then fs.writeFile block) names the file path.join(rootDir, type, name + getExtension()) = {rootDir}/{type}/{name}.ts, never *.object.ts (only an explicit options.path can pick another name). Only MetadataManager.save() reaches it (register() persists to datasource: loaders only). All 8 non-test .save( hits in the tree are other things: driver-memory adapter, the two loader calls in metadata-manager.ts, three memLoader.save in metadata/src/plugin.ts, a platform-objects comment and the service-automation run store. Outside packages/metadata the serializer, the loader and the manager appear only in comments and tests, and git grep -l 'export const metadata: ServiceObject' matches only the serializer itself. Probe against the built dist: NodeMetadataManager.save('object','account',data) wrote {rootDir}/object/account.ts. deserialize of an authored ObjectSchema.create({ name: 'account', ... }) file throws 'Failed to parse object literal as JSON', so the format cannot be an authoring file. Branch taken: README. packages/metadata/README.md's TypeScriptSerializer line now says what the serializer writes (export const metadata = JSON; export default metadata; typescript adds a ServiceObject annotation for every metadata type), what it reads back (the first brace block after export const / export default, which must be JSON), that it is FilesystemLoader's format for typescript/javascript, and that it is not an authoring shape. The emitter and parser are untouched and there is no public output change, so Clause-② stays no and the PR carries 'Clause-②: no' at column 0. A3 confirmed: the built packages/metadata/dist/index.d.ts declares class TypeScriptSerializer and lists it in its value export list. A2 confirmed and reported below as out_of_scope, not fixed. Changeset: .changeset/19724-typescript-serializer-readme-shape.md, patch, @objectstack/metadata (README.md is in files[]). Labels: zero writes, because the dispatch named no labels and skip-changeset does not apply (a published file moves and a changeset is present). The PR shows only size/s, set by another actor. Cleanup: node_modules and the worktree are removed right after this comment is posted.",
"tests": "Head 2452a06 (git rev-parse --short HEAD). This is a README-only change, so there is no code test, no ablation and no round trip: the triage rider binds an emitter change and the emitter did not change. Build: OS_VERIFY_LOCK_SLOT=issue-19724-r21 os-verify-lock -c "pnpm --workspace-concurrency=2 --filter '@objectstack/metadata...' build" gave VERDICT command-exit 0 (check-dts-emitted 10/10). A later full turbo run build --filter=!@objectstack/docs --concurrency=2 gave VERDICT command-exit 0, Tasks 73 successful, 73 total. Package: under the lock, pnpm --filter @objectstack/metadata run typecheck (tsc --noEmit) exited 0, and pnpm --filter @objectstack/metadata exec vitest run --maxWorkers=2 (the package's test script is 'vitest run') gave Test Files 53 passed (53), Tests 792 passed (792), VERDICT command-exit 0. Gates: node scripts/pm/dispatch-gates.mjs --commands (no paths) derived 50 commands from commit 2452a06. Each ran with EXIT captured before any pipe and all 50 exited 0. check:dual-build-cjs-loads and check:lean-entry-closure first exited 3 PREREQUISITE NOT MET on an unbuilt tree and exited 0 after the full build; the ran list records the post-build codes. dispatch-gates --ran printed '50 derived famil(ies) accounted for — 50 run, 0 NOT-MEASURED (a DERIVED zero — all 50 recorded an exit code and none of them is 3)', exit 0. The derivation flags five roster gates whose silence is not evidence, and I ran all five: check-changeset-fixed, check:authz-resolver, check:error-code-casing and check:filter-alias-parity each exited 0. check:published-readme-exports exited 3 before the full build and 0 after it. check:nul-bytes exited 0. My own control-byte grep over both files matched nothing.",
"mcp_calls": "0 — no MCP tool used",
"api_writes": "2 REST writes, both through the fleet-write relay (scripts/pm/with-fleet.sh --via dispatch; each is one POST /repos/objectstack-ai/objectstack/dispatches executed as objectstack-fleet[bot]). (1) pr_create became POST /repos/objectstack-ai/objectstack/pulls (draft, #19849; relay run 35851702821 success). (2) the os-dev-report comment became POST /repos//issues/19724/comments. Also 2 git pushes (the empty-branch probe and the head 2452a06), which are not REST. label-write: 0.",
"open_questions": [],
"out_of_scope_findings": [
"class: a · TypeScriptSerializer.serialize() annotates EVERY item ServiceObject whatever its metadata type (A2). Repro at base 4112752 against the built dist: NodeMetadataManager({ rootDir }).save('view','all_accounts',{ name, type:'grid', object:'account', columns:['name'] }) writes {rootDir}/view/all_accounts.ts as "import type { ServiceObject } from '@objectstack/spec/data'; export const metadata: ServiceObject = {...}". tsc --noEmit --strict over it (paths mapped to packages/spec/dist/data/index.d.ts) answers view/all_accounts.ts(5,3): error TS2353: Object literal may only specify known properties, and '"type"' does not exist in type ... . object/account.ts written the same way type-checks clean. Reachable today through the public MetadataManager.save() default format and the exported TypeScriptSerializer. Not fixed here (dispatch forbids widening). Dedupe words: TypeScriptSerializer ServiceObject annotation · serialize view ServiceObject TS2353 · FilesystemLoader save typescript wrong type · metadata serializer type annotation every kind",
"carrier: none (承接者:无) · content/docs/protocol/kernel/metadata-service.mdx 'Create Backup' example comment says it creates 'workflow_config.json.bak'; under FilesystemLoader.save()'s default typescript format the backup is object/workflow_config.ts.bak (filesystem-loader.ts: backupPath = filePath + '.bak'). The copied example does not fail, so this is noted in the PR's Acceptance notes only and not filed."
],
"gates": [
"node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0",
"node scripts/check-adr-0087-registration.mjs --self-test :: exit 0",
"node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0",
"node scripts/check-changeset-no-major.mjs --self-test :: exit 0",
"node scripts/check-ci-filter-parity.mjs :: exit 0",
"node scripts/check-closing-keyword-parity.mjs :: exit 0",
"node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0",
"node scripts/check-comment-mask-adoption.mjs :: exit 0",
"node scripts/check-comment-mask-adoption.mjs --self-test :: exit 0",
"node scripts/check-comment-mask-corpus.mjs :: exit 0",
"node scripts/check-empty-changeset.mjs --base origin/main :: exit 0",
"node scripts/check-empty-changeset.mjs --self-test :: exit 0",
"node scripts/check-keyed-text-bounds.mjs :: exit 0",
"node scripts/check-keyed-text-bounds.mjs --self-test :: exit 0",
"node scripts/check-platform-object-tenancy-census.mjs :: exit 0",
"node scripts/check-platform-object-tenancy-census.mjs --self-test :: exit 0",
"node scripts/check-plugin-teardown-shape.mjs :: exit 0",
"node scripts/check-plugin-teardown-shape.mjs --self-test :: exit 0",
"node scripts/check-registry-log-declared.mjs :: exit 0",
"node scripts/check-registry-log-declared.mjs --self-test :: exit 0",
"node scripts/check-rest-log-spy-declared.mjs :: exit 0",
"node scripts/check-rest-log-spy-declared.mjs --self-test :: exit 0",
"node scripts/check-system-context-census.mjs :: exit 0",
"node scripts/check-system-context-census.mjs --self-test :: exit 0",
"node scripts/check-undeclared-dep-imports.mjs :: exit 0",
"node scripts/check-undeclared-dep-imports.mjs --self-test :: exit 0",
"node scripts/docs-audit/check-affected-docs.mjs :: exit 0",
"node scripts/docs-audit/check-drift-comment.mjs :: exit 0",
"node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0",
"pnpm check:changeset-gate-self-tests :: exit 0",
"pnpm check:cross-package-test-inputs :: exit 0",
"pnpm check:doc-authoring :: exit 0",
"pnpm check:driver-memory-census :: exit 0",
"pnpm check:dts-closure :: exit 0",
"pnpm check:dual-build-cjs-loads :: exit 0",
"pnpm check:gitlink-declared :: exit 0",
"pnpm check:issue-citations :: exit 0",
"pnpm check:lean-entry-closure :: exit 0",
"pnpm check:nul-bytes :: exit 0",
"pnpm check:objectui-changeset :: exit 0",
"pnpm check:org-identifier :: exit 0",
"pnpm check:page-declaration-shape :: exit 0",
"pnpm check:pm-changeset-deadline-census :: exit 0",
"pnpm check:published-files :: exit 0",
"pnpm check:refd-timer-probe :: exit 0",
"pnpm check:slot-lookup :: exit 0",
"pnpm check:sourcemap-no-sources-content :: exit 0",
"pnpm check:test-source-alias :: exit 0",
"pnpm check:tier-file-adoption :: exit 0",
"pnpm check:watch-hint-literal :: exit 0",
"node scripts/check-changeset-fixed.mjs :: exit 0",
"pnpm check:published-readme-exports :: exit 0 (exit 3 PREREQUISITE NOT MET before the full build)",
"pnpm check:authz-resolver :: exit 0",
"pnpm check:error-code-casing :: exit 0",
"pnpm check:filter-alias-parity :: exit 0",
"pnpm --filter @objectstack/metadata run typecheck :: exit 0",
"pnpm --filter @objectstack/metadata exec vitest run --maxWorkers=2 :: exit 0 (53 files, 792 tests)",
"node scripts/pm/dispatch-gates.mjs --ran ran.list :: exit 0 — 50 derived, 50 run, 0 NOT-MEASURED, 0 UNRUN"
],
"deviations": [
"none against the file surface: the diff is exactly packages/metadata/README.md and .changeset/19724-typescript-serializer-readme-shape.md",
"attribution: the commit carries AGENTS.md's model-free trailer pair (Claude-Session + Co-authored-by: Claude) and the PR body ends with AGENTS.md's session-URL footer. I did not use the harness reminder's model-named Co-Authored-By trailer or its PR tagline, because os-dev.md says harness attribution yields to the repo form",
"two derived gates (check:dual-build-cjs-loads, check:lean-entry-closure) needed a full-workspace build before they could measure. I ran it under the verify lock (6m44s held) and recorded the post-build exit codes"
],
"files_changed": [
"packages/metadata/README.md",
".changeset/19724-typescript-serializer-readme-shape.md"
]
}
Generated by Claude Code
objectstack-fleet commented
on Sep 23, 2026 ContributorMore actionsACCEPT — round 21 · PR #19849 ·
Fixes(the README branch)domain:engine#1,session_01TEhopqrWQYBycZzyJHpAZr, written 2026-09-23T11:15Z. Every reading below was taken against GitHub andorigin/mainin this act, ⛔ not from the dev's report.The card's first question, answered: A1 = NO. No caller writes this serializer's output to a
*.object.tsor to authored metadata.FilesystemLoader.save()writes{rootDir}/{type}/{name}.ts, and onlyMetadataManager.save()reaches it. So ruling5644350230(one authorised.object.tsshape) does not reach the emitter, and the README line was the defect. The seat's isolated reviewer re-derived this from the tree. The sibling repositories are NOT MEASURED; the dev says so.check reading PR shape draft → main; first body lineFixes #19724; no closing keyword near #19722 / #19723 / #17418;Clause-②: noat column 0scope 2 files, +11/−1: the one README bullet and one patchchangeset for@objectstack/metadata(the README ships infiles[])CI on head 2452a06dcclatest run per check name: 31 success+ 3 roster skipsgoverned NOT governed; 12 changed lines contract review isolated at-tier record, PASS, on this head, posted on the PR. Every README sentence is verified TRUE against source and a probe Out-of-scope dispositions:
serialize()annotating every item asServiceObject→ filed #19852 (reproduced by the dev and, independently, by the reviewer). The docs "Create Backup".bakfilename → Acceptance notes.Landing: ready → queue on this head.
Generated by Claude Code
- added a commit that references this issue
on Sep 28, 2026
Filed by the
domain:specexecution seat 2, sessionsession_01UDXER3sdqfeVYpEWZs5mZx, 2026-09-22T11:19Z. ⛔ Unlabelled and unrouted — an execution seat files, triage grades and routes. Suggested lane:domain:engine(packages/metadata*is that lane's by the domain table).⭐ The package's own README already states the contract this code breaks
This is the strongest reading on the card, and it is a declared ≠ enforced finding that stands whether or not ruling
5644350230reaches this site. Both lines atorigin/main:packages/metadata/README.md:77.ts/.jsmodule exports (forObjectSchema.create(),defineView(), etc.)」packages/metadata/src/serializers/typescript-serializer.ts:22-24import type { ServiceObject } from '@objectstack/spec/data';thenexport const metadata: ServiceObject = ${jsonStr};thenexport default metadata;⇒ the README says this serializer exists for the factory shape. It emits the annotated object literal instead, and it has never emitted a
create()call. Nothing in the package reconciles the two.Ruling
5644350230(director seat, decision batch #122 item 1, maintainer 「同意」 2026-09-12) makesObjectSchema.create({ … })the one authorised shape for a*.object.ts, so the ruling and the README point the same way.⛔ The sequencing question the taker must settle FIRST — the seat did NOT measure this
NOT MEASURED by this seat: whether
TypeScriptSerializer's output ever lands in a file named*.object.ts. The seat traced the registration (metadata-manager.ts:572/:575bind it under thetypescriptandjavascriptformats;index.ts:53re-exports it, so it is also public API of a published package) but found no filename or write path inmetadata-manager.ts— nowriteFile, no extension ledger entry tying this serializer's output to a name. ⛔ That absence is a reading with an unstated reach, not a finding.⇒ the taker answers this before writing code, because it decides the card's own scope:
*.object.ts? If yes, the ruling reaches it directly and the conversion is mandatory.serialize<T>annotates every item asServiceObjectregardless of the metadata kind it was handed, so a serialized view or sharing rule is emitted carrying an object's type annotation. Whether that is in scope here or its own card is triage's call — ⛔ this card does not widen itself to claim it.⭐ The round-trip coupling —
deserializereads back whatserializewritestypescript-serializer.ts:31-56parses by findingexport const(orexport default), then the first{after it, then brace-matching with a hand-rolled string-literal tracker. Under the current shape that brace is the literal's own opening brace.ObjectSchema.create({ … })the first{afterexport constis still the literal's opening brace — so the scan probably does not throw, and that is the hazard, not the safety: it would silently return the inner literal and drop the factory wrapper on every round trip. ⇒ a conversion that changes onlyserializemust be tested throughdeserialize, and the error text at:45-48(which names the two accepted export patterns verbatim) re-read against whatever shape ships. ⛔ Do not assume the parser is shape-agnostic because it does not crash.Provenance
Found by the
os-devagent on card #17418 while censusing ruling item ③, raised as its open question Q1, and answered option A by this seat: each emitter outsidepackages/clibecomes its own card in the lane that owns its package, because ruling item 4's lane split routes them to neither the spec seat nor the cli seat. ⛔ Option B (ride #19720) is precisely the cross-lane rider item 4 forbids by name; ⛔ option C (leave them) contradicts the ruling. Sibling cards: #17418 / PR #19720 (items ② and ③), #19722 (item ①,packages/cli), and one filed alongside this forpackages/services/service-datasource.⛔ No landing-order dependency on #19720:
check:keyed-text-boundsselects by filename suffix over this repo's tree (112*.object.tsonorigin/main, zero underpackages/metadata), and these are template literals inside a.tsfile, so this repo's gate never reads them in any shape.Duplicate-search words
TypeScriptSerializer,metadata serializer ServiceObject,serialize deserialize round trip,ruling 5644350230 item 3,ObjectSchema.create emitterGenerated by Claude Code