Skip to content

Commit 51297e9

Browse files
os-billclaude
andauthored
spec/kernel: carve the package-registry persistence out of marketplace into an always-on core capability (#18694)
Fixes #18053 Clause-②: yes Director ruling `5650202813` on #17676 (decision batch #125 item 2, maintainer verbatim 「同意」), **item 1 — the spec-constant half**. The runtime half (items 2, 3, 5) stays on #17676 with the `domain:engine` lane. ⛔ The ruling's direction is not re-opened here; the one judgment this card carries is the NAME, which the ruling deliberately declines to give. ## What changed Three edits in `packages/spec/src/kernel/platform-capabilities.ts`, plus the pins that read it. | constant | before | after | |---|---|---| | `PLATFORM_CAPABILITY_TOKENS` | 28 tokens | **29** — `package-registry` added after `marketplace`; none removed; the relative order of all 28 unchanged | | `PLATFORM_ALWAYS_ON_CAPABILITIES` | 10 entries | **11** — `package-registry` appended at the tail; none removed; the relative order of all 10 unchanged | | `PLATFORM_CAPABILITY_PROVIDERS` | 1:1 with the vocabulary | 1:1 still — one row added: `package-registry` = `@objectstack/service-package`, `open` edition | The full before/after item lists are in the report comment on #18053. ## The name — and the candidates it beat The comparison set, every token in the vocabulary today: `ai`, `ai-studio`, `i18n`, `ui`, `auth`, `automation`, `analytics`, `audit`, `cache`, `storage`, `queue`, `job`, `messaging`, `triggers`, `realtime`, `mcp`, `marketplace`, `email`, `sms`, `sharing`, `pinyin-search`, `reports`, `approvals`, `settings`, `webhooks`, `hierarchy-security`, `ai-seat`, `governance`. **Chosen: `package-registry`.** Four reasons, in order of weight: 1. ⭐ **It is not a new word in this tree — the spec kernel already spells it.** `packages/spec/src/kernel/package-registry.zod.ts` sits in the same directory and opens "# Package Registry Protocol — Defines the runtime state and lifecycle operations for installed packages"; it exports `InstalledPackageSchema` and the installed-package lifecycle types, and its own prose calls a package row "the 'row' in the installed-packages table". The token now names the capability that PERSISTS exactly the protocol that module DEFINES, at the same spelling, in the same kernel. Nothing has to be learned to read it. 2. **Same naming grammar as the set.** Every token is lower-case kebab-case, and every compound one is `QUALIFIER-NOUN` where the noun is the thing and the qualifier narrows it: `ai-studio`, `ai-seat`, `pinyin-search`, `hierarchy-security`. `package-registry` is that shape exactly. 3. **It says what the thing IS** — the registry of installed packages: the `sys_packages` container and the boot hydration that replays it — rather than what a deployment might additionally sell on top of it. 4. **It is not repo-private.** "Package registry" is the industry term for this exact object; ADR-0016's own Architecture Alignment list cites "npm: Package registry with install/uninstall/version management", and `@objectstack/service-package` already says it in caller-visible prose ("The package registry could not store this package"). Rejected, and why: - **`marketplace`** — forbidden by the ruling, and the reason the ruling exists: a token advertising a store that is not there. - **`packages`** — grammatical (the set has plural tokens: `reports`, `approvals`, `webhooks`, `triggers`), but in this tree "package" means both an npm workspace package and a metadata package, and a `requires: ['packages']` line inside a monorepo is exactly the ambiguity the vocabulary header exists to prevent. It also names the noun, not the service. - **`package-store`** — "store" is the connotation the ruling is REMOVING; re-importing it under a fresh spelling defeats the split. - **`package-state`** — collides head-on with `packages/runtime/src/package-state-store.ts`, which is the OTHER medium (the operator's disabled-id set under the ObjectStack home directory). Item 4 is about those two media not being confused; a token named `package-state` would make that confusion permanent. - **`package-persistence`** — names a mechanism, not a service domain. No token in the set names an implementation property; `storage` is the service, not "file-persistence". - **`sys-packages`** — the table name. A repo-private abbreviation, which the card forbids by name. - **`registry`** alone — this tree already has three (the npm registry, ObjectQL's `SchemaRegistry`, the metadata type registry). - **`package-catalog` / `catalog`** — that IS the half `marketplace` keeps. ## Question 1 — does the split move the length, or the always-on boundary derivation? Measured before and after, not assumed. - **Length: +1 on both constants** (28 to 29 tokens, 10 to 11 slate entries). Nothing is removed, so `requires: ['marketplace']` keeps meaning today what it meant yesterday; this widens the accept set and narrows nothing. - **The boundary derivation does NOT move.** Since `68e8b4b53c` the boundary is the rule "every entry that is not a bind target is mounted after ALL of them", with `BIND_TARGETS = queue / job / cache / settings`. `package-registry` is not a bind target: nothing on the slate binds into it during `kernel:ready`, and its one hard requirement is the ObjectQL engine, which is not a capability token. So it joins the TAIL, exactly as the declaration's own comment instructs, and the derived rule covers it on arrival with no new target and no new prefix. - **What DID move is the one assertion that enumerates the tail literally** — the falsifiability control in `platform-capabilities.test.ts`, which pins `orderingViolations(slate + a new bind target)`. That assertion went red on the first edit and is updated from the rule rather than around it. That is the "ordering contract moves with it" half of the card, and it is this PR's first red (Evidence below). - **Media: unchanged at two.** See Item 4. ## Question 2 — what does the CLI's `serve-capability-vocabulary.test.ts` actually follow? Read, not copied from the card's sentence. That file reads the CONSTANTS (`PLATFORM_CAPABILITY_TOKENS`, `PLATFORM_CAPABILITY_PROVIDERS`, `PLATFORM_PLUGIN_WIRED_RUNTIMES`) together with `Serve.CAPABILITY_PROVIDERS` / `Serve.CAPABILITY_TO_TIER` / `Serve.ALWAYS_ON_CAPABILITIES`, and asserts **set relations and package equality**. It enumerates no slate ORDER anywhere. - So it follows **by derivation, not by literal**: the only case in it that a carve-out could turn red is the 1:1 pin *"classifies every vocabulary token, and adds none outside it"* — a token added without a provider row fails there, in both directions. With the row present the file was already green before I edited it (measured). - Its case *"open-edition service tokens name the SAME package as serve CAPABILITY_PROVIDERS"* iterates **serve's** keys, so it never asks the reverse question. That asymmetry is precisely what leaves the gap in the next section invisible to it. - What this PR adds there is the ruling stated through the array `serve` really appends: `Serve.ALWAYS_ON_CAPABILITIES` carries `package-registry` and does **not** carry `marketplace`. Both halves are asserted, because a one-sided pin would stay green on a slate that force-mounted the catalogue half too — the outcome the ruling refused. ## ⚠️ What this PR deliberately does NOT do Measured on `serve`'s capability resolver at `c17ff70f3f`: a slate entry is force-appended to every app's `requires`, and the CLI then mounts it only if `Serve.CAPABILITY_PROVIDERS` keys the token — a token with no entry that IS in the vocabulary is skipped **silently**, by design. That registry keys `marketplace` (= `@objectstack/service-package` / `PackageServicePlugin`) and does not key `package-registry`, and `PackageServicePlugin` has exactly **one** mount path in this repo: that row. Therefore: - a stock boot after this PR is exactly as capable as before — nothing new mounts, and nothing breaks; - the word "always-on" is only KEPT once the runtime half lands (#17676 items 2, 3, 5 — the engine lane); - I did not add that row, because `packages/cli/src/**` is outside this card's declared file surface (0 paths in the diff — the DARK control below). This is recorded in three places a later reader will stand in: the slate declaration's own comment, the changeset, and the report comment — so the engine half is written against a stated fact rather than a discovered one. Two related measured facts, neither changed by this PR: (a) the module header's growth instruction says to add a new token "HERE as well as to the runtime's provider registry", i.e. it expects the two in one PR; (b) the same header claims "the CLI's vocabulary-drift test fails if the registries and this list fall out of sync", which holds only in the serve-to-spec direction. ## Item 4 — the ADR question (report-back; `docs/adr/**` is untouched) What I read: #5047 and its verification comment `5174777602`, which is where the phrase comes from. The two media are (1) the operator's disabled-id set in a flat JSON file under the ObjectStack home directory, written by `packages/runtime/src/package-state-store.ts`, whose header states the choice outright — "intentionally a flat file rather than a `sys_*` object: package lifecycle state is runtime/operational state, not project metadata"; and (2) the package rows themselves in `sys_packages`. ADR-0016 §9.7 records medium (1) and ADR-0025 reuses it by reference ("the ADR-0016 §9.7 `package-state-store.ts` pattern"). My judgment: **no ADR note is owed by this diff, and one becomes worth considering only when the runtime half lands.** - Measured: no ADR names `marketplace` as the owner of `sys_packages`; the only ADR that names `sys_packages` at all is ADR-0087 (a row-level upgrade diagnostic), which this diff does not touch; and no ADR names `PLATFORM_ALWAYS_ON_CAPABILITIES`. So no recorded decision becomes false, and nothing here reverses one — the ruling's own reading holds: this changes which capability OWNS an existing medium, not the number of media. - The honest caveat, which is the engine half's to carry: today medium (2) exists only on deployments that declare `requires: ['marketplace']`, while medium (1) is written unconditionally. Once the runtime half mounts the registry on every stock boot, the drift window ADR-0016 §9.7 knowingly accepted applies to **every** deployment rather than to marketplace-enabled ones only. That is a change in the trade-off's EXPOSURE, not in its SHAPE — a candidate for a one-line amendment to §9.7, filed as its own card by the seat, never assumed and never written from here. ## Evidence **Red before green (the ordering contract).** With only the declaration edited and no test touched: `platform-capabilities.test.ts` = 1 failed / 26 passed, naming *"…and the rule is falsifiable — a hostile slate is named, not shrugged off"*, diff `+ "package-registry"`. Green after the control is updated from the rule: 32 passed. **Ablation — declaration reverted to the merge base, with an on-disk proof and a hash-verified restore.** | leg | on-disk | built artifact | spec suite | CLI pin | |---|---|---|---|---| | mutate | source occurrences 5 to **0** | rebuild exit 0; `ablation-dist-preflight @objectstack/spec` reports the marker **absent from all 216 built files** | **5 failed** / 27 passed | **1 failed** / 13 passed — `expected [ 'queue', 'job', 'cache', …(7) ] to include 'package-registry'` | | restore | `git hash-object` = `b6c0292c5d2b5f0c012f12b8cacbdf97aed38ac7` = the `HEAD` blob | rebuild exit 0; preflight reports the marker **present in 8 built files** | exit 0 | exit 0 | Whole-tree `git status --porcelain` after the restore: empty. The restore leg is pinned to `HEAD` (never a bare `git checkout --`, never a moving ref) and runs from a trap on `EXIT INT TERM`. The CLI leg is measured against the **rebuilt** `dist/`, because `packages/cli` carries no vitest alias for `@objectstack/spec` and resolves it through `exports`. **Controls.** LIT: the token reads 5 occurrences in the declaration and the preflight finds it in 8 built files — the instrument fires. DARK, each of which must read 0 and does: `docs/adr/**` paths in the diff = **0**; `packages/cli/src/**` paths in the diff = **0**; `'marketplace'` inside `PLATFORM_ALWAYS_ON_CAPABILITIES` = **0**. The CLI's `serve-defaults.test.ts` control runs over a FIXED synthetic slate rather than the live one, so it neither moved nor needed to. **Suites and gates, all green at `37a112e0f2`:** `@objectstack/spec` build; `@objectstack/spec` full suite 485 files / 13873 tests; `@objectstack/spec typecheck`; the four CLI capability files (`serve-capability-vocabulary`, `serve-defaults`, `capability-preflight`, `serve-settings-ordering.pin`) 44 tests; and `check:nul-bytes`, `check-empty-changeset`, `check-changeset-no-major`, spec `check:api-surface` / `check:export-origins` / `check:authorable-surface`, `check-spec-docblock-symbol-anchors`, `check:test-source-alias`, `check:cross-package-test-inputs`, `check:published-files`. The full derived family list for this diff is 82 commands; the narrowing to the set above is declared in the report comment, and CI runs the farm. --- _Generated by [Claude Code](https://claude.ai/code/session_01JbZnqu8bt6YqfJsr9vaFb3)_ Co-authored-by: Claude <noreply@anthropic.com>
1 parent 72dd95f commit 51297e9

4 files changed

Lines changed: 142 additions & 1 deletion

File tree

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
---
2+
"@objectstack/spec": minor
3+
---
4+
5+
`package-registry` is a platform capability of its own, and an always-on one: the `sys_packages` container and the boot hydration that replays it no longer hide behind the `marketplace` token, which is left naming only the optional catalogue / browsing half (#18053, director ruling A′ on #17676).
6+
7+
A package is a first-class persistent entity whether or not a deployment has a store — an admin-created package does not depend on the marketplace existing. Until now the only way to get the persistence was `requires: ['marketplace']`, so a stock boot had no `sys_packages` at all and `protocol.installPackage` / `updatePackage` fell back to their in-memory branches: an admin-created package did not survive a restart, under a token advertising a store that was not there.
8+
9+
- **`PLATFORM_CAPABILITY_TOKENS` gains `package-registry`** — one new token, none removed, so `marketplace` keeps working exactly as before for anyone who declares it. The vocabulary is a closed set validated by `defineStack`, so this widens what an app may write, and nothing it already writes stops parsing.
10+
- **`PLATFORM_ALWAYS_ON_CAPABILITIES` gains `package-registry` at the tail.** The slate's ordering contract is a role, not a count: the entry binds into nothing on the slate (its one hard requirement is the ObjectQL engine, which is not a capability token), so it joins after every bind target like any other reader. `--preset minimal` still opts out of the whole slate.
11+
- **`PLATFORM_CAPABILITY_PROVIDERS` gains a row naming `@objectstack/service-package`, `open` edition** — the same package `marketplace` names today, because that package ships exactly one plugin and everything it does is the persistence half. The catalogue surface `marketplace` is left naming ships in `@objectstack/cloud-connection` and is mounted off a resolved marketplace URL, never through the token; repointing the `marketplace` row at it moves the runtime's own resolver with it and is the engine-lane half of the same ruling (#17676 items 2/3/5).
12+
- ⚠️ **Declaration first, runtime second — measured, not assumed.** `objectstack serve` mounts a slate entry only when `Serve.CAPABILITY_PROVIDERS` keys the token, and that registry keys `marketplace`. Until the engine-lane half lands, appending `package-registry` mounts nothing under the standalone CLI: a stock boot is exactly as capable as before, no more and no less. This package is the single list both the CLI and cloud's per-tenant runtime read, which is why the declaration is the half that goes first.

‎packages/cli/test/serve-capability-vocabulary.test.ts‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,6 +42,34 @@ describe('serve capability registries vs spec vocabulary (#3265)', () => {
4242
expect(PLATFORM_CAPABILITY_TOKENS).toContain(token);
4343
}
4444
});
45+
46+
/**
47+
* #17676 ruling A' item 1, read through the array `serve` actually appends.
48+
*
49+
* `Serve.ALWAYS_ON_CAPABILITIES` is a re-export of the spec slate, so this is
50+
* a SURFACE pin rather than a second copy of the spec-side one: it asserts
51+
* the split survives the hop the CLI takes, and that hop is what decides
52+
* which tokens land in an app's `requires`.
53+
*
54+
* ⚠️ Measured on `serve`'s resolver at c17ff70f3f and deliberately NOT
55+
* asserted: `Serve.CAPABILITY_PROVIDERS` keys `marketplace` and does not yet
56+
* key `package-registry`, so appending this token mounts nothing under
57+
* `objectstack serve` until the runtime half of the same ruling lands
58+
* (#17676 items 2/3/5, the engine lane). Pinning that ABSENCE here would
59+
* turn the engine lane's own fix red for doing the ruled thing, so the gap
60+
* is recorded in words and the pin states only what must hold either side of
61+
* it.
62+
*/
63+
it("appends the package-registry persistence to every app, never the catalogue half (#17676 A')", () => {
64+
expect(Serve.ALWAYS_ON_CAPABILITIES).toContain('package-registry');
65+
// The other half of the ruling: browsing stays optional, so an app that
66+
// wants a store still declares it.
67+
expect(Serve.ALWAYS_ON_CAPABILITIES).not.toContain('marketplace');
68+
// …and the split ADDED a token rather than moving one out — both halves
69+
// stay resolvable spellings for `requires`.
70+
expect(PLATFORM_CAPABILITY_TOKENS).toContain('package-registry');
71+
expect(PLATFORM_CAPABILITY_TOKENS).toContain('marketplace');
72+
});
4573
});
4674

4775
// framework#3366 — the installable-provider registry must classify EVERY

‎packages/spec/src/kernel/platform-capabilities.test.ts‎

Lines changed: 62 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -269,7 +269,7 @@ describe('PLATFORM_ALWAYS_ON_CAPABILITIES', () => {
269269
[...PLATFORM_ALWAYS_ON_CAPABILITIES, 'secrets'],
270270
[...BIND_TARGETS, 'secrets'],
271271
),
272-
).toEqual(['email', 'storage', 'sms', 'sharing', 'messaging', 'analytics']);
272+
).toEqual(['email', 'storage', 'sms', 'sharing', 'messaging', 'analytics', 'package-registry']);
273273
});
274274

275275
it('every member is a real platform capability token', () => {
@@ -299,3 +299,64 @@ describe('PLATFORM_ALWAYS_ON_CAPABILITIES', () => {
299299
expect(gated).toEqual([]);
300300
});
301301
});
302+
303+
/**
304+
* #17676 ruling A' item 1 (decision batch #125 item 2, maintainer verbatim
305+
* 「同意」): the package-registry PERSISTENCE — the `sys_packages` container and
306+
* the boot hydration that replays it — is carved out of `marketplace` into an
307+
* always-on core capability named for what it is; `marketplace` is left naming
308+
* only the optional catalogue / browsing half.
309+
*
310+
* BOTH halves are asserted here, because only the pair states the ruling. A
311+
* case that checked the new token alone would stay green on a slate that
312+
* force-mounted `marketplace` as well — which is the outcome the ruling refused
313+
* ("a token advertising a store that is not there"), and the reason the split
314+
* exists rather than a rename.
315+
*/
316+
describe("package-registry carve-out (#17676 ruling A')", () => {
317+
it('is its own vocabulary token — the persistence is named, not spelled `marketplace`', () => {
318+
expect(PLATFORM_CAPABILITY_TOKENS).toContain('package-registry');
319+
expect(isKnownPlatformCapability('package-registry')).toBe(true);
320+
// The catalogue half keeps its token: this is a SPLIT, so the vocabulary
321+
// must carry two tokens afterwards, not one renamed one.
322+
expect(PLATFORM_CAPABILITY_TOKENS).toContain('marketplace');
323+
});
324+
325+
it('has exactly ONE spelling — no second dialect for the same capability', () => {
326+
// The single-list rule this file already enforces against the removed
327+
// camelCase aliases, applied to the new token while its spelling is still
328+
// young: the near-misses a later author could reach for must stay unknown,
329+
// or `requires` grows two ways to ask for one service and the runtimes are
330+
// free to resolve different ones.
331+
for (const nearMiss of ['packages', 'package', 'sys-packages', 'package-store', 'packageRegistry']) {
332+
expect(PLATFORM_CAPABILITY_TOKENS, `'${nearMiss}' must not be a second spelling`).not.toContain(
333+
nearMiss,
334+
);
335+
expect(isKnownPlatformCapability(nearMiss)).toBe(false);
336+
}
337+
});
338+
339+
it('is mounted ALWAYS, and the catalogue half is NOT — the ruling, both ways round', () => {
340+
expect(PLATFORM_ALWAYS_ON_CAPABILITIES).toContain('package-registry');
341+
// Browsing stays optional: an app that wants a store still declares it.
342+
expect(PLATFORM_ALWAYS_ON_CAPABILITIES).not.toContain('marketplace');
343+
});
344+
345+
it('resolves through an open-edition provider — a floor entry must mount without a licence', () => {
346+
const provider = PLATFORM_CAPABILITY_PROVIDERS['package-registry'];
347+
expect(provider, 'the carved-out token needs its own provider row').toBeTruthy();
348+
expect(provider.edition).toBe('open');
349+
expect(provider.package).toBe('@objectstack/service-package');
350+
});
351+
352+
it('classifies like any other open-edition service — never as a typo', () => {
353+
// The authoring-time half: `defineStack` rejects a token the vocabulary
354+
// does not carry, and the preflight reads the classifier. A carve-out that
355+
// added the slate entry without the provider row would surface HERE, as an
356+
// `unknown` on a token every app now force-declares.
357+
expect(classifyRequiredCapability('package-registry', () => true).status).toBe('ok');
358+
const absent = classifyRequiredCapability('package-registry', () => false);
359+
expect(absent.status).toBe('installable');
360+
expect(absent.provider?.package).toBe('@objectstack/service-package');
361+
});
362+
});

‎packages/spec/src/kernel/platform-capabilities.ts‎

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,7 +47,15 @@ export const PLATFORM_CAPABILITY_TOKENS: readonly string[] = Object.freeze([
4747
'triggers',
4848
'realtime',
4949
'mcp',
50+
// `marketplace` and `package-registry` are two capabilities, not one token
51+
// spelled twice (#17676 ruling A' item 1). A package is a first-class
52+
// persistent entity whether or not the deployment has a store, so the
53+
// PERSISTENCE half — the `sys_packages` container and the boot hydration
54+
// that replays it — is a core capability named for what it is and mounted
55+
// always (see {@link PLATFORM_ALWAYS_ON_CAPABILITIES}); `marketplace` is
56+
// left naming only the optional catalogue / browsing half.
5057
'marketplace',
58+
'package-registry',
5159
'email',
5260
'sms',
5361
'sharing',
@@ -155,6 +163,20 @@ export const PLATFORM_CAPABILITY_PROVIDERS: Readonly<Record<string, PlatformCapa
155163
realtime: { package: '@objectstack/service-realtime', edition: 'open' },
156164
mcp: { package: '@objectstack/mcp', edition: 'open' },
157165
marketplace: { package: '@objectstack/service-package', edition: 'open' },
166+
// ⚠️ The SAME package as `marketplace` above, and that is a measured fact
167+
// about the distribution rather than a duplicate row: today
168+
// `@objectstack/service-package` ships exactly one plugin
169+
// (`PackageServicePlugin`), and everything it does is the persistence half
170+
// this token names — it creates `sys_packages`, replays it at `start()`,
171+
// and serves publish/get/list/delete over it. The catalogue / browsing
172+
// surface `marketplace` is left naming ships elsewhere entirely
173+
// (`MarketplaceProxyPlugin` / `MarketplaceInstallLocalPlugin` in
174+
// `@objectstack/cloud-connection`, mounted off a resolved marketplace URL,
175+
// ADR-0008). So the two rows agreeing on a package is what the carve-out
176+
// INHERITED, not what it decided: repointing `marketplace` at the browse
177+
// surface moves the runtime's own resolver with it and is #17676's
178+
// engine-lane half, which this row deliberately does not pre-empt.
179+
'package-registry': { package: '@objectstack/service-package', edition: 'open' },
158180
email: { package: '@objectstack/plugin-email', edition: 'open' },
159181
sms: { package: '@objectstack/service-sms', edition: 'open' },
160182
sharing: { package: '@objectstack/plugin-sharing', edition: 'open' },
@@ -313,6 +335,24 @@ export const PLATFORM_ALWAYS_ON_CAPABILITIES: readonly string[] = Object.freeze(
313335
// authored/previewed inline (Studio) and compiled on the fly. Without it the
314336
// dataset preview + dashboard/report analytics widgets silently no-op.
315337
'analytics',
338+
// `package-registry` is foundational per #17676 ruling A' (decision batch
339+
// #125 item 2): a package is a first-class persistent entity whether or not
340+
// the deployment has a marketplace, so `sys_packages` and its boot
341+
// hydration must exist on a stock boot. Without it `protocol.installPackage`
342+
// / `updatePackage` fall back to their in-memory branches and an
343+
// admin-created package does not survive a restart. It binds into nothing on
344+
// this slate (its only hard requirement is the ObjectQL engine, which is not
345+
// a capability token), so it joins the TAIL like any other reader.
346+
//
347+
// ⚠️ SCOPE, measured on `serve`'s capability resolver at c17ff70f3f: a slate
348+
// entry is force-appended to every app's `requires`, and the CLI then mounts
349+
// it only if `Serve.CAPABILITY_PROVIDERS` keys the token. That registry keys
350+
// `marketplace`, not this token, so under `objectstack serve` this entry is
351+
// inert until the runtime half of the same ruling lands (#17676 items 2/3/5,
352+
// the engine lane). The declaration is deliberately first: it is the single
353+
// list both runtimes read, and the thing the runtime half is written
354+
// against.
355+
'package-registry',
316356
]);
317357

318358
/**

0 commit comments

Comments
 (0)