Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions .changeset/19327-install-door-residual-split.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
---
'@objectstack/spec': patch
---

`PackageInstallBodySchema`'s residual docblock splits clause 1: a manifest missing `version` is refused by the install door now, and only the `type` half is still residual

Clause-②: no

The docblock lists the bodies `POST /api/v1/packages` answers `201` to while
the declaration refuses them. Its clause 1 recorded "a manifest missing `type`
and/or `version`" as ONE class. Since #19326 the door parses
`ManifestSchema.shape.version` by reference, so a manifest missing `version`
answers `400` / `VALIDATION_ERROR` and installs nothing; a manifest missing
`type` still answers `201`. Half of the clause had become false.

The clause is now split: **1a** (missing `version`) is marked CLOSED by #19326
and names the door-side pin, and **1b** (missing `type`) stays an open residual.
The count of five classes is unchanged and still true, because class 1 stays
open through its `type` half; the count sentence now says so. The paragraph
that quotes the runtime's two door drives is updated too. It quoted the
duplicate-id drive as `{ id: 'pkg-a', name: 'A' }`, but that drive has posted a
`version` since #19326 and the reverse-domain id `com.example.pkg-a` since
#19473, and the door answers `400` to `pkg-a`. The docblock now quotes the body
the drive posts, `{ id: 'com.example.pkg-a', name: 'A', version: '1.0.0' }`,
which the declaration refuses on `type` alone and the door answers `201`.

⛔ No behaviour changes. No schema, accept set, export or runtime code moves,
and the other four residual classes are untouched.

**Why this carries a changeset and not `skip-changeset`.** `@objectstack/spec`'s
`files[]` ships `src/**/*.zod.ts` verbatim, and the docblock is also emitted
into `dist/api/index.d.ts` and `dist/api/index.d.mts`. The published content
changes, even though no line of code does.
80 changes: 54 additions & 26 deletions packages/spec/src/api/package-api.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -919,54 +919,82 @@ describe('#18058 — install contract bound to the live door', () => {
describe('the measured bare-form senders are the RESIDUAL, not green fixtures', () => {
/** The `manifest` helper in `packages/runtime/src/package-door-namespace-conflict-code.test.ts` — no `type`. */
const DOOR_DRIVE_CONFLICT = { id: 'com.acme.crm', name: 'com.acme.crm', namespace: 'crm', version: '1.0.0' };
/** The duplicate-id drive in `packages/runtime/src/domain-handler-registry.test.ts` — no `type`, no `version`. */
const DOOR_DRIVE_REGISTRY = { id: 'pkg-a', name: 'A' };
/**
* The duplicate-id drive in `packages/runtime/src/domain-handler-registry.test.ts` — no `type`.
* Two of its keys were repaired, each by the PR that made the door parse that leg: PR #19326
* gave it the `version` it lacked (the docblock's clause 1a, CLOSED), and PR #19473 replaced its
* id `pkg-a`, which `MANIFEST_ID_PATTERN` refuses. The old body is the REVERSED pin beside the
* drive, answered `400`, so ⛔ it is no residual and is not transcribed here. This is the body
* the drive posts: `409` first, then `201` on `?overwrite=true`.
*/
const DOOR_DRIVE_REGISTRY = { id: 'com.example.pkg-a', name: 'A', version: '1.0.0' };

it('the namespace-conflict drive is REFUSED — it carries no `type`', () => {
expect(PackageInstallBodySchema.safeParse(DOOR_DRIVE_CONFLICT).success).toBe(false);
});

it('the domain-handler-registry drive is REFUSED — no `type`, no `version`', () => {
it('the domain-handler-registry drive is REFUSED — it carries no `type`', () => {
expect(PackageInstallBodySchema.safeParse(DOOR_DRIVE_REGISTRY).success).toBe(false);
});

it('the missing keys are what decide it — and since #17534 the registry drive needs its id repaired too', () => {
it('the missing `type` is what decides it, for BOTH drives — the registry drive\'s old id is refused on its own', () => {
// The control that makes the two refusals above a measurement of the
// MANIFEST's required keys rather than of the bare branch existing at all.
expect(PackageInstallBodySchema.safeParse({ ...DOOR_DRIVE_CONFLICT, type: 'app' }).success).toBe(true);
// ⭐ #17534 moved this half. `ManifestSchema.id` carries
// `MANIFEST_ID_PATTERN` now, and `pkg-a` is not reverse-domain notation,
// so completing the missing keys is no longer sufficient for THIS drive —
// it stays refused, on the id's shape rather than on an absent key.
// ⛔ The remedy is to say that, not to relax the pattern: the drive posts
// an id the registry face has always refused to publish.
const registryKeysCompleted = { ...DOOR_DRIVE_REGISTRY, version: '1.0.0', type: 'app' };
expect(PackageInstallBodySchema.safeParse(registryKeysCompleted).success).toBe(false);
// Lit control — the id is what decides it now: the same body with a
// reverse-domain id parses green, so the refusal above is not the missing
// keys coming back.
expect(PackageInstallBodySchema.safeParse({
...registryKeysCompleted, id: 'com.acme.pkg-a',
}).success).toBe(true);
const registryKeysCompleted = { ...DOOR_DRIVE_REGISTRY, type: 'app' };
expect(PackageInstallBodySchema.safeParse(registryKeysCompleted).success).toBe(true);
// ⭐ Until PR #19473 this half ran the other way. The drive posted `pkg-a`,
// which `MANIFEST_ID_PATTERN` has refused since #17534 (PR #18319), so
// completing its keys was not sufficient and this case pinned the
// refusal. PR #19473 made the door parse the id leg as well, so the door
// answers that body `400` (the REVERSED pin beside the drive), and it
// repaired the drive's id. ⛔ The old reading is kept, pointed at the old
// body: refused on the id alone (the lit control is the green parse just
// above, the same body with the drive's current id), so it is no part of
// the `201` residual.
expect(PackageInstallBodySchema.safeParse({ ...registryKeysCompleted, id: 'pkg-a' }).success).toBe(false);
});

/** Bodies the door still answers `201` to while this declaration refuses them — the live residual. */
const DOOR_201_RESIDUALS: ReadonlyArray<Record<string, unknown>> = [
DOOR_DRIVE_CONFLICT,
DOOR_DRIVE_REGISTRY,
{ ...SDK_MANIFEST, label: 'an unknown key on the bare form' },
{ ...SDK_MANIFEST, enableOnInstall: false },
{ manifest: SDK_MANIFEST, enableOnInstall: 'false' },
{ manifest: SDK_MANIFEST, overwrite: 'true' },
];

it('the door answers 201 to all of them anyway — so this declaration is a SUBSET of the door', () => {
// Pinned as prose-with-a-parse rather than a live HTTP drive: the door
// lives in `@objectstack/runtime`, which this package cannot import.
// `packages/runtime/src/domains/packages-install-enable-on-install.test.ts`
// and the two drive files above are where the 201s are measured.
for (const residual of [
DOOR_DRIVE_CONFLICT,
DOOR_DRIVE_REGISTRY,
{ ...SDK_MANIFEST, label: 'an unknown key on the bare form' },
{ ...SDK_MANIFEST, enableOnInstall: false },
{ manifest: SDK_MANIFEST, enableOnInstall: 'false' },
{ manifest: SDK_MANIFEST, overwrite: 'true' },
]) {
for (const residual of DOOR_201_RESIDUALS) {
expect(PackageInstallBodySchema.safeParse(residual).success).toBe(false);
}
});

it('✅ clause 1a is CLOSED — every body in the live residual carries a `version` the declaration accepts', () => {
// PR #19326 made the door parse `ManifestSchema.shape.version` by
// reference: a manifest missing `version` answers `400` /
// `VALIDATION_ERROR` there now and installs nothing. The door-side pin is
// §1 of `packages/runtime/src/domains/packages-install-manifest-version.test.ts`,
// cited rather than repeated — this package cannot import the door. On
// that key the declaration and the door agree, so a versionless body in
// the list above would record a `201` the door no longer answers, which
// is what the registry drive's first transcription, `{ id: 'pkg-a',
// name: 'A' }`, did.
// ⛔ Clause 1b stays open: both drives above still carry no `type`.
for (const residual of DOOR_201_RESIDUALS) {
const manifest = ('manifest' in residual ? residual.manifest : residual) as { version?: unknown };
expect(ManifestSchema.shape.version.safeParse(manifest.version).success).toBe(true);
}
// Lit control — the check bites: a manifest with no `version` key reads
// `undefined` above, and the declaration refuses that.
expect(ManifestSchema.shape.version.safeParse(undefined).success).toBe(false);
});

it('⭐ #17534 closed the one spelling that ran the OTHER way — a whitespace-only `id` is refused HERE now, not only by the door', () => {
// What this pinned before: `handlePackages` trims before keying and
// refuses an empty id, while this declaration ADMITTED `' '` — the one
Expand Down
28 changes: 21 additions & 7 deletions packages/spec/src/api/package-api.zod.ts
Original file line number Diff line number Diff line change
Expand Up @@ -478,10 +478,15 @@ export type PackageInstallRequestParsed = z.infer<typeof PackageInstallRequestSc
*
* ⚠️ What those two drives post is NOT covered by this branch, and saying so
* is the point. Measured: `{ id, name: id, namespace, version: '1.0.0' }` and
* `{ id: 'pkg-a', name: 'A' }` are both refused here (`invalid_union`) because
* neither carries `type`, and the second carries no `version` either. They are
* bare in FORM and incomplete in CONTENT — the form is declared, the content
* is part of the residual below, and they are pinned as REFUSED in
* `{ id: 'com.example.pkg-a', name: 'A', version: '1.0.0' }` are both refused
* here (`invalid_union`) on `type` alone, and the door answers both `201` (the
* second on the `?overwrite=true` limb of its duplicate-id case). The second
* was repaired twice, each time by the PR that made the door parse the leg it
* broke: PR #19326 gave it the `version` it lacked (clause 1a below), and
* PR #19473 replaced its id `pkg-a`, which `MANIFEST_ID_PATTERN` refuses. The
* door answers that old body `400` now, so it is no part of the residual. They
* are bare in FORM and incomplete in CONTENT — the form is declared, the
* content is part of the residual below, and they are pinned as REFUSED in
* `package-api.test.ts` rather than dressed up as green fixtures.
*
* ## The two branches are disjoint — but only ONE of them is closed
Expand Down Expand Up @@ -510,9 +515,18 @@ export type PackageInstallRequestParsed = z.infer<typeof PackageInstallRequestSc
*
* This is a SUBSET description of the live door, deliberately. Measured
* through `HttpDispatcher.handlePackages`, the door additionally answers `201`
* to five classes this schema refuses:
*
* 1. a manifest missing `type` and/or `version` (both door drives above);
* to five classes this schema refuses — class 1 in its `type` half only,
* since PR #19326:
*
* 1. a manifest missing `type` or `version` — recorded as one class until
* PR #19326, split since, because its two halves no longer answer alike:
* - 1a. missing `version` — ✅ CLOSED by PR #19326, no longer residual: the
* door parses `ManifestSchema.shape.version` by reference and answers
* `400` / `VALIDATION_ERROR` without installing, so declaration and door
* agree (door-side pin:
* `packages/runtime/src/domains/packages-install-manifest-version.test.ts`);
* - 1b. missing `type` — still OPEN, answered `201` (both door drives
* above);
* 2. unknown keys on either form — refused by name on the bare branch,
* silently dropped on the wrapped one, `201` either way;
* 3. a string-typed `enableOnInstall` / `overwrite` — the door compares
Expand Down
Loading