Skip to content

Commit 596090e

Browse files
os-steveclaude
andauthored
spec: one authority for enableOnInstall, and a read-out of its other two declarations (#19130)
Fixes #18605 Clause-②: yes — carrier: the changeset `.changeset/18605-enable-on-install-one-authority.md` (`@objectstack/spec`, minor). Three published declarations' stated meaning moves; the accept set does not move at all. ## Ruling bullet 1 was already discharged by PR #18752 — this PR did not skip it Batch #153 item 5, letter 1 carries two bullets. The first one — the install door writes the registry row's `enabled` from `enableOnInstall ?? true` — landed with PR #18752 (card #18058), and this claimant re-derived that against the merged diff before planning, rather than inheriting the card's text: - `packages/runtime/src/domains/packages.ts` reads `body.enableOnInstall === false` off the WRAPPED body, flips the registry row through the same call `PATCH /packages/:id/disable` uses, and then persists the row the door returned to the durable state file. - `packages/runtime/src/domains/packages-install-enable-on-install.test.ts` pins it, header and all: `false` installs disabled in all three records, `false` also moves `status`, `true` installs enabled, an absent key defaults to `true`, the disable is durable across a restart, and a re-install with `true` clears it. - Both still exist on `origin/main` as of this branch's merge (asserted by quoted-exact-name `git grep` against `origin/main`). So the card's own premise — "honoured by no handler" — is **false for the authority's door on today's `main`**, and this PR carries bullet 2 and only bullet 2. ## Bullet 2, verbatim > **One authority**: the request contract in `package-api.zod.ts`. The claimant re-reads the two other declarations — a copy of the request key is folded to a reference; a declaration that means something else (a stored-row field, a marketplace listing attribute) stays and says so. ⛔ **No silent unification of three published declarations.** ## The read-out, per declaration All three read `enableOnInstall: z.boolean().default(true)` with byte-identical description text, so identical shape carried no information. What distinguishes them is the request each sits on and the door that serves it. | declaration | what it is | disposition | |---|---|---| | `api/PackageInstallRequestSchema` | the HTTP wire contract of `POST /api/v1/packages`, the door that honours the key | **the one authority** | | `kernel/InstallPackageRequestSchema` | the request type of the in-process protocol primitive `ObjectStackProtocol.installPackage` | **a COPY of the request key** — referenced | | `marketplace/MarketplaceInstallRequestSchema` | the marketplace channel's install-from-listing request, served by the control plane | **means something else** — stays, and says so | ⚠️ Both of the ruling's parenthetical guesses were falsified by the re-read, and that is recorded rather than quietly worked around. The kernel declaration is **not** the stored-row field: the stored-row field is `InstalledPackage.enabled`, a different key in the same file. The marketplace declaration is **not** a listing attribute: `MarketplaceListingSchema` does not carry it; it sits on the install request beside `listingId`. ### The authority — `api/PackageInstallRequestSchema.enableOnInstall` It is the authority because it is the request contract of the door that honours the key. Its published description now says so, so a reader of the reference page can tell which of three rows is the one that acts: "honoured at POST /api/v1/packages: the installed row's `enabled` is written from this key". Its doc block carries the map to the other two, so nobody has to re-derive this reading a third time. ### Re-read ① — `kernel/InstallPackageRequestSchema.enableOnInstall` is a COPY Same type, same default, same meaning, restated one layer down on the in-process protocol primitive. Two measured facts decide it: - `MetadataProtocol.installPackage` reads `request.manifest` and `request.settings` and nothing else (`packages/metadata-protocol/src/protocol.ts`). The key reaches no code that acts on it there. - The authority's own door does not forward it down that seam: it calls `installPackage({ manifest, settings })` and performs the enable/disable flip itself afterwards, because the durable half must follow the ROW that door returned rather than the request's intent. That is deliberate and documented at the call site. It is therefore a copy, and per the ruling it must not be left unreferenced. **The reference is documentary in the declaration and MECHANICAL in a pin**, for a reason that was measured rather than assumed — see the next section. `packages/spec/src/api/package-install-one-authority.test.ts` parses the authority and the copy over one matrix (absent, `false`, `true`, a string, `null`) and reds on any cell where they disagree, so the copy can no longer drift from the authority silently. ### Re-read ② — `marketplace/MarketplaceInstallRequestSchema.enableOnInstall` means something else Same words, a different commitment, and the difference is the subject of the request it sits on: - Its subject is a marketplace LISTING (`listingId`, `version`, `licenseKey`, `tenantId`). The authority's subject is a MANIFEST. Neither body can be sent where the other is expected, which the pin asserts in both directions. - Its door is the control plane's `POST /api/v1/marketplace/install`; a runtime mounts `/api/v1/marketplace/*` only as a read-only proxy to the configured control plane (`MarketplaceProxyPlugin`). `docs/design/marketplace-publishing.md` §4.3 spells the flow out: the channel fetches the artefact and validates the licence and only THEN maps what it holds into a platform install. So this key is what a caller asks the marketplace to request on its behalf — one translation upstream of the door key. - It is a different party's contract on a different release cadence: the declaration was `cloud/MarketplaceInstallRequest` before it moved into this namespace (`packages/spec/scripts/lib/renamed-defs.ts`). One shared declaration would let a narrowing at the platform door silently narrow a control-plane contract that no PR in this repo can see. So it stays, and its published description now says which of the two it is. ## The prescription that is not executable as written, and the measurement "A copy of the request key is folded to a reference" reads naturally as `enableOnInstall: PackageInstallRequestSchema.shape.enableOnInstall` at the copy's site. **That spelling is not available in this direction, and it is not a style preference — it is measured.** The authority sits ABOVE both copies in the module graph: `PackageInstallRequestSchema` is built from `ManifestSchema` and `InstalledPackageSchema` (declared in `kernel/package-registry.zod.ts`) and from `ArtifactReferenceSchema` (declared in `marketplace/marketplace.zod.ts`). A reference from either copy up to the authority is therefore an import cycle, and it is not a cycle the `lazySchema` proxy absorbs: under `OS_EAGER_SCHEMAS=1` — the mode `gen:schema`, `gen:authorable-surface-base` and `check:authorable-surface` run in — the factory bodies evaluate at module load and the cycle dies. Measured on this branch, both directions, each against a control that passes on the unmodified tree: | leg | command | result | |---|---|---| | control · kernel | eager load of `kernel/package-registry.zod`, unmodified | exit 0, `enableOnInstall` defaults to `true` | | treatment · kernel | the same load with `enableOnInstall: PackageInstallRequestSchema.shape.enableOnInstall` | exit 1, `ReferenceError: Cannot access 'InstalledPackageSchema' before initialization`, raised from `api/package-api.zod.ts` through `lazySchema` | | control · marketplace | eager load of `marketplace/marketplace.zod`, unmodified | exit 0, `enableOnInstall` defaults to `true` | | treatment · marketplace | the same load with the same structural reference | exit 1, `ReferenceError: Cannot access 'ArtifactReferenceSchema' before initialization` | Under the default lazy mode both treatments load fine, which is the dangerous half: the runtime would be green and the generator would die. Both treatments were reverted and the revert proven by blob hash against `HEAD` (`git hash-object` equal, `git diff HEAD` empty) before anything else was written. ⇒ The only structural fold available would be to move the key's literal into a module BELOW both copies and have the authority import it. That was deliberately not taken unilaterally: it moves the declaration out of `package-api.zod.ts`, which is the file the ruling names as the one authority, so it changes the ruling's own terms. It is recorded as an open question below rather than performed. ## What moved on the published surface - Three `.describe()` strings — the text `content/docs/references/**` renders, and the only half of a doc block an author reading the reference page ever sees. - Three doc blocks in the source. - The four generated reference pages that follow from those strings (five table rows; the authority appears twice because `PackageInstallBody` renders its wrapped branch). - One new test file. **What did not move:** no key added, removed, renamed or retyped, no default changed. `check:api-surface`, `check:api-surface-declarations`, `check:authorable-surface`, `check:export-origins` and `check:declaration-map` are all green with no regeneration — the `api-surface-declarations` shards this card was flagged for do **not** move, because a `.describe()` change does not change a `.d.ts` type. ## Verification Run on the merged tree (`git merge origin/main` through `scripts/pm/os-regen-merge.sh`), exit codes captured before any pipe. | what | result | |---|---| | `pnpm --filter @objectstack/spec test` | 496 files / 14536 tests passed | | `pnpm --filter @objectstack/spec typecheck` | exit 0 | | `pnpm --filter @objectstack/spec check:generated` | all 16 generated artifacts up to date | | `eslint . --no-inline-config --format json` | 6879 files reached by eslint's own config, 0 errors, 0 warnings — the union, not a narrowing | | gate families derived by `scripts/pm/dispatch-gates.mjs` and run | see below | Gate families run locally, all exit 0: `check:nul-bytes`, `check-spec-docblock-symbol-anchors`, `check:duration-unit-keys`, `check:cross-package-test-inputs`, `check:test-source-alias`, `check-adr-0087-registration --base origin/main`, `check-changeset-no-major --base origin/main`, `check-empty-changeset --base origin/main`, `check:changeset-gate-self-tests`, `check:pm-widening-tells`, `check:exported-any`, `check:dual-source-exports`, `check:entry-nameability`, `check:variant-docs`, `check:empty-state`, `check:llms-txt`, `check:browser-reachable-entries`, `check:skill-examples`, `check-doc-frontmatter`, `check-docs-section-name`, `check-doc-route-spelling --advisory`, `docs-audit/check-affected-docs`, `check:doc-anchors`, `check:docs-single-h1`. `check:skill-examples` first exited 1 on a build prerequisite (`@objectstack/client-react` had no `.d.ts`), not on this diff; after `pnpm --filter '@objectstack/client-react^...' build` it exits 0 over 258 prose examples. The remaining families the derivation names are CI's farm and are not claimed here. **Reverse verification of the new pin** — the fix was committed first, the mutation landed through `scripts/ablation-replace.mjs` (anchor hit x1, blob `64a17a8bc364` to `f2cacf8c304b`), and the restore was proven against `HEAD` rather than against an exit code: - kernel's `enableOnInstall` default mutated `true` to `false` - predicted direction: the parity cells go red, the rest stay green - observed: `Tests 2 failed | 10 passed` — the two that fail are the absent-key parity cell and the same-default assertion - restored: blob equals HEAD (`64a17a8bc364`), `git diff HEAD` empty ## Acceptance notes Observed while reading, deliberately not fixed here and not filed: - `packages/spec/src/contracts/package-service.ts` declares a FOURTH `enableOnInstall`, on the plain TS interface `InstallPackageInput` for `IPackageService`. It is outside the ruling's three schemas (not Zod, not on the authorable surface), and `IPackageService` has no implementation in this repo — the only place the key is read is an inline fake inside `package-service.test.ts` (`enabled: input.enableOnInstall !== false`). Noted, not filed: the interface is a contract with no consumer here, so nobody is currently misled by it. Carrier if it ever needs one: whoever implements `IPackageService`. - `content/docs/api/metadata-api.mdx` documents the install body inline rather than from the contract, so it will not follow a future change to it. Noted, not filed: a hand-written page drifting from a schema is not one of the three filing classes, and no PR or person is presently heading for that file. Carrier: none. ## Open question recorded for the seat, not answered here The structural fold is available in exactly one shape: move the key's single literal into a module below both copies and have the request contract import it. That would give literally one Zod declaration of the key instead of a pin holding two in step — but it takes the declaration out of `package-api.zod.ts`, which the ruling names as the one authority. Whether the ruling prefers one literal in a lower module or the authority's file keeping its own literal with a mechanical pin is a question about the ruling's terms, so it is recorded rather than decided by the claimant. The current shape is the one that changes nothing the ruling said. A second, separate question the re-read surfaced: the copy's own door (`ObjectStackProtocol.installPackage`) still does not honour the key. Making it honour the key would be new runtime behaviour at a door the ruling did not name — it is safe (every present caller omits the key, so nothing changes today), but it is not this card's to authorise. --- _Generated by [Claude Code](https://claude.ai/code/session_01AmH9bKvGoLjiY86Q4Z3og2)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 875e9ad commit 596090e

9 files changed

Lines changed: 267 additions & 11 deletions

File tree

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
---
2+
"@objectstack/spec": minor
3+
---
4+
5+
`enableOnInstall` is declared in three published schemas; each one now says which of the three governs it, and the two that are not the authority say what they are (#18605).
6+
7+
The install door already honours the key — `POST /api/v1/packages` writes the registry row's `enabled` from `enableOnInstall ?? true` (#18058). What was left was three declarations that looked identical (`z.boolean().default(true)`, same description) with nothing saying which one an author should read.
8+
9+
Clause-②: yes
10+
11+
**The authority**
12+
13+
`PackageInstallRequestSchema` (`api/package-api.zod.ts`) is the one authority, because it is the request contract of the door that honours the key. Its published description now says so: "honoured at POST /api/v1/packages: the installed row's `enabled` is written from this key". Its doc block carries the map to the other two, so a reader never has to guess which of three identical-looking declarations governs.
14+
15+
**`kernel/InstallPackageRequest.enableOnInstall` — a COPY of the request key**
16+
17+
Same type, same default, same meaning, restated on the in-process protocol primitive `ObjectStackProtocol.installPackage`. Its published description now records that this layer does not read it: the implementation reads `manifest` and `settings` only, and the HTTP door does not forward the key down that seam — it calls `installPackage({ manifest, settings })` and performs the enable/disable flip itself, because the durable half must follow the row that door returned rather than the request's intent.
18+
19+
The copy is held to the authority by a **parity pin** rather than by a structural reference. The structural spelling is not available in this direction: the authority is built from `ManifestSchema` and `InstalledPackageSchema`, both declared in `kernel/package-registry.zod.ts`, so `PackageInstallRequestSchema.shape.enableOnInstall` spelled there is an import cycle, and under `OS_EAGER_SCHEMAS=1` — the mode `gen:schema` and `check:authorable-surface` run in — it dies with `ReferenceError: Cannot access 'InstalledPackageSchema' before initialization`. `api/package-install-one-authority.test.ts` parses both declarations over one matrix (absent, `false`, `true`, a string, `null`) and reds on any cell where they disagree.
20+
21+
**`marketplace/MarketplaceInstallRequest.enableOnInstall` — not this key at all**
22+
23+
It stays, and its published description says what it is: the marketplace channel's own install option. That request's subject is a listing (`listingId`, `version`, `licenseKey`, `tenantId`), not a manifest; its door is the control plane's `POST /api/v1/marketplace/install`, of which a runtime mounts only a read-only proxy; and the channel resolves the artefact and validates the licence before mapping what it holds into a platform install. It is one translation upstream of the door key, owned by a different party on a different release cadence, so folding it would let a narrowing at the platform door silently narrow a control-plane contract.
24+
25+
**What does not move**
26+
27+
No key is added, removed, renamed or retyped, and no default changes: the accept set of all three schemas is byte-for-byte what it was, and `api-surface`, `api-surface-declarations`, `authorable-surface` and `authorable-defaults` are all unchanged. What moves is the published description text of three keys and the reference pages generated from it. The `Clause-②` declaration is `yes` as the conservative arm, because three published declarations' stated meaning moves.

content/docs/references/api/package-api.mdx

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -491,7 +491,7 @@ Install package request
491491
| :--- | :--- | :--- | :--- |
492492
| **manifest** | `{ id: string; namespace?: string; defaultDatasource?: string; version: string; … }` || Package manifest to install (AUTHORING stage: `objects` are glob patterns) |
493493
| **settings** | `Record<string, any>` | optional | User-provided settings at install time |
494-
| **enableOnInstall** | `boolean` | optional (default: `true`) | Whether to enable immediately after install |
494+
| **enableOnInstall** | `boolean` | optional (default: `true`) | Whether to enable immediately after install — honoured at POST /api/v1/packages: the installed row's `enabled` is written from this key |
495495
| **overwrite** | `boolean` | optional | Overwrite an already-installed package id instead of answering 409 Conflict |
496496
| **platformVersion** | `string` | optional | Current platform version for compatibility verification |
497497
| **artifactRef** | `{ url: string; sha256: string; size: integer; format?: Enum<'tgz' \| 'zip'>; … }` | optional | Artifact reference for marketplace installation |
@@ -654,7 +654,7 @@ Install package request
654654
| :--- | :--- | :--- | :--- |
655655
| **manifest** | `{ id: string; namespace?: string; defaultDatasource?: string; version: string; … }` || Package manifest to install (AUTHORING stage: `objects` are glob patterns) |
656656
| **settings** | `Record<string, any>` | optional | User-provided settings at install time |
657-
| **enableOnInstall** | `boolean` | optional (default: `true`) | Whether to enable immediately after install |
657+
| **enableOnInstall** | `boolean` | optional (default: `true`) | Whether to enable immediately after install — honoured at POST /api/v1/packages: the installed row's `enabled` is written from this key |
658658
| **overwrite** | `boolean` | optional | Overwrite an already-installed package id instead of answering 409 Conflict |
659659
| **platformVersion** | `string` | optional | Current platform version for compatibility verification |
660660
| **artifactRef** | `{ url: string; sha256: string; size: integer; format?: Enum<'tgz' \| 'zip'>; … }` | optional | Artifact reference for marketplace installation |

content/docs/references/api/protocol.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1910,7 +1910,7 @@ Install package request
19101910
| :--- | :--- | :--- | :--- |
19111911
| **manifest** | `{ id: string; namespace?: string; defaultDatasource?: string; version: string; … }` || Package manifest to install |
19121912
| **settings** | `Record<string, any>` | optional | User-provided settings at install time |
1913-
| **enableOnInstall** | `boolean` | optional (default: `true`) | Whether to enable immediately after install |
1913+
| **enableOnInstall** | `boolean` | optional (default: `true`) | Whether to enable immediately after install — restates the install-door request key, whose one authority is api/PackageInstallRequest; this protocol primitive does not read it |
19141914
| **platformVersion** | `string` | optional | Current platform version for compatibility verification |
19151915

19161916
### Nested Shape: `InstallPackageRequest.manifest`

content/docs/references/kernel/package-registry.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -184,7 +184,7 @@ Install package request
184184
| :--- | :--- | :--- | :--- |
185185
| **manifest** | `{ id: string; namespace?: string; defaultDatasource?: string; version: string; … }` || Package manifest to install |
186186
| **settings** | `Record<string, any>` | optional | User-provided settings at install time |
187-
| **enableOnInstall** | `boolean` | optional (default: `true`) | Whether to enable immediately after install |
187+
| **enableOnInstall** | `boolean` | optional (default: `true`) | Whether to enable immediately after install — restates the install-door request key, whose one authority is api/PackageInstallRequest; this protocol primitive does not read it |
188188
| **platformVersion** | `string` | optional | Current platform version for compatibility verification |
189189

190190
### Nested Shape: `InstallPackageRequest.manifest`

content/docs/references/marketplace/marketplace.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,7 @@ Install from marketplace request
145145
| **version** | `string` | optional | Version to install |
146146
| **licenseKey** | `string` | optional | License key for paid packages |
147147
| **settings** | `Record<string, any>` | optional | User-provided settings at install time |
148-
| **enableOnInstall** | `boolean` | optional (default: `true`) | Whether to enable immediately after install |
148+
| **enableOnInstall** | `boolean` | optional (default: `true`) | Whether to enable immediately after install — the marketplace channel's own install option, not the platform install-door key (api/PackageInstallRequest) |
149149
| **artifactRef** | `{ url: string; sha256: string; size: integer; format: Enum<'tgz' \| 'zip'>; … }` | optional | Artifact reference for direct installation |
150150
| **tenantId** | `string` | optional | Tenant identifier |
151151

packages/spec/src/api/package-api.zod.ts

Lines changed: 36 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -282,9 +282,43 @@ export const PackageInstallRequestSchema = lazySchema(() => z.object({
282282
settings: z.record(z.string(), z.unknown()).optional()
283283
.describe('User-provided settings at install time'),
284284

285-
/** Whether to enable immediately after install */
285+
/**
286+
* Whether to enable the package immediately after install.
287+
*
288+
* ## ⭐ THE ONE AUTHORITY for this key, and the map to the other two
289+
*
290+
* `enableOnInstall` is declared in three published schemas. This one is the
291+
* authority, because it is the request contract of the door that HONOURS it:
292+
* `POST /api/v1/packages` writes the registry row's `enabled` from
293+
* `enableOnInstall ?? true`, through the same registry flip and durable
294+
* state write `PATCH /packages/:id/disable` uses
295+
* (`packages/runtime/src/domains/packages.ts`). A `false` here installs the
296+
* package present-but-not-active and survives a restart; `true` and absent
297+
* install it enabled, which is this declaration's default.
298+
*
299+
* The other two are re-read here so a reader never has to guess which of
300+
* three identical-looking declarations governs:
301+
*
302+
* - `InstallPackageRequestSchema` (`src/kernel/package-registry.zod.ts`) —
303+
* **a COPY of this key**, restated on the in-process protocol primitive
304+
* `ObjectStackProtocol.installPackage`. Same type, same default, same
305+
* meaning; its own implementation does not read it, and this door does not
306+
* forward it down that seam. Held to this declaration by
307+
* `package-install-one-authority.test.ts`, not by an import: the authority
308+
* sits above `kernel/` in the module graph, so a `…Schema.shape.…`
309+
* reference from there is a cycle that dies under `OS_EAGER_SCHEMAS=1`.
310+
* - `MarketplaceInstallRequestSchema` (`src/marketplace/marketplace.zod.ts`)
311+
* — **not this key at all**. That request's subject is a marketplace
312+
* listing, its door is the control plane's `POST /api/v1/marketplace/install`,
313+
* and its `enableOnInstall` is what a caller asks the marketplace channel
314+
* to request on its behalf, one translation upstream of this one. It stays
315+
* a declaration of its own and says why at its own site.
316+
*
317+
* ⛔ Never unify the three silently, in either direction: two of them are
318+
* one commitment and the third is a different party's.
319+
*/
286320
enableOnInstall: z.boolean().default(true)
287-
.describe('Whether to enable immediately after install'),
321+
.describe('Whether to enable immediately after install — honoured at POST /api/v1/packages: the installed row\'s `enabled` is written from this key'),
288322

289323
/**
290324
* Opt back in to overwriting an already-installed package id.
Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
2+
3+
/**
4+
* [#18605] `enableOnInstall` — ONE AUTHORITY, and the two re-reads under it.
5+
*
6+
* The card measured the key declared in three published schemas. #18752 closed
7+
* the first half of the ruling (the install door now honours it). This file
8+
* pins the SECOND half — which of the other two declarations is a copy of the
9+
* request key and which means something else — so neither the fold nor a
10+
* silent unification can happen unobserved:
11+
*
12+
* 1. The authority is `PackageInstallRequestSchema` (`package-api.zod.ts`),
13+
* the request contract of `POST /api/v1/packages`.
14+
* 2. `InstallPackageRequestSchema` (`kernel/package-registry.zod.ts`) is a
15+
* COPY of the request key. It cannot be folded to a structural reference —
16+
* the authority sits above `kernel/` in the module graph, so
17+
* `PackageInstallRequestSchema.shape.enableOnInstall` spelled there is an
18+
* import cycle that dies under `OS_EAGER_SCHEMAS=1`, the mode `gen:schema`
19+
* and `check:authorable-surface` run in. The reference is therefore
20+
* MECHANICAL and lives here: the two declarations are parsed over one
21+
* matrix, and any drift on any cell reds.
22+
* 3. `MarketplaceInstallRequestSchema` (`marketplace/marketplace.zod.ts`)
23+
* means something else and stays. Its subject is a marketplace LISTING and
24+
* its door is the control plane's, not this platform's install door — so
25+
* what is pinned here is the difference that carries that reading, not the
26+
* sameness.
27+
*/
28+
29+
import { describe, it, expect } from 'vitest';
30+
import { PackageInstallRequestSchema } from './package-api.zod';
31+
import { InstallPackageRequestSchema } from '../kernel/package-registry.zod';
32+
import { MarketplaceInstallRequestSchema } from '../marketplace/marketplace.zod';
33+
34+
/** A manifest both install-request contracts accept, so only the key varies. */
35+
const MANIFEST = {
36+
id: 'com.acme.crm',
37+
name: 'Acme CRM',
38+
version: '1.0.0',
39+
type: 'app',
40+
} as const;
41+
42+
/**
43+
* The matrix. Each cell is a body the two contracts must answer identically —
44+
* absent (the default), both booleans, and the non-boolean spelling the door
45+
* itself treats as absent (recorded on `PackageInstallBodySchema`'s residual).
46+
*/
47+
const MATRIX: ReadonlyArray<{ name: string; enableOnInstall?: unknown }> = [
48+
{ name: 'absent — the declared default applies' },
49+
{ name: 'false — install present, not active', enableOnInstall: false },
50+
{ name: 'true — the default, spelled', enableOnInstall: true },
51+
{ name: "'false' — a string, refused by the declaration", enableOnInstall: 'false' },
52+
{ name: 'null — refused by the declaration', enableOnInstall: null },
53+
];
54+
55+
describe('#18605 — `enableOnInstall` has ONE authority', () => {
56+
describe('the authority: `PackageInstallRequestSchema`', () => {
57+
it('defaults to `true` — the value the install door installs enabled on', () => {
58+
const parsed = PackageInstallRequestSchema.parse({ manifest: MANIFEST });
59+
expect(parsed.enableOnInstall).toBe(true);
60+
});
61+
62+
it('carries `false` through — the value the install door installs disabled on', () => {
63+
const parsed = PackageInstallRequestSchema.parse({ manifest: MANIFEST, enableOnInstall: false });
64+
expect(parsed.enableOnInstall).toBe(false);
65+
});
66+
67+
it('refuses a non-boolean by name rather than coercing it', () => {
68+
const result = PackageInstallRequestSchema.safeParse({ manifest: MANIFEST, enableOnInstall: 'false' });
69+
expect(result.success).toBe(false);
70+
expect(result.error?.issues.some((i) => i.path.join('.') === 'enableOnInstall')).toBe(true);
71+
});
72+
});
73+
74+
/**
75+
* ⭐ THE REFERENCE, made mechanical. `InstallPackageRequestSchema` restates
76+
* the authority's key; this is what holds the restatement equal to it in the
77+
* absence of an import that would be a cycle.
78+
*/
79+
describe('the COPY: `kernel/InstallPackageRequestSchema` answers exactly as the authority does', () => {
80+
for (const cell of MATRIX) {
81+
it(`agrees with the authority — ${cell.name}`, () => {
82+
const body: Record<string, unknown> = { manifest: MANIFEST };
83+
if ('enableOnInstall' in cell) body.enableOnInstall = cell.enableOnInstall;
84+
85+
const authority = PackageInstallRequestSchema.safeParse(body);
86+
const copy = InstallPackageRequestSchema.safeParse(body);
87+
88+
expect(copy.success).toBe(authority.success);
89+
if (authority.success && copy.success) {
90+
expect(copy.data.enableOnInstall).toBe(authority.data.enableOnInstall);
91+
}
92+
});
93+
}
94+
95+
it('declares the key with the same type and default, not merely the same name', () => {
96+
const authorityOnly = PackageInstallRequestSchema.parse({ manifest: MANIFEST }).enableOnInstall;
97+
const copyOnly = InstallPackageRequestSchema.parse({ manifest: MANIFEST }).enableOnInstall;
98+
expect(typeof copyOnly).toBe('boolean');
99+
expect(copyOnly).toBe(authorityOnly);
100+
});
101+
});
102+
103+
/**
104+
* ⛔ The marketplace declaration is NOT folded, and these are the measured
105+
* differences that say why. If a later change makes this request a second
106+
* spelling of the install door's body, these reds are the notice.
107+
*/
108+
describe('the OTHER MEANING: `MarketplaceInstallRequestSchema` is a different request', () => {
109+
it('is keyed by a marketplace LISTING, not by a manifest', () => {
110+
expect(Object.keys(MarketplaceInstallRequestSchema.shape)).toContain('listingId');
111+
expect(Object.keys(MarketplaceInstallRequestSchema.shape)).not.toContain('manifest');
112+
});
113+
114+
it('refuses the install door\'s body — nothing can send one where the other is expected', () => {
115+
expect(MarketplaceInstallRequestSchema.safeParse({ manifest: MANIFEST }).success).toBe(false);
116+
expect(PackageInstallRequestSchema.safeParse({ listingId: 'com.acme.crm' }).success).toBe(false);
117+
});
118+
119+
it('declares `enableOnInstall` in its own right, defaulting to `true`', () => {
120+
const parsed = MarketplaceInstallRequestSchema.parse({ listingId: 'com.acme.crm' });
121+
expect(parsed.enableOnInstall).toBe(true);
122+
});
123+
});
124+
});

0 commit comments

Comments
 (0)