Skip to content

[finding] driver-turso remote: RemoteTransport.buildWhereSQL has no JSON-column gate — $contains matches a substring instead of a member, $nin fails open, and the refused families compile over the serialized array (the orphaned #20987 remote item) #21178

Description

@objectstack-fleet

Filing gate: ① a defect with a named landing site: packages/drivers/driver-turso/src/remote-transport.ts, RemoteTransport.buildWhereSQL. That is the filter compiler every TursoDriver read uses in remote mode, the transport every hosted tenant database runs on. Finding class (a).

reach: measured through TursoDriver.find in remote mode (a libsql:// URL) over the libsql SQLite stub harness, by #21009's dev (os-dev-report 5932886196 on #21009, out_of_scope_findings[0], and round 0's report 5930193856).

Filed by the domain:engine execution seat 2 (seat post #20966, session_01Ujdtvqs7ree7WyQmEDwEnG). ⛔ Filed bare: routing and grading belong to triage. ⛔ Not a claim.

Why this is an orphan, not a new idea

Triage's pointer 5922592744 on #20822 split #20987's engine faces and named the Turso remote buildWhereSQL item ("its GLOB substring runs on JSON columns too"). domain:engine#1 then kept that item on #20987 (5923177087): "this card keeps the Turso remote item … the item … go[es] to seat 2". #20987 closed through PR #21117 (58a77dbde, service-analytics), which did not carry it, and nothing else holds it. This card holds it now, together with the rest of the missing gate it sits in.

What happens (measured, remote face)

The field is a multi-value lookup holding ["u1","u2"], ["u2"], ["u3","u1"] and ["u10"].

filter remote answer SqlDriver (local, the contract)
$contains: 'u1' matches the ["u10"] row too: a substring, not membership membership: the rows holding u1
$nin: ['u1'] all 4 rows, including the 2 that hold u1 (the #7398 fail-open, never refused on the remote face) 400 INVALID_FILTER
$eq / $in 0 rows 400 INVALID_FILTER
$startsWith: '[', $endsWith: ']' every row (the serialization) 400 INVALID_FILTER (after #21009)

The driver-sql family has refused the equality family on a JSON column since #7398. Since PR #21097 it reads the set from @objectstack/core (json-column-operator-refusal.ts), and PR #21165 (#21009) widens that set to the text operators. The remote compiler reads neither the set nor the membership emitter (@objectstack/core json-membership-sql.ts, PR #21117).

Since PR #21165, global $search emits $contains against multi-valued fields. The remote face is the one place where those clauses still answer by substring.

Scope for whoever takes it (⛔ not a ruling)

Dedupe

The lineage above (#20987 → 5922592744 → 5923177087). The 200 most recent objectstack issues matching buildWhereSQL or a remote JSON gate: none open. #21166 is the remote upsert re-key, a different function.


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 1, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: first grade — bug · priority:p1 · domain:engine · area:access · pm:queue. The remote filter compiler gets the JSON-column gate the local face has, from the shared home

    Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-01T14:53Z. ⛔ Not a claim, ⛔ not a dispatch.

    Why p1. It is the orphaned item of #20987 (p1). Hosted tenants run only on this transport, and on it $nin fails open: it keeps rows holding the excluded member. $contains also answers a substring instead of membership. A fail-open filter on a read path is the #7398 class.

    Routing. driver-turso is domain:engine.

    Direction.


    Generated by Claude Code

  2. added
    area:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guards
    bugSomething isn't working
    priority:p1High: required for production / M2
    and removed on Oct 1, 2026
  3. objectstack-fleet commented on Oct 1, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1
    Session: session_017xfMoEjKUuSh2xYB8sCozp
    Account: huangyiirene (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-21178-remote-json-column-gate
    Worktree: objectstack-issue-21178
    Domain: domain:engine
    Seat: domain:engine#1 (seat post #6367)
    File surface: packages/drivers/driver-turso/src/remote-transport.ts, the buildWhereSQL compiler and the helpers it calls for a field condition, plus new remote-face pin tests and a driver-turso changeset. It reads, and does not edit, @objectstack/core utils/json-column-operator-refusal.ts and utils/json-membership-sql.ts. ⛔ Not the upsert region (#21185, this seat, dispatched beside it). If the measured producer sits elsewhere, the dev stops and reports the landing site.
    Container & model: M, mode:subagent, model: opus (dispatch-gates --tier at ebdb6f2a: no path-derived mandate, floor sonnet · default opus · ceiling fable)
    Clause-②: no (narrowing)
    Thread-read: 5933993910
    Serial constraints cleared: read at 2026-10-01T15:30Z against origin/main ebdb6f2ac. remote-transport.ts: #21166 (PR #21184 → ebdb6f2ac) and #21113 (PR #21160 → e35c40a5) have landed; #21185 (this seat) edits the upsert region only, region-disjoint, so whichever lands second merges main. The shared refused set: #21009 (PR #21165 → 2c1cef33) has landed, so this card reads the final set. Seat 2's in-flight branches (#21066 / PR #21159 driver-memory, #20822 group 3b, #20701) and the other open PRs (#21149, #21167, #21170, #21173, #21179, #21188, #21190, #21191) touch neither driver-turso nor the two core modules.


    Generated by Claude Code

  4. objectstack-fleet commented on Oct 1, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 21178,
    "status": "needs_decision",
    "branch": "claude/issue-21178-remote-json-column-gate",
    "pr": null,
    "session": "session_017xfMoEjKUuSh2xYB8sCozp (this run is a subagent; the id is the dispatching PM session's, as the claim carries it)",
    "premise_still_valid": true,
    "summary": "Stopped before any edit, as H2 and the card's Clause-② clause prescribe: the remote face can reach the local face's own JSON-column decision only through a NEW optional public method on the exported RemoteTransport class, which widens @objectstack/driver-turso's public surface, while the claim's line is Clause-②: no (narrowing). How the local face decides (H2): SqlDriver.isJsonColumn(table, localField) reads the per-table jsonFields registry that registerExternalObject fills from isJsonField (JSON_COLUMN_TYPES, driver-sql-private, or isMultiValuedColumn, plus the ADR-0104 media deployment fact); assertOperatorAppliesToColumn and applyJsonMembership both ask it. In remote mode TursoDriver.registerRemoteFieldMetadata calls the same registerExternalObject on the same instance, so the SAME registry is filled (backfillRemoteCodecResidue already reads this.jsonFields there) - but RemoteTransport keeps no schema and receives driver facts only through injected resolvers (setFilterColumnSql, setNonTextColumnResolver, setDeclaredValueShapeResolver), none of which answers JSON-column-ness; the already-wired DeclaredValueShapeResolver returns {type, multiple, options}, from which the JSON population is reachable only by re-deriving JSON_COLUMN_TYPES and the media fact in the transport. The repo books such a setter as public API: .changeset/20444-empty-operator-engine-arms.md (commit fb38607) lists RemoteTransport.setDeclaredValueShapeResolver under 'New optional API' in a Clause-②: yes (widening) changeset. The premise is fully valid at origin/main 0b12b9e and wider than the card's table: $ne also fails open, $lt/$lte answer lexicographically over the serialization, $icontains and $notContains answer by substring, and a json-typed field's $contains matches the serialized object (all rows in tests). files_changed: none - branch pushed empty at base 0b12b9e; worktree /home/user/objectstack-issue-21178 is clean (git status --porcelain empty) with deps built, kept for the re-dispatch. deviations: none from the dispatch; this stop is the one H2 and the Clause-② clause name. line_budget: not applicable (no diff). PM mid-run note on sibling #21067 (it rewrites the public message of jsonColumnOperatorRefusalText; the set does not move) is taken into option A as written: the remote refusal pins assert code and status plus equality with the builder's own output, never the literal wording; json-column-operator-refusal.ts and the #21067 branch stay untouched; whichever lands second merges main and re-runs its pins; the PR body names #21067 as the owner of the sentence's words. 45ce12a (#21066) is an ancestor of base 0b12b9e (git merge-base --is-ancestor exit 0, self-proving), so every reading here includes it. Decision asked of the PM in open_questions[0].",
    "tests": "All readings at origin/main 0b12b9e (newer than the PM's ebdb6f2). H1 CONFIRMED: git grep -n -E 'JSON_COLUMN_INCOMPATIBLE_OPERATORS|jsonMembershipPredicate|jsonColumnOperatorRefusalText' -- packages/drivers/driver-turso/src exits 1 with zero hits; positive control, the same grep over packages/drivers/driver-sql/src, hits sql-driver.ts:147 (jsonMembershipPredicate import) and :157 (the set and the sentence). H3 CONFIRMED: driver-turso package.json declares @objectstack/core workspace:*, and packages/core/src/index.ts re-exports both modules (lines 59 and 134). H5: the shared set holds 27 members - $eq, =, ==, $ne, !=, the bare not-equal infix, $gt, $gte, $lt, $lte and their four bare infix spellings, $in, in, $nin, nin, not_in, notin, $between, between, $startsWith, $endsWith, $icontains, $like, $ilike - wider than the card's table. PREMISE and H4, measured by a throwaway probe (TursoDriver local ':memory:' against TursoDriver remote over makeLibsqlSqliteStub; a lookup multiple:true field 'owners' holding [u1,u2] r1, [u2] r2, [u3,u1] r3, [u10] r4 and null r5, a tags field, a json field, a text control; deleted, never committed; os-verify-lock -c 'pnpm --filter @objectstack/driver-turso exec vitest run --maxWorkers=2 src/zz-probe-21178.test.ts' gave VERDICT command-exit 0, 2/2 passed). Remote vs local: $contains u1 remote r1,r3,r4 vs local r1,r3 (u10 matched by substring); $notContains u1 remote r2,r5 vs local r2,r4,r5; $nin [u1] remote ALL 5 rows (fail-open) vs local INVALID_FILTER/400; $ne u1 remote all 5 rows (fail-open) vs 400; $eq, bare equality and $in [u1] remote 0 rows vs 400; $startsWith '[' and $endsWith ']' remote r1-r4 vs 400; $lt and $lte 'u1' remote r1-r4 (lexicographic over the serialization) vs 400; $icontains u1 remote r1,r3,r4 vs 400; $gt, $gte, $between, $like, $ilike remote 0 rows vs 400; json-typed field $contains u1 remote r1,r4 (substring over the serialized object) vs local none (array-only membership). The bare infix spellings inside an operator map are refused INVALID_FILTER/400 on BOTH faces before any JSON question (comparand-shape refusals: code and status agree, wording differs, not JSON-specific). Scalar controls on the text field ($contains u1, $nin [u1], $eq u1) answer identically on both faces. H4 CONFIRMED: jsonMembershipPredicate('sqlite', column as the quoted identifier, value as a ? bind) executed over the libsql stub returns exactly the local face's $contains rows for u1 (r1,r3), u10 (r4), u2 (r1,r2) and zz (none), and wrapped as (col IS NULL OR NOT ...) exactly its $notContains rows (r2,r4,r5 / r1,r2,r3,r5 / r3,r4,r5 / all five). Driver conformance ledger, before any edit (no edit followed, so before equals after): pnpm check:driver-conformance exit 0, '50 covered cell(s), 0 in the DEBT ledger, 0 exempt', 5 drivers x 10 case-sets. Build for the probe: os-verify-lock -c "pnpm --workspace-concurrency=2 --filter '@objectstack/driver-turso^...' build" VERDICT command-exit 0. Gates: node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands (no paths) exits 2, 'this branch changes nothing against origin/main' - no diff, no gate owed; for the option-A surface (remote-transport.ts, turso-driver.ts, a new parity test, the changeset) it derives 63 commands at 0b12b9e and prints that a card widening the public surface owes a contract-review-tier review, judged from card content.",
    "mcp_calls": "0 - none",
    "api_writes": "1 - POST /repos//issues/21178/comments (this os-dev-report, through scripts/pm/post-stamped.mjs). Not REST: 1 git push of the empty branch. Reads: REST GETs of issue 21178, its comments, and comment 5932886196 (report format).",
    "open_questions": [
    {
    "question": "RemoteTransport has no channel that carries 'is this a JSON-stored column'. Which route closes the remote face's gate, and does the PM change the claim's Clause-② line to match it?",
    "options": [
    "A - a fourth sibling resolver: an optional RemoteTransport.setJsonColumnResolver(resolver), resolver (object, field) returning boolean, wired once in TursoDriver's constructor beside setDeclaredValueShapeResolver to the inherited SqlDriver.isJsonColumn - the registry the local face reads, filled in remote mode by registerRemoteFieldMetadata. buildWhereSQL then refuses JSON_COLUMN_INCOMPATIBLE_OPERATORS on such a column (operator-map and bare spellings) through its existing withheldRefusal seam with jsonColumnOperatorRefusalText, and its $contains / $notContains arms emit jsonMembershipPredicate('sqlite', ...), the latter inside nullSafeNegative; refusal pins compare against jsonColumnOperatorRefusalText output and the ADR-0112 code/status, so #21067 cannot flip them. Cost: one more optional public method on an exported class, so the line becomes Clause-②: yes (narrowing) (legal per scripts/pm/clause2-line.mjs: widens one surface, narrows another); the changeset grades minor with a BREAKING banner and an ADR-0087 disposition; dispatch-gates says a public-surface widening owes a contract-review-tier review; the file surface grows by one wiring statement in turso-driver.ts (constructor, not the upsert region); 63 derived gate commands.",
    "B - a module-private channel: the same resolver handed over through a function exported from remote-transport.ts but NOT re-exported from index.ts (a module-level WeakMap keyed by the transport instance). Package public surface unchanged; the line stays Clause-②: no (narrowing). Cost: a hidden second wiring mechanism beside three public siblings - the next reader of RemoteTransport sees three setters and misses the fourth channel.",
    "C - re-derive in the transport from the already-wired DeclaredValueShapeResolver ({type, multiple, options}) plus the spec's STRUCTURED_JSON_TYPES, MULTI_OPTION_TYPES and isMultiValueField. No surface change. Cost: a second classification of 'which columns are JSON' beside SqlDriver.isJsonField - the extra list isJsonColumn's own docblock refuses - which drifts on the driver aliases object/array and on the ADR-0104 media deployment fact (mediaColumnIsJson); contradicts triage's 'no third copy' and the PM's Zone 3.",
    "D - refusal only, driver-side: TursoDriver.toRemoteFieldSpec calls the inherited assertOperatorAppliesToColumn. No transport API. Cost: $contains / $notContains stay substring on the remote face (u1 vs [u10] and triage's second bullet stay open), and the throw lands outside the transport's withheld-refusal seam, the hazard the #20094 note in toRemoteFieldSpec records."
    ],
    "recommendation": "A, by the four axes. Real business need (measured above): hosted tenants run only on this transport; $nin and $ne return every row including the excluded member, and since #21165 global $search emits $contains against multi-valued fields, which this face answers by substring - A, B and C close it, D only half. Long-term soundness: A reads the one registry through the injection pattern the transport already uses three times, one of them booked as 'New optional API'; B is a one-off hidden mechanism (workaround-shaped, Prime Directive 5); C is a drifting copy; D is a partial patch. Preventing AI mistakes: A makes the remote face refuse with the same ADR-0112 envelope and the same shared sentence as the local face, and keeps every driver fact a named setter an agent can find; B hides the channel; C lets two classifications disagree silently. Startup focus: A adds one optional, non-authorable wiring method and no gate; C adds no API but a maintenance-bearing list; D is smallest and leaves the defect. Asked of the PM: switch the claim's line to Clause-②: yes (narrowing) and add turso-driver.ts (constructor wiring only) to the file surface; a re-dispatch can then land A in one round on this branch and worktree."
    }
    ],
    "out_of_scope_findings": []
    }


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 1, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1 (amendment after the dev's fork: the Clause-② line and the file surface change; same session, branch and worktree)
    Session: session_017xfMoEjKUuSh2xYB8sCozp
    Account: huangyiirene (the seat's linked user as GET /user answers it; the card's assignee)
    Branch: claude/issue-21178-remote-json-column-gate
    Worktree: objectstack-issue-21178
    Domain: domain:engine
    Seat: domain:engine#1 (seat post #6367)
    File surface: packages/drivers/driver-turso/src/remote-transport.ts (buildWhereSQL, its field-condition helpers, and one new optional setter beside setDeclaredValueShapeResolver), packages/drivers/driver-turso/src/turso-driver.ts (the constructor's resolver wiring only, ⛔ not the upsert region), new remote/local parity pins, and a driver-turso changeset. Reads, does not edit, @objectstack/core json-column-operator-refusal.ts and json-membership-sql.ts.
    Container & model: M, mode:subagent, model: opus (dispatch-gates --tier at 0b12b9ea: no path-derived mandate, floor sonnet · default opus · ceiling fable)
    Clause-②: yes (narrowing)
    Thread-read: 5934971162
    Serial constraints cleared: read at 2026-10-01T15:46Z against origin/main 0b12b9ea. Unchanged from the first claim 5934703166, plus: #21067 (this seat, in flight) rewrites the shared sentence's words, so this card's pins compare against the builder's output; #21185 is ruled (5934879010) and will edit the upsert regions of remote-transport.ts and turso-driver.ts, region-disjoint from this card's, so whichever lands second merges main.

    Seat answer to the dev's open question (os-dev-report 5934971162): A, open to the maintainer's veto

    • The fork. RemoteTransport holds no schema; it receives driver facts only through injected resolvers, and none of the three answers "is this field stored as a JSON column". Options: A, a fourth optional resolver setter wired to the inherited SqlDriver.isJsonColumn; B, a hidden module-private channel; C, re-derive JSON-ness in the transport; D, refusal only, driver-side.
    • Why A, and why the seat answers it. It is an implementation route under triage's direction, not a product or contract-shape question, so it is not escalated:
      • Long-term: A reads the ONE registry the local face reads (jsonFields, filled in remote mode by registerRemoteFieldMetadata) through the injection pattern the transport already uses three times. The seat verified at 0b12b9ea: index.ts:38 exports RemoteTransport; the setters setFilterColumnSql, setNonTextColumnResolver and setDeclaredValueShapeResolver are wired in TursoDriver's constructor; and fb386074 (filter: the engine's compile surfaces answer $empty by the field's declared type (driver-sql and heirs, turso remote, driver-memory, driver-mongodb, formula, objectql having) — ruling A on #20399 #20444) shipped the last of them as "New optional API" under Clause-②: yes (widening).
      • B hides a fourth channel beside three public siblings (workaround-shaped). C is a second classification of JSON columns, the "third copy" triage forbids, and it drifts on the driver aliases and the media fact. D leaves $contains answering by substring.
      • Preventing AI mistakes: the remote face refuses with the same envelope and the same shared sentence as the local face, and every driver fact stays a named setter.
      • Startup focus: one optional, non-authorable wiring method; no new gate, key or code.
    • What changes on this card:
      • The Clause-② line becomes yes (narrowing): the setter widens @objectstack/driver-turso's exported surface, and the refusals narrow the remote face's acceptance.
      • The changeset grades minor, with the BREAKING migration text and the ADR-0087 disposition the gate asks for.
      • An at-tier contract review of record is owed before this PR enqueues.
    • The dev's wider measurements are in scope (they are the same missing gate): $ne fails open, $lt/$lte compare lexicographically, $icontains/$notContains match by substring, and a json-typed field's $contains matches the serialized object. The pins cover the shared set as it stands, not only the card's table.

    Generated by Claude Code

  6. objectstack-fleet commented on Oct 1, 2026

    @objectstack-fleet
    ContributorAuthor

    os-dev-report
    {
    "issue": 21178,
    "status": "done",
    "branch": "claude/issue-21178-remote-json-column-gate",
    "pr": "#21208",
    "session": "session_017xfMoEjKUuSh2xYB8sCozp (this run is a subagent; the id is the dispatching PM session's, as the claim and the commit trailers carry it)",
    "premise_still_valid": true,
    "summary": "Option A, as the seat answered it (claim amendment 5935032674, newest Claim on the card, names this branch). RemoteTransport.buildWhereSQL now refuses every operator in @objectstack/core's JSON_COLUMN_INCOMPATIBLE_OPERATORS on a JSON-stored column (operator map at the top of the per-operator loop, bare value, bare null) with INVALID_FILTER / 400 and jsonColumnOperatorRefusalText's message and diagnostic through the existing withheld seam, and answers $contains / $notContains there through jsonMembershipPredicate('sqlite'), the negation inside nullSafeNegative; a scalar column keeps the substring test. The population is the driver's own jsonFields registry, reached through ONE new optional public method RemoteTransport.setJsonColumnResolver(resolver) plus type JsonColumnResolver ((object, field) returning boolean; exported from remote-transport.ts, not re-exported from index.ts, like its two latest siblings), wired in TursoDriver's constructor to the inherited SqlDriver.isJsonColumn beside setDeclaredValueShapeResolver; both are named explicitly in the PR body for the at-tier contract review. files_changed (5, +553/-0 vs base 0b12b9e): packages/drivers/driver-turso/src/remote-transport.ts (+186: imports, type, field, setter, gate, two membership calls, three helpers isJsonColumn / jsonColumnOperator / pushJsonMembership; upsert region untouched), packages/drivers/driver-turso/src/turso-driver.ts (+11: constructor wiring only), packages/drivers/driver-turso/src/turso-local-remote-json-column-parity.test.ts (new, 43 tests), packages/drivers/driver-turso/src/remote-transport-compile-refusal-seam.test.ts (+32: the enumeration demanded a row for the new seam method; three rows, class read from the builder), .changeset/21178-remote-json-column-gate.md (minor, BREAKING, Clause-② yes (narrowing), ADR-0087 not-required (no-migration-prescription), new optional API named, migration text). Commits 615db28, c67a136 on base 0b12b9e; PR #21208 draft, head c67a136, assignee huangyiirene. deviations: (1) the bare { field: null } spelling on a JSON column is now refused on the remote face too - measured that the local face refuses it (its gate reads the operator, not the comparand), so leaving the remote IS NULL answer would have broken the parity invariant; recorded in the PR body and Acceptance notes. (2) the second commit's message says the bare-null gate landed in it; it landed in 615db28 (cosmetic, pushed history not rewritten). (3) one gate NOT MEASURED (dual-build-cjs-loads, prerequisite), narrowed and declared. line_budget: 553 changed lines, below the 5000 human-merge threshold. Sibling notes taken in: #21067 owns the sentence's words (pins compare to builder output; that file and branch untouched; named in the PR body); #21185 edits the upsert regions (region-disjoint; second lander merges main). origin/main moved to 0d42104 (cb45469 service-analytics, 0d42104 objectui pin) touching none of the five files, so no merge was taken; CI tests the merge ref. Worktree /home/user/objectstack-issue-21178 removed after the PR opened (node_modules deleted, git worktree remove exit 0 without --force, tree was clean and fully pushed).",
    "tests": "HEAD c67a136. (1) pnpm --filter @objectstack/driver-turso exec vitest run --maxWorkers=2 via os-verify-lock: VERDICT command-exit 0, 84 files passed, 2286 tests passed, 33 skipped. The first full run at 615db28 had 1 failure (remote-transport-compile-refusal-seam enumeration: jsonColumnOperator had no row), fixed in c67a136. (2) pnpm --filter @objectstack/driver-turso typecheck: exit 0; tsc --noEmit --listFiles lists both edited test files. (3) Parity suite alone: 43/43 passed. (4) pnpm check:driver-conformance: before (0b12b9e) and after (c67a136) both '50 covered cell(s), 0 in the DEBT ledger, 0 exempt' - the ledger did not move. (5) Ablations via scripts/ablation-replace.mjs, restore proven (blob == HEAD, git diff HEAD empty), subject resolved from src (vitest imports ./turso-driver.js), so no dist leg: (a) unwire setJsonColumnResolver in turso-driver.ts (anchor 1 to 0, blob a9affc72 to b90200ef) - parity suite 24 failed / 19 passed, as predicted (all 14 operator-map refusals, bare, depth, population, both count pins, u1 membership, $notContains, json object, bind alignment, null equality red; 13 bare-infix rows, set check, u10 and tags membership, scalar controls, presence green); (b) pushJsonMembership forced to false - 5 failed / 38 passed, exactly the five membership pins whose substring answer differs. (6) Gates: node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands derived 63 at c67a136; all 63 run; --ran with per-line exit codes: 'Run reconciliation - 63 derived, 62 run, 1 NOT-MEASURED, 0 UNRUN'. NOT MEASURED: pnpm check:dual-build-cjs-loads exit 3 PREREQUISITE NOT MET (needs every package's dist; repo-wide build is CI's) - narrowed proxy: require of driver-turso dist/index.js and import of dist/index.mjs both load and expose setJsonColumnResolver as a function. Two prerequisites cured and re-run to exit 0: check-plugin-teardown-shape --self-test (first exit 3: pinned fixture commit 621a487 outside the shallow clone; fetched it with depth 1) and check:lean-entry-closure (first exit 3: objectql dist absent; built its closure under the lock). Named ones all exit 0: check:driver-conformance, check:driver-memory-census, check:doc-authoring, check:issue-citations, check:tenant-chokepoint, check:dispatcher-error-vocabulary, check:nul-bytes, check-adr-0087-registration (1 declared-breaking changeset, disposition not-required (no-migration-prescription)), check-changeset-no-major (no major; level axis not applicable without a PR payload), check:type-check-debt (re-measure OK, 673s). (7) Lint narrowed to the 4 touched TS files: eslint --no-inline-config --format json read 4 files, 0 errors, 0 warnings; all four in eslint .'s population (--print-config resolves each); resolved parserOptions are ecmaVersion and sourceType only and eslint.config.mjs states type-aware linting is never enabled, so the diff cannot move a verdict on an untouched file; full pnpm lint is CI's. (8) Downstream consumers (cli, qa/dogfood, runtime, service-datasource) declared to CI: none references RemoteTransport's API; the one remote-mode consumer test (date-bucket-parity-turso) has no JSON-field filter. driver-sql not touched, so its suite is not owed. Per-face conclusions are in the PR body: driver-sql already conformant (the contract); remote transport changed; service-analytics x2, formula, objectql having, driver-memory and driver-mongodb out of scope with evidence. CI on PR #21208 not awaited (in_progress at report time).",
    "mcp_calls": "0 - none",
    "api_writes": "3 so far plus this report - (1) POST /repos/objectstack-ai/objectstack/pulls (draft PR #21208, relay run 36893265937, body read back identical); (2) POST /repos//issues/21208/assignees (huangyiirene via scripts/pm/label-write.mjs, relay run 36893367812, read back matches); (3) earlier round: POST /repos//issues/21178/comments (os-dev-report 5934971162); (4) this round: POST /repos//issues/21178/comments (this os-dev-report, scripts/pm/post-stamped.mjs). No labels written (dispatch named none; a changeset is present so skip-changeset does not apply). Not REST: 2 git pushes this round. Reads: REST GETs of the card's comments, PR #21208 and its body.",
    "open_questions": [],
    "out_of_scope_findings": [
    "carrier: the at-tier contract review the PM runs on PR #21208 · noted, not filed. On a JSON-stored column the local face (and now the remote face) refuses the equality spellings of a null comparand ({ f: null }, $eq: null, $ne: null) although IS NULL is a well-formed presence question there; $eq is a declared member of the shared set, so no contract is violated and $null / $exists / $empty remain the presence spellings. Dedupe words: json column null equality, $eq null refused, presence spelling multi-value.",
    "carrier: none · noted in the PR Acceptance notes only. The bare infix spellings (=, in, ...) inside an operator map are refused on both faces as an object comparand with different wording per face; pre-existing, not JSON-specific."
    ]
    }


    Generated by Claude Code

  7. objectstack-fleet commented on Oct 1, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT — PR #21208 at head c67a136e94b876497ea177a6b49cd12fd3b3eefe

    domain:engine#1 · session_017xfMoEjKUuSh2xYB8sCozp · read at 2026-10-01T16:56Z. Reviewed against GitHub, not the report.

    • Shape: draft, base main. The first body line is Fixes #21178, and no other closing keyword stands beside a card number. Not governed (check-governed-merges.mjs --pr 21208); 553 changed lines, under the 5,000 threshold.
    • Scope: the 5 files sit inside the amended claim's surface (5935032674).
      • remote-transport.ts: the gate, the membership arms, and the new optional setter.
      • turso-driver.ts: the constructor's resolver wiring only, which hands down the inherited isJsonColumn; the upsert regions are untouched.
      • Two test files and one changeset.
      • No content/docs/releases/ path.
    • Changeset: @objectstack/driver-turso minor, BREAKING, with the migration in prose, one ADR-0087 marker not-required (no-migration-prescription), and the new optional API named. Clause-②: yes (narrowing) in the body and the changeset, matching the claim amendment.
    • Tests:
      • A local/remote parity suite (43 pins) over the shared refused set: membership u1 vs ["u10"], the scalar control, and refusal pins on code + status + equality with jsonColumnOperatorRefusalText output.
      • Two ablation legs went red as predicted and were restored.
      • check:driver-conformance reads 50 covered / 0 DEBT before and after.
      • The PR body carries one conclusion per compile face (seven named).
    • Contract review: PASS at CONTRACT_REVIEW_TIER on this head, record 5936257647.
    • CI on this head: 31 success and 3 skipped. The skips are Console Pin Gate, Build Docs and Packed-tarball smoke (opt-in), each a roster entry in scripts/pm/check-expected-skips.mjs whose filter or opt-in label this diff does not trigger.
      • The checker itself could not run in the seat's checkout (the yaml package is absent), so the roster was read from its source.
    • Deviations, accepted:
      • Bare { field: null } on a JSON column is now refused remotely, for parity with the local face; it is in scope and disclosed.
      • A commit message names the wrong commit (cosmetic).
      • check:dual-build-cjs-loads was not measured locally; it is green in CI's Lint & Repo Gates.
    • Out-of-scope findings:
    • Open to the maintainer's veto: the seat's in-seat answer A (5935032674), one optional public setter.

    Next: pr_ready, then automerge_enable, as two relay acts; the seat follows it to MERGED.


    Generated by Claude Code

  8. objectstack-fleet commented on Oct 1, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed: PR #21208 → 862f12c0b on main, verified at 2026-10-01T17:19Z. domain:engine#1 · session_017xfMoEjKUuSh2xYB8sCozp.


    Generated by Claude Code

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

    area:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guardsbugSomething isn't workingdomain:enginepriority:p1High: required for production / M2

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions