Repository navigation
Commit 486b0a9
feat(metadata-core, metadata-protocol, runtime)!: an App and its modules share one customization pair (#31134)
<!-- claude-projects-attribution: {"github_login":"marchtian"} -->
_Requested by **田騏** · [project
thread](https://claude.ai/code/project/chan_01VmuUhXU9SSUzqJYyXSmYSi?thread=cmsg_01VmuUhXU9SSUzqJYyXSmYSiP9RS1KjicEPyef7VNhFzFa)_
Fixes #29594
Clause-②: yes (narrowing) — the ensure door refuses two asks it
accepted: a package that reaches several Apps and whose namespace picks
none of them (was `201` with its own pair, now `409`, merge question
11), and a module whose earlier per-package pair holds the App's pair
namespace (was `201`/`200`, now `409 RESOURCE_CONFLICT` naming that
pair, merge question 12); `appOf` now returns `string | undefined`. The
widening half: a module of an App gets the App's pair, and
`@objectstack/metadata-core` exports `pairsOf`, `AppOfRefusal` and
`PairOfRefusal`. Claim amendment 6707871792.
Metadata-protocol: semantic
Skills-docs: follow-up owed — skills/objectstack-platform/SKILL.md (the
"Customizing an installed package" paragraph names one pair per base; it
should say a module of an App customizes in the App's pair)
## 维护者速读
- 改了什么:一个 App 和它带来的模块现在共用一个定制包 `<App id>.custom`。HotCRM 的销售 App 和 3
个模块,无论从哪个包点「定制」,都落进同一个 `app.objectstack.hotcrm.custom`。
- 为什么改:按裁决 6691022506,定制单位是 App。以前每个模块各要一个定制包,命名空间相撞时直接 422,HotCRM
的模块根本定制不了。
- 风险与回滚:有两种少见情况从成功变成 409(同时挂两个 App 且命名空间选不出的包;模块的旧定制包占着 App 的命名空间),PR 和
changeset 里写了改法。旧版本给模块建的按包定制包继续被读取和服务,不迁移、不删除;它占着 App 的命名空间时,ensure 门回
409 说明原因,不再回 422。回滚就是还原本 PR,已建的 App 定制包仍是普通可写包。
- 先看:`packages/metadata-core/src/customization-pair-lookup.ts`(appOf /
pairOf / pairsOf),然后
`packages/metadata-protocol/src/protocol-customization-package.ts`(ensure
门)。
- 两分钟验证:`cd packages/runtime && npx vitest run --project local
src/domains/packages-ensure-customization.test.ts
src/domains/meta-object-extension-item.test.ts`,看「App unit」两组用例。
- 需要你做的:无,席位落地
## BREAKING
Accept-set narrowing at `POST /api/v1/packages/:id/ensure-customization`
(`@objectstack/metadata-protocol`) and a return-type change in
`@objectstack/metadata-core`. ADR-0087 disposition (in the changeset):
`not-required (no-migration-prescription)`, since no schema key moves
and nothing at rest changes.
- **A package that reaches several Apps and whose namespace picks
none.** Before: `201` with its own pair `<package id>.custom`. Now: `409
RESOURCE_CONFLICT`, nothing created. Remedy: declare the one App the
package belongs to in `manifest.dependencies`, or give it that App's
namespace.
- **A module whose earlier per-package pair holds the App's pair
namespace.** Before: the module's own `<module id>.custom` (`201`, then
`200`). Now: `409 RESOURCE_CONFLICT` naming that pair, from the App and
every member; the pair stays installed and served. Remedy: keep
customizing in that pair, or export it (`GET
/api/v1/packages/<pair>/export`), delete it, and ensure again to get the
App's pair.
- **`appOf` returns `string | undefined`** (was `string`); `undefined` =
no single App. Remedy: `appOf(id, installed) ?? id` keeps the old
reading; `pairOf`'s refusal reason may now be `'no_single_app'`
(`PairOfRefusal`).
## What changes
The customization unit is the App, as ruled on #29594 (ruling record
6691022506):
> **The customization unit is the App** (ADR-0019 D1–D4: the `type: app`
package and the `type: module` / `plugin` packages its artifact brings;
ADR-0130 D1/D6: one artifact, one version). One pair per App: `<app
id>.custom` (HotCRM → `app.objectstack.hotcrm.custom`), namespace = the
App package's own namespace + `_custom`, dependency = the App's
`^major`. A module never owns a pair. Customize on any object of any
member lands in the App's pair; the ensure door returns the App's
existing pair (200) instead of 422. 422 `NAMESPACE_CONFLICT` stays for a
namespace held by a package outside the App's artifact (registry
`coOwnersOf()` arms unchanged).
Kernel objects stay not customizable in this version (ruling record
6691217245). ADR-0146 D3 as amended by #31027 governs; merge questions
10, 11 and 12 are implemented as written.
- **`appOf` reads the installed manifests**
(`packages/metadata-core/src/customization-pair-lookup.ts`). A `type:
'app'` package is its own App. Any other package's App is the installed
non-kernel `type: 'app'` package it declares in `manifest.dependencies`;
with none declared directly, the walk follows declared parents and stops
at the first App on each path (cycle-safe, kernel and uninstalled ids
skipped). Several Apps: the one holding the package's namespace; none of
them, or several: `undefined`, and `pairOf` refuses with `no_single_app`
(merge question 11). A package that declares no App is its own unit (N2
holds). A kernel package (scope `system` / `cloud`) keeps its own
answer, because no platform pair is created in this version; a new
metadata-protocol pin holds that list equal to
`READ_ONLY_PACKAGE_SCOPES`.
- **Two questions, two readers.** `pairOf(base)` answers where a
customization of `base` lands (the App's pair). `pairsOf(base)` /
`isPairOf(pair, base)` answer which packages' rows are `base`'s
customizations: the App's pair, then the per-package pair an earlier
version created for `base` when it is installed (merge question 12).
Every read site PR 2 threaded now asks `pairsOf` / `isPairOf`: list
reads, layered reads, overlay merge, served-overrides census,
translation bundle binding, environment rows.
- **The ensure door** (`protocol-customization-package.ts`) answers the
App's pair for any member: id, namespace `<app ns>_custom`, dependency
`{ [app]: ^<app major> }`, name `<App name> (custom)`, and
`basePackageId` = the App. `201` the first time any member asks, `200`
after. Refusals: a kernel base `409` (unchanged); `no_single_app` `409`;
the App namespace held by a legacy pair of a member of the same App `409
RESOURCE_CONFLICT` naming it (it stays installed and served; never
created beside it); held by any package outside the App `422
NAMESPACE_CONFLICT` (unchanged).
- **Write closure.** `widenPairClosureToAppMembers`
(`protocol-write-package-scope.ts`) lets a pair whose closure includes
its App reach every installed package whose `pairOf` is that pair, so an
override or an `object_extension` of a module's item saved into the
App's pair is admitted. The packaged-base refusal's pending-pair scope
uses the same widening.
- **Legacy pairs honoured by every reader** (merge question 12, contract
review should-fix 1): `isPairRowRemoval` asks `isPairOf`, so a legacy
`<module>.custom` still removes its own declared override past the
sealed-removal refusal; the bundle-member reader (`pairsOfAddedMember` /
`findPairAddedMemberRow`, `protocol-write-gates.ts`) reads every pair in
`pairsOf` order; the customizations read (`packages.ts`) lists object
extensions from every pair and never counts a pair as another package's
contribution; the sealed and undeclared-same-name sentences name the
legacy pair as a pair. No reader is App-pair-only.
- **Census** (`protocol-packageless-overlay-migration.ts`): the
incomplete-declaration census reads the base from the row's declared
`overrides.package` checked against `isServedOverrideRowOf`, instead of
deriving it from the pair id, so a module's override in the App's pair
is listed under the module.
- **Uninstall** (`registry.ts`): the refusal for an installed pair reads
the pair named for the package among `pairsOf`. Uninstalling the App is
refused while the App's pair is installed; uninstalling a module is not;
a module whose own earlier pair is installed is still refused naming it.
- **Lint** (`validate-customization-namespace.ts`):
`namespace-no-customization-pair` skips a delivered package whose
`appOf` is another delivered package. `@objectstack/lint` gains a
`workspace:*` dependency on `@objectstack/metadata-core`.
- **Dogfood helper** (`packages/qa/dogfood/test/customization-pair.ts`):
`ensurePair` reads the pair id from the door's answer and asserts it.
- Docs: `content/docs/concepts/metadata-lifecycle.mdx`,
`content/docs/deployment/install-modes.mdx`,
`content/docs/api/metadata-api.mdx`.
- `packages/objectql/src/plugin.ts` is not edited by this PR (draft PR
#31093 edits it; no overlap).
### N1 rule (decided here)
A per-package pair an earlier version created for a module (namespace
e.g. `crm_svc_custom`) is **not moved** by this PR: it stays its own
installed package and is served beside the App's pair through `pairsOf`.
When a move ceremony is built, the objects it carries keep their
`crm_svc_custom_<short>` names as a **declared exception**, and the
App's pair is not given a set of namespaces. Why: no live instance
exists to move; renaming a data-holding table is refused by the
platform; and a namespace set would permanently widen what the App's
pair owns, where a declared exception stays visible and bounded.
## Tests
- `packages/metadata-core/src/customization-pair-lookup.test.ts`: 13
resolution rows (App, HotCRM module, module with a kernel dependency,
nested module, plugin with two Apps where one holds the namespace, two
Apps where neither does, standalone App, own-unit plugin, cycle,
uninstalled App dependency, kernel, kernel declaring an App, no
namespace), nothing-installed equals the old derivation, HotCRM's four
packages answer one pair, legacy arm of `pairsOf` / `isPairOf`.
- `packages/runtime/src/domains/packages-ensure-customization.test.ts`,
"the App unit": module first then App and others `[201, 200, 200, 200]`,
one pair, one publish; outside-App namespace `422`; legacy member pair
`409` from the App and every module, legacy unchanged; two-App plugin
`409`; own-unit plugin its own pair `201`.
- `packages/runtime/src/domains/meta-object-extension-item.test.ts`: the
HotCRM-shape rehearsal (below).
- `packages/runtime/src/domains/meta-legacy-pair-served.test.ts`
(contract review should-fix 2; on both topologies, a host-config kernel
and an environment kernel, since the incremental review): HotCRM-shape
App + module shipping a dashboard and a doc, and the module's legacy
pair `app.objectstack.hotcrm.service.custom` holding a declared override
written straight to the store (a row an earlier version wrote). Through
`HttpDispatcher`'s catch-all, the override is served at the module's
address by name, in the list and in the layered read; through
`RestServer`'s `DELETE /meta/:type/:name?package=<legacy pair>` the pair
removes it (the doc, which has no overlay channel, included), and the
module's item is served again. A third case: the legacy pair still saves
a declared override.
- `packages/objectql/src/registry-assert-package-uninstallable.test.ts`:
the App refused naming the App's pair, a module not; a module with its
legacy pair refused naming it.
-
`packages/objectql/src/protocol-packageless-overlay-migration.test.ts`:
a module's override in the App's pair is listed under the module.
-
`packages/metadata-protocol/src/package-writability-kernel-scopes.test.ts`:
`appOf`'s kernel scopes equal `READ_ONLY_PACKAGE_SCOPES`.
### Rehearsal
HotCRM (`dev:crm`) is not reachable from this checkout, so the rehearsal
is an in-repo fixture of the same shape, run through the real dispatcher
and the real lookup (nothing stubbed): App `app.objectstack.hotcrm`
(namespace `crm`) and three modules `…service` / `…revenue` /
`…marketing` (type `module`, namespace `crm`, each declaring the App
`^6`), registered with `engine.registerApp` on a SqlDriver `:memory:`
kernel. Ensure via `HttpDispatcher.handlePackages` answers `[[app, 201,
app.objectstack.hotcrm.custom], [service, 200, same], [revenue, 200,
same], [marketing, 200, same]]` with one pair installed. An
`object_extension` adding `sla_tier__c` to the module object
`crm_service_case` is saved and published into the App's pair;
`getMetaItem`, `getMetaItemLayered` and the registry read it back, its
extenders are `[app.objectstack.hotcrm.custom]`, and a record inserted
with `sla_tier__c: 'gold'` stores it.
## Acceptance notes
- Negative pins were ablated (refused behaviour put back, build, dist
preflight, run red, restore with blob == HEAD):
- 422 kept for an outside-App namespace: the same-App guard forced true
→ 3 red (`the namespace held by a package outside the App: 422
NAMESPACE_CONFLICT still, asked from a module` and the two older 422
pins); restored → green.
- Kernel 409: the kernel-scope check disabled → 5 red (kernel `system` /
`cloud` ensure, kernel legacy pair, flow clone for both scopes);
restored → green.
- App resolution: `appOf`'s single-App answer replaced by the package
itself → 3 red (module-first ensure, legacy-pair 409, rehearsal);
restored → green.
- Known limits, left as follow-ups: the pair-added bundle member reader
(`protocol-write-gates.ts`) reads only the App's pair, not a legacy
pair; no ceremony re-identifies a legacy pair as the App's (merge
question 12 leaves it open); the six `ui/packages/app-shell` call sites
that spell `customizationPackageId(base)` (domain:ui); the list-row
`customizable` flag needs a spec sub-card.
## Verification
<details><summary>Scoped tests</summary>
```
metadata-core Test Files 25 passed · Tests 989 passed
metadata-protocol Test Files 320 passed | 4 skipped · Tests 22524 passed | 26 skipped (1 red fixed, file re-run 91/91); new kernel-scopes pin 2/2
objectql Test Files 564 passed · Tests 11422 passed; registry files 49 passed / 698 after the uninstall fix; uninstall file 11/11
runtime Test Files 554 passed · Tests 8914 passed | 19 skipped; App-unit files 3 passed / 87
lint Test Files 197 passed · Tests 8331 passed (re-run after the alias: 8330 + runtime-lazy-deps raced a concurrent rebuild of lint/dist, re-run alone 6/6)
cli (unit) Test Files 396 passed · Tests 5576 passed
cloud-connection Tests 935 passed (full re-run after the uninstall fix)
service-automation Test Files 264 passed · Tests 3431 passed
service-datasource Test Files 72 passed · Tests 1279 passed
typecheck metadata-core, metadata-protocol, objectql, lint, runtime, qa/dogfood :: exit 0
eslint (changed) :: exit 0
```
</details>
<details><summary>Ablations</summary>
```
# 1 · 422 kept for a namespace held outside the App (metadata-protocol/src/protocol-customization-package.ts)
anchor "if (ownerBase !== null && customizationPackageId(ownerBase).ok && appOf(ownerBase, installed) === appId) {" x1 -> x0
blob 8f868ff -> 1ad2eed1b3de · dist preflight: marker present in 2 built files
× the composed namespace is owned by another package: 422 NAMESPACE_CONFLICT naming the owner
× an owner the registry would read as a co-owner is refused all the same: the pair namespace is the pair's alone
× the namespace held by a package outside the App: 422 NAMESPACE_CONFLICT still, asked from a module
Tests 3 failed | 40 passed (43)
restored: blob == HEAD (8f868ff), git diff HEAD empty; rebuilt, dist preflight --absent ✓
restored run (3 files): Test Files 3 passed · Tests 87 passed
# 2 · kernel 409 (same file)
anchor " if (kernelScope !== undefined) {" x1 -> x0
blob 8f868ff -> 2f0051e03486 · dist preflight: marker present in 2 built files
× a kernel base (scope 'system'): 409, not customizable in this version, nothing created
× a kernel base (scope 'cloud'): 409, not customizable in this version, nothing created
× a kernel pair an earlier version created stays installed: only the ensure call is refused
× scope 'system', no `package` named: 409 with the ensure door's sentence, and the engine holds no copy
× scope 'cloud', no `package` named: 409 with the ensure door's sentence, and the engine holds no copy
Tests 5 failed | 59 passed (64)
restored: blob == HEAD (8f868ff), git diff HEAD empty; restored run green (as above)
# 3 · App resolution (metadata-core/src/customization-pair-lookup.ts)
anchor " if (apps.length === 1) return apps[0];" x1 -> x0
blob 16a7bd5 -> ee33f4027082 · dist preflight: marker present in 2 built files
× a module first, then the App and the other modules: one app.objectstack.hotcrm.custom, 201 then 200
× a per-package pair an earlier version created for a module holds the namespace: 409 naming it, never 422; it stays installed (merge question 12)
× ensure on the App and each module answers one app.objectstack.hotcrm.custom; a module object extended there reads back
Tests 3 failed | 63 passed (66)
restored: blob == HEAD (16a7bd5), git diff HEAD empty; rebuilt, dist preflight --absent ✓
```
</details>
<details><summary>Dispatch gates</summary>
Derived at `5ac6ed0f` (111 commands); the later merge of `origin/main`
(`016eb459`) touches none of this diff's files and derives the same
list.
```
node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0
node scripts/check-adr-0087-registration.mjs --self-test :: exit 0
node scripts/check-adr-links.mjs :: exit 0
node scripts/check-adr-links.mjs --self-test :: exit 0
node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0
node scripts/check-closing-keyword-parity.mjs :: exit 0
node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0
node scripts/check-comment-mask-adoption.mjs :: exit 0
node scripts/check-comment-mask-adoption.mjs --self-test :: exit 0
node scripts/check-comment-mask-corpus.mjs :: exit 0
node scripts/check-console-intercept-disarm.mjs :: exit 0
node scripts/check-console-intercept-disarm.mjs --self-test :: exit 0
node scripts/check-doc-route-spelling.mjs --advisory :: exit 0
node scripts/check-doc-route-spelling.mjs --self-test :: exit 0
node scripts/check-docs-section-name.mjs :: exit 0
node scripts/check-docs-section-name.mjs --self-test :: exit 0
node scripts/check-dts-emitted.mjs --self-test :: exit 0
node scripts/check-empty-changeset.mjs --base origin/main :: exit 0
node scripts/check-empty-changeset.mjs --self-test :: exit 0
node scripts/check-engine-split-ratio.mjs --self-test :: exit 0
node scripts/check-issue-citations.mjs :: exit 0
node scripts/check-osv-exemptions.mjs :: exit 0
node scripts/check-osv-exemptions.mjs --self-test :: exit 0
node scripts/check-plugin-teardown-shape.mjs :: exit 0
node scripts/check-plugin-teardown-shape.mjs --self-test :: exit 0
node scripts/check-section-landing-index.mjs :: exit 0
node scripts/check-section-landing-index.mjs --self-test :: exit 0
node scripts/ci/assert-turbo-remote-cache.mjs --self-test :: exit 0
node scripts/ci/plan-test-core-shards.mjs --self-test :: exit 0
node scripts/ci/prune-turbo-cache.mjs --self-test :: exit 0
node scripts/ci/snapshot-next.mjs --self-test :: exit 0
node scripts/docs-audit/check-affected-docs.mjs :: exit 0
node scripts/docs-audit/check-drift-comment.mjs :: exit 0
node scripts/osv-base-relative.mjs --self-test :: exit 0
node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0
node scripts/release-pending-publish.mjs --self-test :: exit 0
pnpm --filter @objectstack/lint run check:doc-formula-expressions :: exit 0
pnpm --filter @objectstack/lint run check:doc-security-posture :: exit 0
pnpm --filter @objectstack/spec run check:docs :: exit 0
pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0
pnpm --filter @objectstack/spec run check:empty-state :: exit 0
pnpm --filter @objectstack/spec run check:liveness :: exit 0
pnpm --filter @objectstack/spec run check:llms-txt :: exit 0
pnpm --filter @objectstack/spec run check:skill-examples :: exit 0
pnpm --filter @objectstack/spec run check:strictness-ledger :: exit 0
pnpm --filter @objectstack/spec run check:variant-docs :: exit 0
pnpm --filter @objectstack/spec run check:yaml-examples :: exit 0
pnpm check:changeset-fixed :: exit 0
pnpm check:changeset-gate-self-tests :: exit 0
pnpm check:ci-filter-parity :: exit 0
pnpm check:closed-ceremony-text :: exit 0
pnpm check:corpus-claim-drift :: exit 0
pnpm check:cross-package-test-inputs :: exit 0
pnpm check:dispatcher-error-vocabulary :: exit 0
pnpm check:doc-anchors :: exit 0
pnpm check:doc-authoring :: exit 0
pnpm check:doc-frontmatter :: exit 0
pnpm check:docs-audit-scope :: exit 0
pnpm check:docs-redirects :: exit 0
pnpm check:docs-single-h1 :: exit 0
pnpm check:docs-spec-enumerations :: exit 0
pnpm check:docs-transcript-drift :: exit 0
pnpm check:driver-memory-census :: exit 0
pnpm check:dts-closure :: exit 0
pnpm check:dual-build-cjs-loads :: exit 0
pnpm check:durability-log-level :: exit 0
pnpm check:engine-double-contract :: exit 0
pnpm check:error-status-conformance :: exit 0
pnpm check:filter-alias-parity :: exit 0
pnpm check:gitlink-declared :: exit 0
pnpm check:issue-citations :: exit 0
pnpm check:keyed-text-bounds :: exit 0
pnpm check:lean-entry-closure :: exit 0
pnpm check:logger-receiver-detach :: exit 0
pnpm check:manifest-repository-directory :: exit 0
pnpm check:merge-driver :: exit 0
pnpm check:nul-bytes :: exit 0
pnpm check:objectql-double-limit :: exit 0
pnpm check:org-identifier :: exit 0
pnpm check:overlay-whitelist-table :: exit 0
pnpm check:override-consistency :: exit 0
pnpm check:page-declaration-shape :: exit 0
pnpm check:platform-object-tenancy-census :: exit 0
pnpm check:pnpm-flag-spellings :: exit 0
pnpm check:published-files :: exit 0
pnpm check:published-readme-links :: exit 0
pnpm check:query-options-erasure :: exit 0
pnpm check:react-page-adapter-contract :: exit 0
pnpm check:refd-timer-probe :: exit 0
pnpm check:registry-log-declared :: exit 0
pnpm check:rest-log-declared :: exit 0
pnpm check:rest-log-spy-declared :: exit 0
pnpm check:role-word :: exit 0
pnpm check:route-envelope :: exit 0
pnpm check:service-providers :: exit 0
pnpm check:skill-identifier-liveness :: exit 0
pnpm check:slot-lookup :: exit 0
pnpm check:sourcemap-no-sources-content :: exit 0
pnpm check:system-context-census :: exit 0
pnpm check:test-source-alias :: exit 0
pnpm check:tier-file-adoption :: exit 0
pnpm check:turbo-task-graph :: exit 0
pnpm check:type-check-coverage :: exit 0
pnpm check:type-check-debt :: exit 0
pnpm check:undeclared-dep-imports :: exit 0
pnpm check:vendor-export-contract-resolve :: exit 0
pnpm check:vendor-version-stamps :: exit 0
pnpm check:vouch-for-reference-callers :: exit 0
pnpm check:watch-hint-literal :: exit 0
pnpm check:where-matcher :: exit 0
pnpm check:workspace-manifest-cycles :: exit 0
dispatch-gates --ran: 111 derived, 111 run, 0 NOT-MEASURED, 0 UNRUN :: exit 0
pnpm lint :: exit 0
node scripts/check-platform-checklist.mjs --changed-from origin/main :: exit 0
```
First pass: 3 gates exited 3 (missing builds / the pinned teardown
fixture commit) and `check:test-source-alias` exited 1 (lint needed an
anchored `@objectstack/metadata-core` alias; the rehearsal test needed a
module-top `import '@objectstack/metadata'`). Both fixed in `5ac6ed0f`;
the prerequisites were built and the four re-run with every changeset,
alias and test-input gate, all exit 0.
</details>
<details><summary>Narrowing follow-up at the final head
<code>775c97b5</code></summary>
```
changeset commit 775c97b (Clause-② yes (narrowing), BREAKING banner, ADR-0087 not-required)
node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0 (ADR-0087 exemption no-migration-prescription noticed)
node scripts/check-adr-0087-registration.mjs --self-test :: exit 0
node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0
node scripts/check-empty-changeset.mjs --base origin/main :: exit 0
pnpm check:changeset-fixed :: exit 0
pnpm check:changeset-gate-self-tests :: exit 0
dogfood battery (os-verify-lock, build closure --filter=@objectstack/dogfood... then pnpm --filter @objectstack/dogfood test, no shard, no path)
Test Files 435 passed | 3 skipped (438)
Tests 3609 passed | 41 skipped (3650)
os-verify-lock: VERDICT command-exit 0 · held the lock 1971s (32m51s)
```
</details>
<details><summary>Contract-review patch round at the final head
<code>fb2fd21e</code></summary>
```
should-fix 1 + 2: commits c0172dd..787f842, then origin/main merged (fb2fd21)
ablation · legacy pair's reads (metadata-core pairsOf legacy arm disabled)
blob 16a7bd5 -> 44efe1c0117c · dist preflight: marker present in 2 built files
× the pair still saves a declared override of the module's item (the write gate admits it)
× its declared override is served at the module's address (by name, list, layered) … byName/list/layered answered "Service board (shipped)"
× a doc (no overlay channel) the legacy pair overrides is served … by-name answered the shipped doc
Tests 3 failed (3)
restored: blob == HEAD, git diff HEAD empty; rebuilt, dist --absent ✓ -> Tests 3 passed (3)
ablation · legacy pair's removal (isPairRowRemoval back to the single pairOf)
blob cca50ea -> dad267a4800c · dist preflight: marker present in 2 built files
× a doc (no overlay channel) … its removal passes the sealed-removal refusal as the pair's own
403 NOT_OVERRIDABLE "… sealed against removal: its type takes no environment overlay …"
Tests 1 failed | 2 passed (3)
restored: blob == HEAD, git diff HEAD empty; rebuilt, dist --absent ✓ -> Tests 3 passed (3)
typecheck metadata-core, metadata-protocol, objectql, runtime, lint :: exit 0
metadata-protocol Test Files 322 passed | 4 skipped · Tests 22527 passed | 26 skipped
objectql Test Files 564 passed · Tests 11425 passed
runtime Test Files 555 passed · Tests 8923 passed | 19 skipped
pnpm lint :: exit 0
dispatch-gates --commands: 111 derived at fb2fd21, all 111 :: exit 0
dispatch-gates --ran: 111 derived, 111 run, 0 NOT-MEASURED, 0 UNRUN :: exit 0
dogfood battery at fb2fd21 (os-verify-lock, build closure then pnpm --filter @objectstack/dogfood test)
Test Files 435 passed | 3 skipped (438)
Tests 3609 passed | 41 skipped (3650)
os-verify-lock: VERDICT command-exit 0 · held the lock 1684s
```
</details>
<details><summary>Incremental-review round at the final head
<code>481d4049</code></summary>
```
A. changeset: '@objectstack/runtime': minor added (packages.ts changes GET /packages/:id/customizations); title scope names runtime
B. meta-legacy-pair-served.test.ts runs on both topologies (environmentId undefined and 'env_hotcrm'): Tests 6 passed (6)
nit: packages.ts pair loop no longer an if…for on one line
ablation · the protocol door's pair-row pass (protocol-diff-delete-load.ts:498, `!pairRowRemoval` removed)
blob 9df68e0 -> bee509989cbd · dist preflight: marker present in 2 built files
× a doc (no overlay channel) … its removal passes the sealed-removal refusal as the pair's own (environment kernel)
"Metadata item 'doc/crm_service_guide' is provided by a managed package and is sealed against removal …" (refusePackagedBaseRemoval's own sentence)
Tests 1 failed | 5 passed (6)
restored: blob == HEAD, git diff HEAD empty; rebuilt, dist --absent ✓ -> Tests 6 passed (6)
origin/main merged at 481d404
runtime typecheck :: exit 0 · App-unit + legacy-pair files 3 passed / 72 · src/domains/packages* 58 files / 1008 passed
dispatch-gates --commands: 111 derived at 481d404, all 111 :: exit 0; --ran: 111 run, 0 NOT-MEASURED, 0 UNRUN :: exit 0
dogfood battery at 481d404: Test Files 435 passed | 3 skipped (438) · Tests 3609 passed | 41 skipped (3650) · VERDICT command-exit 0 (2423s)
```
</details>
🤖 Generated with [Claude Code](https://claude.com/claude-code)
<!-- signed: domain:server#2 · marchtian ·
session_017ihL2dM9PvPPB9BMVdhf2R -->
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 66b17a3 commit 486b0a9
32 files changed
Lines changed: 1131 additions & 218 deletions
File tree
- .changeset
- content/docs
- api
- concepts
- deployment
- packages
- lint
- src
- metadata-core/src
- metadata-protocol/src
- objectql/src
- qa/dogfood/test
- runtime/src/domains
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
111 | 111 | | |
112 | 112 | | |
113 | 113 | | |
114 | | - | |
| 114 | + | |
| 115 | + | |
115 | 116 | | |
116 | 117 | | |
117 | 118 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
115 | 115 | | |
116 | 116 | | |
117 | 117 | | |
118 | | - | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
| 121 | + | |
| 122 | + | |
| 123 | + | |
| 124 | + | |
| 125 | + | |
119 | 126 | | |
120 | 127 | | |
121 | 128 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
112 | 112 | | |
113 | 113 | | |
114 | 114 | | |
115 | | - | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
116 | 118 | | |
117 | 119 | | |
118 | 120 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
51 | 51 | | |
52 | 52 | | |
53 | 53 | | |
| 54 | + | |
54 | 55 | | |
55 | 56 | | |
56 | 57 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
5 | 5 | | |
6 | 6 | | |
7 | 7 | | |
8 | | - | |
9 | | - | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
10 | 11 | | |
11 | 12 | | |
12 | 13 | | |
| |||
36 | 37 | | |
37 | 38 | | |
38 | 39 | | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
39 | 44 | | |
40 | 45 | | |
41 | 46 | | |
| |||
55 | 60 | | |
56 | 61 | | |
57 | 62 | | |
| 63 | + | |
58 | 64 | | |
59 | 65 | | |
60 | 66 | | |
| |||
104 | 110 | | |
105 | 111 | | |
106 | 112 | | |
107 | | - | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
108 | 121 | | |
| 122 | + | |
109 | 123 | | |
110 | 124 | | |
111 | 125 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1 | 1 | | |
2 | 2 | | |
3 | | - | |
| 3 | + | |
4 | 4 | | |
5 | 5 | | |
6 | 6 | | |
7 | 7 | | |
8 | 8 | | |
9 | 9 | | |
| 10 | + | |
10 | 11 | | |
11 | 12 | | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
12 | 23 | | |
13 | 24 | | |
14 | 25 | | |
| |||
0 commit comments