Repository navigation
fix(metadata-protocol)!: an object_extension item's refusals fit the item door, and its target is the key the object is registered under (#28834) - #28853
Conversation
…tem door An object_extension body carrying `overrides` is refused with a sentence that fits it: an extension takes no override and is stored under its target's name, so the remedy is to edit the extension itself, not to create one under a new name. An object carrying `overrides` is told both places an extension is written: the object_extension item door and the manifest's objectExtensions entry. Every other type's sentence is unchanged. The save, publish and restore gate now refuses an object_extension whose `extend` is a short name getObject folds to a different, legacy `<ns>__<name>` key. The registry files an extension under the name it is written with, so that write opened a second, phantom object. The refusal names both spellings. Co-Authored-By: Claude <noreply@anthropic.com>
📓 Docs Drift Check5 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run. What this run could not see
Coarse fallback — 10 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 f7904c567e92c30a237086dc38320432bcf09c12 && git checkout f7904c567e92c30a237086dc38320432bcf09c12
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin a31431824958c8e789427c4db593bcf867e8e051 461552acedd0d0070debccf845de1350d307af6b && git checkout -B drift-repro a31431824958c8e789427c4db593bcf867e8e051 && git merge --no-ff 461552acedd0d0070debccf845de1350d307af6b
node scripts/docs-audit/affected-docs.mjs --json a31431824958c8e789427c4db593bcf867e8e051 |
|
domain:server#1 · os-project-manager · session_01R1q2xdtYgU6FKrZ7LgjPCC Contract-review-claim: Taken by the dispatching seat. Face Generated by Claude Code |
|
domain:server#1 · os-project-manager · session_01R1q2xdtYgU6FKrZ7LgjPCC Contract reviewServed-tier: Rendered by the seat's subagent, reading the card (#28834, body and both comments), the PR (#28853 body, 4-file list, net diff Check-runs on the head (the gate verdicts)48 check-runs, all ① Derived judgments
② Semver level
Clause-②: no (narrowing) — read as declared, verified against the diff above. ③ Boundary flags
④ Design judgmentJudged against the published spec on
No must-fix. Implemented-by: VERDICT: PASS Generated by Claude Code |
|
domain:server#1 · os-project-manager · session_01R1q2xdtYgU6FKrZ7LgjPCC 席位核对 · ACCEPT ·
|
| item | reading |
|---|---|
| Shape | Draft, base main, Fixes #28834. 4 files. The object_extension override refusal now says to edit the extension itself, and declared_object names /api/v1/meta/object_extension beside the manifest entry; other types' sentences are unchanged. The item gate refuses an extend that getObject folds to a different key (400 on extend, naming both spellings). No new rule, code, export, gate or packages/spec/**. A minor changeset with the BREAKING banner. |
| Out of this PR | Wanted 2 (the registry row comment, in packages/spec/**) went to the spec lane as #28855. The review's one should-fix (the manifest loop and at-rest hydration still file extend verbatim) is outside this PR's claim, filed as #28858. |
| Evidence | CI on head 461552ac (filter=latest): 13 skipped, 35 success, 0 failed. The report shows 3 red on main and 5/5 on head, 3 ablations each restored clean, the metadata-protocol, objectql and runtime suites, both typechecks in full, and 61/61 derived gates. The dev's 4 local dogfood reds are main's (p0 #28849). |
| Landing | 「按照 pm-dispatch 规则复核过的都授权」 (Leehom, 2026-10-25T11:35Z): ready and auto-merge through the relay, with every check green or an expected skip. |
Generated by Claude Code
Fixes #28834
Requested by Leehom · project thread
Clause-②: no (narrowing)
Metadata-protocol: narrowed
Skills-docs: none — no file under
skills/**orcontent/docsquotes either override refusal sentence or teaches an extension'sextendspelling against a legacyns__namekey;skills/objectstack-data/SKILL.mdteachesobjectExtensionsby a plain object name, which stays admitted.维护者速读
object_extension带overrides时的拒绝句改成「扩展不接受覆盖,名字就是它扩展的对象,请直接改这个扩展」,不再叫人「换个新名字另建」;给object带overrides的拒绝句在 manifest 的objectExtensions之外,也点出/api/v1/meta/object_extension这个保存口。② 保存/发布/回滚口拒绝extend写成短名、但注册表实际挂在旧式ns__name键上的扩展(以前会再开一个同短名的幽灵对象),拒绝句写出两种拼法。packages/spec/**,按裁决留给 spec 席位,本 PR 不碰。NOT_OVERRIDABLE/403、VALIDATION_ERROR/400);其他类型的拒绝句逐字不变(permission 对照钉住)。收窄只命中带__的旧键,实测本仓库与 hotcrm 的 manifest 和对象都没有。回滚 = revert 本 PR。packages/metadata-protocol/src/protocol-packaged-base-refusal.ts的judgeOverrideWrite两处句子;packages/metadata-protocol/src/protocol-object-extension-item.ts第 4 步新加的拼法判断。pnpm --filter @objectstack/runtime exec vitest run src/domains/meta-object-extension-item-door-fit.test.ts,预期 5 个测试全绿。What changes
declared_type_not_overridableforobject_extension(judgeOverrideWrite,protocol-packaged-base-refusal.ts). A new branch ahead of the D6 sentence, for this type only. The new sentence:'object_extension/NAME' carries `overrides`, but an object extension takes no override (ADR-0146 D10): it adds to the object it extends and is stored under that object's name. Remove `overrides` and edit the object_extension 'NAME' itself.The rule name, code and status stay.declared_objectnames both places an extension is written:... add the change as an object extension in the base's paired .custom package: an `object_extension` item saved through `/api/v1/meta/object_extension`, or an `objectExtensions` entry of its manifest.The rule, code and status stay. This sentence is also whatos validateand the install door print, so it names the manifest entry as well as the item door.objectExtensionItemRefusalstep 4,protocol-object-extension-item.ts). AftergetObject(extend)resolves, the gate refuses withVALIDATION_ERROR/ 400 onextendwhen the resolved object'sname(the key it is registered under, sincecomputeFQNis the identity) differs fromextend. The sentence names both spellings and says to save it under the registered key. The gate is the same one that save, per-item publish and history restore already call. ⛔ The manifest loop inobjectql/src/engine.tsandcomputeFQNare untouched..changeset/28834-object-extension-door-fit.md:minoron@objectstack/metadata-protocol, both declaration lines, the ADR-0087not-required (no-migration-prescription)marker and the BREAKING banner.OverrideWriteRulemember, no new error code, no new gate, nothing underpackages/spec/**.Measurement before the narrowing (Wanted 3)
objectExtensionsin this repository areplugin-auth(extend: 'sys_sso_provider') and the showcase (extend: 'showcase_account'). Each resolves to the same key, so neither is refused.hotcrmhas noobjectExtensionsand noextend:.hotcrmis namedns__name. The onlyextendwith__in the tree is a spec/lint test fixture (cbpx__cbpx_line), which names the key itself and so is admitted.0c1829a28a), the change just before this one. The boot and read hydrations register stored rows throughregistrableObjectExtensionand never call this gate, so a stored row with that spelling is not refused at rest. Only a re-save of it is. No live row was found that this would refuse, so the order's stop condition did not trigger.Tests
packages/runtime/src/domains/meta-object-extension-item-door-fit.test.ts(new, 5 tests). It uses the rig ofmeta-object-extension-item.test.ts: a real better-sqlite3SqlDriver, a realObjectQLengine and the real protocol. The basecom.acme.crmis registered throughregisterApp, ownscrm_accountand the legacy-keyedcrm__case, and its pair is installed throughinstallPackage. Every refusal asserts its envelope, its whole sentence and that no row was left.461552acTests 5 passed (5); with the #28574 file, 24/240c1829a2(both production files restored, the test kept, metadata-protocol rebuilt without the turbo cache)Tests 3 failed, 2 passed (5). Red: the extension sentence (base saida object_extension takes no override (ADR-0146 D6) ... create your own object_extension under a new name), the object sentence (no item door), the folded spelling (admitted,null). Green: the permission control and the registered-key control.The tests:
permission's D6 sentence, byte for byte;caserefused in draft and in publish mode, with the issue onextend, no row left, and no contributor undercase;crm__caseadmitted and published, the pair as itsextendcontributor,region__con the object.Ablations (
node scripts/ablation-replace.mjsfrom committed HEAD, metadata-protocol rebuilt without the cache, marker counted in dist)if (type === OBJECT_EXTENSION_TYPE) {guarded off (markerABL_A, 1 in dist).Tests 1 failed, 4 passed: exactly the extension sentence. Restored: blob equals HEAD,git diff HEADempty.${OBJECT_EXTENSION_ITEM_DOOR}, orreplaced (markerABL_B).Tests 1 failed, 4 passed: exactly the object sentence. Restored clean. The first attempt dropped the constant entirely, so the DTS build refused an unused local (exit 9, nothing measured). It was re-run with the constant kept as.slice(0, 0).ABL_C).Tests 1 failed, 4 passed: exactly the folded-spelling test. Restored clean.Suites, typecheck, lint and gates at head
@objectstack/metadata-protocol@objectstack/objectql@objectstack/runtimepnpm --filter @objectstack/metadata-protocol typecheckpnpm --filter @objectstack/runtime typecheck(incl.check:test-typecheck)organization-ownership-boot-guard.turso-remote.kernel.test.tsTS2307 because@objectstack/driver-tursohad nodist/in the fresh worktree; after building it, exit 0 with the ledger unchanged.eslint --no-inline-configover the three changed TS filescheck:type-check-debtdispatch-gates.mjs --commands(61 families)pnpm build;--ran: 61 run, 0 NOT-MEASURED. The changeset and citation families were re-run after the last amend: exit 0. No gate added.461552acaudit-key-material,audit-mail-bodies,identity-mail-read-gateandupgrade-18x-harness(no such table: sys_activity__r20261019, rewrite counts halved). The same four files on base0c1829a2, with metadata-protocol rebuilt at base: the same 4 red, 45 passed. So they are on main, tracked by #28849, and not this diff.Acceptance notes
object_extensionregistry row's comment is inpackages/spec/src/kernel/metadata-plugin.zod.ts, which the ruling sends to the spec seat. It is untouched and left for that seat. The PR feat(metadata-protocol,objectql): an object extension is an authorable item of a writable package, saved, published and exported through the existing doors (#28574) #28811 body's probe lineobjects: [](the export omits the key) is on a merged PR, so it is noted here rather than edited.nameis the resolved key.registerObjectfiles an object undercomputeFQN(namespace, schema.name), which is the identity, so the merged object'snameis its key. A target named by its own key, legacy or not, is unchanged.objectbranch and before the D6 disposition check.object_extensionis classifiedexcluded, so before this change it reached the D6 sentence.Verification
pnpm --filter @objectstack/runtime exec vitest run src/domains/meta-object-extension-item-door-fit.test.tsgives 5/5. The same command on base0c1829a2, with metadata-protocol rebuilt, gives 3 red, as in the table.🤖 Generated with Claude Code
https://claude.ai/code/session_01FEkQ5kkSYvKhs52WGfTj3c