Repository navigation
fix(spec): read a stored object_extension row as an objectExtensions entry, and list its refused fieldPatches keys - #29589
Conversation
…entry, and list its refused fieldPatches keys The stored pass returned an object_extension row untouched because its type mapped to no stack collection, so no conversion or refused-shape detector ran over a stored extension. Map it in the stored-only map, add the REFUSED_STORED_SHAPES detector for object-extension-field-patch-key-grammar-refused, and correct that entry and the #29366 changeset, which said --stored does not list such a row. Claude-Session: https://claude.ai/code/session_01JQwxbXpawDQBhgKU2ssyrw Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): 5 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 2 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 168 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 0d8655e5130f53459b9276ea92f0befb26b8f02d && git checkout 0d8655e5130f53459b9276ea92f0befb26b8f02d
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin d3175d59d036d3c2819d5e7913c8c406c1ea634d 91fbde288442373ea5a36341ba83dea58172938c && git checkout -B drift-repro d3175d59d036d3c2819d5e7913c8c406c1ea634d && git merge --no-ff 91fbde288442373ea5a36341ba83dea58172938c
node scripts/docs-audit/affected-docs.mjs --json d3175d59d036d3c2819d5e7913c8c406c1ea634d
|
1 similar comment
📓 Docs Drift CheckThis PR changes 1 package(s): 5 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 2 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 168 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 0d8655e5130f53459b9276ea92f0befb26b8f02d && git checkout 0d8655e5130f53459b9276ea92f0befb26b8f02d
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin d3175d59d036d3c2819d5e7913c8c406c1ea634d 91fbde288442373ea5a36341ba83dea58172938c && git checkout -B drift-repro d3175d59d036d3c2819d5e7913c8c406c1ea634d && git merge --no-ff 91fbde288442373ea5a36341ba83dea58172938c
node scripts/docs-audit/affected-docs.mjs --json d3175d59d036d3c2819d5e7913c8c406c1ea634d
|
|
domain:spec#3 · os-warren · session_01JQwxbahe4v7GBD94QeSnir Contract reviewServed-tier: Inputs read: card #29554 body and all three comments (triage, claim, os-dev-report); PR #29589 body, file list (7 files) and the net diff against merge base ① Derived judgments
② Semver level
Clause-②: no (no accept set moves; the stored pass and the ③ Boundary flags
④ Design judgment
Should-fix (follow-up card, not this PR; outside the file surface): at the two rehydration seams the once-per-process notice dedupe keys on No must-fix. Implemented-by: VERDICT: PASS Generated by Claude Code |
|
domain:spec#3 · os-warren · session_01JQwxbahe4v7GBD94QeSnir 维护者速读
席位核对ACCEPT ·
Generated by Claude Code |
|
domain:spec#3 · os-warren · session_01JQwxbahe4v7GBD94QeSnir Maintainer approval · 2026-11-03T00:11ZWarren approved this PR's open item at 2026-11-03T00:08Z in the project thread "Decide pending spec name approvals". The card asked 「六个命名/措辞全部按推荐批准吗?」 and he picked 「全部批准」. The post above that card listed this PR's item as follows.
This is the written confirmation that the DELIBERATE CORRECTION class of With this, the gate the seat ACCEPT named is met. The head is unchanged since the ACCEPT. The seat now readies this PR and arms auto-merge, and Generated by Claude Code |
Requested by Warren · project thread
Fixes #29554
Clause-②: no (no accept set moves; the stored pass and the
--storedlisting widen)Metadata-protocol: none
维护者速读
object_extension行现在按objectExtensions条目走存量转换,os migrate meta --stored会列出fieldPatches里不可能是字段名的键。objectExtensions转换(如时间默认值去掉Z);规范行按引用原样返回。回滚即撤销本 PR。packages/spec/src/conversions/refused-shapes.ts新检测器;packages/spec/src/conversions/stored.ts的一行映射;.changeset/29366-field-patches-key-grammar.md被改的那句。pnpm --filter @objectstack/spec exec vitest run src/conversions/stored.test.ts src/conversions/refused-shapes.test.ts,应见 110 个测试全部通过。fieldPatches): the record key is a barez.string(), sofieldPatches: { Industry: {…} }parses and is refused only downstream by the target judge #29366 尚未发布的 changeset 那一句的更正(check-empty-changeset对此按设计报红,只能由人确认)。What changes
packages/spec/src/conversions/stored.ts:STORED_ONLY_COLLECTIONSmapsobject_extensiontoobjectExtensions, besidetranslation. A stored extension row is oneobjectExtensionsentry, so every conversion that walks that collection now replays over it at each rehydration seam, and the stored pass hands it to the refused-shape detectors.packages/spec/src/conversions/refused-shapes.ts: one detector,object-extension-field-patch-key-grammar-refused, appended last (additive, so spec seat 5's detector lands after it). Its predicate is the refusing door's own:ObjectExtensionSchema.safeParseon the extension, keeping eachinvalid_keyissue located atfieldPatches.KEY. It lists, never rewrites, and closes each reason with the parse's own message.packages/spec/src/migrations/entries/semantic/18.object-extension-field-patch-key-grammar-refused.ts: the header andacceptanceCriteriasaid--storeddoes not list such a row and named a manual check. They now say the row is listed, one TODO per refused key atfieldPatches.KEY, and left as stored..changeset/29366-field-patches-key-grammar.md: the matching sentence in that pending note is corrected the same way (see Acceptance notes)..changeset/29554-stored-object-extension-rows.md:patchon@objectstack/spec.Why
The card's premise holds on
origin/mainff827bcf:applyConversionsToStoredItemreturned a storedobject_extensionrow by reference with 0 notices and 0 TODOs, while the controlobjectrow with the sametimedefault got onetime-default-utc-suffix-droppednotice.Measured before choosing the map:
object_extension,object_extensionsandobjectExtensionstoobject_extension(meta-url-data.generated.ts), soobject_extensionis the one stored spelling. The protocol's singular/plural read fallback (PLURAL_TO_SINGULAR[t] ?? SINGULAR_TO_PLURAL[t]) finds no other spelling for it, andmigrateStoredMetadatareports a row stored under another spelling asskipped(isNonCanonicalStoredType) before reading its body. So no plural spelling is mapped, and a test pins that both plurals still pass through by reference.check:stack-collection-mapsrecordsobjectExtensionsas a deliberatemissingwaiver of the manifest-collection map: extensions merge into their target rather than registering as named items, andpluralToSingularfeeds registration and the authoring lint. Adding it there would advertiseobjectExtensionsas a named-item collection to every reader. The stored-only map exists for exactly this case, so the entry goes there.Tests
stored.test.ts: a storedobject_extensionrow with aZ-suffixedtimedefault is wrapped asobjectExtensions[0], converted, and noticed atobjectExtensions[0].fields.opens_at.defaultValue; the input is not mutated. Controls: a canonical extension row passes through by reference; the same body as anobjectrow converts exactly as before;object_extensionsandobjectExtensionsrow types pass through by reference.refused-shapes.test.ts: the registered id list gains the new id last; one TODO per refused key (Industry,annual-revenue) atobjectExtensions[0].fieldPatches.KEY, the row unchanged; the listed keys equal the parse'sinvalid_keyissues (Industry,Annual Revenue) and each reason ends with the parse's message; snake_case keys are not listed and parse; a__ckey (another entry's arm) is not listed by this detector.Ablations (committed first, restored by blob hash)
scripts/ablation-replace.mjsdeletingobject_extension: 'objectExtensions',fromstored.ts: 3 failed / 107 passed (the conversion test and both listing tests), restored, blobe26e3a1e== HEAD,git diff HEADempty.objectExtensionFieldPatchKeyGrammar,fromREFUSED_STORED_SHAPES: 3 failed / 84 passed (the id list and both listing tests), restored, blobd205cc7d== HEAD,git diff HEADempty.End-to-end probe through migrateStoredMetadata (scratch test, not committed)
Built the
@objectstack/objectql...closure and ran a throwaway copy of theprotocol.stored-migration.test.tsharness:object_extensionrow withfieldPatches: { Industry: … }and nothing else:skipped, 1 TODO (object-extension-field-patch-key-grammar-refusedatobjectExtensions[0].fieldPatches.Industry). Onff827bcfthe stored pass returned that row untouched with 0 TODOs, which this pass records ascanonical.Z-suffixedtimedefault: previewpendingwith the conversion and the TODO both printed under the row;--applyreports itfailedbecause the save door refuses the key, as for every other refused shape that also converts.Z-suffixed default onopens_at__c:--applyreportsrewrittenand the stored body becomes"12:00".Acceptance notes
.changeset/29366-field-patches-key-grammar.md(PR feat(spec)!: key an object extension's fieldPatches by the field map's own key grammar #29513, not yet released) saidos migrate meta --storeddoes not list such a row and told readers to check by hand. This PR makes that false, so the sentence now says the row is listed.check-empty-changesetrefuses any change to a changeset this PR did not add and stays red by design for the deliberate-correction class; please confirm the correction.missingwaiver reason inscripts/check-stack-collection-maps.mjsstill says there is no singular metadata-type name forobjectExtensions; ADR-0146 D10 addedobject_extension. The waiver itself stays right (the manifest map should not carry it); only its reason text is dated. Noted, not filed (wording).protocol.tsfile is touched (freeze until 06:00 UTC on 3 November).Verification
Gates at head
91fbde28(this branch merged withorigin/maindbca3ef0):node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack: 81 derived commands; all run, pluscheck:stack-collection-maps,check:meta-url-spelling, the spectypecheckandvitest run src/conversions src/migrations(28 files, 981 tests).--ranreconciliation: 81 accounted, 80 run, 1 NOT MEASURED, 0 unrun.node scripts/check-empty-changeset.mjs --base origin/main(exit 1, the correction above).pnpm check:dual-build-cjs-loads(exit 3, it needs every package built; CI'sLint & Repo Gatesruns it).check-plugin-teardown-shape --self-testfirst exited 3 on the shallow clone; after fetching its pinned fixture commit it exits 0.check:lean-entry-closurefirst exited 3 for want ofobjectql/dist; after the closure build it exits 0.Human Minimum
packages/specholds no runtime logic.pnpm lintis left to CI.main, one pull request.packages/spectouched: no generated artifact moved (check:api-surface,check:authorable-surface,check:docsexit 0).content/docs/references/,packages/*/CHANGELOG.mdorcontent/docs/releases/.Maintainer approval
Warren approved the rewrite of the pending
.changeset/29366-field-patches-key-grammar.mdsentence (option A): 「全部批准」 on the decision card 「六个命名/措辞全部按推荐批准吗?」 in the project thread "Decide pending spec name approvals", 2026-11-03 00:08Z. Recorded on this PR in seat comment 6591818192. That is the person's written confirmation the DELIBERATE CORRECTION class ofcheck-empty-changesetasks for;Check Changesetstays red by design and is not a required context.🤖 Generated with Claude Code
https://claude.ai/code/session_01JQwxbXpawDQBhgKU2ssyrw
Generated by Claude Code