Repository navigation
metadata: the declared save option sortKeys is honoured by the json and yaml formats but silently ignored by the default typescript format #19872
Description
Activity
objectstack-fleet commented
on Sep 23, 2026 ContributorAuthorMore actions定级
pm:queue·priority:p3·bug·domain:engine——sortKeys在默认的 typescript 格式下被静默忽略Path: platform-core
Triage: lands in
packages/metadata/src/serializers/typescript-serializer.ts(never readsoptions.sortKeys) ⇒domain:engine; rationale: a declared save option is honoured byjson/yamland silently dropped bytypescript, which isFilesystemLoader.save()'s default format, so a caller asking for deterministic key order gets unsorted keys with no signal; the cost is noisy diffs of saved files, not wrong data ⇒ p3; PR #19865 (same file) has landed, so the site is free.分诊席(
session_01Tw7jnJinGHvoGSi8aFkhPJ),2026-09-23T15:24Z。本席读完了卡面(本卡尚无评论)。本席的读数(
origin/maindabf8d795e)sortKeys出现次数:typescript-serializer.ts0,json-serializer.ts2,yaml-serializer.ts2。metadata-persistence.zod.ts:273声明了sortKeys: z.boolean().optional();filesystem-loader.ts:413默认format = 'typescript'。- 卡面说要排在 PR fix(metadata): TypeScriptSerializer annotates each item with its own metadata type's spec type, never ServiceObject for a non-object #19865 之后:该 PR 已落地(
e9eb2244d5,TypeScriptSerializer.serialize()annotates every item asServiceObjectwhatever its metadata type — a saved view (or any non-object kind) is written as a.tsfile that failstscwith TS2353 #19852),⛔ 不再挂阻塞。
与卡面一致。
判定
- 业务后果:把元数据存到文件、要求
sortKeys: true的人,在默认格式下拿到的键顺序没有排,也没有任何提示。这个选项本来就是为了让保存下来的文件 diff 稳定。 p3:声明了却不生效(北极星第 4 条「错的必须响亮」),但只影响键顺序,⛔ 不影响数据内容。- 修法方向(⛔ 不是裁定,卡面已写):对 typescript 格式包着的那段 JSON 正文用与 JSON 序列化器相同的排序;或者对这个格式响亮地拒收
sortKeys。要去掉的是「静默接受」。
Generated by Claude Code
- addedbugSomething isn't workingSomething isn't workingand removed
on Sep 23, 2026 objectstack-fleet commented
on Sep 23, 2026 ContributorAuthorMore actionsClaim: PM loop round 21
Session:session_01TEhopqrWQYBycZzyJHpAZr
Branch:claude/issue-19872-ts-serializer-sortkeys
Worktree:objectstack-issue-19872
Domain:domain:engine
Seat:domain:engine#1
File surface:packages/metadata/src/serializers/typescript-serializer.ts, its tests underpackages/metadata/src/serializers/, theTypeScriptSerializerparagraph ofpackages/metadata/README.mdif it states option handling, one.changeset/*.md(stop on breach; explain in the report). ⛔ No change to any declaration reachable from an@objectstack/metadataexportsentry
Container & model:S,mode:subagent,model: sonnet(no path-derived mandate; floor tier — a declared option the other two formats already honour)
Clause-②: no
Thread-read: none
Serial constraints cleared:PR #19865 (#19852), which edited typescript-serializer.ts, landed as e9eb2244d5. No open PR and no in-flight claim touches packages/metadata/src/serializers.Written 2026-09-23T15:53Z. North Star clause 3 read this act: product P0/P1 are open. This card is a product defect (a declared save option silently ignored by the default format), not a tooling or contract-hygiene card, so it is dispatchable at p3.
Why
Clause-②: no:sortKeysis already declared and honoured by the json and yaml formats; the typescript format starts honouring it. No accept set widens and no public surface moves.
Generated by Claude Code
objectstack-fleet commented
on Sep 23, 2026 ContributorAuthorMore actionsos-dev-report
{
"issue": 19872,
"status": "done",
"branch": "claude/issue-19872-ts-serializer-sortkeys",
"pr": "#19901",
"session": "session_01TEhopqrWQYBycZzyJHpAZr",
"premise_still_valid": true,
"summary": "A1: reproduced on origin/main (dabf8d7) — sortKeys:true sorted the json output's keys and left typescript's unsorted; javascript (same TypeScriptSerializer class, same renderModule() code path) is affected identically and is fixed by the same code change (pinned separately). A2: JSONSerializer's sort is deep/recursive (recurses into nested objects; arrays keep element order) — matched exactly, not approximated as top-level-only. .d.ts diff: built @objectstack/metadata on origin/main (dabf8d7, via a comparison worktree) and on this branch's head (67015fe), diffed every exports entry's emitted .d.ts (., ./node, ./migrations, ./errors, ./view-container) — identical for 4/5; index.d.ts (and node.d.ts, which re-exports it, also checked identical) differ by exactly one added JSDoc comment on JSONSerializer's pre-existing private sortObjectKeys member, no declaration line changed, class shape unchanged. Implementation: extracted JSONSerializer's recursive key sort into a new package-internal packages/metadata/src/serializers/sort-object-keys.ts (not re-exported from any @objectstack/metadata exports entry). TypeScriptSerializer's renderModule() — the one function both the public serialize() and the package-internal serializeTypeScriptForMetadataType() call — now sorts its JSON body with that same shared function when options.sortKeys is true; sortKeys absent/false takes the untouched path (byte-identical output). JSONSerializer's own private sortObjectKeys() is kept as a thin delegate to the shared function (rather than deleted) specifically so its declared shape, and the package's emitted .d.ts, stay unchanged apart from the added comment. Added a patch changeset for @objectstack/metadata. packages/metadata/README.md's TypeScriptSerializer paragraph is silent on which SerializeOptions it honours (no false sortKeys claim to correct), so left unedited per the file surface's own stated condition.",
"tests": "pnpm --filter '@objectstack/metadata^...' build (dependency closure): exit 0. pnpm --filter @objectstack/metadata build on both origin/main (dabf8d7, in a throwaway comparison worktree) and HEAD (67015fe): exit 0 both, .d.ts diffed as described above. pnpm --filter @objectstack/metadata test: 54 files / 821 tests passed, exit 0 — includes new describe('sortKeys (#19872)') in serializers.test.ts (7 tests: sortKeys:true through serialize(), serializeTypeScriptForMetadataType(), and the javascript format, each byte-identical to JSONSerializer's own sorted output; sortKeys absent/false byte-identical to origin/main through both direct calls and FilesystemLoader.save(); a serialize/deserialize round trip with sortKeys:true for view and object; FilesystemLoader.save() honouring sortKeys:true for object through the loader path, A3). pnpm --filter @objectstack/metadata typecheck: exit 0. Ablation (fix committed first, at 67015fe): reverted json-serializer.ts + typescript-serializer.ts to origin/main's version and removed the new sort-object-keys.ts, ranvitest run src/serializers/serializers.test.ts -t 'sortKeys (#19872)'— 4 failed (every sortKeys:true pin) / 3 passed (byte-identity-off + round trip, order-insensitive) / 23 skipped, exit 1 as expected; restored withgit checkout HEAD -- <3 files>, verifiedgit diff HEADempty andgit hash-object==git rev-parse HEAD:<path>for all three files; rebuilt, exit 0. Gates:node scripts/pm/dispatch-gates.mjs --commandson the final commit (67015fe) derived 59 families; ran all 59 with exit codes captured before any pipe (cmd > log 2>&1; code=$?), reconciled with--ran <file recording 'cmd :: exit N'>: '59 derived famil(ies) accounted for — 56 run, 3 NOT-MEASURED (3 DERIVED from a recorded exit 3)'. The 3: pnpm check:dual-build-cjs-loads, pnpm check:lean-entry-closure, pnpm check:type-check-debt — each exits 3 and self-declares 'PREREQUISITE NOT MET ... this is NOT a pass and NOT a finding: nothing was measured', needing a fullpnpm build/turbo run buildacross the whole ~82-package monorepo (dist for packages far outside @objectstack/metadata's dependency closure, e.g. objectql, runtime, the apps) — outside the local dependency-closure build scope; CI's Build Core / Type Check jobs cover these. The other 56: exit 0. Also ran the live verdict (the known dispatch-gates gap: its check:issue-citations family runs only --self-test):node scripts/check-issue-citations.mjs— exit 0 ('every citation this change adds resolves').",
"mcp_calls": "5 — mcp__github__issue_read (method: get_comments, on #19872), mcp__github__issue_read (method: get, on #19872), mcp__github__pull_request_read (called with an invalid pullNumber before I had the PR number; client-side InputValidationError, no server call made), mcp__github__pull_request_read (method: get, on #19901, to verify the body wrote back verbatim), mcp__github__list_pull_requests (to find PR #19901 by head branch). All five are reads; zero MCP write tools were used.",
"api_writes": "2 — POST /repos/objectstack-ai/objectstack/pulls (op pr_create, draft:true, via scripts/pm/with-fleet.sh --via dispatch, relay run 35888473148, conclusion success) and POST /repos//issues/19872/comments (this report, same relay). git push (the empty-branch probe and the real commit) is not a REST write and did not go through the relay, per the branch's own routing rule.",
"open_questions": [],
"out_of_scope_findings": []
}objectstack-fleet commented
on Sep 23, 2026 ContributorAuthorMore actionsACCEPT — round 21 · PR #19901 ·
Fixesdomain:engine#1,session_01TEhopqrWQYBycZzyJHpAZr, written 2026-09-23T16:42Z. Every reading below was taken against GitHub andorigin/mainin this act, ⛔ not from the dev's report.check reading head 67015feb5fPR shape draft → main; first body lineFixes #19872; no other closing keyword;Clause-②: noat column 0, agreeing with thepatchchangesetscope 5 files: JSONSerializer's recursive key sort moved verbatim into a package-internalsort-object-keys.ts(the private method kept as a thin delegate so the class's declared shape is unchanged),renderModule()sorts the JSON body whensortKeysis true, tests, one changesetreach the javascriptformat shares the class and the path and is fixed by the same change; bothserialize()and the loader's internal path honour the optionbyte-identity sortKeysabsent or false: 28/28 format × path combinations byte-identical to the base (contract reviewer's measurement on built dists)published surface the emitted declarations of all 5 exportsentries differ only by one comment;sort-object-keys.tsis reachable from noneablation reverting the fix reds exactly the four sortKeys: truepinsgoverned NOT governed ( check-governed-merges --pr 19901)contract review PASS on this head (5798812881) CI every check successor a roster skip on this head before readyOut-of-scope findings — dispositions: none reported by the dev. The contract reviewer's flags are all acceptable as they stand: yaml's key order differs from json's only for integer-like keys (pre-existing), and
packages/metadata/README.mdcredits key sorting toJSONSerializeralone (an understatement, not false) → Acceptance notes.Process note: the dev's final report omitted the
files_changed,deviationsand structuredgatesfieldsos-dev.mdrequires; the seat read the file list from the diff and the gates from the report's prose.Landing: ready → queue on this head once CI is fully green.
Generated by Claude Code
- added a commit that references this issue
on Sep 28, 2026
Filing gate: ① a defect with a named landing site,
TypeScriptSerializerinpackages/metadata/src/serializers/typescript-serializer.ts(it never readsoptions.sortKeys). Finding class (b): a declared option that one format does not honour.Filed by the
domain:engineexecution seat 1 (session_01TEhopqrWQYBycZzyJHpAZr) from the out-of-scope findings of its #19852 dev (report 5795522449 on #19852). The seat's isolated contract reviewer confirmed it on PR #19865 (record 5796391892). ⛔ Filed bare: routing and grading are triage's. ⛔ Not a claim.What happens
MetadataSaveOptionsSchemadeclaressortKeys: z.boolean().optional()(packages/spec/src/system/metadata-persistence.zod.ts).FilesystemLoader.save()passes it to the serializer.JSONSerializerandYAMLSerializerhonour it;TypeScriptSerializerignores it, andtypescriptisFilesystemLoader.save()'s default format, soMetadataManager.save()routed to the filesystem drops the option by default.Probe (the #19852 dev): serialize an object with
sortKeys: true. Thejsonkeys come out[fields, label, name]; thetypescriptkeys stay[name, label, fields]. Onorigin/mainthe seat countedsortKeysreferences:typescript-serializer.ts0,json-serializer.ts2.Reach
Anyone saving metadata to the filesystem with
sortKeys: truein the default format gets unsorted keys and no signal. Deterministic key order is what the option exists for (stable diffs of saved files).Suggested shape (⛔ not a ruling)
Apply the same key sort the JSON serializer uses to the JSON body the typescript format wraps, or refuse
sortKeysfor that format loudly. Silent acceptance is the one outcome to remove. It lands in the same file as #19852 (PR #19865), so it is serial behind that PR.Filing-gate answers
packages/metadataafter triage routes it (the domain table:domain:engine), after PR fix(metadata): TypeScriptSerializer annotates each item with its own metadata type's spec type, never ServiceObject for a non-object #19865 lands.closedincluded:sortKeys ignored typescript serializer MetadataSaveOptions sortKeys typescript format→ 1 hit:TypeScriptSerializer.serialize()annotates every item asServiceObjectwhatever its metadata type — a saved view (or any non-object kind) is written as a.tsfile that failstscwith TS2353 #19852 (the annotation defect, not this one).Dedupe words:
sortKeys typescript serializer ignored·TypeScriptSerializer sortKeys·MetadataSaveOptions sortKeys typescript formatGenerated by Claude Code