Repository navigation
fix(metadata-protocol,app-shell): serve a pair's mapping override, let a pair remove any override it holds, name a working remedy for an excluded type (#30062) - #30226
Conversation
…ry open type's pair-row removal Claude-Session: https://claude.ai/code/session_01Utp6CaHjE2N5aEp4ogQnWY Co-authored-by: Claude <noreply@anthropic.com>
…base-named removal's unchanged answer Claude-Session: https://claude.ai/code/session_01Utp6CaHjE2N5aEp4ogQnWY Co-authored-by: Claude <noreply@anthropic.com>
… own record and names a remedy that works for it Claude-Session: https://claude.ai/code/session_01Utp6CaHjE2N5aEp4ogQnWY Co-authored-by: Claude <noreply@anthropic.com>
…that offer it Claude-Session: https://claude.ai/code/session_01Utp6CaHjE2N5aEp4ogQnWY Co-authored-by: Claude <noreply@anthropic.com>
…for every overridable type, excluded-type remedies, tile agrees with the door Claude-Session: https://claude.ai/code/session_01Utp6CaHjE2N5aEp4ogQnWY Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): 1 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 — 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 586b70389c2e1c7fe5d2ef9366825d65128f3a9c && git checkout 586b70389c2e1c7fe5d2ef9366825d65128f3a9c
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 31260ca8e446fa4fbe94ff8fd2a9030ddfb53a9a c503347163e4d6a786e5723d3a20e1276bd46429 && git checkout -B drift-repro 31260ca8e446fa4fbe94ff8fd2a9030ddfb53a9a && git merge --no-ff c503347163e4d6a786e5723d3a20e1276bd46429
node scripts/docs-audit/affected-docs.mjs --json 31260ca8e446fa4fbe94ff8fd2a9030ddfb53a9a
|
…dable type but action, and removing it brings the base's back; a capability changes only with a new version Claude-Session: https://claude.ai/code/session_01Utp6CaHjE2N5aEp4ogQnWY Co-authored-by: Claude <noreply@anthropic.com>
|
domain:server#5 · os-bill · session_01Utp6CYVV6fcAQrNTDQRvPb 席位核对 · ACCEPT · head
|
| Item | Reading |
|---|---|
| Shape | Draft, base main, author objectstack-fleet[bot] (relay pr_create). Body: attribution block, then Refs #30062 (items 1, 2, 4, 5); no closing keyword anywhere in the body, so the merge leaves #30062 open by design. |
| Premise | Re-read by the seat at origin/main 0fd363d1 before dispatch: mapping absent from SEALED_ROW_SERVED_OVERRIDE_TYPES; isPairRowRemoval admitted CONDITIONS_DELIVERED = {flow} only. The diff widens it to isOpenOverrideType (D9-undelivered action still refused). |
| Gates | Check-runs on 5663983b: 60 success, 9 skipped, 0 failure, 0 pending; mergeable_state clean. Dev's local reconciliation: 95 derived, 94 run, 1 NOT MEASURED (check:dual-build-cjs-loads, no local dist; covered by CI). |
| Declarations | Clause-②: no, citing ADR-0146 D2 and the chartered revert; the served arm of item 1 was chosen, so nothing narrows. Metadata-protocol: semantic; changesets are minor on @objectstack/metadata-protocol and the app-shell package. Skills-docs: follow-up #30229. |
| Docs (read sentence by sentence) | concepts/metadata-lifecycle.mdx: "for every type of OVERRIDABLE_METADATA_TYPES … but action" matches the diff's isOpenOverrideType; the removal bullet matches isPairRowRemoval. api/metadata-api.mdx: the capability sentence was checked against metadata-plugin.zod.ts:1269 (allowRuntimeCreate: false). |
| Accepted deviations | Item 3 not in this PR: its spec half is #30193 (spec lane, in flight); the server half (getPackaged*Base accessors for page, dataset, app, book and the PACKAGED_BASE_ACCESSORS rows) stays on #30062. Item 4 is UI-only in PageShell.tsx; #30063 / #30064 were told of the tile expectation (6646961716, 6646971486). |
| Out of scope | filed #30193 (item 3 spec half) · filed #30229 (skills paragraph) · ablations A1–A5 red then restored, per the report. |
| Landing | Not governed (0 of 15 paths), 527 changed lines (< 3000). Seat lands: pr_ready + automerge_enable. At MERGED, #30062 is released with item 3's server half as pm:blocked, Blocked-by: #30193. |
… that landed on main Claude-Session: https://claude.ai/code/session_01Utp6CaHjE2N5aEp4ogQnWY Co-authored-by: Claude <noreply@anthropic.com>
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 41960860949 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 分类: 失败的 job(日志抽取,best effort):
跨 PR 相同签名(24h,按失败测试文件聚合):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
|
domain:server#5 · os-bill · session_01Utp6CYVV6fcAQrNTDQRvPb 席位核对 · ACCEPT (re-affirmed) · head
|
| Item | Reading |
|---|---|
| Queue round | Dequeued (CI_FAILURE, aggregate #30253): three #29705 pins in DirectoryPage.stateVocabulary-29705.test.tsx hard-coded page 只读. Cause: #30063 (page door) and #30064 (dataset door, AREA_CUSTOMIZE_EDITOR_TYPES) landed on main; with item 4 the page tile reads 可定制 by rule. This is the "whichever lands second flips those" case the body predicted. |
| Fix | Merged origin/main bb86ff96c; studioOffersCustomize also reads AREA_CUSTOMIZE_EDITOR_TYPES (otherwise the dataset tile would still read 只读); the three pins now expect page 可定制, the read-only control row is api, and the agreement DOORS include the new constant. These are expectation changes for a behaviour item 4 deliberately changes, not weakened pins. |
| Gates | Dev: 29705 file 114/114, the 6 PageShell/DirectoryPage files 150/150, app-shell type-check 0, ui eslint 0, the 5 ui-reading gates green. CI on c5033471 running; the merge-group full suite decides. |
| Body | Acceptance notes updated by the seat: the queue-round line and the #30106 serial note (landed as #30207). |
| Landing | automerge_enable re-sent. |
Requested by Bill · project thread
domain:server#5 · os-bill · session_01Utp6CYVV6fcAQrNTDQRvPb
维护者速读
BASE.custom)里声明覆盖的 mapping 现在真的生效(导入用的是改过的映射);定制包里任何可覆盖类型的覆盖都能删掉,删掉后基包原件回来(doc、mapping 以前报 403);给不可覆盖类型加overrides时,拒绝信息引用该类型自己的依据并给出一条走得通的出路;「全部元数据」里的类型瓷砖在只读包上显示「可定制」当且仅当 Studio 真的给了「定制」入口(flow、app 以前误显示「只读」)。.custom里的覆盖项。旧行为对 doc/mapping 拒绝了这条路,mapping 覆盖保存发布后不起作用,拒绝文案一律叫人「新建同类」而有些类型根本不能新建。?package=BASE.custom);点名基包的删除仍是原答案,覆盖照旧生效(消融 A3 为证)。错误码与状态不变,无 spec 改动。回滚即撤 PR。packages/metadata-protocol/src/protocol-packaged-base-refusal.ts(isPairRowRemoval的放宽与excludedTypeRemedy),packages/metadata-protocol/src/protocol-package-bindings.ts(SEALED_ROW_SERVED_OVERRIDE_TYPES加mapping),ui/packages/app-shell/src/views/metadata-admin/PageShell.tsx(studioOffersCustomize)。pnpm --filter @objectstack/objectql exec vitest run src/protocol-custom-override-bare-slot-envelope.test.ts -t 30062:每个可覆盖类型在两种内核上,删除定制包的覆盖后所有读都回到基包原件;点名基包的删除不动任何东西。Refs #30062 (items 1, 2, 4, 5)
Clause-②: no — ADR-0146 D2 ("an item in the customization package replaces the base's item, and removing it brings the base's back") and ADR-0146's revert ("Restore package original" = delete the overriding item from
.custom, so the base's document is served again) already admit both the served mapping override and the pair's removal of its own doc or mapping override; the 403 and the unserved row refused that text. A removal naming the base keeps today's answer, and no accepted write is refused that was accepted before.Metadata-protocol: semantic
Skills-docs: follow-up #30229 —
skills/objectstack-platform/SKILL.md's ADR-0146 paragraph says the read honours a pair's declared override "today only for a view, dashboard, report, email_template, translation or flow"; that list predates page, app, dataset, doc and book, and now mapping, and it says nothing of the removal.skills/**is outside this card's order, so the seat carries the edit.What changes
Item 1 — a mapping override is served.
packages/metadata-protocol/src/protocol-package-bindings.ts:mappingjoinsSEALED_ROW_SERVED_OVERRIDE_TYPES(page, app, dataset, doc, book) andBORROWED_ENVELOPE_DECLINED_TYPES. A mapping is sealed (allowEnvironmentOverlay: false) and open for override (ADR-0146 D2, no D9 condition), and its pair row was declined with every other stored row of a name a managed package ships, so a saved and published mapping override answered the base's body on every read. Now the by-name read (no?packageand?package=BASE), both lists, the layered read and the pair's per-package layer serve it. The import door (@objectstack/restimport-mapping.ts:63) and the connector pull (service-automationconnector-pull.ts:244) resolve a named mapping through that package-lessgetMetaItem, so they get the pair's mapping. The served arm was chosen, not "the save refuses loudly", soClause-②: nodoes not narrow. Comment-only updates inprotocol-lock-state.ts,protocol-registry-hydration.tsandprotocol.tsname the sixth type.Item 2 — the pair's removal of its own override works for every overridable type. Measured: the doc and mapping 403 came from
refusePackagedBaseRemoval(protocol-write-gates.ts: a sealed managed item on a kernel that does not merge the overlay at read), whichdeleteMetaItemUnauditedskips only whenisPairRowRemovaladmits the row, andisPairRowRemovaladmitted onlyCONDITIONS_DELIVERED = {flow}.protocol-packaged-base-refusal.ts:isPairRowRemovalnow admits the pair's row for every typeisOverridableMetadataTypeopens (isOpenOverrideType, soactionstill waits for D9); the pair check is unchanged (the package the removal names must be the base's customization package). The repository's delete gate then takes theruntime-onlyintent the declared save wrote, which every overridable type'sallowRuntimeCreateadmits. No spec flag moved. A removal naming the base (?package=BASE) is untouched: on the host-config kernel it answers{ success: true, reset: false }with "No row … remains" and removes nothing; on the environment kernel a doc or mapping still answers403 NOT_OVERRIDABLE.Item 4 — the All metadata tile agrees with the Customize door.
ui/packages/app-shell/src/views/metadata-admin/PageShell.tsx: newstudioOffersCustomize(type)= overridable and offered by a Studio door (isStudioCustomizeType: view, dashboard, report, app;AREA_FORM_EDITOR_TYPES: email_template, translation; and the flow header). On a read-only packagetypeStateWordreads it instead ofallowEnvironmentOverlay, so flow and app read 可定制, and doc, mapping, page, dataset and book read 只读 until a door exists.StudioCustomizeControls.tsxis not touched (seat ruling): a door added to its list moves the tile with it.Item 5 — an excluded type's refusal names a remedy that works for it.
judgeOverrideWrite'sdeclared_type_not_overridablesentence cited ADR-0146 D6 and told every type to "create your own TYPE under a new name". It now cites the type's own basis fromOVERRIDE_TYPE_CLASSIFICATION(EXCLUSION_BASIS_CITATION: D6 for the security catalog and code-only types, D1 for a datasource, D9 for a behavioural switch, D10 for a field, ADR-0126 §3 for one-shot types, D2 otherwise) andexcludedTypeRemedyprescribes, in order: Setup › Datasources for a datasource; the object extension for a field (OBJECT_EXTENSION_ITEM_DOOR); "create your own TYPE under a new name" only where the registry admits a runtime create; the type's ADR-0126 Regime C prescription where it has a row (packagedBaseRegimePrescription: a sharing rule namesPOST /api/v1/sharing/rulesand its switch); Setup › Queues for a queue; otherwise "an X is defined only in package source, so it changes only with a new version of 'BASE'".articleForfixes "a api". Code (NOT_OVERRIDABLE), status (403) and rule are unchanged, and the permission sentence is byte-identical (pinned byruntimemeta-object-extension-item-door-fit.test.ts:161).Docs (head
5663983b, added by the card thread after the CI Docs Drift Check, comment 6648033732).content/docs/concepts/metadata-lifecycle.mdx: a declared override is served for every type ofOVERRIDABLE_METADATA_TYPESbutaction(which waits for ADR-0146 D9's conditions); the import door and the connector pull run the pair's mapping; a new bullet, removing the override (DELETE …?package=hotcrm.custom, "Restore package original") brings the base's back.content/docs/api/metadata-api.mdx: a capability is not copied under a new name (its registry entry hasallowRuntimeCreate: false); it changes only with a new version of the package that declares it, and anoverrideson a type that takes none is refused403 NOT_OVERRIDABLEwith that type's own route. This PR made the old sentences false, so they are fixed here.Tests
packages/objectql/src/protocol-custom-override-bare-slot-envelope.test.ts:mappingjoins the sealed served types (by-name, lists, layered, per-package, after a restart, with and without a metadata service). New describe[#30062] a pair's removal of its own override brings the base's item back, over view, dashboard, report, email_template, page, app, dataset, doc, book, mapping, flow and translation, on both kernels: (1) the removal naming the pair succeeds, the pair row is gone, and every read serves the base's item again; (2) a removal naming the base keeps main's exact answer per kernel and type, removes nothing, and the override stays served.packages/objectql/src/protocol-flow-custom-override.test.ts: the old flow-only admission pins (a mapping pair-row delete 403, a view deleteoverride-artifact) are rewritten to the new behaviour (removed;runtime-only). They pinned the service-automation: a declared override in <base>.custom is the packaged flow — armed over the code package, rebound in one reload, one ledger switch #28412 flow-only scope (contract review 6559358939), which item 2 supersedes.packages/metadata-protocol/src/protocol.packaged-base-refusal.test.ts: each of the 17 excluded non-D10 types cites its own basis, never reads "a" before a vowel, names a create only where the registry admits one, and the named remedies (sharing rule route and switch, Setup › Queues, Setup › Datasources, object extension, new version of the base) appear where they belong.packages/metadata-protocol/src/protocol-package-bindings.sealed-override.test.ts: the served set includesmapping.ui/packages/app-shell/src/views/metadata-admin/DirectoryPage.stateVocabulary-29705.test.tsx: for every overridable type plus permission, object and api, under both overlay flags, the tile reads 可定制 exactly when a door offers Customize; flow and app read 可定制, doc and mapping 只读, a writable flow 可新增; the flow list header renders 可定制.Ablations
scripts/ablation-replace.mjs(wrap mode); for objectql legs the mutatedmetadata-protocolwas rebuilt andscripts/ablation-dist-preflight.mjsproved the marker indist/before the run, then absent after restore. Each leg restored with blob == HEAD andgit diff HEADempty.isPairRowRemovalback to flow-onlymappingout ofSEALED_ROW_SERVED_OVERRIDE_TYPEStypeStateWordback to readingallowEnvironmentOverlayAcceptance notes
origin/mainb7075d99: the rule that keeps an override's own label over the base's translation exists only intranslateView,translateDashboard,translateReportandtranslateObject(packages/spec/src/system/i18n-resolver.ts, thepackagedBaseoption), and@objectstack/restwiresPACKAGED_BASE_ACCESSORS(meta-item-read-gate.ts:2954) for object, dashboard, view and report only.translatePage(:3393),translateDataset(:2270),translateApp(:1756) andtranslateBook(:2441) take onlyResolveOptionsand apply the catalog translation unconditionally, which is the card's page/dataset defect. The ruling's first rank is unmet for every type: where the value differs from the base's, the resolver keeps the authored value, so a translation in the pair never gets in. Both halves changepackages/spec/src/**, so the seat splits the spec half to the spec lane; the server half (getPackaged*Baseaccessors and REST table rows) stays on Declared overrides: a mapping override publishes but is never served, doc and mapping overrides cannot be restored, and an override's label can be hidden by translation #30062.domain:uicards). Both add a door toSTUDIO_CUSTOMIZE_TYPES(page and dataset). The new agreement rows derive from that list, so they follow on their own. The older Studio: one vocabulary for an item's state across the pillars, All metadata and Access #29705 pins in the same test file hard-codepage只读 (the tile at:129, the list header at:178–:186); whichever of this PR and Studio: customize an installed package's page, so a customer can change a home or landing page without a code release #30063 lands second flips those to 可定制. Studio: customize an installed package's dataset, showing what else it changes before the customer publishes #30064's claim names "All metadata tile for datasets": after this PR the tile follows the door with no extra change.engine.registry.registerItem('translation', …, BASE), and the removal pin reads the base's bundle back from there..customexport installed on another environment reports success and the override is not applied #30106 (protocol-served-overrides.ts, read path) landed as PR fix(cloud-connection,cli): install a .custom export into the target's writable pair #30207 (02:52Z) and is inorigin/mainbb86ff96c, merged at headc5033471.c5033471). Mergedorigin/mainbb86ff96cafter Studio: customize an installed package's page, so a customer can change a home or landing page without a code release #30063 (page door,797f9257c) and Studio: customize an installed package's dataset, showing what else it changes before the customer publishes #30064 (dataset door,AREA_CUSTOMIZE_EDITOR_TYPES,6f095b9c1) landed:studioOffersCustomizealso readsAREA_CUSTOMIZE_EDITOR_TYPES, andDirectoryPage.stateVocabulary-29705.test.tsx's 'a read-only package: overlay types can be customized…', 'the view list header reads 可定制, the page list header 只读' and 'typeStateWord: the mapping in every context' now expect page 可定制 (filter count 隐藏只读 (4); the read-only control row isapi), per Declared overrides: a mapping override publishes but is never served, doc and mapping overrides cannot be restored, and an override's label can be hidden by translation #30062 item 4 and ADR-0146 D2. This is the "whichever lands second flips those" case noted above. Local: that file 114/114, the 6 files that read PageShell/DirectoryPage 150/150, app-shell type-check 0.Verification
Head
69812d8212(baseb7075d99,origin/main474c030bmerged in once, no conflict, no rebase). The affected closure was rebuilt after the merge under the shared lock.pnpm --filter @objectstack/metadata-protocol test: 312 files passed (4 skipped), 22 375 tests passed (26 skipped).typecheck: exit 0.pnpm --filter @objectstack/objectql test: 548 / 548 files, 10 910 / 10 910 tests.typecheck: exit 0.@objectstack/runtimesrc/domains/meta-object-extension-item-door-fit.test.ts: 6 / 6 (it pins the permission refusal sentence byte for byte).ui: the six test files that importPageShellorDirectoryPage(DirectoryPage.stateVocabulary-29705,.diagnosticsLinkTone-12768,.environmentOverlay-12141,PageShell.narrowHeader-12306,metadataAreaHost.customizations-28568,metadataAreaHost.scope-28561): 149 / 149.turbo run type-check --filter=@object-ui/app-shell: 68 / 68 tasks. eslint on the two ui files: 0 errors (one morereact-refresh/only-export-componentswarning, for the newstudioOffersCustomizeexport beside the already-exportedtypeStateWord).pnpm lint: exit 0.node scripts/check-nul-bytes.mjs: exit 0.Gates: dispatch-gates --commands, each exit code captured before any pipe
95 derived families at head
5663983b(the docs commit added 29 docs families and 7 doc-reading families): 94 exit 0, 1 NOT MEASURED.dispatch-gates --ranexit 0. (At69812d82: 65 derived, 64 exit 0, 1 NOT MEASURED.)pnpm check:dual-build-cjs-loadsexit 3, "PREREQUISITE NOT MET — this gate reads built output, and some package has no dist/" (32 unbuilt packages outside this diff's closure); left to CI.node scripts/check-plugin-teardown-shape.mjs --self-testfirst exited 3 on the shallow clone (pinned fixture commit unreachable); aftergit fetch origin 621a4876…it exits 0 (48 cases).pnpm check:type-check-debtfirst exited 3 (lock queue timeout, nothing measured); re-run under the lock: exit 0, "0 raw tsc error(s) total, none above its recorded number".check:authz-resolver,check:error-code-casing(both keep a roster underpackages/),check:overlay-whitelist-table,check:changeset-fixed: all exit 0.Change set: 15 paths, +461 / −66 against
origin/main(13 code and test paths +448 / −61, plus the two docs pages).Human Minimum
packages/specholds no runtime logic. (not touched)pnpm lintpasses.main, one pull request.packages/spectouched: n/a.content/docs/references/,packages/*/CHANGELOG.mdorcontent/docs/releases/.🤖 Generated with Claude Code
Generated by Claude Code