Skip to content
Merged
34 changes: 34 additions & 0 deletions .changeset/rest-meta-audit-reset-casts-retired.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
---
"@objectstack/rest": patch
---

refactor(rest): the audit and reset door call sites are compiled against the declared contract (#11678, #11679)

The `GET /meta/:type/:name/audit` and `DELETE /meta/:type/:name` doors in
`packages/rest/src/rest-server.ts` reached their protocol methods through
`(p as any)` — once for each feature-detection guard, once for each call — so
the compiler checked nothing about the request literals they built. The two
casts were load-bearing in opposite ways, both measured: the audit door's on
**member existence** (`auditMetaItem` was undeclared in `packages/spec`
entirely — removing the cast answered `TS2339`), the reset door's on **request
shape** (`deleteMetaItem` was declared, but its request schema carried 2 of the
8 members the door sends — removing the cast answered `TS2353` on six keys).

With `MetadataProtocol.auditMetaItem` declared and
`DeleteMetaItemRequestSchema` caught up (the spec half of this landing), the
guards are now `typeof p.auditMetaItem !== 'function'` / `if (!p.deleteMetaItem)`
and each request is a named const typed against the spec contract — the reset
door through `TransportScopedMetaRequest<DeleteMetaItemRequest>` (it still
spreads the transport-level `environmentId`, which stays layered on by the
#9741 envelope rather than becoming a protocol key), the audit door as a plain
`AuditMetaItemRequest` (it stopped sending `environmentId` when #8747 scoped
the read, so there is no transport member left to layer on).

**No behaviour change of any kind, and nothing about the wire moves.** The
outgoing payloads are byte-identical (same keys, same conditional spreads); the
edits hoist each literal into a const and drop type-level casts. The 501
feature-detection guards survive on purpose: both members are declared
**optional** (a kernel may implement neither door), and each guard is also what
narrows its member to callable at the call site. An undeclared key in either
literal is now a compile error instead of a payload member no contract has ever
seen.
13 changes: 13 additions & 0 deletions .changeset/spec-audit-meta-item-member.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
---
"@objectstack/spec": minor
---

**`MetadataProtocol` declares the optional `auditMetaItem` member, and the audit door's request/response schemas join the spec** (#11678 — the #11006 maintainer-ruled pattern, 2026-08-22 option B, carried one door over).

`GET /api/v1/meta/:type/:name/audit` — the ADR-0010 §3.6 compliance trail behind Studio's 审计日志 / Audit log tab — was a step behind the half-declared publish door #11006 adjudicated: **neither** side was declared (`auditMetaItem` appeared nowhere in `packages/spec`), so the REST door reached the verb through `(p as any)` twice (feature-detection guard + call) and its request literal was compiled against nothing.

Additive, not breaking:

- `AuditMetaItemRequestSchema` / `AuditMetaItemRequest` — `{ type, name, organizationId?: string | null, limit? }`, mirroring the implementation's parameter type in `@objectstack/metadata-protocol` member for member. `organizationId` is nullable because the REST door always sends it, possibly `null` (#8747's fail-closed tenant scoping: `null`/absent = env-wide rows only, never every tenant's). `limit` declares no bounds because the implementation clamps to [1, 500] rather than refusing. `environmentId` stays out by the #9741 ruling (transport-level routing key) — and on this door it is not even on the wire any more (#8747 removed it; the implementation never read it).
- `AuditMetaItemResponseSchema` / `AuditMetaItemResponse` — the `{ events: [...] }` body, newest first, with the closed `operation` (save/publish/rollback/delete/reset) and `outcome` (allowed/denied/forced) vocabularies and the ADR-0010 §3.3 `lockState`. The #9426 miss-vs-fault honesty is recorded in the declared types: `{ events: [] }` is the honest answer for a clean trail, a find-less host engine, or an unprovisioned audit table — never for a missing capability (501 before the call) and never for a failed read (propagated, not invented into an empty trail).
- `MetadataProtocol.auditMetaItem?(request: AuditMetaItemRequest): Promise<AuditMetaItemResponse>` — optional like its `deleteMetaItem` / `getMetaItemLayered` siblings: additive to a shipped contract, implementation predating declaration. An undeclared key in a request literal at the member's call shape is now a compile error.
17 changes: 17 additions & 0 deletions .changeset/spec-delete-meta-item-request-members.md
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
---
"@objectstack/spec": minor
---

**`DeleteMetaItemRequestSchema` declares the contract members the REST reset door sends** (#11679 — the #11006 maintainer-ruled pattern on the request-shape half).

`MetadataProtocol.deleteMetaItem` was declared all along, but its request schema declared 2 of the 8 members `DELETE /api/v1/meta/:type/:name` sends — so the door's call site had to stay behind an `(p as any)` cast (removing it surfaced `TS2353` on six keys, the opposite half of the publish door's `TS2339`), and the one member most worth having a contract — `organizationId`, which selects WHICH overlay row a reset destroys (ADR-0005 org partition; an org-less delete reaches the environment-wide row) — was on the wire with no declaration behind it.

Additive, not breaking — the five contract-level members join the schema, mirroring the implementation's parameter type in `@objectstack/metadata-protocol`:

- `organizationId?` — tenant scope for the reset (#8805); load-bearing, decides which row the delete destroys.
- `parentVersion?` — the ADR-0008 optimistic-concurrency pin (REST: the `If-Match` header); absent = last-write-wins.
- `actor?` — identity recorded on the history tombstone row (one producer, #7749); absent = recorded actor-less, never "system" (#4556).
- `state?` — `'active' | 'draft'`; `draft` discards the pending draft overlay only.
- `dropStorage?` — destructive opt-in (default false): also drop the object's physical table (`object` + `active` only; never `sys_`).

Two wire members stay out, by ruling rather than omission: `environmentId` (transport-level routing key per #9741, layered on by `packages/rest`'s `TransportScopedMetaRequest`) — and there are no internal coordination keys on this door (`_skipSeedApply` is publish-batch-only).
Loading
Loading