Skip to content

[Decision] Retire the unserved "Virtual ObjectQL" wire protocol in data-engine.zod.ts (A), or keep it as a planned contract (B)? (#27028) #27274

Closed

Description

@objectstack-fleet

Filed by domain:spec seat 1 (#6017) · session session_01WGufzfhobWEgUUrjz4rrbh · 2026-10-28

⛔ Not a claim. ⛔ Not a dispatch. This card asks for one letter. Nothing is built until it is answered.

Filing gate: ② a decision only the maintainer can make. Triage 6459409175 on #27028 says that removing a published wire protocol is a protocol-surface decision, and that the seat brings a decision card with the four facets to the maintainer before removing anything. The measurement is from the #27028 dev thread, phase 1: read-only, with no edit and no PR.

Related: #27028 (tracker; it becomes the execution card on either answer) · #4618 (batch? retirement, ADR-0119 D3) · #26954 · #27182 (the "nobody parses it" retirement route this follows)

维护者速读

  • 改了什么: packages/spec/src/data/data-engine.zod.ts 里发布了一整套「Virtual ObjectQL」请求协议(DataEngineRequestSchema 及 9 个分支、DataEngineContractSchema、5 个类型,共 16 个导出),设想给另一个进程里的数据引擎用。这样的引擎不存在,也没有任何代码解析它。
  • 实测: 本仓 0 个读者(ui/、skills/、examples/、OpenAPI 都是 0);hotcrm、objectui、docs.objectos.ai 也都是 0(对照:它们分别有 311、2095、74 个文件在用 spec)。唯一真正跨 VM 调引擎的地方(QuickJS 沙箱)用的是另一种形状。
  • 风险点: 发布出去的 JSON Schema 允许调用方自带 context.isSystem,execute 分支是原始 SQL —— 谁要是照着实现,就是一扇提权门。另外 content/docs/kernel/contracts/data-engine.mdx 还在教已退役的 engine.batch?.(),两种选项都会顺手修掉。
  • 选项:
选项 做什么 影响 工作量
A 退役(推荐) 删掉这 16 个导出和 5 个私有辅助;按「无人解析」路线登记(一条 D3 + 10 个 retired-defs),spec minor,不需要 major 只有直接 import 这些名字的 TS 代码升级时编译报错(实测没有);无存量数据、无运行时行为变化;回滚 = revert 一个 PR,大部分是删除,远低于 3000 行
B 保留,标为计划中契约 描述里加 [EXPERIMENTAL — not enforced],写进 ROADMAP 并指定未来读者 假入口继续对外发布;以后真要用,得先收窄(禁止外部传 isSystem、管住 execute)再新建一扇和现有 REST / batch / MCP 重复的数据门(需新 ADR) 现在很小;兑现时是多包多 PR
  • 推荐 A。 内外都零读者的协议是假入口(AGENTS.md 第 10 条);要兑现它,得先收窄、再造一扇与四扇现有门重复的数据门。A 的同时建议把 DataEngineContractSchema 一起退(它已经和 IDataEngine 对不上,也不生成 JSON Schema),并在协议 18 内以 D3 登记的 minor 落地,而不是等协议 19 的 major。
  • 请回: A / B。

Every reading below was taken on objectstack-ai/objectstack origin/main at
1287b2fa (2026-10-28 13:26Z) unless it names another tree.

The question

packages/spec/src/data/data-engine.zod.ts publishes a serialized request
protocol for "Virtual Data Engines": an engine that lives in another process
and receives its calls over HTTP, a message queue or a plugin boundary. No such
engine exists, and nothing parses the protocol. Do we retire it from the
published surface (A)
, or keep it and record it as a planned contract with
no reader yet (B)
?

What the block is

Kind Members
Exported request schemas (10) DataEngineRequestSchema (the method union) and its 9 arms: DataEngineFindRequestSchema, …FindOneRequestSchema, …InsertRequestSchema, …UpdateRequestSchema, …DeleteRequestSchema, …CountRequestSchema, …AggregateRequestSchema, …ExecuteRequestSchema, …VectorFindRequestSchema
Exported function contract (1) DataEngineContractSchema (section 8, z.function() members)
Exported types (5) DataEngineRequest, DataEngineRequestParsed, DataEngineExecuteRequest, DataEngineInsertRequest, DataEngineVectorFindRequest
Private helpers used only by the arms (5) RpcQueryOptionsSchema, foldRpcQueryOptions, RpcLegacyFilterMixin, foldRpcLegacyFilter, RPC_WHERE_SLOT

These are not part of the block, and A keeps them: RPC_QUERY_ALIAS_SLOTS,
foldQueryAliasSlots and aliasConflictIssue (the query-transport dialect and
ENGINE_WHERE_SLOTS in packages/objectql/src/engine.ts read them), every
*OptionsSchema (IDataEngine is typed with them; DataEngineInsertOptions
alone has 22 referencing files), and QueryTransportParamsSchema /
QueryWithTransportSchema.

Measured

1. Readers in the repo: zero. A whole-tree git grep for the 16 exported
names and RpcQueryOptionsSchema finds 25 files. None parses or imports the
block outside packages/spec/src/data/. ui/ has 0 hits, skills/ has 0,
examples/ has 0, and the generated OpenAPI document (json-schema/openapi.json
in the 18.2.0 tarball) has 0. The hits are:

  • the defining file and its own test (data-engine.test.ts: 10 describe
    suites, part of the integration suite, and 5 rows of the projection-parity
    table);
  • generated ledgers: api-surface/data.json 16, export-origins/data.json 16,
    declaration-map/data.json 14, json-schema.manifest/data.json 10,
    authorable-surface/data.json 35 rows over the 9 arms,
    authorable-defaults/data.json 1, dropped-refinements.baseline.json
    8 entries, docs-import-surface.baseline.json 6,
    unemitted-schemas.baseline.json 1 (DataEngineContractSchema),
    variant-docs.json 1 (method:aggregate|…|vectorFind), and
    type-alias-convention.pin.test.ts 3 Iso_ rows;
  • the generated reference page content/docs/references/data/data-engine.mdx
    (24 mentions) and content/docs/references/index.mdx (1);
  • prose only: contracts/data-engine.ts:61 ("the RPC (Virtual Data Engine)
    boundary"), ui/view-grouping-query.ts:172, objectql/src/engine.ts:601,
    DEVELOPMENT_PLAN.md (2 rows), ADR-0119 line 61 (history, no edit needed),
    the chore(spec): retire IDataEngine.batch? per ADR-0119 D3 — declared-but-unimplemented, zero callers #4618 D3 entry 17.data-engine-batch-retired.ts, and CHANGELOGs.

The nearest real boundary chose a different shape. The QuickJS hook and action
sandbox (runtime/src/sandbox/quickjs-runner.ts:696-703) is the one place
where engine calls cross a VM boundary today. It exposes
ctx.api.object(name).find/findOne/count/aggregate/insert/update/delete/updateMany/deleteMany/upsert,
gated by capabilities, and parses no schema at the crossing. The wire batch
validates with CrossObjectBatchRequestSchema / BatchUpdateRequestSchema from
api/batch.zod.ts.

DataEngineContractSchema has already drifted from the real contract.
IDataEngine reads take a trailing options?: BaseEngineOptions
(contracts/data-engine.ts:320-323), and it declares vectorFind? and
execute?. The Zod contract has neither.

2. What is published. @objectstack/spec@18.2.0 (npm latest, protocol
18.0.0) ships the block in four places:
json-schema/data/DataEngine{,Find,FindOne,Insert,Update,Delete,Count,Aggregate,Execute,VectorFind}Request.json
(10 files) plus the bundle json-schema/objectstack.json; the dist/*.d.ts;
src/data/data-engine.zod.ts; and api-surface/. Each JSON Schema carries
$id: https://schema.objectstack.io/v18.2.0/data/DataEngineRequest.json, and so
on. Nothing in this repo deploys that host. No workflow names it, and its only
appearance is SCHEMA_BASE_URL in build-schemas.ts. Whether the host serves
anything is NOT MEASURED: this container's proxy refuses the CONNECT. The
exports map has no ./json-schema/* subpath, so the JSON files are reachable
only by a file path into node_modules. The reference page is published on the
docs site.

3. Outside readers: none found. Shallow clones, read-only, searched for the
16 names, RpcQueryOptions, "Virtual ObjectQL" and "Virtual Data Engine",
CHANGELOGs excluded. Each repo has a control: the number of files that do import
@objectstack/spec.

Repo @ commit Block hits Control: files importing @objectstack/spec
hotcrm @ 353dace (2026-10-28) 0 311
objectui @ b2c0ade (archived, 2026-10-26) 0 2095
docs.objectos.ai @ cab35fd (2026-10-28) 0 74

NOT MEASURED: third-party npm dependents and public GitHub code outside the
org. This session's code search is scoped to this repo.

4. Docs that teach it. The hand-written page
content/docs/kernel/contracts/data-engine.mdx imports DataEngineRequest for
one purpose: a batch?(requests: DataEngineRequest[], …) member in its
IDataEngine listing (line 52), with a worked
engine.batch?.([{ method: 'insert', … }]) example (lines 572-582). batch?
was retired by ADR-0119 D3 (#4618). An author who copies the example gets a tsc
error on a typed engine. On an untyped one, ?. makes the three inserts a
silent no-op. This page is wrong under either option and is fixed in the same PR
whichever way the maintainer decides.

5. The published JSON Schema is not a safe wire contract as written.
BaseEngineOptionsSchema.context is ExecutionContextSchema. So every read and
write arm admits a caller-supplied context carrying isSystem, userId,
permissions and positions. "isSystem" is present in the published
DataEngineFindRequest.json. The execute arm carries a raw driver command
(command: unknown, the SQL escape hatch). Eight of the ten request defs are
listed in dropped-refinements.baseline.json. That means a validator using the
published JSON Schema accepts requests that the Zod parse refuses, such as the
alias-conflict folds.

Option A: retire (recommended)

What changes. One PR, packages/spec plus docs:

  • Delete the 10 request schemas, DataEngineContractSchema, the 5 types and the
    5 private helpers. Leave a short in-file note where the block stood, in the
    same style as the DataEngineBatchRequestSchema note.
  • Registration follows the "nobody parses it" route, the precedent being
    18.document-schemas-retired (spec(field): retire the @deprecated CurrencyValueSchema (still exported at field.zod.ts:581) — ADR-0104 D1 says deleted; prerequisite of the multi-currency epic #8345 #27182):
    • one D3 semantic entry, entries/semantic/18.data-engine-rpc-protocol-retired.ts;
    • 10 entries/retired-defs/18.data__DataEngine*Request.ts files for the
      defs leaving json-schema.manifest/;
    • no retiredKey() tombstone and no D2 conversion, because no metadata
      source authors these and no parse exists for a prescription to reach;
    • changeset: @objectstack/spec minor, Clause-②: yes (narrowing),
      Metadata-protocol: narrowed (the authorable-surface/ rows move, which
      rules out none), marker <!-- adr-0087: registered data-engine-rpc-protocol-retired -->,
      and a FROM → TO table. FROM DataEngine*RequestSchema TO the doors that
      exist: IDataEngine in process, REST /data/:object, and POST {basePath}/batch.
  • The runtime-export-retired (D9) and type-surface-only (D8) markers do not
    apply. D8 predicate 2 refuses any diff under packages/spec/**.
  • No major is needed, and none is allowed. check-changeset-no-major
    refuses major in the launch window, and ADR-0087 lets a pre-GA break ship
    one-step as long as it lands as a chain step (the D3 entry above).
  • Generated and baseline files that move (regenerated, never hand-edited):
    api-surface/data.json −16, export-origins/data.json −16,
    declaration-map/data.json −14, json-schema.manifest/data.json −10,
    authorable-surface/data.json −35 (leave authorable-surface.base.json
    alone), authorable-defaults/data.json −1, dropped-refinements.baseline.json
    −8 entries plus its measured totals, docs-import-surface.baseline.json −6,
    unemitted-schemas.baseline.json −1, variant-docs.json −1,
    type-alias-convention.pin.test.ts −3 rows (the census count moves too), and
    the reference page plus its index row.
  • Hand edits: the 10 test suites and the projection-parity rows; the contracts
    docs page (import, batch member, batch example); the three comment
    sites, one of which is packages/objectql/src/engine.ts, so that package
    needs a patch changeset or the comment rides an unpublished path; and the
    2 DEVELOPMENT_PLAN.md rows.
  • Packages that move: @objectstack/spec (minor) and, for the comment only,
    @objectstack/objectql. ui/ does not move, so no Type Check UI fix is
    needed.
  • Governed surfaces: none expected. skills/*/references/_index.md names the
    file by its header line, and the file stays. That is NOT MEASURED until
    phase 2 runs check:generated --fix; if it moves an _index.md, the PR
    becomes Tier H.
  • Size: estimated well under the 3,000-line threshold. Most of it is deletions.

Why. It is a declared capability that nothing delivers, the ADR-0049
"remove" arm, and AGENTS.md Prime Directive #10 says not to advertise one.
Measured readers are zero in this repo and in the three sister repos. The only
real cross-boundary engine surface uses a different shape. The published JSON
Schema would be an escalation door if anyone implemented it as written
(context.isSystem, raw execute). The block costs every engine-options card
a tax: #4618 and #26954 both had to edit it without anyone using it.

Risk. An unknown TypeScript consumer importing these names gets a compile
error on upgrade. The compiler is the channel, and the D3 entry and changeset
name the replacements. A tool that reads
node_modules/@objectstack/spec/json-schema/data/DataEngine*Request.json finds
the files gone in the new version. Versioned $id URLs for old versions are
unaffected. No stored metadata and no runtime behaviour changes.

Rollback. Revert the PR. It is pure deletion with no data at rest and no
conversion to undo. The revert removes the retired-defs entries together with
the re-added schemas, so the RETIRED_DEFS_BY_MAJOR "still published" gate
stays consistent.

Option B: keep, and record it as a planned contract

What changes.

What enforcing it later costs (the reader it would need).

  • A door. A route or queue consumer, owned by exactly one package, that
    parses DataEngineRequestSchema and dispatches to IDataEngine.
  • Narrowing first. The door has to bind the caller's identity into
    ExecutionContext and refuse a wire-supplied context (above all
    isSystem). It has to refuse or capability-gate execute remotely, and drop
    context.transaction (an unknown handle that cannot cross a wire). That is
    a narrowing of the published protocol before its first reader.
  • A client. A RemoteDataEngine implements IDataEngine to produce the
    requests.
  • Discovery. Advertise the door, and mount everything advertised
    (ADR-0076 D12, AGENTS.md "Machine-readable surfaces must not lie").
  • Overlap. It duplicates the existing doors: REST /data/:object,
    POST /batch, the metadata protocol's findData / batchData, MCP tools
    and the client SDK. That is why AGENTS.md route-ownership rule 1 would need
    an ADR to justify a second door, and a new ADR is Tier H.
  • Size. A multi-package, multi-PR effort, with no North Star row it
    advances today (docs/NORTH-STAR.md and ROADMAP.md have 0 hits for the
    data engine RPC).

Risk. The false affordance stays published. AI authors and codegen keep
reading a protocol that nothing serves, and the JSON Schema keeps advertising a
caller-supplied isSystem. The tax on every engine-options change continues.

Rollback. Trivial. Remove the markers.

What we ask the maintainer

  1. A or B? The recommendation is A. A protocol with zero readers,
    inside or outside the repo, is a false affordance. Enforcing it would first
    require narrowing it and building a second data door that duplicates four
    existing ones.
  2. If A: retire the whole set, DataEngineContractSchema included (it has
    drifted from IDataEngine and is unemitted), and land it as a D3-registered
    minor inside protocol 18 rather than waiting for a protocol 19 major. Both
    are the recommendation.

Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 28, 2026

    @objectstack-fleet
    ContributorAuthor

    Brief-claimed-by: session_01QSeKYo7qYsX6HsAneHBMP2 · batch #424 · 2026-10-28T14:26Z

    Director seat, summon #53 (ledger objectstack#12708). Presented to the maintainer in batch #424 as item 1 of 1 — the spec seat's decision card on the unserved "Virtual ObjectQL" wire protocol (#27028's phase-1 measurement).

    Seat increment (re-read at origin/main 0d45c0e7, 2026-10-28T14:26Z):

    Prior rulings read: Virtual ObjectQL · DataEngineRequest · batch? → ADR-0119 D3 (#4618), #26954, #27182 (route); thread: none (0 comments); #27028 triage 6459409175.

  2. objectstack-fleet commented on Oct 28, 2026

    @objectstack-fleet
    ContributorAuthor

    Ruling recorded · domain:spec seat 1 (#6017) · session session_01WGufzfhobWEgUUrjz4rrbh · 2026-10-28T14:40Z

    The maintainer chose A (retire) at 2026-10-28T14:38Z, on the decision card the seat posted in the project thread alongside this issue. It answers the question this card asks, and only that question.

    What it settles:

    • The whole set is retired: DataEngineRequestSchema and its 9 arms, DataEngineContractSchema, the 5 types and the 5 private helpers.
    • It lands as a D3-registered @objectstack/spec minor inside protocol 18, following the "nobody parses it" route: one D3 semantic entry plus 10 retired-defs entries. There is no tombstone and no conversion.
    • The content/docs/kernel/contracts/data-engine.mdx batch? section is fixed in the same PR.

    Execution card: #27028. This card's question is answered, and the seat closes it once the claim is posted there.

  3. objectstack-fleet commented on Oct 29, 2026

    @objectstack-fleet
    ContributorAuthor

    Decided and landed · domain:spec seat 1 (#6017) · session session_01WGufzfhobWEgUUrjz4rrbh · 2026-10-29T03:03Z

    The maintainer chose A, retire (ruling record 6472013580). It landed as PR #27324 (merge 9ac5ae5b) under #27028. This decision card is closed as completed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions