You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
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
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
Config (the driver's own config contract): serviceRoot, auth through external.credentialsRef until D7 lands (bearer / basic), page size, request timeout.
Reads: find/findOne/count on the entity set named by object.external.remoteName; key from $metadata. Push down $filter (comparison, and/or/not, in, contains/startswith/endswith), $orderby, $top/$skip, $count=true, $select; follow @odata.nextLink. supports claims only what is translated; anything else is left to the floor.
Writes (gated by ADR-0015's allowWrites + writable): POST / PATCH / DELETE by key, If-Match with the row's @odata.etag when present; a remote 4xx surfaces through the floor's EXTERNAL_WRITE_REFUSED mapping.
A dialect seam (v4 now): response envelope, $metadata reader, filter-function and literal spellings and the count option go through one dialect object, so a v2 dialect is a new file, not a rewrite.
Tests: a recorded-fixture conformance suite against a public OData v4 sample service shape (TripPin-style $metadata + responses), pushdown translation tables, floor engagement (an untranslatable predicate is applied in memory), write refusal mapping.
Clause-②: decided by the dev (a new package; no spec key is planned) · Metadata-protocol: none unless the config contract lands in spec.
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.
{
"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"
]
}
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.
Activity
objectstack-fleet commentedon Oct 29, 2026
epic:#27261#1 · marchtian · session_017ihL2c5z3q4h8inrn5kwqV
Claim: PM loop round 8 2026-10-29T22:10Z
Session:
session_017ihL2c5z3q4h8inrn5kwqV(epic PM); implementing card threadsession_017ihL2gRrVctbMWaHbABDchAccount:
marchtianBranch:
claude/project-thread-wj01ct(the card thread's platform-assigned branch — measured platform exception toclaude/issue-<n>-<slug>)Worktree:
objectstack-issue-27741Domain:
domain:serverSeat:
epic:#27261#1(epic PM; nodomain:*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 publishesdriver.odata, workspace wiring the new package needs (pnpm-workspace.yaml/ root configs / lockfile),.changeset/**(stop on breach, including anypackages/spec/src/**orDRIVER_VOCABULARYedit; explain in the report)Container & model:
L,mode:thread,model: default tier (dispatch-gates --tier: no path-derived mandate),effort: highClause-②: 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/mainno OData driver exists (packages/drivers: memory, mongodb, sql, sqlite-wasm, turso), andneedsExternalDriverFloorfloors 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
credentialsRefauth only here.objectstack-fleet commentedon Oct 29, 2026
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" ] }objectstack-fleet commentedon Oct 30, 2026
epic:#27261#1 · marchtian · session_017ihL2c5z3q4h8inrn5kwqV
Landed · 2026-10-30T01:33Z
PR #27796 merged through the queue as
0a4b0fb223on 2026-10-30T01:32:02Z (head7f91132b; seat ACCEPT 6512051940, the review of record for a no-contract-face PR; APPROVED byos-zhuangpast the 3000-line gate).@objectstack/driver-odatais onmain: a declarative OData v4 driver published as the plugin driver iddriver.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 intoODataTransport.authorization()'s source; #27745 ($metadataimport) and #27747 (guide) are unblocked and dispatched next.