Repository navigation
Commit ba77509
docs(spec): split the install door's residual clause 1 into its closed
Fixes #19327
Clause-②: no
`PackageInstallBodySchema`'s docblock in
`packages/spec/src/api/package-api.zod.ts` 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. PR #19326 made the door parse `ManifestSchema.shape.version`
by reference, so only the `type` half of that clause is still true. This
PR splits the clause.
The diff is the docblock, one spec test file and one changeset. No
schema, accept set, export or runtime code moves, and residual classes 2
to 5 are untouched.
## Both halves re-measured at `origin/main` `fdeeea0cc9`, before any
edit
Both halves read exactly as the card says.
| half | body sent to the door | status | `error.code` | installed |
|---|---|---|---|---|
| control | wrapped, well-formed | 201 | none | yes |
| `version` | wrapped, no `version` | **400** | `VALIDATION_ERROR` | no
|
| `version` | bare, no `version` | **400** | `VALIDATION_ERROR` | no |
| `type` | wrapped, no `type` | **201** | none | yes |
| `type` | bare, no `type` | **201** | none | yes |
**How it was driven.** I used a one-shot probe, deleted right after the
reading and never committed. It was a vitest file at
`packages/runtime/src/domains/probe-19327-oneshot.test.ts` with the
harness of `packages-install-manifest-version.test.ts`: a real
`HttpDispatcher`, spied protocol and registry install writers, and
`OS_HOME` redirected. It called `handlePackages('', 'POST', body, {},
admin)` once per row and printed status, code and whether either writer
was called. It ran in one invocation with the existing drives that the
docblock's parenthesis «both door drives above» points at:
```
bash scripts/pm/os-verify-lock.sh -c "pnpm --filter @objectstack/runtime exec vitest run --project local --maxWorkers=2 src/domains/probe-19327-oneshot.test.ts src/domains/packages-install-manifest-version.test.ts src/domain-handler-registry.test.ts src/package-door-namespace-conflict-code.test.ts"
Test Files 4 passed (4)
Tests 82 passed (82)
os-verify-lock: VERDICT command-exit 0
```
- **The `type` half, from both existing drives.** Each drive posts a
manifest with no `type`, and each asserts that the door answers `201`:
- `package-door-namespace-conflict-code.test.ts` posts `{ id, name: id,
namespace, version: '1.0.0' }`.
- The duplicate-id case in `domain-handler-registry.test.ts` posts `{
id: 'com.example.pkg-a', name: 'A', version: '1.0.0' }` (`:600`): `409`
first, then `201` on `?overwrite=true`. The body `{ id: 'pkg-a', name:
'A', version: '1.0.0' }` has been that file's REVERSED pin since PR
#19473 and is answered `400` (`:622-623`), so it is not a residual.
- **The `version` half, from #19326's door test.** §1 of
`packages/runtime/src/domains/packages-install-manifest-version.test.ts`
pins `400` + `VALIDATION_ERROR` + neither writer called, on both body
forms. It is **cited, not duplicated**.
## What changed
**`packages/spec/src/api/package-api.zod.ts` (docblock only)**
- **Clause 1 is split:**
- **1a, missing `version`:** marked CLOSED, naming PR #19326 and the
door-side pin.
- **1b, missing `type`:** still OPEN, answered `201`, with «both door
drives above».
- The labels 1a/1b match the table in #19326's own body, so the two
records read the same way.
- **The count stays true.** "five classes" remains correct because class
1 is still open through its `type` half. The count sentence now says
«class 1 in its `type` half only, since PR #19326». Numbering 1 to 5 is
unchanged, so no citation of clauses 2 to 5 moves.
- **In-place fix of the drives paragraph (same defect class).** The
paragraph above the list quoted the registry drive as `{ id: 'pkg-a',
name: 'A' }` and said «the second carries no `version` either». That
drive was repaired twice: PR #19326 gave it a `version`, and PR #19473
replaced its id `pkg-a`, which `MANIFEST_ID_PATTERN` refuses. At the
merge base `fdeeea0cc9` and at `origin/main` it posts `{ id:
'com.example.pkg-a', name: 'A', version: '1.0.0' }`. The paragraph is
clause 1b's antecedent, so left alone it would have filed a
no-longer-posted body under the open residual. It now quotes the body
the drive posts, says the declaration refuses both drives on `type`
alone and the door answers both `201`, credits both repairs, and says
the door answers the old `pkg-a` body `400`.
**`packages/spec/src/api/package-api.test.ts` (the pin of the clause)**
- `DOOR_DRIVE_REGISTRY` now transcribes the body the drive posts: `{ id:
'com.example.pkg-a', name: 'A', version: '1.0.0' }`. Its comment credits
both repairs, and its `it` title drops "no `version`".
- The control that rested on the stale id is now «the missing `type` is
what decides it, for BOTH drives — the registry drive's old id is
refused on its own». It asserts that the registry drive parses once
`type` is added, as the conflict drive does. The old `pkg-a` reading is
kept, pointed at the old body: `{ ...registryKeysCompleted, id: 'pkg-a'
}` is refused on the id alone, and the green parse just above it is its
lit control.
- The residual list is hoisted to `DOOR_201_RESIDUALS`, and the existing
"the door answers 201 to all of them anyway" assertion is unchanged over
it.
- **New pin:** «✅ clause 1a is CLOSED — every body in the live residual
carries a `version` the declaration accepts». It checks each residual's
manifest with `ManifestSchema.shape.version`. Its lit control is that
`undefined` is refused. It cites the door-side pin rather than repeating
it, because this package cannot import the door.
**`.changeset/19327-install-door-residual-split.md`: `patch` for
`@objectstack/spec`**
A changeset is owed, not `skip-changeset`, because `files[]` ships
`src/**/*.zod.ts` and the docblock is also emitted into the built
declarations. After the build, the new clause text is in
`dist/api/index.d.ts` and `dist/api/index.d.mts`, and the old spelling
"and/or `version`" hits 0 across `dist` and `src`. The bump is `patch`
because it is a text correction in a released package, with no API
change.
Note that npm `@objectstack/spec@17.4.0` has no
`PackageInstallBodySchema` at all: grep 0, with
`PackageInstallRequestSchema` = 4 as the control in the same file. The
stale clause has therefore not shipped yet. The next release would be
the first to carry it.
## Verification, round 1 (head `826e612b39`)
**Reverse verification.** The fix was committed first. I then restored
the old drive transcription `{ id: 'pkg-a', name: 'A' }` through
`scripts/ablation-replace.mjs`, with the anchor counted 1 → 0, the
replacement 0 → 1, and the blob `e976bff69a` → `5b952ed89a`.
- **Predicted beforehand:** 2 red, 73 green. The two reds were the new
1a pin, plus the #17534 lit control in "the missing keys are what decide
it", which now takes the drive's `version` through the spread.
- **Observed:** `Tests 2 failed | 73 passed (75)`, exactly those two.
- **Restore:** the blob is back to `e976bff69a`, equal to HEAD, and `git
diff HEAD` is empty. Restore was proven by hash, not by exit code.
**Spec package**
- **Test:** `pnpm --filter @objectstack/spec exec vitest run --project
local --maxWorkers=2` gives `Test Files 527 passed (527)`, `Tests 15504
passed | 1 todo`, exit 0.
- **Typecheck:** `pnpm --filter @objectstack/spec run typecheck` exits
0. `check:test-typecheck` reports OK. `tsc -p tsconfig.test.json
--listFilesOnly` lists `src/api/package-api.test.ts` (1 hit), and that
file has no debt-ledger entry.
- **Build:** `turbo run build --filter=@objectstack/spec` exits 0. `git
status` was clean afterwards, so no generated artifact moved.
**Gates.** From `node scripts/pm/dispatch-gates.mjs --repo
objectstack-ai/objectstack --commands` at this head: 82 derived, run one
by one with each exit code captured before any pipe. `--ran` reports: «✓
dispatch-gates --ran: 82 derived famil(ies) accounted for — 82 run, 0
NOT-MEASURED (a DERIVED zero — all 82 recorded an exit code and none of
them is 3)».
- Two gates first answered exit 3 (PREREQUISITE NOT MET) on this fresh
worktree: `check:dual-build-cjs-loads` and `check:type-check-debt`.
After `turbo run build --filter='./packages/*'
--filter='./packages/*/*'` (72 tasks, exit 0), both measured 0:
- «✓ check:dual-build-cjs-loads — 104 published require entry point(s)
across 67 package(s) load».
- «check-type-check-coverage --re-measure: OK — 4 ledger entr(ies)
re-measured».
**Lint (a narrowed pass, stated as one).** I ran `eslint
--no-inline-config --format json` on the three touched paths. The JSON
has 3 results. The two `.ts` files show 0 errors and 0 warnings. The
`.md` is outside eslint's own population ("File ignored because no
matching configuration was supplied"). This narrowing covers everything
the full run would: `eslint.config.mjs` never enables type-aware linting
(its note at lines 327-328: no `parserOptions.project`, no typed rules),
so this diff cannot move the verdict of any untouched file. The full
`pnpm lint` union is CI's.
**History.** The first commit `b685c67ad2` also rewrote the pending
`.changeset/18058-install-door-contract-rebind.md`.
`check:empty-changeset`'s foreign-changeset rule refused that with exit
1. The second commit `826e612b39` restores the file byte-identical to
the merge base, and the gate reads exit 0 at head. Acceptance note 1
below has the details.
## Patch round: the registry drive, transcribed as it posts (head
`0251069c5`)
The at-tier contract review of head `826e612b39` (record `5808378321`)
failed one item. This PR had transcribed the registry drive as `{ id:
'pkg-a', name: 'A', version: '1.0.0' }` and filed it under the open
`201` residual. That drive stopped posting that body at PR #19473; the
body is now the drive file's REVERSED pin, answered `400`. Commit
`0251069c5` corrects every copy in one round. The clause split, 1a's
CLOSED marking, the count sentence and the `patch` level are unchanged.
**Read at both refs before any edit.**
`packages/runtime/src/domain-handler-registry.test.ts` and
`packages/runtime/src/package-door-namespace-conflict-code.test.ts` are
each byte-identical at the merge base `fdeeea0cc9` and at `origin/main`
`2c1011b01b`:
- The duplicate-id case posts `{ id: 'com.example.pkg-a', name: 'A',
version: '1.0.0' }` (`:600`). It is answered `409`, then `201` on
`?overwrite=true` (`:601-604`).
- The REVERSED pin posts `{ id: 'pkg-a', name: 'A', version: '1.0.0' }`
(`:622`) and asserts `400` (`:623`).
- The namespace drive posts `{ id, name: id, namespace, version: '1.0.0'
}` (`:83`) and asserts `201` on the first install.
**Clause 1b re-judged against both drives.** Both still show it, so 1b
stays byte-unchanged.
- **Declaration**, from a one-shot `tsx` probe on `src` (not committed):
both real bodies fail `ManifestSchema` on `type` alone, and both parse
once `type: 'app'` is added. The stale body fails on `id` and `type`.
- **Door**, at `0251069c5`: `pnpm --filter @objectstack/runtime exec
vitest run --project local --maxWorkers=2 --reporter=verbose
src/domain-handler-registry.test.ts
src/package-door-namespace-conflict-code.test.ts` gives `Tests 57 passed
(57)`. That includes the duplicate-id case (`409`, then `201`), the
REVERSED pin (`400`) and namespace section 1 (first install `201`).
**What changed**
- `package-api.zod.ts`, the drives paragraph only:
- It quotes the real body, and says the declaration refuses both bodies
on `type` alone while the door answers both `201`.
- It credits the `version` repair to PR #19326 and the id repair to PR
#19473.
- It says the door answers the old `pkg-a` body `400`, so that body is
not part of the residual.
- `package-api.test.ts`:
- `DOOR_DRIVE_REGISTRY` holds the real body.
- The control that rested on the stale id now asserts that the real body
parses once `type` is added. It keeps the old `pkg-a` reading, refused
on the id alone.
- The clause-1a pin's comment now names the versionless first
transcription it meant.
- `DOOR_201_RESIDUALS` and its doc comment are unchanged: with the
corrected constant, every entry is a body the door answers `201`.
- `.changeset/19327-install-door-residual-split.md`: the drives sentence
names the body the drive posts, and the `pkg-a` body the door answers
`400`.
**Reverse verification.** Run after the fix was committed, through `node
scripts/ablation-replace.mjs`, which put the stale transcription back.
- Mutation: anchor ×1 → ×0, replacement ×0 → ×1, blob `37e99f04ca` →
`0331f39bc4`.
- Predicted beforehand: 1 red, the renamed control.
- Observed: `Tests 1 failed | 74 passed (75)`. The failure is at
`package-api.test.ts:945` (`expected false to be true`), the parse of
the real body plus `type`.
- Restored: the blob is back to `37e99f04ca`, equal to HEAD, and `git
diff HEAD` is empty.
- Round 1 had no assertion that could go red on this transcription. This
head has one.
**Verification at `0251069c5`**
- **`package-api.test.ts`:** `Tests 75 passed (75)`. On `origin/main`
`2c1011b01b` plus this branch's patch it gives `Tests 79 passed (79)`;
the patch applies cleanly beside #19937's changes to the same two files.
- **Spec tests:** `--project local` gives `Test Files 527 passed (527)`
and `Tests 15504 passed | 1 todo`. The three `repo`-project tests that
read `.changeset/` or mention `package-api` give `136 passed`.
- **Typecheck:** `pnpm --filter @objectstack/spec run typecheck` exits
0. `package-api.test.ts` is in `tsconfig.test.json`'s program and has no
debt entry.
- **Build:** the built `dist/api/index.d.ts` and `.d.mts` each carry the
real body once and the stale body zero times.
- **Gates:** `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` derives 82. Deriving on `origin/main` plus
the patch gives the same 82, with an empty set difference both ways.
- `--ran` on the record as captured: «82 derived famil(ies) accounted
for — 80 run, 2 NOT-MEASURED (2 DERIVED from a recorded exit 3)».
- The two were `check:dual-build-cjs-loads` and `check:type-check-debt`,
with PREREQUISITE NOT MET on a fresh worktree.
- After `turbo run build --filter='./packages/*'
--filter='./packages/*/*'` (72 tasks, exit 0), separate re-runs measured
both at exit 0: «104 published require entry point(s) across 67
package(s) load» and «4 ledger entr(ies) re-measured … none above its
recorded number».
- **Lint, a narrowed pass stated as one:** `eslint --no-inline-config
--format json` on the three paths gives 3 results.
- Both `.ts` files have 0 errors and 0 warnings. The `.md` is outside
eslint's population.
- `eslint.config.mjs` enables no type-aware linting (its note at
`:327-328`), so this diff cannot move any untouched file's verdict.
- The full `pnpm lint` is CI's.
## Acceptance notes
1. **The pending release note
`.changeset/18058-install-door-contract-rebind.md` restates the same
clause.**
- It reads «a manifest missing `type` and/or `version` (both of the
runtime's own door drives post one)». That has been half false since
#19326.
- It will publish into the `@objectstack/spec`, `@objectstack/runtime`
and `@objectstack/client` CHANGELOGs at the next release.
- It is not edited here. Correcting another PR's pending release note is
a release decision that `check:empty-changeset` routes to a person, and
that gate stays red on any PR that makes the edit. The proposed
replacement sentence goes to the seat in the report.
2. **Clause 5 of the same residual list is stale too.**
- It says the door answers `400` to a whitespace-only `id` «this
declaration admits».
- Since PR #18319, `ManifestSchema.id` carries `MANIFEST_ID_PATTERN` and
the declaration refuses it. `package-api.test.ts` already pins that
refusal.
- Not touched: the card fences the other classes. Reported to the seat.
3. **The header of
`packages/runtime/src/domains/packages-install-manifest-version.test.ts`
counts the docblock's five classes differently from the docblock.**
- The header counts `version`, `type`, unknown keys, string-typed
options and bare-form options.
- The docblock has `type`/`version` as one class and the whitespace `id`
as class 5.
- This predates this PR and is not made false by it. That runtime file
is not touched.
---
_Generated by [Claude
Code](https://claude.ai/code/session_013RDBh5DqXd2xnLwvHLgLFr)_
---------
Co-authored-by: Claude <noreply@anthropic.com>version half and open type half (#19935)1 parent b46dec3 commit ba77509
3 files changed
Lines changed: 108 additions & 33 deletions
File tree
- .changeset
- packages/spec/src/api
| 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 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
965 | 965 | | |
966 | 966 | | |
967 | 967 | | |
968 | | - | |
969 | | - | |
| 968 | + | |
| 969 | + | |
| 970 | + | |
| 971 | + | |
| 972 | + | |
| 973 | + | |
| 974 | + | |
| 975 | + | |
| 976 | + | |
970 | 977 | | |
971 | 978 | | |
972 | 979 | | |
973 | 980 | | |
974 | 981 | | |
975 | | - | |
| 982 | + | |
976 | 983 | | |
977 | 984 | | |
978 | 985 | | |
979 | | - | |
| 986 | + | |
980 | 987 | | |
981 | 988 | | |
982 | 989 | | |
983 | | - | |
984 | | - | |
985 | | - | |
986 | | - | |
987 | | - | |
988 | | - | |
989 | | - | |
990 | | - | |
991 | | - | |
992 | | - | |
993 | | - | |
994 | | - | |
995 | | - | |
996 | | - | |
| 990 | + | |
| 991 | + | |
| 992 | + | |
| 993 | + | |
| 994 | + | |
| 995 | + | |
| 996 | + | |
| 997 | + | |
| 998 | + | |
| 999 | + | |
| 1000 | + | |
| 1001 | + | |
997 | 1002 | | |
998 | 1003 | | |
| 1004 | + | |
| 1005 | + | |
| 1006 | + | |
| 1007 | + | |
| 1008 | + | |
| 1009 | + | |
| 1010 | + | |
| 1011 | + | |
| 1012 | + | |
| 1013 | + | |
999 | 1014 | | |
1000 | 1015 | | |
1001 | 1016 | | |
1002 | 1017 | | |
1003 | 1018 | | |
1004 | | - | |
1005 | | - | |
1006 | | - | |
1007 | | - | |
1008 | | - | |
1009 | | - | |
1010 | | - | |
1011 | | - | |
| 1019 | + | |
1012 | 1020 | | |
1013 | 1021 | | |
1014 | 1022 | | |
1015 | 1023 | | |
| 1024 | + | |
| 1025 | + | |
| 1026 | + | |
| 1027 | + | |
| 1028 | + | |
| 1029 | + | |
| 1030 | + | |
| 1031 | + | |
| 1032 | + | |
| 1033 | + | |
| 1034 | + | |
| 1035 | + | |
| 1036 | + | |
| 1037 | + | |
| 1038 | + | |
| 1039 | + | |
| 1040 | + | |
| 1041 | + | |
| 1042 | + | |
| 1043 | + | |
1016 | 1044 | | |
1017 | 1045 | | |
1018 | 1046 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
475 | 475 | | |
476 | 476 | | |
477 | 477 | | |
478 | | - | |
479 | | - | |
480 | | - | |
481 | | - | |
| 478 | + | |
| 479 | + | |
| 480 | + | |
| 481 | + | |
| 482 | + | |
| 483 | + | |
| 484 | + | |
| 485 | + | |
| 486 | + | |
482 | 487 | | |
483 | 488 | | |
484 | 489 | | |
| |||
507 | 512 | | |
508 | 513 | | |
509 | 514 | | |
510 | | - | |
511 | | - | |
512 | | - | |
| 515 | + | |
| 516 | + | |
| 517 | + | |
| 518 | + | |
| 519 | + | |
| 520 | + | |
| 521 | + | |
| 522 | + | |
| 523 | + | |
| 524 | + | |
| 525 | + | |
| 526 | + | |
513 | 527 | | |
514 | 528 | | |
515 | 529 | | |
| |||
0 commit comments