From 8982f87bb760646b42423e4ac26975d60e4a2034 Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 29 Oct 2026 05:29:22 +0000 Subject: [PATCH 1/2] docs(spec): say which Virtual ObjectQL request arms admitted a caller-supplied context The D3 reason of data-engine-rpc-protocol-retired and the retirement comment in data/data-engine.zod.ts said every arm admitted a caller-supplied context; the execute arm carried an untyped options record and vectorFind had none, so the claim now names the read and write arms. The strictReadonlyWrites docblock no longer names the retired Virtual Data Engine boundary, and the strictness ledger's data-engine.zod.ts row records the ten sites that left with the protocol. The pending 27028 note is superseded by a new changeset rather than edited. Co-Authored-By: Claude Claude-Session: https://claude.ai/code/session_01WGufziSEJnb5QGLZRcRJua --- .changeset/27346-data-engine-rpc-retirement-wording.md | 10 ++++++++++ docs/audits/2026-07-unknown-key-strictness-ledger.md | 2 +- packages/spec/src/contracts/data-engine.ts | 4 ++-- packages/spec/src/data/data-engine.zod.ts | 4 ++-- .../semantic/18.data-engine-rpc-protocol-retired.ts | 3 ++- 5 files changed, 17 insertions(+), 6 deletions(-) create mode 100644 .changeset/27346-data-engine-rpc-retirement-wording.md diff --git a/.changeset/27346-data-engine-rpc-retirement-wording.md b/.changeset/27346-data-engine-rpc-retirement-wording.md new file mode 100644 index 00000000000..b778067ef93 --- /dev/null +++ b/.changeset/27346-data-engine-rpc-retirement-wording.md @@ -0,0 +1,10 @@ +--- +'@objectstack/spec': patch +--- + +docs(spec): the Virtual ObjectQL retirement's D3 reason says which request arms admitted a caller-supplied `context` + +Clause-②: no +Metadata-protocol: none + +Supersedes, in the same release, one sentence of the `feat(spec)!: retire the unserved Virtual ObjectQL request protocol from data/data-engine.zod.ts` note: "every arm admitted a caller-supplied `context` (`isSystem` included)". The corrected wording is: every read and write arm (`find`, `findOne`, `insert`, `update`, `delete`, `count`, `aggregate`) admitted a caller-supplied `context` (`isSystem` included). The `execute` arm carried an untyped options record rather than a `context` key, and the `vectorFind` arm had no `context` at all. The D3 semantic entry `data-engine-rpc-protocol-retired` carries the same correction, so `os migrate meta` and the upgrade guide read it. The `strictReadonlyWrites` docblock on `WriteObservabilityOptions` no longer names the retired "Virtual Data Engine" boundary: the option cannot be serialized and is not reachable from a REST/wire body. No schema, export or runtime behaviour changes. diff --git a/docs/audits/2026-07-unknown-key-strictness-ledger.md b/docs/audits/2026-07-unknown-key-strictness-ledger.md index 4784ee03e55..b2f6c8c13cc 100644 --- a/docs/audits/2026-07-unknown-key-strictness-ledger.md +++ b/docs/audits/2026-07-unknown-key-strictness-ledger.md @@ -715,7 +715,7 @@ column does not move and the `strip` column falls by the count of what left. | File | Class | Note | |---|---|---| | `object.zod.ts` | authorable | top-level already guarded (#1535); **all 14 inner blocks strict** — 13 at 批 20, and the held 14th (`IndexSchema`) closed 2026-08-16 once its hold's evidence was spent: the hold was the #5114 class caught pre-ship (objectui's `FALLBACK_SCHEMAS.index` had drifted, offering `where`/`brin`, and the editor PUT the whole object through `saveMetaItem`), gated on the objectui rename (#5247) and an ADR-0049 answer for `type`/`partial` (#5248). #5248 answered remove (PR #5842, `retiredKey` tombstones + protocol-17 migration); **objectui#4772 then converged the editor to the declared `name`/`fields`/`unique` surface**, so the close shipped with a curated `where` guidance entry (database-layer prescription — deliberately NOT a rename onto the retired `partial` tombstone, which would be finding 7) and per-key pins that the tombstones' own prescriptions survive the strict path. Semantic entry `object-index-unknown-keys-refused` (protocol 18). The file's row left the remaining-strip map by reaching zero; the hold's full history is preserved in that map's section prose and in `object-strictness-batch20.test.ts` §4, which flipped from pinning the strip to pinning the close | -| `data-engine.zod.ts` | wire (p) | engine contract shapes (was 14 — `DataEngineBatchRequestSchema` retired with `IDataEngine.batch?`, #4618) | +| `data-engine.zod.ts` | wire (p) | engine contract shapes (was 14 — `DataEngineBatchRequestSchema` retired with `IDataEngine.batch?`, #4618; ten more sites — `DataEngineRequestSchema` and its nine arms — retired with the Virtual ObjectQL request protocol, #27028) | | `seed-loader.zod.ts` | wire | **re-verdicted 2026-08-14 (#4001 batch D)** — the `(p)` split's "authored" half dissolved under measurement. The seed FILE shape this file's old note meant is `SeedSchema` (`seed.zod.ts`, a registered type, strict since the registered-types batch) — it enters this file only as the `seeds[]` VALUE inside `SeedLoaderRequestSchema`, so the authored half was never one of these 12 sites. Every producer of the request/config halves is framework code, enumerated: `runtime/app-plugin.ts` ×3 (org replay / inline seed / hot-reload), `runtime/domains/packages.ts`, `metadata-protocol/protocol.ts` `applySeedBodies`, `metadata-protocol/seed-loader.ts` `validate()`, `cloud-connection` marketplace install — all `.parse()`/`.safeParse()` over config LITERALS written in code; no `defineStack` key, no metadata type, no Studio form, no CLI flag writes a loader config. `SeedIdentity` is server-constructed by design (`execution-context.zod.ts`: "never client-supplied"); graph/resolution/result/error shapes are built by the loader itself (`buildDependencyGraph`, `buildResult`). BFS from all 26 metadata-type roots + `ObjectStackSchema` (6689 nodes; `ObjectSchema` positive control REACHABLE, fresh uncarried shape negative control UNREACHABLE, synthetic-carrier flip green, same run): all 12 UNREACHABLE. Same class as `data-engine.zod.ts` — an internal service contract (`ISeedLoaderService`), machine-fed at every door | | `field.zod.ts` | authorable | partially strict | | `filter.zod.ts` | open | query dialect — user data flows through the predicate values; validated semantically elsewhere | diff --git a/packages/spec/src/contracts/data-engine.ts b/packages/spec/src/contracts/data-engine.ts index 8c3c210504e..d725f7dc971 100644 --- a/packages/spec/src/contracts/data-engine.ts +++ b/packages/spec/src/contracts/data-engine.ts @@ -169,8 +169,8 @@ export interface WriteObservabilityOptions { * * This is an IN-PROCESS option, exactly like its neighbour above, and for the * same structural reason: `WriteObservabilityOptions` is not the serializable - * `EngineUpdateOptionsSchema` bag, so nothing here crosses the RPC / Virtual - * Data Engine boundary or is reachable from a REST/wire body. A remote caller + * `EngineUpdateOptionsSchema` bag, so nothing here can be serialized, and + * none of it is reachable from a REST/wire body. A remote caller * cannot set it, and gets NEITHER behaviour: its write is stripped and * committed, silently from its side, exactly as before this option existed — * a 200 whose read-only columns kept their stored values. That is deliberate diff --git a/packages/spec/src/data/data-engine.zod.ts b/packages/spec/src/data/data-engine.zod.ts index cd3547b66cf..f468673a2ef 100644 --- a/packages/spec/src/data/data-engine.zod.ts +++ b/packages/spec/src/data/data-engine.zod.ts @@ -1343,8 +1343,8 @@ export type QueryWithTransportParsed = z.infer; // `ctx.api.object(name).find(...)` and parses no request schema; the wire batch // validates with `CrossObjectBatchRequestSchema` / `BatchUpdateRequestSchema` // from `../api/batch.zod.ts`. As published the protocol was also unsafe to -// implement: every arm admitted a caller-supplied `context` (`isSystem` -// included), and `execute` carried a raw driver command. Its private helpers +// implement: every read and write arm admitted a caller-supplied `context` +// (`isSystem` included), and `execute` carried a raw driver command. Its private helpers // (`RpcQueryOptionsSchema` and the `filter`-only fold) went with it; // `RPC_QUERY_ALIAS_SLOTS` above stays, since the transport dialect and the // engine fold by it. diff --git a/packages/spec/src/migrations/entries/semantic/18.data-engine-rpc-protocol-retired.ts b/packages/spec/src/migrations/entries/semantic/18.data-engine-rpc-protocol-retired.ts index 8e565ff8511..fff540fba4c 100644 --- a/packages/spec/src/migrations/entries/semantic/18.data-engine-rpc-protocol-retired.ts +++ b/packages/spec/src/migrations/entries/semantic/18.data-engine-rpc-protocol-retired.ts @@ -36,7 +36,8 @@ export const entry: SemanticMigration = { + 'against controls of 311, 2095 and 74 files importing `@objectstack/spec`. The one real ' + 'boundary engine calls cross, the QuickJS hook and action sandbox, speaks ' + '`ctx.api.object(name)` and parses no request schema. As published the protocol was also ' - + 'unsafe to implement: every arm admitted a caller-supplied `context` (`isSystem` included) ' + + 'unsafe to implement: every read and write arm admitted a caller-supplied `context` ' + + '(`isSystem` included) ' + 'and the `execute` arm carried a raw driver command, so enforcing it would first have ' + 'meant narrowing it and building a second data door beside REST, batch, MCP and the ' + 'sandbox. `DataEngineContractSchema` restated the engine methods as `z.function()` ' From d450f8b99d87a866819768e16d112edd63ff276b Mon Sep 17 00:00:00 2001 From: Claude Date: Thu, 29 Oct 2026 07:02:34 +0000 Subject: [PATCH 2/2] docs(objectql): the onFieldsDropped listener is described without the retired Virtual Data Engine boundary Co-Authored-By: Claude Claude-Session: https://claude.ai/code/session_01WGufziSEJnb5QGLZRcRJua --- content/docs/protocol/objectql/security.mdx | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/content/docs/protocol/objectql/security.mdx b/content/docs/protocol/objectql/security.mdx index 0f841f792a0..6ea15e42a00 100644 --- a/content/docs/protocol/objectql/security.mdx +++ b/content/docs/protocol/objectql/security.mdx @@ -390,8 +390,8 @@ which the engine computes on every read and no driver has a column for, is dropp every write in every context, `isSystem` included. The vocabulary is open — it grows as the write path gains legal strips — so branch on `reason` exhaustively rather than treating "not `readonly_when`" as "read-only". The listener is an in-process -callback: it is delivered by the local engine, and does **not** cross the RPC / Virtual -Data Engine boundary, so a remote caller never receives these events. Without a listener, +callback: it is delivered by the local engine, it cannot be serialized, and no REST/wire +body reaches it, so a remote caller never receives these events. Without a listener, the only trace is a server-side `WARN` naming the object, the field, and both remedies. ---