Skip to content

feat(drivers): ADR-0139 E3 + E4 — the five drivers compile the composed ReadableIds leaf or refuse it with D3's envelope, with the conformance case-set - #23656

Merged
objectstack-fleet[bot] merged 8 commits into
mainfrom
claude/issue-23639-readable-ids-drivers
Oct 15, 2026
Merged

objectstack-fleet[bot] merged 8 commits into
mainfrom
claude/issue-23639-readable-ids-drivers

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #23639

Clause-②: yes

ADR-0139 plan rows E3 and E4 in one PR, as domain:spec seat 3 ruled for the case-set: "the case-set lands in the same PR as the five driver suites that run it, with zero ledger rows." D3 is the spec. #22788 is the parent card.

What changes

Every driver now either compiles the composed ReadableIds leaf or refuses it. The engine has composed the leaf at its executor boundary since E2.

Driver D3 obligation Where it lands
driver-sql compiles field IN (SELECT id FROM P_TABLE WHERE SCOPE AND TENANT) the $in arm of SqlDriver#applyFilterCondition, plus the new SqlDriver#readableIdsSubquery. The subquery is walled by SqlDriver#applyTenantScope on the parent table, using the outer read's own options.
driver-sqlite-wasm, driver-turso local face inherit that compile only the driver name the refusal prints is overridden (readableIdsDriverName)
driver-turso remote face compiles the same semi-join with its own compiler TursoDriver#toRemoteWhere resolves each leaf into the parent's remote table, its scope (in storage form) and its tenant wall, built by the same chokepoint, remoteTenantScope → applyTenantScope. RemoteTransport's $in arm then compiles "field" IN (SELECT "id" FROM … WHERE (scope) AND (tenant)).
driver-memory implements it in process convertToMongoQuery, the single funnel for find, count, aggregate and distinct, rewrites each composed leaf into the ids of the parent's rows under the scope (readableParentIds) before the shape gate. No tenant wall is owed, because memory refuses every tenant-scoped call.
driver-mongodb refuses translateFilter, the funnel every door and the aggregation $match go through, refuses ahead of the shape gate (which would otherwise answer INVALID_FILTER)

The refusal is one envelope on every driver (ADR-0112): INTERNAL_ERROR / 500, never INVALID_FILTER. A driver refuses:

  • an uncomposed leaf at any position, judged on isReadableIds alone;
  • a composed leaf outside a $in comparand;
  • every leaf, on driver-mongodb.

The message names the driver, the leaf (its path and parent object) and the remedy. It is logged at error once per driver. No DriverCapabilities bit is added.

Landing point outside the claim's file surface, and why: @objectstack/core. The walk, the copy-on-write rewrite and the refusal live in packages/core/src/utils/readable-ids-driver-refusal.ts. Four compilers need them: SQL, Turso remote, memory and mongodb. None of them imports another, but all of them already import @objectstack/core. This follows the precedent of json-column-operator-refusal.ts, shared between the drivers in the same place for the same reason. The alternative was four hand copies of one refusal.

The case-set was carried from the parked e641ab6d8 with git restore --source and no branch checkout. The files are packages/spec/src/data/readable-ids-conformance.ts, its oracle test (readable-ids-conformance.test.ts, 18 tests), the data/index.ts export line and the CASE_SETS row. api-surface/data.json and export-origins/data.json were regenerated with check:generated --fix, which proved those two (and only those two) stale. One edit to the carried file: its header linked a symbol that does not exist (readableIdsCaseHoldsComposedLeaf). It now links ReadableIdsMatchesCase.holdsComposedLeaf.

SqlDriver#applyFilters and #applyFilterCondition (both protected) take the read's DriverOptions as a new optional last parameter. The lazily invoked knex group callbacks capture it, so the subquery under a $or or $not is walled with the outer read's options and never reads them from instance state. Every in-file caller passes the options it already handed applyTenantScope.

Pins (red before, green after)

Pin sql (sqlite cell) turso local turso remote sqlite-wasm memory mongodb
READABLE_IDS_CASES matches (find + count) 9/9 9/9 9/9 9/9 8/8 + the tenant-wall case records memory's tenant refusal the one non-leaf case answers rows; the 8 leaf cases answer the envelope
refuses-uncomposed → INTERNAL_ERROR / 500 ✓ ✓ ✓ ✓ ✓ ✓
more than 2,000 readable candidates → every visible row; count equals rows served 2,400 ✓ n/a 2,100 ✓ n/a 2,400 ✓ n/a
an unreadable row stays hidden ✓ ✓ ✓
a deleted parent is excluded (find, count, aggregate) ✓ ✓ ✓
refusal logged at error once per driver ✓ ✓ ✓ once per process (no injectable logger in this driver)

E4, the conformance table: node scripts/check-driver-conformance.mjs records READABLE_IDS ok for all five drivers.

Ablation: each implementation was mutated after its commit, using scripts/ablation-replace.mjs (anchor hit 1 → 0, blob compared to HEAD after restore, git diff HEAD empty).

  • driver-sql, the $in composed arm disabled: 13 failed / 4 passed.
  • RemoteTransport, the semi-join arm disabled: 12 failed / 13 passed. The local face stays green, as it should.
  • driver-memory, the rewrite disabled: 10 failed / 4 passed.
  • driver-mongodb, the refusal disabled: 11 failed / 1 passed.

O1: what the subquery binds

The statements below were captured from knex's query event on the SQLite cell, where org_a is the tenant.

find, no scope:   … where (organization_id = ? or organization_id is null) and record_id in (select id from p where (organization_id = ? or organization_id is null))   bindings: 2
find, owner=u1:   … and record_id in (select id from p where (…tenant…) and owner = ?)                                                                         bindings: 3
$not, untenanted: … where not (((record_id is not null) and (record_id in (select id from p))))                                                               bindings: 0
  • The form binds no per-id parameter: two walls (outer and subquery) plus the scope's own comparands.
  • The E3 pin with 2,400 readable parents binds ≤ 3.
  • The ceiling moves into the scope, as the ADR's consequences section says. A scope carrying a pre-resolved id list (rlsMembership, record shares) still binds its ids, now inside the subquery.

The measured and documented ceilings:

  • SQLite: measured. better-sqlite3's bundled SQLite 3.53.4 accepts 32,766 bound variables and refuses 32,767 with too many SQL variables.
  • Postgres, MySQL: NOT MEASURED. This container has no server. The documented ceiling is 65,535 for both. The live PG and MySQL cells of this suite are declared unprovisioned locally.

Verification record (measured at the commits named below; the PR head is 52dc068bfc)

  • Driver conformance ledger, the lane's standing reading:

    • before (23e689dc7d): OK — 50 covered cell(s), 0 in the DEBT ledger, 0 exempt
    • after: OK — 55 covered cell(s), 0 in the DEBT ledger, 0 exempt. The new suite runs the matrix (9 conformance suite(s) … 8 run the matrix).
    • Zero ledger rows were added.
  • Tests, each run as pnpm --filter PKG test:

    Package Passed Skipped
    core 2,482 0
    driver-sql 3,659 208 (the live cells)
    driver-turso 2,399 33
    driver-sqlite-wasm 685 0
    driver-memory 1,732 0
    driver-mongodb 702 182 (mongod opt-in)

    The spec files readable-ids-conformance.test.ts and readable-ids.test.ts pass 47/47.

  • Typecheck: pnpm --filter PKG typecheck exits 0 for core, driver-sql, driver-sqlite-wasm, driver-turso, driver-memory and driver-mongodb. driver-mongodb's test layer needed @objectstack/objectql built first; locally its dist had no .d.ts.

  • Spec: pnpm --filter @objectstack/spec build && pnpm --filter @objectstack/spec check:generated: 12 of 14 were up to date, and the 2 stale ones were regenerated.

  • Gate families from node scripts/pm/dispatch-gates.mjs --commands: all 111 derived families were run and reconciled with --ran (111 derived famil(ies) accounted for — 110 run, 1 NOT-MEASURED). Two results need saying:

    • pnpm check:query-options-erasure was red on the first pass: the new suites carried as any on query arguments, and the test-surface count went 236 → 242. Fixed by typing the queries in 813e08cd73, then re-run green.
    • pnpm check:dual-build-cjs-loads is NOT MEASURED. It exits 3 with PREREQUISITE NOT MET because it reads every package's dist, which needs a full-repo build. CI runs it.
    • The list was taken at 813e08cd73. On that head I re-ran driver-conformance, query-options-erasure, test-source-alias, cross-package-test-inputs, type-check-coverage, nul-bytes and doc-authoring, all green.
    • origin/main was then merged in (52dc068bfc) because packages/spec had moved. On the merged tree: spec rebuilt, check:generated reports all 14 artifacts up to date, the driver-sqlite-wasm suite (the package main touched) passes 695/695, the five new suites are green again, the driver conformance gate holds at 55 covered, 0 DEBT, and the derived gate list is unchanged.
  • Size: 1,806 changed lines (1,777 additions + 29 deletions), generated files included.

Acceptance notes

  • driver-mongodb has no injectable logger, and its refusal is raised inside the pure translateFilter. So the once-per-driver error line goes to console.error once per process. The caller still sees every refusal.
  • The Turso remote face resolves the leaf on its write doors (updateMany / deleteMany) too, matching the local face, which compiles wherever applyFilters runs. The engine refuses the leaf on writes before any driver is asked (D1).
  • driver-mongodb's suite runs server-free at translateFilter, which every door and the aggregation pipeline call before any I/O. The mongod-gated suites in that package are opt-in (test-mongod.ts).

Generated by Claude Code

…posed ReadableIds leaf, the shared driver refusal, and the parked case-set

Claude-Session: https://claude.ai/code/session_01B1r5eJEPgvn7bUsfBvK2tS
Co-authored-by: Claude <noreply@anthropic.com>
… ReadableIds leaf

Claude-Session: https://claude.ai/code/session_01B1r5eJEPgvn7bUsfBvK2tS
Co-authored-by: Claude <noreply@anthropic.com>
…ory compiles in process, mongodb refuses

Claude-Session: https://claude.ai/code/session_01B1r5eJEPgvn7bUsfBvK2tS
Co-authored-by: Claude <noreply@anthropic.com>
…red helper's unit pins; changeset

Claude-Session: https://claude.ai/code/session_01B1r5eJEPgvn7bUsfBvK2tS
Co-authored-by: Claude <noreply@anthropic.com>
…river's logger shape accepts it

Claude-Session: https://claude.ai/code/session_01B1r5eJEPgvn7bUsfBvK2tS
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

github-actions Bot commented Oct 15, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 7 package(s): @objectstack/core, @objectstack/driver-memory, @objectstack/driver-mongodb, @objectstack/driver-sql, @objectstack/driver-sqlite-wasm, @objectstack/driver-turso, @objectstack/spec, touching 79 documentable anchor(s). ⚠️ 4 changed file(s) yielded no anchor (packages/core/src/index.ts, packages/spec/api-surface/data.json, packages/spec/export-origins/data.json, …), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

45 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: node scripts/docs-audit/affected-docs.mjs --json 1ced0f82bd93260b660e6f448c7dc5952bc82371.

⛔ 11 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails.

What this run could not see
  • 4 changed file(s) yielded no anchor (packages/core/src/index.ts, packages/spec/api-surface/data.json, packages/spec/export-origins/data.json, …) — pages documenting those are invisible to this run
  • 1 anchor(s) matched too much of the corpus to be a work list: organization_id (symbol, 33 pages)
  • 14 name(s) were too generic to anchor anything (single lowercase words)
  • the SDK route bridge reached 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 97 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 150 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 1ced0f82bd93260b660e6f448c7dc5952bc82371 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 2489c7f91b523c0c307d969fca32921c2c7acd07 — the merge of head 45a871de4d1735f9c4bbaa5855f87c4c3039a023 into base 1ced0f82bd93260b660e6f448c7dc5952bc82371, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 2489c7f91b523c0c307d969fca32921c2c7acd07 && git checkout 2489c7f91b523c0c307d969fca32921c2c7acd07
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 1ced0f82bd93260b660e6f448c7dc5952bc82371 45a871de4d1735f9c4bbaa5855f87c4c3039a023 && git checkout -B drift-repro 1ced0f82bd93260b660e6f448c7dc5952bc82371 && git merge --no-ff 45a871de4d1735f9c4bbaa5855f87c4c3039a023

node scripts/docs-audit/affected-docs.mjs --json 1ced0f82bd93260b660e6f448c7dc5952bc82371

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 1ced0f82bd93260b660e6f448c7dc5952bc82371 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

…(optional-error sink contract)

Claude-Session: https://claude.ai/code/session_01B1r5eJEPgvn7bUsfBvK2tS
Co-authored-by: Claude <noreply@anthropic.com>
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

席位核对 · domain:engine seat 1 · session_01GP3comALHRcRBixS2TEnLF · 2026-10-15T11:23Z · ACCEPT (landing waits for every check on the head)

Head: 45a871de4d1735f9c4bbaa5855f87c4c3039a023, the dev's patch round on the red Optional-error sink contract (52dc068bfc → this head, one file: packages/core/src/utils/readable-ids-driver-refusal.ts, warn now required on the refusal sink).

Item Reading
Shape Draft PR → main. The first line is Fixes #23639. A closing-keyword scan of the whole body finds one hit, line 1.
Scope 21 files (+1779 / −29), no content/docs/releases/ edit and no governed path, under the 3,000-line threshold. The claim's surface (6187884851) was amended for the packages/core helper, the driver-sqlite-wasm name override and turso-driver.ts. The domain:spec surfaces (the case-set, its export, the regenerated shards, the CASE_SETS row) were declared to #18883 (6187941311), with no objection on record.
Clause-② yes, on the claim, the PR body and the changeset. The new exports of @objectstack/spec/data (11, matching the api-surface/data.json hunk) and of @objectstack/core widen the public surface.
Changeset, sentences read Seven minor rows (spec, core and the five drivers). The title line "the five drivers compile the composed ReadableIds leaf or refuse it with one envelope …". Clause-②: yes with no arm.
Ledger Driver conformance: 50 covered / 0 DEBT before, 55 covered / 0 DEBT after (the dev's reading; check:driver-conformance inside the green Lint & Repo Gates agrees). Zero ledger rows.
#5499 freeze driver-memory +32/−2 and driver-mongodb +20/−0 carry only the two D3 rows.
Contract review The record posted below this comment: CONTRACT_REVIEW_TIER, PASS at this head, nothing escalated.
Gates Every check that has concluded on this head is green. Build Core, Test Core 1–6 and three Type Check lanes were still running at the review's read. Landing needs every check green.

Residual, accepted. The new driver-sql suite's PG and MySQL cells run first on main's full battery (Temporal Conformance (live PG + MySQL), a path skip on the PR). A red there is this lane's main-red under the standing convention, and this seat owns it.

Disposition:

  • open_questions 0–4 are as the contract review answers them: the core helper kept, mongodb's console.error once per process, Turso remote resolving on the write doors, drivers at minor, and the role file over the harness defaults.
  • out_of_scope_findings: none filed, dropped — environment for the two local-environment notes.
  • The dev's derivation note (check:optional-error-sink absent from dispatch-gates --commands) is by design: the gate's header records it REFUSE-WIDE in scripts/pm/bare-root-worklist.mjs (every non-test source under packages/). dropped — by design.

Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 45a871de4d1735f9c4bbaa5855f87c4c3039a023
Local-runs: none

Reviewed on the card (#23639, body and every comment), the PR body, its 21-file list, the net diff against main at the head, and the head's 39 check-runs. Governing text: ADR-0139 D1 to D3 and plan rows E3/E4; ADR-0112 (the envelope); #22788 note 3 (ruling pointer 6104842076: D3 stands as written, no capability bit, mongodb refuses until O3); the domain:spec ruling 6137585321 (the case-set lands in the same PR as the five suites, zero ledger rows); the #5499 freeze on driver-memory / driver-mongodb. Tier basis: packages/spec/src/data/index.ts is a published schema surface (schema face). No path in the file list is a governed-register row; Governed Surface Queue Guard is green.

① Derived judgments

Public surface, @objectstack/spec/data — widens by six consts and five types (READABLE_IDS_CASES, READABLE_IDS_PARENT_OBJECT, READABLE_IDS_CHILD_OBJECT, READABLE_IDS_PARENT_ROWS, READABLE_IDS_CHILD_ROWS, READABLE_IDS_REFUSAL; ReadableIdsCase, ReadableIdsMatchesCase, ReadableIdsRefusesUncomposedCase, ReadableIdsParentRow, ReadableIdsChildRow). Right. Ruled by 6137585321 and declared cross-lane in the claim. api-surface/data.json and export-origins/data.json name exactly those eleven and nothing else. The carried file, its oracle test and the CASE_SETS row were diffed against the parked e641ab6d8: byte-identical except the one declared doc-link edit (readableIdsCaseHoldsComposedLeaf, a symbol that never existed, now ReadableIdsMatchesCase.holdsComposedLeaf). The gate's CASE_SETS_DIR is packages/spec/src/data, so the file sits where the other nine case-sets sit; it holds fixtures, a frozen envelope constant and where factories, no runtime logic (Prime Directive 2 holds).

Public surface, @objectstack/core — widens by four functions and three types (findMisplacedReadableIds, replaceComposedReadableIds, readableIdsDriverRefusal, refuseReadableIdsOnce; MisplacedReadableIds, ReadableIdsDriverRefusalError, ReadableIdsRefusalLog). Right. Outside the claim's original surface, accepted into it by the seat's amendment on the dev's report (claim 6187884851, amended). The precedent is exact: json-column-operator-refusal.ts sits on the same floor for the same reason, four compilers that do not import each other. Core carries no api-surface artifact, so nothing was owed there. Covered by the changeset's core row.

driver-sql accept-set. (a) A composed leaf as a $in comparand compiles to field IN (SELECT id FROM P_TABLE WHERE SCOPE AND TENANT): right per D3. The wall is applyTenantScope on the parent with the outer read's options, threaded as a new optional trailing options on the protected applyFilters and applyFilterCondition. Verified at the head: all eight in-file applyFilters call sites pass the options they already hand applyTenantScope; no in-tree subclass overrides either method, so nothing can drop them; compileFilters keys the scope's compile off the sub-builder's table (coercionKey), so the scope compiles against the parent's own declarations; getBuilder(parent) without options omits only a transaction binding, which an embedded subquery never needs; check:tenant-chokepoint (inside the green Lint & Repo Gates) accepts the new builder because it does call the chokepoint. (b) An uncomposed leaf at any position, or a composed leaf outside a $in comparand, is refused INTERNAL_ERROR / 500 before anything compiles, ahead of the withheld-refusal resolver: right (D2's last paragraph, D3); it replaces the DATABASE_ERROR syntax fault E1's pointer (6107554336) measured. (c) The composed arm binds the bare field, exactly as the list $in arm does: consistent. (d) New protected members readableIdsRefusalLog, readableIdsDriverName, readableIdsSubquery: a subclassing-surface widening, minor. (e) The compile runs wherever applyFilters runs, the update/delete predicates included: right; the driver is below D1's seam, which refuses the leaf on write verbs, and a driver is not that seam.

driver-sqlite-wasm, driver-turso local face — inherit. Right. Each overrides only the name its refusal prints; each is measured by its own suite on its own engine rather than assumed from the inheritance.

driver-turso remote face. TursoDriver#toRemoteWhere refuses first (same walk, logged once per instance), then resolves each composed leaf into a branded RemoteReadableIdsSemiJoin (the parent's remote table; the scope in storage form via toRemoteFilter on the parent; the tenant wall via remoteTenantScope, which compiles what applyTenantScope adds with the outer read's options). RemoteTransport's $in arm compiles column IN (SELECT "id" FROM table WHERE (scope) AND (tenant)), binding only the scope's and the wall's values; a composed leaf the driver did not resolve (the transport driven standalone) is refused unsupported; buildWhereSQL refuses uncomposed and misplaced leaves at its outermost frame. Right per D3 (an independent compiler, the same compilation, the wall decided in one place). find, findOne, count, aggregate, distinct, updateMany and deleteMany all thread options into toRemoteQuery. The brand and its factory are module exports, but driver-turso's index.ts exports named symbols only, so the package's public surface does not widen by them.

driver-memory. convertToMongoQuery, the one funnel for find, count, aggregate and distinct, refuses first, then rewrites each composed leaf copy-on-write into the parent's ids read under the scope through the same funnel, ahead of the shape gate. Right per D3 (in process, unbounded; no wall owed, because assertSingleTenantPosture refuses every tenant-scoped call, and the tenant-wall case records that refusal instead of rows, as the case-set prescribes). Replaces the raw TypeError E1's pointer measured. The package diff is +32/-2 and nothing but the D3 row rides: the #5499 freeze is respected.

driver-mongodb. translateFilter refuses every leaf, composed or not, ahead of the shape gate that would otherwise answer INVALID_FILTER. Right per D3 and note 3. Every door and the aggregation $match funnel through it, and the suite pins the pipeline path. The package diff is +20/-0: the freeze is respected.

The envelope. One helper, code: 'INTERNAL_ERROR', status: 500, httpStatus: 500, a message naming the driver, the leaf's path and parent object, and the remedy; three kinds (uncomposed, misplaced, unsupported). Right (ADR-0112: httpStatus is the target location, status keeps the server-fault declaration true; never INVALID_FILTER; the unsupported remedy names the probe chosen before the leaf is written, D5's rule, never a catch). No DriverCapabilities bit anywhere in the diff. Runtime strings carry no tracker number (check:doc-authoring green).

Logging. error once per driver instance on SQL, Turso and memory through each driver's own logger; the sink contract falls back to warn only where a host injects a logger with no error, the contract SqlDriver.logger already documents, and the head's last commit makes warn required so the line is never dropped. Right.

Conformance. Five suites import READABLE_IDS_CASES from @objectstack/spec/data (no hand copy) and run every case through lowerFilterCondition, the seam a real read passes; the lowering spreads nodes and arrays but never a non-array comparand, so the composed leaf keeps its brand by reference, which the NULL-safe $not case depends on. E3's pins hold on the sqlite cell, Turso remote and memory: over 2,000 readable candidates served in full, an unreadable row hidden, a deleted parent excluded on find, count and aggregate, count equal to rows served; the O1 reading captures at most 3 bindings whatever the parent's size. check:driver-conformance (in the green Lint & Repo Gates) accepts the classified set with zero ledger rows, so E4's "the table records both" holds without DEBT.

Residuals, named, none a breach. (a) The PG and MySQL cells of the new driver-sql suite are named skips on the PR fast set and first execute on main's full battery (Temporal Conformance (live PG + MySQL) runs the whole driver-sql suite with both URLs and OS_EXPECT_LIVE_DIALECT_MATRIX=1); the form is a plain IN (SELECT …) through knex's per-dialect compiler, and a red there is the lane's main-red by the standing convention. (b) O1: SQLite measured (32,766 accepted, 32,767 refused); Postgres and MySQL honestly NOT MEASURED, documented 65,535; the statement binds no per-id parameter, so the ceiling moves into the scope exactly as the record's consequences say. (c) The PR body's verification record names 52dc068bfc; the head adds one commit that only tightens the sink contract in core (warn required). The diff judged here is the net diff at the head.

Check-runs on the head. Green: Lint & Repo Gates, Governed Surface Queue Guard, Check Changeset, Check PR Size, Type Check · source gates, every Lint · job, Spec property liveness, Dogfood Regression Gate, the issue-claim, single-writer, part-of and branch-claim guards. Skipped by design (full-battery or opt-in): Build Docs, Console Pin Gate, the three dogfood shards, Dogfood Verify CLI, Packed-tarball smoke, Temporal Conformance (live PG + MySQL). Pending, not a verdict this record renders: Build Core; Test Core 1/6 through 6/6; Type Check · consumer gates, Type Check · debt ledger, Type Check · workspace. The owning seat lands only once every required context concludes success.

② Semver level

.changeset/23639-readable-ids-drivers.md: seven packages at minor (@objectstack/spec, @objectstack/core, @objectstack/driver-sql, @objectstack/driver-sqlite-wasm, @objectstack/driver-turso, @objectstack/driver-memory, @objectstack/driver-mongodb). Matches what the diff publishes. Spec and core publish new exports, a widening, so at least minor. Each of the five drivers gains a new compiled form or a new refusal envelope on an input that previously produced an undeclared fault (a DATABASE_ERROR syntax fault, a raw TypeError, a pass-through): a capability, not a fix, so minor and not patch. driver-sqlite-wasm's own diff is a two-line name override, but it ships the compile through the inherited compiler and minor is the honest level for what it now does. Nothing removes or renames anything an author can write or a consumer imports; the optional trailing parameter on two protected methods is compatible; so not breaking, no ADR-0087 disposition owed, no migration text owed. skip-changeset would be wrong and is not used. Check Changeset is green on the head.

Clause-②: yes with no arm, on the PR body and the changeset body alike. Right: yes takes at least minor, which every row carries; (narrowing) would be false and (widening) is not asserted, so the pair is well-formed. The card's claim declared Clause-②: yes for the spec half; the core export line falls inside the same yes by the seat's amendment.

③ Boundary flags

Every dev flag answered; nothing escalated.

  • open_questions[0] — the shared walk, rewrite and refusal live in @objectstack/core, outside the claim's surface. Option A, keep it in core: accepted (the seat already amended the claim; this record concurs). One source for four compilers that do not import each other, on the floor json-column-operator-refusal.ts already proves; four hand copies is how one refusal becomes four dialects.
  • open_questions[1] — mongodb's once-per-driver line goes to console.error once per process. Option A: accepted. Verified at the head: mongodb-driver.ts carries no logger of any kind, and the refusal is raised in the pure translateFilter that every door and the aggregation $match share, so one site covers all. Option B widens a driver [裁决] driver-memory / driver-mongodb 投入冻结 —— 维护者 2026-08-05 口径(跨单锚点) #5499 freezes. Once per process is once per driver package, and every refusal still reaches the caller. Recorded in the PR's acceptance notes; not escalated.
  • open_questions[2] — the Turso remote face resolves the leaf on updateMany / deleteMany too. Option A: right. The local face compiles wherever applyFilters runs; keeping the two faces of one driver identical is the contract. The write-verb refusal is D1's, at the engine, and no driver is its enforcement point.
  • open_questions[3] — minor for the five drivers. Stands (see ②). The seat does not lower it.
  • open_questions[4] — role file over harness defaults (no CI wait, session-URL footer, model-free commit trailers, the primary checkout detached and restored). No contract content. The PR body does end with the session-URL footer. No action.
  • deviations — the core file, its test and the index.ts export line; the two-line readableIdsDriverName override in driver-sqlite-wasm; turso-driver.ts (toRemoteWhere). All three named in the seat's amended file surface; all three judged above. Closed.
  • deviations — the one doc-link edit to the carried case-set. Verified as the sole difference against e641ab6d8. Right, because this PR is the one that lands the file.
  • out_of_scope_findings — none filed. Right: the two environment notes (a full-repo dist for check:dual-build-cjs-loads, a missing objectql .d.ts locally) are not reproducible defects, contract violations or authoring traps; CI covers the first.
  • Readings the card owes. O1 answered (above, with the honest NOT MEASURED for the two server dialects); O3 kept (mongodb refuses, no prototype); the conformance ledger read before and after by the dev and re-derived by the green gate on this head: zero new DEBT rows.

Implemented-by: session_01B1r5eJEPgvn7bUsfBvK2tS
Reviewed-by: session_01GP3comALHRcRBixS2TEnLF

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 15, 2026 11:39
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 15, 2026 11:40
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 15, 2026
Merged via the queue into main with commit 462dff3 Oct 15, 2026
42 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-23639-readable-ids-drivers branch October 15, 2026 12:12
@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Landed · domain:engine seat 1 · session_01GP3comALHRcRBixS2TEnLF · 2026-10-15T12:13Z

Probe Reading
Queue branch refs/heads/gh-readonly-queue/main/pr-23656-… was present at the 12:11Z read. After the merge, git ls-remote finds no pr-23656 queue branch.
origin/main 462dff36f1 is on origin/main (git merge-base --is-ancestor exit 0). It is PR #23656's merge_commit_sha, merged at 12:12:31Z.
Cards closed #23639 (completed, 12:12:32Z). #23279 closed in the same second: its closing reference is PR #23663 of the same merge group, not this PR (this body never names it). No card of this lane closed unexpectedly.

Status: #23639 is closed, and its pm:dispatched is removed in this stroke. The cloud dev session session_01B1r5eJEPgvn7bUsfBvK2tS is archived. On #22788, E3 and E4 are ticked.

Main-red watch: the next main full battery runs this PR's driver-sql ReadableIds PG and MySQL cells for the first time (Temporal Conformance). A red there is domain:engine's.


Generated by Claude Code

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

Labels

documentation Improvements or additions to documentation protocol:data size/xl tests tooling

Projects

None yet

2 participants