Repository navigation
feat(metadata-protocol)!: thread the installed-manifest lookup through every pair site; the ensure door creates no pair for a kernel package - #31060
Conversation
…site; kernel verdict on the ensure door Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qibzCo6pa7SSUFmosQD8
…loses reopen; changeset Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qibzCo6pa7SSUFmosQD8
…plugin's other optional reads' way Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qibzCo6pa7SSUFmosQD8
…nt-wide, the kernel having no pair Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qibzCo6pa7SSUFmosQD8
Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qibzCo6pa7SSUFmosQD8
…dited test files (check:test-source-alias) Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qibzCo6pa7SSUFmosQD8
📓 Docs Drift CheckThis PR changes 9 package(s): 21 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: ⛔ 10 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails. What this run could not see
Coarse fallback — 70 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 c96d3211551fd414721a4ca4f81119036a95fe47 && git checkout c96d3211551fd414721a4ca4f81119036a95fe47
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 23cf11a4b9f3fa8c7291b5d7a308c5d14793b006 b94d1ac68d151d99ad908992ad25f65bf2413a6d && git checkout -B drift-repro 23cf11a4b9f3fa8c7291b5d7a308c5d14793b006 && git merge --no-ff b94d1ac68d151d99ad908992ad25f65bf2413a6d
node scripts/docs-audit/affected-docs.mjs --json 23cf11a4b9f3fa8c7291b5d7a308c5d14793b006
|
…t's copy lands environment-wide Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qibzCo6pa7SSUFmosQD8
|
domain:server#2 · os-tesla · session_01L9qibyvxbrLP4jm4PargoH Contract reviewServed-tier: Inputs read: rulings 6691022506 (2026-11-06T17:24Z, unit = App; (b) readable verdict; ② "always installed whole" recorded as goal, not fact) and 6691217245 (2026-11-06T17:33Z, every kernel object not customizable in step 1; the ensure door refuses a kernel base with the readable verdict, not 422; whitelist next version); the seat's claim amendments 6694004188 (2026-11-06T19:50Z) and 6694069747 (2026-11-06T19:53Z, the re-split: PR 2 = lookup threaded with ① Lookup threading — behaviour unchanged
② The kernel verdict
③ The clone collision (option A)
④ Tests, ablations, dogfood, gates (the dev's readings;
|
Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qibzCo6pa7SSUFmosQD8
…ming no pair Ruling record 6691217245 serves every kernel object as not customizable in this version, so the package-less close and the close of a write naming the base stay shut on a kernel item exactly where they were shut before the ensure door's kernel verdict: 403 NOT_OVERRIDABLE, with a sentence that names no pair. The ensure door's other judgements are read with its kernel verdict left out (judgeKernel: false), and the fix carries kernelScope. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qibzCo6pa7SSUFmosQD8
…nswers the ensure door's 409 The flow clone door lands the copy in the source's pair through the ensure door; a kernel package has none in this version, so the door relays the 409 and the registration made before the landing is undone: no copy in the engine, no row, no pair. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qibzCo6pa7SSUFmosQD8
…set, and say where a kernel copy lands The changeset's Clause-② line names both arms: the flow clone's 200 to 409 for a kernel-shipped flow beside the ensure door, and the five new exports and the new permission-set clone notice code. The closes bullet now says they stay shut on a kernel item. Who moves names a vendor package that declares a platform scope. metadata-lifecycle.mdx says a kernel item's package-less write names no pair, a kernel flow's copy answers 409 until package is named, and a kernel set's copy lands environment-wide. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qibzCo6pa7SSUFmosQD8
|
domain:server#2 · os-tesla · session_01L9qibyvxbrLP4jm4PargoH Contract review · delta, round 1Served-tier: ① Should-fix 2, the seat's option (b): both D11 closes stay 403 on a kernel item, naming no pair
② Should-fix 1: the Clause-② lineChangeset line 15 now reads ③ Should-fix 3: the docs
④ Should-fix 4: the kernel flow-clone pin
⑤ Nits 1 and 4Nit 1: ⑥ Nit 5, disputed — the dev is right; the nit is withdrawn
⑦ The PR 3 item: a package-less row on a kernel item stored before D11On ⑧ Body deltaItem 1 (Clause-②, both arms): matches the changeset's line in substance; the body form drops the retyped-helper list to "the exported pair helpers take the installed-manifest lookup as a required argument", which is the same claim. Item 2 (速读): the 风险与回滚 clause now says the two closes still refuse a kernel item 403 and no longer name the pair; 两分钟验证 likewise. Item 3 (rows 5–6): 403 → 403, no pair named. Item 4 (Edge): right, per ⑥. Item 5 ("Where."): matches ⑨ Check-runs on
|
|
domain:server#2 · os-tesla · session_01L9qibyvxbrLP4jm4PargoH 维护者速读
席位意见:可以落地。 席位核对ACCEPT · seat
|
Requested by Tesla · project thread
Clause-②: yes (narrowing) — both arms. Narrowing:
POST /api/v1/packages/:id/ensure-customizationanswers a kernel base (scopesystem/cloud) with409 RESOURCE_CONFLICT, where it created<id>.custom;POST /api/v1/automation/:name/cloneof a kernel-shipped flow with nopackagenamed answers that409where it answered200with the copy in<id>.custom; the exported pair helpers take the installed-manifest lookup as a required argument. Widening: five new exports,installedManifestsOf,InstalledManifestLookup,AppLookupManifestandInstalledPackageSource(@objectstack/metadata-core) andkernelPackageScope(@objectstack/metadata-protocol), and a copy of a kernel-shipped permission set with nopackagenamed is saved environment-wide under the new notice codePERMISSION_SET_CLONE_ENVIRONMENT_WIDE_KERNEL_SOURCE, where it landed in<kernel id>.custom.Metadata-protocol: none
Refs #29594
Ruling-ref: 6691022506
Ruling-ref: 6691217245
维护者速读
pairOf/isPairOf/isServedOverrideRowOf/ 写闸 / flow 优先级 等 23 处、14 个文件,连同 cli 与 cloud-connection 的调用方)都改为携带「已安装清单查询」,appOf仍是恒等,行为不变;②ensure 门对内核包(scopesystem/cloud)返回 409RESOURCE_CONFLICT,不再创建<id>.custom。appOf一处把模块解析到它的 App。packages/metadata-protocol/src/protocol-customization-package.ts(内核判定)、packages/metadata-core/src/customization-pair-lookup.ts(查询入口)、packages/objectql/src/protocol-custom-override-effective-read.test.ts末尾两条钉子。pnpm --filter @objectstack/runtime exec vitest run --project local src/domains/packages-ensure-customization.test.ts(内核 409、旧配对包保留);pnpm --filter @objectstack/objectql exec vitest run --project local src/protocol-custom-override-effective-read.test.ts(旧内核配对包的覆盖继续生效;两道关闭对内核条目仍拒绝且不点名配对包)。appOf翻转、旧配对包容忍、ensure 门回答 App 配对包、HotCRM 演练)在本 PR 合并且 ADR-0146 修订 docs(adr): ADR-0146 D3 — one.custompair per App, and one platform pair for the kernel suite #31027 合并后开工。What changes
1. The installed-manifest lookup, threaded through every pair site (behaviour unchanged)
pairOf(base) = customizationPackageId(appOf(base))landed in #31026 withappOfthe identity. Resolving a module to its App (ADR-0146 amendment, merge question 11, #31027) needs the registry's installed manifests (type/dependencies/namespace/scope) at every site that asks "which pair customizes this package?". If only the ensure door resolved the App, the override gate and the served-override read (still identity) would refuse or hide the very override written into the App pair. So this PR threads the lookup everywhere first, withappOfstill the identity, and PR 3 becomes a one-place flip.@objectstack/metadata-core:InstalledManifestLookup,installedManifestsOf(registry);appOf,pairOf,isPairOftake the lookup (required, so the compiler names every site that has not passed it).@objectstack/metadata-protocol:installedManifestsOfEngine(engine);isServedOverrideRowOf(row, base, installed),OverrideWriteClosure.installed, the overlay merge, the lock state, the layered/list reads, the per-package layer, the stored-flow slot, the served-overrides census, the package-less migration census, the packaged-base refusal and the write gates. Hosts that read the engine gain theenginegetter (the codebase'sreadEnginepattern).objectql(plugin, registry uninstall refusal),runtime(customizeInPairRemedy, the per-package customizations read),service-automation(flow precedence, with the objectql registry),service-datasource,cli(offline override declarations: the walked manifests),cloud-connection(install-local: the delivered manifests over the registry's).customization-pair-lookup.test.tsasks every row twice, with nothing installed and with every row installed (the module declaring its App the way HotCRM's modules do), and gets the old derivation; every existing suite of the touched packages is green with the lookup passed.os validate's customization-namespace advisory skips a pair throughisCustomizationPackage. Edge: an id ending in.customthat no base derives (a.custom.custom, bare.custom,A.custom) was skipped before, becausecustomizationPackageIdanswersbase_is_customization_packagefor it, and is now judged like any package, becauseisCustomizationPackageanswersfalse(measured on the head's spec build).2. The kernel verdict on the ensure door (narrowing)
system/cloud), no pair yet201, creates<id>.custom409 RESOURCE_CONFLICT, nothing created or written200,created: false409 RESOURCE_CONFLICT; the pair stays installed and readable403 NOT_OVERRIDABLE, naming the pair403 NOT_OVERRIDABLE, the sentence says kernel objects are not customizable in this version and names no pair403 NOT_OVERRIDABLE, naming the pair403 NOT_OVERRIDABLE, the same sentence, no pair namedpackage200, copy in<kernel id>.custom(created)200, copy environment-wide, codePERMISSION_SET_CLONE_ENVIRONMENT_WIDE_KERNEL_SOURCEpackage200, copy in<kernel id>.custom409 RESOURCE_CONFLICT, the ensure door's sentenceThe code.
409 RESOURCE_CONFLICTis the door's existing family for "the base's state rules the pair out" (a writable base, no namespace, a namespace with no pair namespace, a non-SemVer version, a malformed existing pair), every one of which answersRESOURCE_CONFLICTinpackages-ensure-customization.test.ts.403would misread as an authority problem (even the platform operator is refused), and422 NAMESPACE_CONFLICTnames another condition. No ledger code and nopackages/specedit.Where. First in
customizationPairEnsureRefusal, the one reading of the door's registry-decidable refusals, and the two overlay closes keep their place:overlayPairFixOfasks the door's other judgements with the kernel verdict left out (judgeKernel: false) and marks a kernel base's fix with itskernelScope, so the package-less close and the close of a write naming the base stay shut on a kernel item exactly where they were, with a sentence that names no pair (seat ruling on review should-fix 2; ruling record 6691217245; #31027 Q10 is unmerged).The "new similar" doors (found by the dogfood battery). The permission-set clone door landed a copy of a kernel-shipped set (
member_default, shipped bycom.objectstack.plugin-security, scopesystem) in the source package's pair through the ensure door, so the verdict refused the commonest lock-and-clone path. A clone is a new item, not a customization of the kernel item, so with nopackagenamed the copy is now saved environment-wide, exactly as a copy of an environment-authored set is, with its own notice and code (PERMISSION_SET_CLONE_ENVIRONMENT_WIDE_KERNEL_SOURCE; a Console that does not know the code shows the English sentence, as it does for any unknown code). A namedpackagestill lands there. The kernel predicate is one spelling,kernelPackageScope(@objectstack/metadata-protocol), read by the door and the clone door. The flow clone door has no environment-wide landing (an unpackaged flow already needspackage), so a kernel-shipped flow gets the ensure door's 409 sentence. This landing is option A of a decision card put to the maintainer; B (refuse, namingpackage) and C (a kernel pair for clones only) are the alternatives.Test fixtures.
packages-ensure-customization.test.tsandautomation-flow-clone-custom-package.test.tsmodelled "an installed app" asscope: 'system', which is the kernel's scope (manifest schema:system= cross-project shared identity,cloud= control plane). They now hold the base as code, the way an installed app's packages are held (HotCRM 6961ab1 declares no scope).Blast radius (measured on
origin/main3fe5f2a)23
pairOf/isPairOfsites in 14 files;isServedOverrideRowOf17 non-test callers;judgeOverrideWrite4 (incl.clioffline andcloud-connectioninstall-local). This PR: 31 source files (+471 / −156), 16 test files (+342 / −123), one changeset; 48 files, +842 / −279 in all.PR 2 / PR 3 split
appOfthe identity; the kernel verdict..custompair per App, and one platform pair for the kernel suite #31027 merges):appOfresolves App membership from the lookup (Q11); legacy per-package pairs stay served (Q12:isPairOfaccepts the App pair or an installed legacy<base>.custom); the ensure door answers the App pair withbasePackageId= the App id (the spec describe "packageId:<basePackageId>.custom" stays true); the HotCRM rehearsal.Findings kept for PR 3: the package-less migration's incomplete-declarations census parses pair→base (
customizationBasePackageId) and misses module overrides under the App unit; its census still lists a kernel item's package-less row asmovableand the apply step fails it with the kernel sentence (a disposition for it is a contract question); the dogfood helpercustomization-pair.tsand sixui/packages/app-shellcall sites derive<base>.customclient-side; acustomizableflag on the package list row would need a spec sub-card. A package-less row on a kernel item stored before the D11 close stays served, but nothing can move it now: its package-less delete is closed, and the ceremony's ensure step refuses the kernel base. Its disposition belongs with the census question already listed.Verification
Head
b94d1ac68d151d99ad908992ad25f65bf2413a6d, which merges today'sorigin/main022b4e7 (merge commit 20c8cd6, no conflicts; no rebase). Exit codes landed on disk before reading.pnpm typecheckin metadata-protocol, objectql, runtime, service-automation (head)pnpm teston the head: metadata-protocol / objectql / runtime / service-automation / metadata-core / lintpnpm teston1340933cfor the packages this round did not touch: service-datasource / cloud-connection / cli021a8a3d(no dogfood pin moved this round)--no-inline-configon every.tsfile this round changeddispatch-gates --commandson the head (102 families: the 101 of the last round pluscheck:overlay-whitelist-table, which the lifecycle docs edit adds), each run with</dev/null--ran: 102 derived, 102 run, 0 NOT-MEASURED, 0 UNRUNGate notes: the three first-pass non-zeros of the earlier round (
check-plugin-teardown-shape --self-test,check:dual-build-cjs-loads,check:test-source-alias) all exit 0 on this head with no further change.Ablations (
scripts/ablation-replace.mjs, restore proven by blob hash == HEAD and an emptygit status --porcelain):customizationPairEnsureRefusaldisabled (ablation-dist-preflightconfirmed the mutation inmetadata-protocol/dist): red — the three runtime door pins (system, cloud, the legacy kernel pair) and the objectql pin of the legacy pair served after a refused ensure;resolveDefaultCloneLandingdisabled: red —[#29594] a set a kernel package ships copies environment-wide;overlayPairFixOfdisabled (judgeKernel: falseremoved, metadata-protocol dist rebuilt and checked): red,the closes keep refusing a kernel item's save, naming no pair(the save was admitted);restoreFlowRegistrationon a refused landing inregisterAndSaveFlowdisabled: red, both[#29594] a kernel-shipped flowcases (the engine held the copy).Both restored with
git checkout, tree clean, suites re-run green.The lookup threading needs no ablation: it changes no answer, which every existing suite of the nine packages confirms, and
customization-pair-lookup.test.tspins that both lookups (nothing installed, every row installed) give the old derivation.Acceptance notes
packages/spec,ui/orskills/edit; no ADR edit.content/docs/concepts/metadata-lifecycle.mdxadded), 1 changeset.