Skip to content

Commit 7e1b048

Browse files
docs(spec): the kernel install request's enableOnInstall says what the in-process primitive does (#19691)
Part of #19339 Clause-②: no The kernel install request's `enableOnInstall` told authors, on two published reference pages and inside the `@objectstack/spec` tarball, that the in-process protocol primitive does not read the key. That sentence was true when it was written and stopped being true when `MetadataProtocol.installPackage` started honouring it (`482d584121`). Nothing went red: `check:docs` holds the generated page equal to the `.describe()`, and the two still agreed with each other — internal consistency, never truth. ## The correction, and where its content came from ⛔ Not from the card's prose. The replacement text was read off the implementation `482d584121` landed (`packages/metadata-protocol/src/protocol.ts`, the `requestedEnabled` arms) and the matrix its changeset publishes: | `enableOnInstall` | what the in-process primitive now does | |:--|:--| | `true` | `enablePackage` — clears a disable, including a boot-seeded one | | `false` | `disablePackage` — the row and its `status` both move | | absent | no lifecycle call at all; the row the registry returned stands | `=== true` / `=== false`, never a truthiness test and never a `??` default, so a non-boolean value is read as absent rather than coerced. The new `.describe()`: > Whether to enable immediately after install — restates the install-door request key, whose one authority is api/PackageInstallRequest; this protocol primitive honours it on the registry row: true enables, false disables, absent makes no lifecycle call The scope words "on the registry row" are load-bearing and the doc block above the key spells out why: the durable disabled-package record is keyed by environment, which an `InstallPackageRequest` does not carry, so this seam moves the registry row for the life of the process and `POST /api/v1/packages` still owns the half that survives a restart. Understating that would swap one false sentence for another. ## Premise, re-verified on this tree rather than inherited - `482d584121` is an ancestor of this branch: `git merge-base --is-ancestor 482d584 HEAD` exits 0. Control leg on the same checkout with a commit known to be in that history exits 0 too, so the positive reading is not a shallow-clone artefact. - Carrier census, radius stated: `git grep` over the **entire tracked tree**, no pathspec. The exact sentence stood in exactly 3 places before this branch — the source describe, `content/docs/references/api/protocol.mdx`, `content/docs/references/kernel/package-registry.mdx`. Dark control on a nonsense phrase of the same shape: 0 hits, so the probe discriminates. A narrower radius of `packages/spec/src/**` returns 1 and would have read as "the card overstates"; the radius is the thing that has to be declared. - After this branch the same probe finds the sentence only inside this PR's own changeset, where it is quoted in the past tense as the text being corrected. ## The two pages are DERIVED — measured, not assumed Three readings, each from a committed state: 1. before regeneration, `pnpm --filter @objectstack/spec check:generated` named **exactly one** stale artifact — `content/docs/references/**` — and the other 14 green; 2. `check:generated --fix` ran `gen:docs` and rewrote **exactly** those two pages, one table row each; 3. after regeneration all 15 are up to date. A hand-written page cannot produce that sequence. ⛔ Neither page was touched by hand. ## Changeset — measured, not pattern-matched `patch` on `@objectstack/spec`, and `skip-changeset` would be a false declaration. The question is only whether the changed bytes ship, so it was answered against the package's own `files[]` after a real build: - **subject** — `packages/spec/src/kernel/package-registry.zod.ts` matches `src/**/*.zod.ts` and is present in `npm pack --dry-run` (2030 files). The corrected sentence also reaches 8 files under `dist/` and 3 under `json-schema/`, both listed in `files[]`. - **positive control** — `dist/index.d.ts` is in the same listing, as it must be. - **negative controls** — 0 `*.test.ts` and 0 `content/` paths are in that listing, so the instrument is not simply saying yes. The two regenerated `.mdx` pages publish to the docs site, not to the tarball. Level: nothing is added, removed, renamed or retyped and no default moves — `check:api-surface`, `check:authorable-surface` and `authorable-defaults` are all green with no diff — so this is a correction to a published description, not a widening. The behaviour change it describes graded `patch` itself, and a description that follows it cannot outrank it. ## Verification **Gates** — ⛔ not a recalled list. Derived from the real change set with `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands`, exit codes landed to a file **before any pipe**, then reconciled: ```text ✓ dispatch-gates --ran: 101 derived famil(ies) accounted for — 101 run, 0 NOT-MEASURED (a DERIVED zero — all 101 recorded an exit code and none of them is 3). ``` All 101 exit 0, at `42abf1d357`. Six first answered **exit 3 = PREREQUISITE NOT MET** — `check:doc-formula-expressions`, `check:doc-security-posture`, `check:skill-examples`, `check:docs-transcript-drift`, `check:dual-build-cjs-loads`, `check:lean-entry-closure`. That was cleared with a workspace build and all six were re-run to exit 0; ⛔ an exit 3 was never read as a pass. The first derivation printed a **STALE TREE** refusal-shaped warning (the branch was behind `origin/main`, and `.github/workflows/lint.yml` plus `package.json` had moved inside the range — the two files families are derived from). `origin/main` was merged through `scripts/pm/os-regen-merge.sh`, the chain regenerated, and the list derived again from the merged tree: 101 commands, byte-identical to the first. `origin/main` has moved 2 commits since; the same query over that newer range returns **0** workflow or `package.json` hits, against a **control** over the earlier range that returns 2 — so the derived family set cannot have moved under those two commits. **Tests** | run | result | |:--|:--| | `pnpm --filter @objectstack/spec test` | 513 files, 14973 passed, 1 todo | | `pnpm --filter @objectstack/spec typecheck` | pass — `tsc --noEmit` excludes `**/*.test.ts`, and the test layer is covered by the second leg of the same script, `check:test-typecheck` (53 files / 257 errors / 142 pinned signatures held, shrink-only) | | `pnpm lint` (repo-wide `eslint . --no-inline-config`) | exit 0 | | `pnpm --filter @objectstack/spec check:generated` | all 15 up to date | No new test. The card rules on this itself: `check:docs` can only ever prove the page equals the describe, so no instrument on this seam could have caught the rot, and inventing one here would be a new verification surface the card did not ask for. What would catch it is a reader, which is what the card is. **Merge hygiene** — after merging `origin/main`, every incoming entry was asserted present against `origin/main` by exact name (the migration registry row, the migration entry body at 43 lines, the `NavigationMode` count equal on both sides at 4, all three incoming changesets), and this branch's own four carriers re-asserted. Nothing was swallowed in either direction. ## Acceptance notes **One carrier of the same denial is deliberately left standing, because it is fenced out of this card's surface.** `packages/spec/src/api/package-api.zod.ts:389` still reads "its own implementation does not read it" about this same key. The dispatch holds that file for another card and another seat, so it is reported rather than edited. The card's executable criterion names it, which is why this PR says `Part of` and not a closing keyword: landing this alone leaves that half of the criterion open. **A pending release note carries the same root and is also left standing.** `.changeset/18605-enable-on-install-one-authority.md` states that the kernel copy's published description "now records that this layer does not read it". This PR is what makes that sentence false, and the note is unreleased, so the release that consumes it would otherwise assert both halves. It was NOT edited here on purpose: `pr-automation.yml` route 0 names editing somebody else's pending note the DELIBERATE CORRECTION class, which requires a written confirmation on the PR and deliberately leaves `Check Changeset` red for a person to adjudicate. That is a decision about a release, not a dev edit, and it is handed to the review seat. **Sequencing.** #19273 rewrites the same field's published text from the shape side. Whichever lands second will regenerate the same two table rows. ⛔ Not this PR's to sequence. **Clause-② hint, recorded and answered.** `dispatch-gates` flags `packages/spec/src/**` as a clause-② SUSPECT surface. It is a hint, not a verdict: this diff adds no key to a published payload, changes no accept or reject behaviour, and leaves the accept set byte-for-byte — the declaration stays `no`. ⛔ No label was written by this branch. The dispatch named none, and `skip-changeset` is refuted by the measurement above; `needs:contract-review` is the review seat's to place. --- 🤖 Generated with [Claude Code](https://claude.com/claude-code) _Generated by [Claude Code](https://claude.ai/code/session_013RDBh5DqXd2xnLwvHLgLFr)_ --- _Generated by [Claude Code](https://claude.ai/code/session_013RDBh5DqXd2xnLwvHLgLFr)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 40626bd commit 7e1b048

4 files changed

Lines changed: 53 additions & 14 deletions

File tree

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
---
2+
"@objectstack/spec": patch
3+
---
4+
5+
`kernel/InstallPackageRequest.enableOnInstall` no longer tells authors the in-process primitive ignores the key — it now states the three states that primitive really applies (#19339).
6+
7+
The declaration's published description read "this protocol primitive does not read it". That was true when it was written and stopped being true when `MetadataProtocol.installPackage` started honouring the key (`482d584121`): `true` enables, `false` disables, and an ABSENT key makes no lifecycle call at all. Nothing went red, because `check:docs` holds the generated reference page equal to the `.describe()` and the two still agreed with each other — internal consistency, not truth.
8+
9+
Clause-②: no
10+
11+
**What moves**
12+
13+
The `.describe()` text of one key, the doc block above it, and the two reference pages generated from that text (`references/api/protocol.mdx`, `references/kernel/package-registry.mdx`). It now reads: "restates the install-door request key, whose one authority is api/PackageInstallRequest; this protocol primitive honours it on the registry row: true enables, false disables, absent makes no lifecycle call".
14+
15+
The scope word "on the registry row" is load-bearing and is spelled out in the doc block: the durable disabled-package file is keyed by environment, which an `InstallPackageRequest` does not carry, so this seam moves the registry row for the life of the process and `POST /api/v1/packages` still owns the record that survives a restart.
16+
17+
**What does not move**
18+
19+
No key is added, removed, renamed or retyped, and no default changes — the accept set is byte-for-byte what it was, and `api-surface`, `authorable-surface` and `authorable-defaults` are all unchanged. `PackageInstallRequestSchema` (`api/package-api.zod.ts`) remains the one authority for this key, and the parity pin that holds the copy to it is untouched.

‎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 — restates the install-door request key, whose one authority is api/PackageInstallRequest; this protocol primitive does not read it |
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 honours it on the registry row: true enables, false disables, absent makes no lifecycle call |
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 — restates the install-door request key, whose one authority is api/PackageInstallRequest; this protocol primitive does not read it |
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 honours it on the registry row: true enables, false disables, absent makes no lifecycle call |
188188
| **platformVersion** | `string` | optional | Current platform version for compatibility verification |
189189

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

‎packages/spec/src/kernel/package-registry.zod.ts‎

Lines changed: 32 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -293,19 +293,39 @@ export const InstallPackageRequestSchema = lazySchema(() => z.object({
293293
* ⛔ Never let the two drift: `src/api/package-install-one-authority.test.ts`
294294
* parses BOTH over one matrix and reds when they disagree on any cell.
295295
*
296-
* ## ⚠️ This contract's own implementation does not read the key
296+
* ## ⭐ This contract's own implementation HONOURS the key — on the registry row
297297
*
298298
* This schema types the in-process protocol primitive
299-
* `ObjectStackProtocol.installPackage` (`src/api/protocol.zod.ts`), whose
300-
* implementation reads `request.manifest` and `request.settings` and nothing
301-
* else (`packages/metadata-protocol/src/protocol.ts`). The HTTP door does
302-
* NOT forward the key down this seam either: it calls
303-
* `installPackage({ manifest, settings })` and performs the enable/disable
304-
* flip itself afterwards, because the durable half must follow the ROW that
305-
* door returned rather than the request's intent. So an `enableOnInstall`
306-
* spelled on THIS request reaches no code that acts on it — which is why the
307-
* `.describe()` says so on the published reference page rather than
308-
* repeating the authority's promise a layer that cannot keep it.
299+
* `ObjectStackProtocol.installPackage` (`src/api/protocol.zod.ts`), and that
300+
* implementation (`packages/metadata-protocol/src/protocol.ts`) applies the
301+
* same rule the HTTP door applies — 「缺省 = 保持,有旗 = 设置」 — through the
302+
* same registry verbs `PATCH /packages/:id/enable` and
303+
* `PATCH /packages/:id/disable` use:
304+
*
305+
* - `true` ⇒ `enablePackage` — clears a disable, including a boot-seeded one;
306+
* - `false` ⇒ `disablePackage` — the row and its `status` both move;
307+
* - ABSENT ⇒ no lifecycle call at all; the row the registry returned stands.
308+
*
309+
* `=== true` / `=== false`, never a truthiness test and never a `??` default:
310+
* the THREE states are the contract, and a non-boolean value is read as
311+
* ABSENT rather than coerced. The `.default(true)` below never reaches that
312+
* path — nothing parses an install request through this schema there — so an
313+
* absent key arrives intact and is read as absent.
314+
*
315+
* ⚠️ What this seam does NOT write, stated so the scope is not over-read: the
316+
* runtime's DURABLE disabled-package file. That record is keyed by
317+
* ENVIRONMENT (`setPackageDisabled(environmentId, id, disabled)`,
318+
* `packages/runtime/src/package-state-store.ts`) and an
319+
* `InstallPackageRequest` carries no environment, so the key cannot even be
320+
* formed here; that module also lives in `@objectstack/runtime`, which
321+
* depends on the protocol package and not the other way round. It is also why
322+
* the HTTP door still calls `installPackage({ manifest, settings })` and
323+
* performs its own enable/disable flip afterwards rather than forwarding the
324+
* key down this seam: the durable half must follow the ROW that door returned
325+
* rather than the request's intent. So an `enableOnInstall` spelled on THIS
326+
* request moves the registry row — what every in-process reader serves from —
327+
* for the life of the process; a caller that needs the choice to survive a
328+
* restart goes through `POST /api/v1/packages`.
309329
*
310330
* ## ⛔ Why the reference is documentary and not `…Schema.shape.…`
311331
*
@@ -322,7 +342,7 @@ export const InstallPackageRequestSchema = lazySchema(() => z.object({
322342
* mechanical half of the reference, and it is the half that can fail.
323343
*/
324344
enableOnInstall: z.boolean().default(true)
325-
.describe('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'),
345+
.describe('Whether to enable immediately after install — restates the install-door request key, whose one authority is api/PackageInstallRequest; this protocol primitive honours it on the registry row: true enables, false disables, absent makes no lifecycle call'),
326346
/**
327347
* Current platform version for compatibility checking.
328348
* When provided, the system compares this against the package's

0 commit comments

Comments
 (0)