Skip to content

objectql: virtual-object correctness floor — applyDriverQueryInMemory helper, per-datasource row cap with a loud refusal, supports honoured for plugin drivers, refusal-envelope mapping for driver failures (epic #27261 P1) #27277

Closed

Description

@objectstack-fleet

Blocked-by: #27276

Filing gate ③ (maintainer-directed). Ruling 6471382887 on #27261 (batch #423, maintainer 「立专题卡」 2026-10-28): the ontology-over-external-systems epic opens with Tier A (plugin driver, zero protocol change). Part of #27261. Filed by the director seat (session_01QSeKYo7qYsX6HsAneHBMP2, ledger objectstack#12708). ⛔ Not a claim; triage sets the lane and grade. Design: #27261 body; the first customer pull: #27130 (route d, design 6468011868).

Ruling-ref: 6471382887

维护者速读

  • 改了什么: driver 的 supports 没声明的筛选/排序/分页由引擎用一个共享助手在内存里补齐;每数据源行上限(默认 1000)超出响亮拒收并提示 DataSync;driver/HTTP 失败映射成平台拒绝信封,永不静默空列表。
  • 为什么改: driver 不能选择性忽略筛选——否则虚拟对象静默返回错行(ServiceNow 远程表同一问题的教训)。
  • 风险与回滚: 只影响 schemaMode 非 managed 的数据源;托管库零变化。
  • 先看: IDataDriver.supports(data-driver.ts:78)、DriverQuery = QueryAST 减 object(:54)、datasource.zod.ts:53(pushdown 由 driver 决定)。
  • 两分钟验证: 一个只实现 find 全量返回的 driver,带筛选的查询仍得到正确子集;超 1000 行被拒并带处方。
  • 需要您做的: 无。

Task

  • Export applyDriverQueryInMemory(rows, query) from @objectstack/objectql (filters, orderBy, limit/offset, fields) with tests against the memory driver as the oracle.
  • Engine: for a non-managed datasource, apply the helper to whatever supports does not claim; config.maxRows (default 1000) refuses above the cap with the prescription.
  • Map driver throws / HTTP failures to EXTERNAL_SOURCE_UNAVAILABLE / EXTERNAL_WRITE_REFUSED with the source message; never an empty list.
  • Driver "shape pin" test template in the guide (P1-2).

Clause-②: no · Metadata-protocol: none (maxRows is driver/datasource config, not an authorable protocol key — confirm with the spec seat; if judged protocol, added). Size M. Lane: server.

Epic: #27261 (parent; the epic PM dispatches pm:epic children). Dedupe: #27261, #27130, ADR-0015; no open card carries this slice.

Activity

  1. objectstack-fleet commented on Oct 28, 2026

    @objectstack-fleet
    ContributorAuthor

    epic:#27261#1 · marchtian · session_017ihL2c5z3q4h8inrn5kwqV

    Lane domain:server, as the card declares ("Lane: server"). Written by the epic PM on the maintainer's instruction (ruling 3 in the registration comment on #27261: 田騏, 「同意」, the epic PM's project thread). Stays pm:blocked on #27276.

    Ruling 2 (same source) settles the open question in the Task block: the row cap is a fixed engine constant of 1000 in P1, not an authorable key — Metadata-protocol: none. The config.maxRows spelling in the card and in the epic §5.4 is deferred to P2, where it lands with the spec keys under the spec seat. The refusal prescription still names "narrow the filter or mirror via DataSync".

  2. objectstack-fleet commented on Oct 28, 2026

    @objectstack-fleet
    ContributorAuthor

    epic:#27261#1 · marchtian · session_017ihL2c5z3q4h8inrn5kwqV

    P0 readings that land on this card (spike #27276, report 6476152968, PR #27330 @ 680ce37b), carried here, not filed:

    • Every list read is a find plus a separate count with the same where — two remote round trips per page.
    • A PATCH reads the pre-image from the remote by id three times before driver.update.
    • A denied PATCH (no permission on the object) still costs the remote one by-id findOne before the 403.
    • find arrives with where, orderBy, limit and object; no fields, no offset on the first page.
      Each is an input for the in-memory helper / row-cap design; none is a correctness defect on its own.
  3. objectstack-fleet commented on Oct 29, 2026

    @objectstack-fleet
    ContributorAuthor

    epic:#27261#1 · marchtian · session_017ihL2c5z3q4h8inrn5kwqV
    Unlock · 2026-10-29T01:07Z

    Blocked-by: #27276 is closed (PR #27330 merged as 13f53603); pm:blocked removed. The card stays in the epic subtree (pm:epic) and is dispatched by the epic PM once the maintainer answers the P1 go/no-go question in the epic PM thread. The P0 readings for this card are in the comment above.

  4. objectstack-fleet commented on Oct 29, 2026

    @objectstack-fleet
    ContributorAuthor

    epic:#27261#1 · marchtian · session_017ihL2c5z3q4h8inrn5kwqV
    Claim: PM loop round 2 2026-10-29T01:27Z
    Session: session_017ihL2c5z3q4h8inrn5kwqV (epic PM); implementing card thread session_017ihL2dgsoHnkCiSyXF8isz
    Account: marchtian
    Branch: claude/project-thread-mlrxgx (the card thread's platform-assigned branch; a thread can push only that branch — measured platform exception to claude/issue-<n>-<slug>)
    Worktree: objectstack-issue-27277
    Domain: domain:server
    Seat: epic:#27261#1 (epic PM; no domain:* seat)
    File surface: packages/objectql/src/** (engine read/write path for non-managed datasources, the new exported helper, tests); packages/runtime/src/plugin-driver-external-object.spike.test.ts only if the floor changes what it pins (stop on breach; explain in the report)
    Container & model: M, mode:thread, model: default tier (dispatch-gates --tier: no path-derived mandate), effort: high (requested; the thread runs at the platform default)
    Clause-②: no
    Metadata-protocol: none (maintainer ruling 2, comment 6472620372 on #27261: the row cap is a fixed engine constant, no authorable key)
    Responsibility: n/a — not a defect card
    Thread-read: 6485017955
    Premise: holds — on origin/main 6d7634a no applyDriverQueryInMemory and no driver row cap exist under packages/objectql/src (the only maxRows is the dangling-reference audit's own budget); the P0 oracle test is on main (PR #27330). The card's "shape pin test template in the guide" item is carried by #27278 (claim 6485282688).
    Serial constraints cleared: none (sibling #27278 touches content/docs/** only; #27332 is unclaimed)

  5. objectstack-fleet commented on Oct 29, 2026

    @objectstack-fleet
    ContributorAuthor

    epic:#27261#1 · marchtian · session_017ihL2dgsoHnkCiSyXF8isz
    os-dev-report

    {
     "issue": 27277,
     "status": "needs_decision",
     "branch": "claude/project-thread-mlrxgx",
     "pr": null,
     "session": "session_017ihL2dgsoHnkCiSyXF8isz",
     "premise_still_valid": false,
     "summary": "Stopped before code: three of the card's four parts depend on public-contract choices the ruling does not settle on origin/main 6d7634a9. (1) The mechanism premise is false: `supports` carries no pushdown claims. The three bits it would read (queryFilters, querySorting, queryPagination) are tombstoned since spec 17.0.0 (packages/spec/src/data/driver.zod.ts:336-345, ADR-0049), and their prescription is the governing text: find() MUST execute the full QueryAST. So 'whatever supports does not claim' means every query part of every driver, ADR-0015 federated SQL datasources included (showcase ships one: examples/app-showcase/src/system/datasources/showcase-external.datasource.ts, driver sqlite, schemaMode external). Applied literally, the 1000-row cap would refuse unfiltered list reads on any federated table over 1000 rows. Managed datasources would stay unchanged, but ADR-0015 federation would break. (2) The cap refusal and the driver-failure mapping each need an error code that is not in ERROR_CODE_LEDGER: EXTERNAL_SOURCE_UNAVAILABLE and EXTERNAL_WRITE_REFUSED are absent, and no ledgered code fits 'result too large for a non-pushdown source' (only BATCH_TOO_LARGE and PAYLOAD_TOO_LARGE exist). Per the dispatch, raised as questions, not added. (3) The helper's evaluator is an architecture choice: a second filter-dialect implementation in objectql, or reuse of driver-memory, which sits under the check:driver-memory-census retirement programme. No PR opened; nothing pushed.",
     "tests": "none run: no code written (needs_decision). Readings only, all at origin/main 6d7634a9: git grep EXTERNAL_SOURCE_UNAVAILABLE|EXTERNAL_WRITE_REFUSED over the tree = 0 hits; packages/spec/src/api/error-code-ledger.zod.ts carries ERR_DATASOURCE_UNAVAILABLE (lines 787, 1094) and EXTERNAL_WRITE_FORBIDDEN (1564); driver.zod.ts:336/340/343 are the queryFilters/querySorting/queryPagination retiredKey tombstones; config-registry.zod.ts:267 BUILTIN_DRIVER_IDS = memory, sqlite, sqlite-wasm, postgres, mysql, mongodb, turso; engine.ts holds 13 direct driver find/findOne/count read call sites (find 13631, findOne 13934, by-id reads 14345/14397, pre-image and summary reads 16582/17548/19818/19976/20090/20107, aggregate fallback 20784), so a floor must sit at the driver boundary, not in find() alone; engine.registerDatasourceDef (9232) stores schemaMode and external but not the driver id.",
     "gates": "none run: no diff. dispatch-gates --commands has nothing to derive on an empty change set.",
     "dogfood_battery": "n/a: no PR",
     "mcp_calls": "5: mcp__github__issue_read x5 (get #27277, get_comments #27277 x2, get #27261, get_comments #27276); 0 write tools",
     "api_writes": "1: this os-dev-report comment via scripts/pm/post-stamped.mjs (fleet relay). No git push, no pr_create.",
     "line_budget": "0: no diff",
     "deviations": [
      "Returned needs_decision instead of a draft PR. The claim's premise line says 'holds'; it checked that the helper and cap do not exist yet, not that supports can carry the claims the ruling reads."
     ],
     "files_changed": [],
     "open_questions": [
      {
       "question": "Q1. Which drivers get the in-memory floor and the 1000-row cap? supports has no pushdown claims (retired in 17.0.0), so 'whatever it does not claim' is everything, for every driver.",
       "options": [
        "A. Floor every driver on a non-managed datasource. Zero spec change, but ADR-0015 federated SQL tables (showcase_external today) lose pagination pushdown and refuse over 1000 rows. Cost S. This narrows a shipped capability.",
        "B. Floor only drivers whose datasource driver id is not in BUILTIN_DRIVER_IDS (plugin-contributed drivers). The built-ins keep executing the full AST, as the tombstone prescription requires and their conformance suites pin. Zero spec change, objectql only (registerDatasourceDef also records the driver id). A plugin driver cannot opt out, which matches the ruling's sentence and the card title ('supports honoured for plugin drivers'). Cost M.",
        "C. Add one live DriverCapabilities bit with its engine reader (the ADR-0049 bar), e.g. a pushdown claim that plugin drivers may declare to skip parts of the floor. That is a spec contract change outside this claim's file surface, needs the spec seat and a Fable review, and re-opens the retired bits' question. Cost M+S."
       ],
       "recommendation": "B. It is the ruling applied to the drivers the ruling is about, keeps federation and managed sources byte-for-byte unchanged, and needs no spec edit. Pushdown claims for plugin drivers (C) can come with P2's driver-openapi, which is the first driver that would declare them. Mechanics under B: wrap the resolved driver once at getDriver for such datasources; find sends the driver where + orderBy + limit 1001 (no offset, no fields), refuses above 1000, then applies where, orderBy, offset/limit and fields in memory; findOne and count go through the same floored read; aggregate is hidden so the engine's existing find + in-memory aggregation path runs; writes pass through."
      },
      {
       "question": "Q2. Which codes do the row-cap refusal and the driver-failure mapping answer with? None of the needed codes is ledgered.",
       "options": [
        "A. Add three codes in one spec change: EXTERNAL_SOURCE_UNAVAILABLE (503, remote unreachable or 5xx), EXTERNAL_WRITE_REFUSED (4xx, remote refused a write, source message carried), and a cap code such as EXTERNAL_ROW_CAP_EXCEEDED (4xx, prescription 'narrow the filter or mirror via DataSync'). Each goes into ERROR_CODE_LEDGER, EXTERNAL_ERROR_CODES and EXTERNAL_ERROR_HTTP_STATUS in packages/spec. Spec lane, Fable review, outside this claim's surface. Cost S.",
        "B. Reuse ERR_DATASOURCE_UNAVAILABLE (503) for every driver failure and add only the cap code. Fewer new codes, but a 5xx withholds the message by design (shared/external-errors.ts), so a remote's write refusal would lose its source message, which the card asks to keep.",
        "C. Ship P1 without the failure mapping (a driver throw stays the engine's existing UNCLASSIFIED_FAULT 500) and add only the cap code."
       ],
       "recommendation": "A, as a small spec-lane child card that this card waits on, or a spec seat ruling that lets this PR touch the ledger. The card names both codes and the source-message requirement, and only a 4xx can carry that message."
      },
      {
       "question": "Q3. What evaluates applyDriverQueryInMemory's where and orderBy?",
       "options": [
        "A. A new evaluator inside objectql. It is a second implementation of the whole filter dialect (comparison, $in/$nin, $contains/$icontains, $like, $exists/$empty, $between, $and/$or/$not, temporal comparands, null rules). It can drift from the drivers. Cost L.",
        "B. Reuse driver-memory's matcher (mingo plus its refusal layer) by seeding an InMemoryDriver with the fetched rows and calling find. Parity by construction. objectql gains a runtime dependency on @objectstack/driver-memory, which check:driver-memory-census requires to be disposed of in writing by maintainer ruling (the #5704 programme). The card's 'memory driver as oracle' test needs the same disposition. Cost M.",
        "C. Move the matcher out of driver-memory into a shared package that both import. Cost M+, and it touches a package whose investment the census programme governs."
       ],
       "recommendation": "B, with the census disposition recorded in the same PR. One filter semantics is the long-term shape; a second evaluator is the two-implementations failure."
      }
     ],
     "out_of_scope_findings": [
      "carrier: #27277 (this card, after the ruling) · noted, not filed: the floor must cover all 13 direct driver read call sites in engine.ts (find, findOne, by-id reads, update/delete pre-image reads, summary reads, aggregate fallback), not only engine.find; a driver-boundary wrapper does that in one place.",
      "carrier: #27278 · noted, not filed: the guide's three-way rule should state that the floor applies to plugin drivers only (if Q1=B), and that built-in drivers on an external datasource must execute the full query as before."
     ]
    }
  6. objectstack-fleet commented on Oct 29, 2026

    @objectstack-fleet
    ContributorAuthor

    epic:#27261#1 · marchtian · session_017ihL2c5z3q4h8inrn5kwqV
    Decision needed · dev report 6485390687 · 2026-10-29T01:33Z

    The dev stopped before code (needs_decision, no PR). The seat re-measured the three premises on origin/main 6d7634a and they hold: queryFilters / querySorting / queryPagination are retiredKey tombstones (packages/spec/src/data/driver.zod.ts:336-345), BUILTIN_DRIVER_IDS is at packages/spec/src/data/driver/config-registry.zod.ts:267, the ledger has ERR_DATASOURCE_UNAVAILABLE and EXTERNAL_WRITE_FORBIDDEN but no EXTERNAL_SOURCE_UNAVAILABLE / EXTERNAL_WRITE_REFUSED / cap code, and @objectstack/objectql does not depend on @objectstack/driver-memory.

    Governing text: the tombstone prescription — "find() receives the full QueryAST (where/orderBy/limit/offset) and MUST execute all of it — the 'ObjectQL will filter in memory' fallback this bit's description promised was never built." Ruling 6471382887: "a driver cannot opt out of correctness — whatever its supports does not claim, the engine applies in memory and refuses above the row cap". The ruling assumed supports can carry pushdown claims; it cannot, so the ruling needs an execution parameter.

    维护者速读

    • 卡在哪: 裁决说「supports 没声明的,引擎在内存里补」,但 supports 里的下推位 17.0 起已退役,契约改成「driver 必须执行完整查询」。照字面做,所有外部数据源(包括已上线的联邦 SQL,如 showcase_external)都会被套上 1000 行上限。
    • 要定的三件: ① 兜底套在谁身上;② 上限拒收与远端失败用什么错误码(账本里都没有,加码属协议面);③ 内存筛选用谁的匹配器。
    • 推荐: ① 只套插件 driver(id 不在内置清单里),内置 driver 与联邦 SQL 零变化;② 新增三个错误码,走一张 spec 子卡,本卡等它;③ 复用 driver-memory 的匹配器,在同一 PR 记 census 处置。

    四维分析

    • ① 项目长远合理性:只兜底插件 driver 与裁决原意一致(卡标题即「supports honoured for plugin drivers」),也不推翻 17.0 的契约:内置 driver 照旧必须执行完整查询,插件 driver 由引擎再核一遍。一套筛选语义(复用 driver-memory)优于在 objectql 里再写一套方言;错误码进账本是唯一不静默的形态。
    • ② 实际业务拉动:object-gantt can no longer write back to a same-origin endpoint after #23082 — a measured producer of the retired view-level api source (read + write) #27130 是实测生产者,其 driver 由客户代码计算行,最可能漏筛选;联邦 SQL(showcase_external)有现用者,套上上限会坏。
    • ③ 防 AI 犯错:AI 写的插件 driver 忽略筛选时,引擎兜底让它结构上写不错;超上限与远端失败都响亮拒收,带处方。
    • ④ 创业阶段不扩散:零 spec 键;新增的只有三个错误码(有具名读者:拒收信封)和一条 census 处置;联邦 SQL、托管库不动。代价:spec 子卡要走 spec 车道和 Fable 复核,而 spec 席目前停派(2026-10-25 起)。

    Options (one ruling, three parts)

    Workload: A ≈ M (objectql) + S (spec codes); B ≈ M; C ≈ S.
    State: pm:dispatched → needs-user-decision in the same act; the card thread waits and does not hold a slot. Prior rulings read: 6471382887, 6472620372 (ruling 2), the 17.0 tombstones.

  7. objectstack-fleet commented on Oct 29, 2026

    @objectstack-fleet
    ContributorAuthor

    director#1 · os-bill · session_01Utp6CbQLcy8yg6vhJVXXi8

    Brief-claimed-by: session_01Utp6CbQLcy8yg6vhJVXXi8 · batch #427 · 2026-10-29T02:35Z

    Director seat, summon #54 (ledger objectstack#12708, opening mark 6485456694). Presented to the maintainer in batch #427 as item 1 of 1 — the epic PM's decision request 6485414780 on dev report 6485390687 (needs_decision, no PR, premise corrected).

    Seat increment (re-read at origin/main eef83734, 2026-10-29T02:35Z):

    • Premises re-run, all hold: queryFilters / querySorting / queryPagination are retiredKey tombstones whose prescription reads "find() receives the full QueryAST … and MUST execute all of it — the 'ObjectQL will filter in memory' fallback … was never built" (driver.zod.ts L336–345); BUILTIN_DRIVER_IDS is derived from DRIVER_VOCABULARY (config-registry.zod.ts L267); the ledger carries ERR_DATASOURCE_UNAVAILABLE (L787, L1094) and EXTERNAL_WRITE_FORBIDDEN (L1564) and none of EXTERNAL_SOURCE_UNAVAILABLE / EXTERNAL_WRITE_REFUSED / a row-cap code (0 hits under packages/); @objectstack/objectql has no dependency on @objectstack/driver-memory; applyDriverQueryInMemory 0 hits; the showcase ships an ADR-0015 federated datasource (showcase-external.datasource.ts: driver: 'sqlite', schemaMode: 'external') that a literal reading of the ruling would cap at 1,000 rows.
    • Prior rulings: 6471382887 (standing rule "a driver cannot opt out of correctness — whatever its supports does not claim, the engine applies in memory and refuses above the row cap") and ruling 2 of 6472620372 (the cap is a fixed engine constant of 1000 in P1, Metadata-protocol: none). The ruling's premise — that supports carries pushdown claims — is false on main since spec 17.0.0, so the ruling needs the execution parameter the epic PM asks for; nothing in it is overturned. check-prior-rulings.mjs --card 27277 names ADR-0062 D1 / D8, ADR-0119 D1, ADR-0139 D3, ADR-0019 D4 — term matches. The epic PM's note that the spec seat is paused since 2026-10-25 is NOT verified by this seat: the domain:spec seat post ([PM seat] domain:spec · seat 1 — ⏳ vacant #6017) reads 🟢 os-support-ai in its title.
    • Facets on the three parts (seat's reading; the epic PM's analysis 6485414780 stands as the card-level analysis):
      • Q1 (who gets the floor): ① the tombstone prescription is the contract for the built-in drivers and their conformance suites pin it; the ruling's sentence was written about plugin drivers (the card title says so). Flooring only drivers whose id is outside BUILTIN_DRIVER_IDS applies the ruling to its subject and leaves ADR-0015 federation and managed sources byte-for-byte unchanged. ③ an AI-written plugin driver that ignores where cannot return wrong rows: the engine re-applies the query. ④ zero spec change. A new capability bit (the epic PM's Q1-C) re-opens the retired bits and is a contract change — P2's driver-odata is the first driver that would declare pushdown, so that is where it belongs if ever.
      • Q2 (codes): ③ decides — a refusal without a ledgered code is either a 500 (UNCLASSIFIED_FAULT, silent about the cause) or a 503 that withholds the remote's message by design; the card asks for the source message to be carried, and only a 4xx ledgered code does that. ④ three codes with a named reader (the refusal envelope) are not a gate and not an authorable key.
      • Q3 (evaluator): meta-rule ② of the decision framework — one operation, two implementations is the failure the framework names; a second filter dialect inside objectql would drift from the drivers. Reusing driver-memory's matcher gives parity by construction; the cost is an engine → memory-driver runtime dependency, which check:driver-memory-census requires a written maintainer disposition for. A shared matcher package (the epic PM's Q3-C) is the two-years-later shape and is the follow-up when a second consumer appears, not the P1 step.
    • Seat's letter: A as the epic PM frames it — Q1 = floor only non-built-in (plugin) drivers; Q2 = add EXTERNAL_SOURCE_UNAVAILABLE, EXTERNAL_WRITE_REFUSED and a row-cap code through one spec child card in this subtree (Fable-tier review; objectql: virtual-object correctness floor — applyDriverQueryInMemory helper, per-datasource row cap with a loud refusal, supports honoured for plugin drivers, refusal-envelope mapping for driver failures (epic #27261 P1) #27277 Blocked-by: it); Q3 = reuse driver-memory's matcher, with the census disposition recorded in the same PR — the maintainer's letter is that disposition. Not B (the cap cannot refuse loudly in P1, and a silent cap is the exact failure the floor exists to prevent). Not C (drops the ruling's floor; an AI-written plugin driver's wrong rows go undetected).
    • Workload of A: domain:server M (objectql, the driver-boundary wrapper covering all 13 direct driver read call sites) + domain:spec S (three ledger codes, Tier by face: packages/spec/src/** ⇒ CONTRACT_REVIEW_TIER review). B ≈ M; C ≈ S.
    • After the ruling: the epic PM files the spec child, writes Blocked-by: on objectql: virtual-object correctness floor — applyDriverQueryInMemory helper, per-datasource row cap with a loud refusal, supports honoured for plugin drivers, refusal-envelope mapping for driver failures (epic #27261 P1) #27277, and re-dispatches the card thread on the same branch once the codes land (or in parallel if the maintainer lets the engine PR carry a temporary pin against the child's codes — the epic PM's call under pm:epic). needs-user-decision → pm:dispatched/pm:blocked is the epic PM's stroke, not this seat's: the card carries its assignee and a live card thread.

    Prior rulings read: supports, pushdown, row cap, in memory, driver-memory, error ledger → 6471382887, 6472620372 ruling 2, driver.zod.ts tombstones (spec 17.0.0 / ADR-0049); thread: 6485414780; repo: objectstack-ai/objectstack

  8. objectstack-fleet commented on Oct 29, 2026

    @objectstack-fleet
    ContributorAuthor

    epic:#27261#1 · marchtian · session_017ihL2c5z3q4h8inrn5kwqV
    Ruling recorded · claim amendment · 2026-10-29T04:00Z

    Maintainer ruling on decision request 6485414780 (dev report 6485390687). 田騏, 2026-10-29T03:56Z, in the epic's Fable analysis thread of this project: 「同意你的决策,继续派发开发」. The scope messages in the same thread: 「内存驱动不是短期的重点,可以给你参考」 and 「本体这个需求暂时也可以不考虑单库多租户」. The draft accepted is §7 of that thread's analysis. It differs from both letters above (6485414780 option A, 6486327362) on Q2 and Q3.

    Q1 = B. Who gets the floor. The in-memory floor and the 1000-row cap apply only to a datasource whose driver id is not built in (resolveDriverId ∉ BUILTIN_DRIVER_IDS) and that holds no verified-pushdown credential. In P1 the credential set is empty. Built-in drivers are unchanged, including ADR-0015 federated SQL (showcase_external), and so are managed datasources.

    • The floor wraps once at the driver boundary. The wrapper goes into the drivers map, so the engine's === identity checks see one instance.
    • A read sends where + orderBy + limit 1001 and no offset/fields. More than 1000 rows returned is a refusal.
    • count and findOne go through the same floored read.
    • aggregate is hidden, so the engine's existing in-memory aggregation runs. Writes pass through.
    • No capability bit is added.
    • registerDatasourceDef stores the driver id.

    Q2 = A′. Error codes. Two new codes go under the @objectstack/objectql ledger entry, with the status carried on the error:

    • EXTERNAL_WRITE_REFUSED (422) carries the remote's message.
    • EXTERNAL_ROW_CAP_EXCEEDED (422) carries the prescription "narrow the filter or mirror via DataSync".

    An unreachable remote, a 5xx or a read failure reuses ERR_DATASOURCE_UNAVAILABLE (503), widening its kind if needed. EXTERNAL_SOURCE_UNAVAILABLE is dropped.

    The ledger rows ride in this card's PR as their own commit, with a full Fable review before merge. There is no spec child card and no Blocked-by:.

    Q3 = A′. Evaluator. There is no @objectstack/driver-memory dependency; the memory driver is reference only, so no census disposition is owed. objectql's matchesHaving walker is lifted to the driver-where position, and $like/$ilike, orderBy, offset/limit and fields are added. The oracle is the spec conformance case tables, not the memory driver. A divergence found against the memory driver becomes a finding card, not a fix here.

    Out of P1: shared-database multi-tenancy on external objects. Epic P3, done only when pulled: a verified-pushdown credential, count reuse of the candidate set, and driver-memory converging on the engine evaluator.

    Claim amendment (claim 6485327899):

    Carried to #27278 (guide PR #27349):

    State: needs-user-decision → pm:dispatched in the same act.

  9. objectstack-fleet commented on Oct 29, 2026

    @objectstack-fleet
    ContributorAuthor

    epic:#27261#1 · marchtian · session_017ihL2c5z3q4h8inrn5kwqV
    Claim amendment (file surface, docs) · 2026-10-29T04:01Z

    This corrects the "Carried to #27278" block of 6487532394. The three guide passages the ruling names describe behaviour this card ships, not behaviour on main:

    • owner/sharing columns absent means fail-closed, map an owner through columnMap or set sharingModel;
    • the driver's three-step progression (① return everything, which is correct; ② push where/orderBy down, which is faster and still re-checked; ③ the conformance kit, P3);
    • what the 1000-row cap means, plugin drivers only.

    The guide on PR #27349 is correct for main today: it calls the floor and the driver-failure refusal "planned". So the passages land in this card's PR, which also turns those two "planned" sentences into the shipped behaviour.

    File surface adds content/docs/data-modeling/virtual-objects.mdx. Serial constraint: edit it only after PR #27349 has merged, rebasing onto it. If #27349 has not merged when the code is ready, the docs commit waits for it.

  10. objectstack-fleet commented on Oct 29, 2026

    @objectstack-fleet
    ContributorAuthor

    epic:#27261#1 · marchtian · session_017ihL2dgsoHnkCiSyXF8isz
    os-dev-report

    {
      "issue": 27277,
      "status": "done",
      "branch": "claude/project-thread-mlrxgx",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/27403",
      "session": "session_017ihL2dgsoHnkCiSyXF8isz",
      "premise_still_valid": true,
      "summary": "Per ruling Q1=B/Q2=A'/Q3=A': a datasource with schemaMode not managed and a driver id resolveDriverId does not know is served through a floored driver (external-driver-floor.ts), stored once in the engine drivers map from both registerDriver and registerDatasourceDef (DatasourceDef gains driver). The driver is sent where+orderBy+limit 1001; the engine applies where/orderBy/offset/limit/fields via applyDriverQueryInMemory (the having walker lifted to a where position with $like/$ilike), findOne/count use the same read, aggregate is hidden, writes pass through. >1000 rows => EXTERNAL_ROW_CAP_EXCEEDED 422; remote 4xx on a write => EXTERNAL_WRITE_REFUSED 422 (remote message, details.remoteStatus); other remote failures => ERR_DATASOURCE_UNAVAILABLE 503 reason remote, cause logged at warn. Spec ledger rows + regenerated reference docs in a separate commit (ApiErrorSchema.code gains both members; api-surface unchanged).",
      "tests": "New: driver-query-in-memory.conformance.test.ts 106/106 (FILTER_LOGIC, FILTER_TEXT incl. every rejection row, PAGINATION ordered/unordered/zero-limit, TEMPORAL lowered + tokens at TEMPORAL_NOW); external-driver-floor.test.ts 17/17 through the real engine with a push-nothing driver (which datasources, both registration orders, managed unwrap, one-instance pin getDriverByName===datasource()===getDriver(object), subset/sent-query, findOne, count, aggregate in memory, 1000 ok / 1001 refused on find/findOne/count, read throw 503 without cause, 4xx write 422, 502 write 503); driver-where-like.test.ts 6/6. Suites under the verify lock: objectql 482 files/9071 tests; rest 334 files; service-datasource 58 files; runtime 471 files/7668 tests; spec ledger+contract+alias pins 93/93. typecheck (tsc + check:test-typecheck) green for objectql and runtime. P0 spike re-pinned 12/12.",
      "gates": "dispatch-gates --commands on head 9f12119993 derives 110 families; all 110 run with exit codes captured before any pipe; --ran: 110 derived, 110 run, 0 NOT-MEASURED, 0 UNRUN (DERIVED zero). First-pass reds fixed in this PR: check:docs (generated references re-recorded), check:query-options-erasure (test as-any casts removed, 244->236). Exit-3 prerequisites re-measured green: check-plugin-teardown-shape --self-test (fetched pinned fixture), check:dual-build-cjs-loads (after closure build). CI check-runs: pending at report time.",
      "dogfood_battery": "title carries '!': run under the verify lock on the commit before 9f12119993 (which changed only the new test's casts and generated reference docs) · VERDICT command-exit 0 · Test Files 348 passed + 3 skipped / 351 · Tests 2806 passed + 20 skipped / 2826 · no failures",
      "line_budget": "1306 changed lines (+1282/-24) before the generated docs; under the 3000 human-merge threshold",
      "files_changed": [
        ".changeset/27277-plugin-driver-floor.md",
        "content/docs/references/api/automation-api.mdx",
        "content/docs/references/api/contract.mdx",
        "content/docs/references/api/error-code-ledger.mdx",
        "packages/objectql/src/core.ts",
        "packages/objectql/src/driver-connect-errors.ts",
        "packages/objectql/src/driver-query-in-memory.conformance.test.ts",
        "packages/objectql/src/driver-query-in-memory.ts",
        "packages/objectql/src/driver-where-like.test.ts",
        "packages/objectql/src/engine.ts",
        "packages/objectql/src/external-driver-errors.ts",
        "packages/objectql/src/external-driver-floor.test.ts",
        "packages/objectql/src/external-driver-floor.ts",
        "packages/objectql/src/having-filter.ts",
        "packages/objectql/src/index.ts",
        "packages/runtime/src/plugin-driver-external-object.spike.test.ts",
        "packages/spec/src/api/error-code-ledger.zod.ts"
      ],
      "deviations": [
        "packages/runtime/src/plugin-driver-external-object.spike.test.ts is outside the claimed surface: the P0 spike pinned the pre-floor calls (find limit 50, count, findOne), so it is re-pinned to what the remote now sees (find with limit 1001; findOne/count answered from find; floored instance unwraps to the driver; a start_date tie on the paged list breaks by id).",
        "content/docs/references/api/{contract,automation-api,error-code-ledger}.mdx are generated records check:docs requires for the ledger change (ApiErrorSchema.code gains both members); committed with the spec commit.",
        "Docs passages in content/docs/data-modeling/virtual-objects.mdx not included: #27349 is open (not merged) at report time; the PR went out without them as the claim amendment says.",
        "The changeset declares BREAKING (accept-set narrowing: a plugin-driver read matching >1000 rows is now 422) with adr-0087 not-required (no-migration-prescription), so the title carries '!' and the full dogfood battery ran.",
        "Spec commit owes the epic PM's full Fable review; no Fable review was run in this thread."
      ],
      "mcp_calls": "reads only: mcp__github__issue_read (card #27277, before context compaction) and mcp__github__pull_request_read get #27349; no write tools",
      "api_writes": "relay (fleet-write dispatch as objectstack-fleet[bot]): pr_create #27403 (9950 bytes read back identical, assignee marchtian); this os-dev-report comment via post-stamped; plus git push of the branch. No assignee/label writes on the card.",
      "open_questions": [
        {
          "question": "Predicate writes (updateMany/deleteMany) pass where through to the plugin driver unfloored; a driver that ignores where over-writes. Floor them?",
          "options": [
            "A: on floored datasources, read matching ids through the floor (capped) and write by id",
            "B: refuse predicate writes on floored datasources (multi write rejected as when the method is absent)",
            "C: leave as is, document"
          ],
          "recommendation": "B for P1 (fail-closed, one line in the floor), A when the epic needs bulk writes on external objects"
        },
        {
          "question": "A ReadableIds leaf (controlled-by-parent read scope) is refused through the floor (ADR-0139 D3). Accept that external objects cannot be controlled-by-parent in P1?",
          "options": [
            "A: accept and document in virtual-objects.mdx",
            "B: resolve the leaf to an id list in the engine before the floor (capped)"
          ],
          "recommendation": "A now; B as a follow-up card only if a scenario needs it"
        }
      ],
      "out_of_scope_findings": []
    }

    Generated by Claude Code

  11. objectstack-fleet commented on Oct 29, 2026

    @objectstack-fleet
    ContributorAuthor

    epic:#27261#1 · marchtian · session_017ihL2dgsoHnkCiSyXF8isz
    os-dev-report

    {
      "issue": 27277,
      "status": "done",
      "round": "rework 1 (seat record 6488877466) + docs item 6 (relay 06:05Z, #27332 measurements)",
      "branch": "claude/project-thread-mlrxgx",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/27403",
      "head": "7561b148",
      "session": "session_017ihL2dgsoHnkCiSyXF8isz",
      "premise_still_valid": true,
      "summary": "Commit 3d06ce76 fixes items 1-5. (1) registerDatasourceDef without `driver` keeps the stored driver id, so the floor survives the connection service's driver-less re-registration. (2) Only 400/404/409/412/422 map to EXTERNAL_WRITE_REFUSED (exported as EXTERNAL_WRITE_REFUSED_STATUSES); 401/403/407/429 and everything else map to ERR_DATASOURCE_UNAVAILABLE 503 with the cause logged. (3) updateMany/deleteMany are hidden on the floored Proxy (get undefined, has false) like aggregate, so updateByIdExpecting takes driver.update by id and multi-row predicate writes get the engine's existing refusal; ExternalWriteOperation loses the two members. (4) compareRows compares as epoch ms when either side is a Date; a non-date string stays as written. (5) Docs nits. Commit 7561b148 (item 6): the sharing callout now says an explicit sharingModel alone is not a way out; the ways out are public_read_write with beforeUpdate, or a declared owner field the driver returns (columnMap applies only on built-in SQL drivers); member deletes stay refused on public_read_write unless the driver returns created_by or a policy or share grants delete; links #27332. The changeset follows.",
      "tests": "external-driver-floor.test.ts 31/31: + re-registration sequence (def with driver, driver, def without driver ⇒ still floored and the where-ignoring driver answers only matches); 400/404/409/412/422 ⇒ 422; 401/403/407/429 ⇒ 503 with 'sk_live_123' absent from the message; updateMany/deleteMany absent ('in' false, get undefined, inner still has them); updateByIdExpecting ⇒ inner update by id, not updateMany; multi update/delete refused with no driver write. driver-query-in-memory.mixed-temporal.test.ts 3/3 (asc, desc, paged). Under the verify lock: objectql typecheck (tsc + check:test-typecheck) exit 0; objectql 483 files / 9088 tests; service-datasource 58/984; runtime spike 12/12.",
      "gates": "dispatch-gates --commands derives 110 families (same set on 3d06ce76 and 7561b148). All 110 ran on 3d06ce76, exit 0. The 34 docs-family commands re-ran on 7561b148 after the callout edit, exit 0. --ran on the merged record: 110 derived, 110 run, 0 NOT-MEASURED, 0 UNRUN (DERIVED zero). CI on 3d06ce76: every check-run success or skipped, including TypeScript Type Check, Test Core 1-8/8, Lint & Repo Gates, Check Changeset, PR-body guards, Governed Surface Queue Guard and Build Docs. CI on 7561b148 was pending at report time.",
      "dogfood_battery": "3d06ce76 (code-identical to 7561b148, which adds docs only) · os-verify-lock VERDICT command-exit 0 · Test Files 348 passed + 3 skipped / 351 · Tests 2806 passed + 20 skipped / 2826 · no failures",
      "files_changed_this_round": [
        ".changeset/27277-plugin-driver-floor.md",
        "content/docs/data-modeling/virtual-objects.mdx",
        "packages/objectql/src/driver-query-in-memory.mixed-temporal.test.ts",
        "packages/objectql/src/driver-query-in-memory.ts",
        "packages/objectql/src/engine.ts",
        "packages/objectql/src/external-driver-errors.ts",
        "packages/objectql/src/external-driver-floor.test.ts",
        "packages/objectql/src/external-driver-floor.ts",
        "packages/objectql/src/index.ts"
      ],
      "deviations": [
        "Multi-row predicate writes on a floored datasource get the engine's existing generic refusal text, 'Update/Delete requires an ID or options.multi=true', which reads oddly when multi was true. It is the documented no-updateMany path and is unchanged here; the tests pin the substring 'options.multi=true'. A dedicated message would be an engine-dispatch change outside this card.",
        "Docs item 6 came from the epic PM relay (not the seat comment) and is a separate docs commit."
      ],
      "mcp_calls": "reads only this round: mcp__github__pull_request_read (get #27349, get_comments #27403, get_check_runs #27403), mcp__github__get_job_logs (job 123280224903, a lane cancelled by the force-push on superseded head 9f121199)",
      "api_writes": "git push x2 (3d06ce76, 7561b148; fast-forward) and this os-dev-report comment via post-stamped. No card assignee or label writes.",
      "open_questions": [],
      "out_of_scope_findings": []
    }

    Generated by Claude Code

  12. objectstack-fleet commented on Oct 29, 2026

    @objectstack-fleet
    ContributorAuthor

    epic:#27261#1 · marchtian · session_017ihL2c5z3q4h8inrn5kwqV
    Landed · 2026-10-29T07:39Z

    Reading Value
    PR #27403 merged 2026-10-29T07:38Z as 86edcaf87b16ed91038d33cd54b3759e8e07faad (merge queue), head 490c3c33
    Card closed completed by Fixes #27277

    State: pm:dispatched removed in the same act. Landed by the epic PM after seat ACCEPT 6490487900, following two REWORK rounds and an independent full Fable review that covered the spec commit. The maintainer's order (田騏, 2026-10-29T04:19Z): 「你应该使用 pm-dispatch 技能处理 issue 和 pr 落地」. The follow-ups named in the ACCEPT are filed as their own cards in the epic subtree.

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

Metadata

Metadata

Assignees

Labels

area:recordsBusiness objects, records, the views that show data, usable forms, searchdomain:serverDomain lane — seat card indexed by label:pm:seatenhancementNew feature or requestpm:epicpriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions