Skip to content

driver-odata: a declarative OData v4 datasource driver (plugin-published id, pushdown for $filter/$orderby/$top/$skip/$count, the engine floor for the rest) — epic #27261 P2 #27741

Closed

Description

@objectstack-fleet

epic:#27261#1 · marchtian · session_017ihL2c5z3q4h8inrn5kwqV
Filing gate: ③ maintainer-directed — 田騏 at 2026-10-29T21:58Z 「现在启动全部 P2」 (epic comment 6509296853, D1 amended), at 2026-10-29T21:59Z 「OData 驱动 只做 v4」 (6509311454), at 2026-10-29T22:01Z 「同意」 (generic P2 parts open source in this repository; vendor-specific drivers such as Salesforce stay out of it).
Ruling-ref: 6486145005 (#27299, Tier B = declarative OData) · 6486136301 (#27298, D7) · 6471382887 (#27261 D1–D6)
Dedupe (open AND closed): "OData datasource driver $metadata import connector credential external datasource" → 5 hits, none this work (#27298 is the ruled decision card).
Reader: the epic PM (#27261) dispatches it in the epic subtree.
type: Feature

维护者速读

  • 做什么: 新包 packages/drivers/driver-odata,一个声明式 OData v4 数据源驱动。管理员声明服务地址和实体集(object.external.remoteName)即可把 SAP、Dynamics 365 等系统的实体作为对象实时读写,不写代码。
  • 为什么: 面向全球客户,OData 是 SAP / Dynamics 365 的标准接口,也是 Salesforce Connect、Dataverse 声明式外部数据的底座。
  • 范围: 只做 v4;代码里留「方言」接缝,将来 v2 另立卡补上。
  • 需要您做的: 无。

Task

Clause-②: decided by the dev (a new package; no spec key is planned) · Metadata-protocol: none unless the config contract lands in spec.

Activity

  1. added
    enhancementNew feature or request
    pm:epicReserved by a dedicated epic PM, parent or sub-issue; other PMs never take it; never with pm:queue
    domain:serverDomain lane — seat card indexed by label:pm:seat
    pm:dispatchedDispatched to a dev agent by /pm-dispatch
    on Oct 29, 2026
  2. objectstack-fleet commented on Oct 29, 2026

    @objectstack-fleet
    ContributorAuthor

    epic:#27261#1 · marchtian · session_017ihL2c5z3q4h8inrn5kwqV
    Claim: PM loop round 8 2026-10-29T22:10Z
    Session: session_017ihL2c5z3q4h8inrn5kwqV (epic PM); implementing card thread session_017ihL2gRrVctbMWaHbABDch
    Account: marchtian
    Branch: claude/project-thread-wj01ct (the card thread's platform-assigned branch — measured platform exception to claude/issue-<n>-<slug>)
    Worktree: objectstack-issue-27741
    Domain: domain:server
    Seat: epic:#27261#1 (epic PM; no domain:* seat). Maintainer order, 田騏 2026-10-29T21:58Z: 「现在启动全部 P2」; 2026-10-29T21:59Z 「OData 驱动 只做 v4」
    File surface: packages/drivers/driver-odata/** (new package), the server plugin that publishes driver.odata, workspace wiring the new package needs (pnpm-workspace.yaml / root configs / lockfile), .changeset/** (stop on breach, including any packages/spec/src/** or DRIVER_VOCABULARY edit; explain in the report)
    Container & model: L, mode:thread, model: default tier (dispatch-gates --tier: no path-derived mandate), effort: high
    Clause-②: no
    Metadata-protocol: none
    Responsibility: epic #27261 P2 (ruling 6486145005, letter B); the engine floor from #27277 is the correctness net it relies on
    Thread-read: none
    Premise: holds — on origin/main no OData driver exists (packages/drivers: memory, mongodb, sql, sqlite-wasm, turso), and needsExternalDriverFloor floors any non-built-in driver id on a non-managed datasource.
    Serial constraints: #27745 and #27747 are Blocked-by this card. #27742 / #27746 (D7) are not; use credentialsRef auth only here.

  3. objectstack-fleet commented on Oct 29, 2026

    @objectstack-fleet
    ContributorAuthor

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

    {
      "issue": 27741,
      "status": "done",
      "branch": "claude/project-thread-wj01ct",
      "pr": "https://github.com/objectstack-ai/objectstack/pull/27796",
      "session": "session_017ihL2gRrVctbMWaHbABDch",
      "premise_still_valid": true,
      "summary": "Premise held on origin/main 81d00a4e: packages/drivers had memory, mongodb, sql, sqlite-wasm, turso and no OData driver; needsExternalDriverFloor floors any non-built-in driver id on a non-managed datasource. New package @objectstack/driver-odata (fixed group, 18.2.0): createODataDriverPlugin publishes ODataDriver as driver.NAME (default driver.odata), a plugin driver id; DRIVER_VOCABULARY and packages/spec/src untouched. Config contract (strict zod, parsed at plugin build): name, serviceRoot (http/https, refused with userinfo or query), version 4.0, auth none|bearer|basic(username), pageSize, requestTimeoutMs; no credential key. Credential = the datasource's external.credentialsRef, read from engine.listDatasourceDefs() and resolved through options.secrets (the host's SecretBinder read half) at the first request, re-resolved once on 401; fails closed on missing ref, missing store, unresolvable handle, undeclared datasource, ref under scheme none. Reads: $filter/$orderby/$select/$top/$skip, @odata.nextLink (same origin only), key appended to $orderby on paged reads, count via $count=true only when the where translated completely. Translator never narrower: $not never pushed, $ne/$nin on nullable carry 'or P eq null', text only eq/in/contains/startswith/endswith/ASCII icontains, unspellable comparands left to the floor. ReadableIds leaves refused by the driver itself (readableIdsDriverRefusal). Writes POST/PATCH/DELETE by key with If-Match from the last read's @odata.etag, declared properties only; upsert/bulk/transactions/execute/dropTable refused by name (400). introspectSchema: entity sets, EDM types, keys, single-valued navigations with bound set as lookup candidates (for #27745). Dialect seam: ODataDialect (headers, envelope, etag, error body, CSDL reader, literals, functions, in-list, count option); only odataV4Dialect ships. Two files outside the claimed surface, both mechanical consequences of a new published package: packages/spec/llms.txt package count 72 to 73 (check:llms-txt red otherwise; spec patch added to the changeset) — not packages/spec/src. D7 (#27746) case: not on main at bcc0a1a3 (git grep of origin/main packages/types/src for a connector-credential seam: 0 hits), so not wired; the auth header comes from one function, ODataTransport.authorization() in transport.ts, fed by the plugin's credential() source in plugin.ts — the later wiring replaces those two.",
      "tests": "On head bcc0a1a3: pnpm --filter @objectstack/driver-odata test = Test Files 6 passed (6), Tests 425 passed (425), exit 0; typecheck exit 0; eslint packages/drivers/driver-odata exit 0. Suites: filter.test.ts (54-row translation table), dialect-v4.test.ts (CSDL reader + envelope), odata-driver.test.ts (recorded TripPin-shaped service: reads, paging, writes, ETag, keys, introspection, credentials), odata-driver.floor.test.ts (real ObjectQL engine + floor, objects bound by engine.syncSchemas: in-memory application of $like, count through floor, 400 read = INVALID_QUERY, 400 write = EXTERNAL_WRITE_REFUSED 422, 503 withheld), plugin.test.ts (service name, config refusals, bearer/basic, 5 fail-closed), odata-driver.conformance.test.ts (all 11 shared case-sets, 326 cases, through engine+floor over an in-process OData service that evaluates the $filter, read sets under ordinal and case-insensitive collation). Ablation (one-shot): nullable $ne disjunction removed in filter.ts = 1 failed / 53 passed (the '$ne on a nullable property' row); restored, blob equals HEAD, git diff HEAD empty; tests import source.",
      "gates": "node scripts/pm/dispatch-gates.mjs --commands (no paths) on merged head bcc0a1a3 derived 77; --ran: '77 derived famil(ies) accounted for — 77 run, 0 NOT-MEASURED (a DERIVED zero — all 77 recorded an exit code and none of them is 3)'. All 77 exit 0. First pass on 02401a71 (72 derived): check:llms-txt exit 1 (count 72 vs 73) and check:driver-conformance exit 1 (11 CONSUMED cells) — both fixed, now 66 covered cells, 0 DEBT, 0 EXEMPT; check-plugin-teardown-shape --self-test and check:dual-build-cjs-loads exit 3 (prerequisites) — fixture commit fetched, dist present on the rerun, both exit 0. CI check-runs on #27796 not yet read (PR just opened).",
      "dogfood_battery": "n/a — no ! in the title and no (narrowing) arm",
      "line_budget": "no line budget in the claim; three-dot diff vs origin/main: 26 files, +3689/-2 = 3691 changed lines, OVER the 3000 human-merge threshold — lands only with an authorized APPROVE (LICENSE copy is 201 lines)",
      "files_changed": [
        "packages/drivers/driver-odata/** (new package: package.json, tsconfig.json, vitest.config.ts, README.md, LICENSE, src/{index,config,dialect,dialect-v4,filter,transport,odata-driver,plugin}.ts, 6 test files, src/__fixtures__/{trippin-metadata.xml,recorded-service.ts,odata-service.ts})",
        ".changeset/27741-driver-odata.md",
        ".changeset/config.json",
        "pnpm-lock.yaml",
        "packages/spec/llms.txt"
      ],
      "mcp_calls": "reads only: issue_read (get #27741, get_comments #27741, get_comments #27666), pull_request_read (get #27648 for the body form). No MCP writes.",
      "api_writes": "1 relay write so far as objectstack-fleet[bot]: fleet-write pr_create, run 40974161877 = #27796 (draft forced, labels [enhancement], no assignees; read-back 12930 bytes sent, 12930 stored, identical). Plus this report via post-stamped --comment=27741. Plus git pushes of the branch (head bcc0a1a3).",
      "open_questions": [
        "Landing needs 田騏's APPROVE on #27796 (3691 lines over the 3000 threshold)."
      ],
      "deviations": [
        "Report comment: the seat signature line comes first and os-dev-report second, as post-stamped requires.",
        "packages/spec/llms.txt edited (two numbers) outside the claimed file surface; it is not packages/spec/src and not DRIVER_VOCABULARY; the edit is forced by check:llms-txt on any new published package. @objectstack/spec added to the changeset for it.",
        "A first gate pass was interrupted by a container restart and resumed from the recorded exit codes; the final --ran reconciliation is over all 77 on bcc0a1a3."
      ],
      "out_of_scope_findings": [
        "carrier: none · noted, not filed — no $batch arm: the bulk verbs are refused rather than looped; a follow-up if a customer needs atomic multi-row writes. Dedupe words: odata $batch bulk write driver-odata"
      ]
    }
  4. objectstack-fleet commented on Oct 30, 2026

    @objectstack-fleet
    ContributorAuthor

    epic:#27261#1 · marchtian · session_017ihL2c5z3q4h8inrn5kwqV
    Landed · 2026-10-30T01:33Z

    PR #27796 merged through the queue as 0a4b0fb223 on 2026-10-30T01:32:02Z (head 7f91132b; seat ACCEPT 6512051940, the review of record for a no-contract-face PR; APPROVED by os-zhuang past the 3000-line gate). @objectstack/driver-odata is on main: a declarative OData v4 driver published as the plugin driver id driver.odata, held behind the engine floor, with a v2 dialect seam. Follow-ons: #27798 (#27744) adds the driver-odata test that runs the conformance kit; #27819 (#27746) wires the D7 connector credential into ODataTransport.authorization()'s source; #27745 ($metadata import) and #27747 (guide) are unblocked and dispatched next.

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

Metadata

Metadata

Assignees

Labels

area:datadomain:serverDomain lane — seat card indexed by label:pm:seatenhancementNew feature or requestpm:epicReserved by a dedicated epic PM, parent or sub-issue; other PMs never take it; never with pm:queuepriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions