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
drivers(turso): RemoteTransport refuses a cube-qualified aggregation alias, so every analytics cube query on a remote datasource is an un-enveloped 500 #14113
Filed while fixing #13714 (analytics time-dimension granularity 500). Same defect class, one driver face over, not fixed by that PR — it needs its own judgement call, so it is filed rather than patched inline.
What
RemoteTransport.aggregate (Turso remote mode) holds the aggregation alias to SAFE_IDENTIFIER:
A dot fails that regex. Every analytics measure is addressed as CUBE.MEASURE, and ObjectQLStrategy uses that dotted name verbatim as the aggregation alias — so on a Turso remote datasource every analytics cube query that reaches driver.aggregate throws:
a bare Error with no code and no status, which mapDataError then serves as an opaque 500 — the #11455 / #8931 shape this repo has been closing door by door.
Why it is a defect and not the guard doing its job
The alias is written into a double-quoted identifier (AS "${alias}"), where a dot is inert and legal SQL. AggregationNodeSchema declares alias: z.string() — an output-column key, not a reference — and the in-memory, MongoDB and (as of #13714) SQL faces all project it verbatim. So this face refuses a name the contract permits and its own emission could carry safely.
⚠️ The guard is still doing real work in the field position, and the alias position needs something: alias is interpolated raw, so an alias containing a " would break out of the quoting. The fix is therefore not "drop the check on the alias" — it is to let the alias be any string and escape the quote character (" → ""), the same distinction #13714 drew between a qualified reference and a single name.
Reproduction sketch
Point an analytics cube at a Turso remote datasource and run any cube query (measures: ["some_cube.count"]). Bucketed or not: remote mode publishes queryDateGranularity: {} so a bucketed query buckets in memory, but the un-bucketed one still reaches aggregate and eats the refusal.
Scope note
Local-file Turso mode inherits SqlDriver and is covered by #13714's repair; this is the remote transport's own hand-written SQL face only.
⭐ This card was filed by a seat that had the fix in hand for the sibling face and deliberately did not patch it inline, because the alias position needs a judgement the field position does not. That restraint is why this card is precise; honour it by not collapsing the two positions.
Zone 1 — BINDING. ⛔ NOT re-adjudicable.
1.1 — ⛔ Do NOT drop the check on the alias. Escape the quote character instead. The card is explicit and correct: alias is interpolated raw into AS "${alias}", so an alias containing a " breaks out of the quoting. Removing the guard trades a false refusal for an injection seam. The repair is: let the alias be any string, and escape " → "" — the standard SQL doubled-quote escape inside a quoted identifier.
1.2 — ⛔ The guard stays intact in the field position.SAFE_IDENTIFIER is doing real work there. This card touches the alias position only. A diff that relaxes field has left the card.
1.3 — Follow #13714's distinction, do not re-derive a different one. That PR drew exactly this line one face over: a qualified reference (which must be validated) versus a single output name (which must be quoted and escaped). AggregationNodeSchema declares alias: z.string() — an output-column key, not a reference — and the in-memory, MongoDB and (post-#13714) SQL faces all project it verbatim. ⇒ This face is the outlier, and the contract is what the other three already do.
Zone 2 — PM mechanism assumptions. ⚠️ MEASURE. Falsification welcome.
2.1 — I assume the anchors have drifted. The card cites remote-transport.ts:71 for SAFE_IDENTIFIER. ⚠️ Re-derive it; the card was filed 2026-09-01 and origin/main has moved to 1403d943 since.
2.2 — I assume this is confined to packages/drivers/driver-turso. The card's scope note says local-file Turso mode inherits SqlDriver and is already covered by #13714, so only the remote transport's own hand-written SQL face is in play. ⚠️If your diff reaches packages/drivers/driver-sql, stop and report — that would mean #13714's repair is incomplete, which is a different card.
2.3 — I assume the un-bucketed path is the reproducing one. The card says remote mode publishes queryDateGranularity: {}, so a bucketed query buckets in memory and never reaches aggregate — it is the un-bucketed cube query that eats the refusal. ⭐ Get your reproduction on the path that actually reaches driver.aggregate, or you will measure a green that means nothing.
2.4 — I assume the error is currently un-enveloped, and that this is half the defect. The card measures a bare Error with no code and no status, which mapDataError then serves as an opaque 500 — the #11455 / #8931 shape this repo has been closing door by door. ⚠️Measure whether your fix leaves any remaining refusal on this path un-enveloped. If a genuinely unsafe alias can still reach the user as a bare 500 after your change, say so — closing the false refusal while leaving the true one opaque is only half the card.
Zone 3 — advisory. ⛔ Not binding.
The doubled-quote escape is one line; the value is in the test that proves a dotted CUBE.MEASURE alias now survives end-to-end and that an alias containing a " is still handled safely rather than breaking the statement. Those two are the pair.
You conclude the guard must be relaxed rather than the alias escaped ⇒ that contradicts 1.1 ⇒ stop and report your reading, do not proceed on it.
Any other open PR has claimed remote-transport.ts since dispatch ⇒ report rather than racing. (Measured at dispatch against the 10 open PRs: none touches it. ⚠️ A file-overlap reading has a shelf life measured in merges — re-check before you push.)
Verification expected
A test pinning both directions: a dotted CUBE.MEASURE alias reaching aggregate on the remote transport now succeeds, and an alias containing a " is escaped rather than breaking out. The field position still refusing what it refused before (that is your regression control). A firing positive control on any zero. Plus a changeset, push, and a DRAFT PR saying Closes #14113.
⛔ Report anything unmeasured as NOT MEASURED. ⛔ No test skipped, disabled, quarantined or re-baselined. ⛔ Never git stash. ⛔ Never edit content/docs/releases/. Worktree-first.
Clause-②: yes — widens the accept set of the remote-transport aggregation alias position (a dotted CUBE.MEASURE alias is now accepted and escaped rather than refused). Path limb: no.
{
"issue": 14113,
"status": "done",
"branch": "claude/issue-14113-turso-remote-alias-quoting",
"pr": "https://github.com/objectstack-ai/objectstack/pull/14248",
"premise_still_valid": true,
"summary": "RemoteTransport.aggregate's aggregation alias is now ESCAPED rather than gated: new private helper aliasIdentifierSql doubles the quote character (\" becomes \"\") and the assertSafeIdentifier call was removed from that position ONLY. This is the alias half of the distinction #13714 drew on driver-sql, whose aliasIdentifierSql routes the same position through knex wrapIdentifier. Clause-2: YES — the guard stays intact in the `field` position and in `object`, both carrying explicit regression controls; the diff is 3 files, none in driver-sql, so STOP condition 1 was not triggered. Zone 2.1 FALSIFIED: SAFE_IDENTIFIER is still at remote-transport.ts:71, unmoved, at base 66ecc50a (main had already advanced past the 1403d943 the order cited). Zone 2.2 and 2.3 HOLD as stated. Zone 2.4 measured, and half of it remains open — see open_questions.",
"tests": "Base 66ecc50a, final commit 689d223f; the gate union and every tally below were run at that commit. (1) New suite: `pnpm --filter @objectstack/driver-turso exec vitest run --maxWorkers=2 src/remote-transport-aggregation-alias-quoting.test.ts` => 'Test Files 1 passed (1) / Tests 9 passed (9)'. It executes against a real SQLite-backed libsql stub rather than asserting on a SQL string, deliberately: only EXECUTING the statement tells 'escaped' apart from 'broke out', since a string assertion passes on an alias that terminates its own quoting. (2) Whole package: `pnpm --filter @objectstack/driver-turso test` => 'Test Files 41 passed (41) / Tests 1120 passed (1120)' — no regression, including the landed #6401 groupBy-alias refusal pin. (3) `pnpm --filter @objectstack/driver-turso typecheck` exit 0, and confirmed NOT vacuous over the new code: `tsc --noEmit --listFiles | grep -c` the new test file = 1, not 0. (4) ABLATION / reverse verification, direction predicted BEFORE running: restore `this.assertSafeIdentifier(alias);` + `AS \"${alias}\"`; predicted the 3 dotted-alias cases and 2 quote-escape cases go red BY THROWING inside the call, and the 3 position controls + default-alias case stay green. MEASURED: 5 failed / 4 passed of 9, case for case as predicted, with 'RemoteTransport: unsafe identifier rejected: \"showcase_delivery.count\"' — the card's reported symptom reproduced VERBATIM — plus the same sentence naming 'won\"count' and the DROP TABLE text. REBUILD LEG: none needed, and that is measured not assumed — packages/drivers/driver-turso/dist does not exist (only the dependency closure was built, via `pnpm --filter '@objectstack/driver-turso^...' build`), so the subject under test resolves from SOURCE; the deps it imports (driver-sql, spec, core) resolve through exports to dist and were built. MUTATION CONFIRMED ON DISK before the run by anchor counts on the intended text, not by the editor's exit code: gate call 0->1, new emission 1->0, old emission 0->1, plus git diff --stat non-empty. RESTORE PROVEN BY OBSERVATION afterwards, not by exit code: `git checkout HEAD -- ABSOLUTE_PATH` (HEAD named explicitly, never a bare checkout, which restores from a possibly-polluted index), then `git diff HEAD` empty AND `git hash-object` == `git rev-parse HEAD:PATH` = 03cafbad400580140062033ab6f2fffc20d31735, byte-identical; the script carried `trap restore EXIT INT TERM` with ABSOLUTE paths throughout. Suite re-run green (9/9) on the restored tree. (5) GATES: 34 families derived from the real change set by `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands` (harvested runnably, not grepped out of the prose, so neither spelling nor the convention block could be dropped). 31 exited 0. THREE ARE NOT MEASURED, NOT RED, each by its own printed verdict line: check-test-completeness.mjs (exit 3, 'the local reading for this gate is NOT MEASURED ... It is not a red'), check:dual-build-cjs-loads (exit 3, 'PREREQUISITE NOT MET — this gate reads built output ... This is NOT a pass: nothing was measured'), check:type-check-debt (exit 3, 'PREREQUISITE NOT MET'). All three need a full workspace `pnpm build` that CI performs; none is affected by this diff's semantics. Every exit code was captured AFTER redirecting to a file, never through a pipe. Also run beyond the derived set: scripts/check-nul-bytes.mjs exit 0 (7781 files) plus a direct control-byte grep of the 3 changed files, clean.",
"mcp_calls": "1 — a single targeted search_issues for the duplicate scan. Channel switch DECLARED: repo-scoped REST reads work in this session (200), but /search/issues is structurally blocked by the session proxy (\"sessions are bound to their configured repositories\"), and the zero-quota payload channel does not cover search. Control: the search returned #14113 itself, a known-must-hit, so the empty duplicate result is a real reading rather than a silently-zeroed session. Everything else went through zero-MCP channels: the issue body and all comments via the single-card embedded JSON payload, PR listing / issue creation / PR creation / read-backs via repo-scoped REST curl, and the rest via git.",
"open_questions": [
{
"question": "Zone 2.4, the half this card does NOT close: assertSafeIdentifier still throws a bare `Error` (no `code`, no `status`) for the positions that keep it — `field`, `object`, `groupBy` outKey, and the DDL/backfill sites — so a genuinely unsafe FIELD on this same aggregate path still reaches the caller as an opaque 500 through mapDataError. The alias position now refuses nothing, so no un-enveloped refusal remains THERE, but the #11455 / #8931 shape is still open one position over. What envelope should an injection refusal carry?",
"options": [
"A — leave it as filed-not-fixed: this card escapes the alias only, and the envelope question gets its own card that can rule once for every assertSafeIdentifier site (aggregate, find, DDL, canonical backfill) rather than per-caller.",
"B — envelope it here as INVALID_QUERY/400, treating an unsafe identifier as a caller mistake.",
"C — envelope it here as a distinct security-refusal code/status, on the reading that an injection attempt is not an ordinary malformed query."
],
"recommendation": "A. Long-term soundness (weighted highest): the guard has ~10 call sites across aggregate, DDL, schema sync and index creation, and the right envelope is a property of the GUARD, not of the aggregate door — ruling it inside a card dispatched for the alias position would set the contract for DDL and backfill callers as a side effect, which is exactly the per-caller drift ADR-0112 exists to stop. Real business need: the reachable population is a caller sending a genuinely unsafe identifier, which no shipped producer does (ObjectQLStrategy emits bare resolved column names), so this is diagnosability, not a live outage — unlike the alias refusal, which broke every cube query. Making AI-written code hard to get wrong: B and C both look mechanical but are not — no landed evidence pins which is right, and guessing installs a wire contract that later has to be broken to correct. Startup scope discipline: the false refusal is what was costing real queries and it is now closed; enveloping the true refusal is a separate, smaller, non-urgent improvement. Reported rather than guessed, per the dispatch order's own instruction to say so."
}
],
"out_of_scope_findings": [
"filed as #14235: RemoteTransport still gates the groupBy alias (the second output-name position in the same method) while driver-sql escapes it post-#13714 — NOT patched inline because that position carries a landed pin (#6401, remote-transport-groupby-node.test.ts) asserting the refusal, so reversing it is a judgement rather than a mechanical edit, failing criterion 2 of the bounded in-place-fix exemption. Measured as NOT on the reproducing path: ObjectQLStrategy.resolveFieldName resolves a dimension to member.sql or member.split('.')[1], so only the measure arrives dotted. A regression control in the new suite pins the position's current behaviour so it cannot drift silently."
]
}
Reviewer:domain:engine execution PM, session session_01Q5WBDtaUnoz5XuJ6jk8pQ5, reviewing the dev seat's contract increment. Provenance: maintainer ruling 2026-08-31 (in-seat review by an at-tier PM), re-authorised by the maintainer this shift: 「给你切换到 Fable了,你现在可以自己复审pr了」.
Fuse reading, verbatim:get_session → external_metadata.last_served_model = claude-fable-5-1; CONTRACT_REVIEW_TIER = 'claude-fable-5'. ⚠️ Literal compare reads ≠; the served model is the newer Fable and the maintainer authorised explicitly. Recorded, not hidden.
Carriers:--pair 14248 → exit 0 with the gate hung on both. Clause-②: yes — the alias position's accept set widens (a dotted CUBE.MEASURE alias is now accepted and escaped rather than refused). Path limb no.
① Derived judgments
change
judgement
aggregate alias: assertSafeIdentifier removed from the alias position; emitted through new aliasIdentifierSql = "${String(alias).replace(/"/g, '""')}"
Correct. Zone 1.1 exactly: escape, do not drop the check. A " is doubled, so bucket"; DROP TABLE …; -- stays one inert column name — pinned by executing the statement against a real SQLite-backed libsql stub, not by string assertion (the only way "escaped" and "broke out" differ).
field and object positions
Unchanged, still gated — regression-controlled in the new suite. Zone 1.2 held.
groupBy alias position
Deliberately unchanged (carries a landed pin, #6401; not on the reproducing path since resolveFieldName de-dots dimensions). Filed as #14235. Correct restraint.
Contract alignment
AggregationNodeSchema.alias: z.string() — an output-column key; in-memory, MongoDB and post-#13714 SQL faces project it verbatim. This face was the outlier; now consistent.
packages/drivers/driver-sql
untouched (STOP 1 not triggered).
② Semver
@objectstack/driver-turso: patch — bug fix on a driver face, no new API. Consistent with the changeset.
✅ LANDED — PR #14248 merged as 443f0d33 (merge queue, 2026-09-02T01:43Z), verified by content on origin/main
pm:dispatched → stripped. Card closed by the PR's Closes #14113.
Discriminating probe:aliasIdentifierSql — absent on merge-base 66ecc50a, present on origin/main in packages/drivers/driver-turso/src/remote-transport.ts; that file's last-touching commit on main is 443f0d33, this PR's merge.
Fences held: 3 files; packages/drivers/driver-sql untouched (STOP 1 never fired — #13714's repair stands complete on its side); packages/spec 0. Clause-② (yes, alias accept set widens) reviewed in-seat at tier — PASS on 689d223f; gate cleared on both carriers in the same stroke.
Delivered: the aggregation alias position is escaped (" → "") instead of gated; a dotted CUBE.MEASURE alias now reaches the database and the value comes back under the caller's own key; field / object / groupBy positions unchanged and regression-controlled. driver-turso patch.
Follow-ups on the board:#14235 (the groupBy alias position's gating question, dev-filed) · #14287 (assertSafeIdentifier still a bare un-enveloped Error on the field/object/groupBy/DDL positions — filed by this seat from the review).
Activity
os-steve commentedon Sep 1, 2026
路由:
domain:engine(⛔ 不是domain:services)·bug· p2 ·pm:queue由
domain:servicesPM 席(座位贴 #6021)在 #13714 的落地窗口分诊。落点packages/drivers/turso/**⇒ 车道表domain:engine行(packages/drivers/driver-*于 2026-08-19 并入 engine,原domain:drivers标签退役)。⇒ 不归本席派发,交domain:engine车道。p2 的理由
Error、无 code、无 status ⇒ 不只是坏,而且不可分诊 —— 调用方拿不到能路由的错误面。这比 analytics: cube query with a time-dimension granularity (month) on a date dimension crashes 500 DATABASE_ERROR #13714 原症状(至少是DATABASE_ERROR/500)更难查。⭐ 与 #13714 的关系:同一类,不同面,⛔ 不可合卡
#13714 的根因是 knex 的
??绑定把别名当引用去解析(按.切分),修法是让别名走client.wrapIdentifier成为一个标识符。本卡是同一个输入撞上另一道墙:Turso 的
RemoteTransport把聚合别名约束在SAFE_IDENTIFIER(不许带点),于是 analytics 那个天生带点的<cube>.<measure>被注入守卫挡下。⇒⚠️ 修法不同,所以必须分卡:#13714 是「别名该被引号包成一个标识符」;本卡是「注入守卫该转义引号字符,而不是拒绝点号」。⛔ 把守卫放松成允许点号是错的方向 —— 那会削弱注入防护;要做的是让它按标识符转义规则处理,而不是按字符白名单拒绝。这一判断需要该车道自己复核,本席只做路由,⛔ 不代裁修法。
派发时的提醒
Generated by Claude Code
os-support-ai commentedon Sep 1, 2026
Claim:
domain:engineexecution PM · sessionsession_01Q5WBDtaUnoz5XuJ6jk8pQ5· R12 · branchclaude/issue-14113-turso-remote-alias-quoting· worktree../objectstack-14113-turso-aliasofforigin/main.pm:queue→pm:dispatched.⭐ This card was filed by a seat that had the fix in hand for the sibling face and deliberately did not patch it inline, because the alias position needs a judgement the
fieldposition does not. That restraint is why this card is precise; honour it by not collapsing the two positions.Zone 1 — BINDING. ⛔ NOT re-adjudicable.
1.1 — ⛔ Do NOT drop the check on the alias. Escape the quote character instead. The card is explicit and correct:
aliasis interpolated raw intoAS "${alias}", so an alias containing a"breaks out of the quoting. Removing the guard trades a false refusal for an injection seam. The repair is: let the alias be any string, and escape"→""— the standard SQL doubled-quote escape inside a quoted identifier.1.2 — ⛔ The guard stays intact in the
fieldposition.SAFE_IDENTIFIERis doing real work there. This card touches the alias position only. A diff that relaxesfieldhas left the card.1.3 — Follow #13714's distinction, do not re-derive a different one. That PR drew exactly this line one face over: a qualified reference (which must be validated) versus a single output name (which must be quoted and escaped).
AggregationNodeSchemadeclaresalias: z.string()— an output-column key, not a reference — and the in-memory, MongoDB and (post-#13714) SQL faces all project it verbatim. ⇒ This face is the outlier, and the contract is what the other three already do.Zone 2 — PM mechanism assumptions.⚠️ MEASURE. Falsification welcome.
2.1 — I assume the anchors have drifted. The card cites⚠️ Re-derive it; the card was filed 2026-09-01 and
remote-transport.ts:71forSAFE_IDENTIFIER.origin/mainhas moved to1403d943since.2.2 — I assume this is confined to⚠️ If your diff reaches
packages/drivers/driver-turso. The card's scope note says local-file Turso mode inheritsSqlDriverand is already covered by #13714, so only the remote transport's own hand-written SQL face is in play.packages/drivers/driver-sql, stop and report — that would mean #13714's repair is incomplete, which is a different card.2.3 — I assume the un-bucketed path is the reproducing one. The card says remote mode publishes
queryDateGranularity: {}, so a bucketed query buckets in memory and never reachesaggregate— it is the un-bucketed cube query that eats the refusal. ⭐ Get your reproduction on the path that actually reachesdriver.aggregate, or you will measure a green that means nothing.2.4 — I assume the error is currently un-enveloped, and that this is half the defect. The card measures a bare⚠️ Measure whether your fix leaves any remaining refusal on this path un-enveloped. If a genuinely unsafe alias can still reach the user as a bare 500 after your change, say so — closing the false refusal while leaving the true one opaque is only half the card.
Errorwith nocodeand nostatus, whichmapDataErrorthen serves as an opaque 500 — the #11455 / #8931 shape this repo has been closing door by door.Zone 3 — advisory. ⛔ Not binding.
The doubled-quote escape is one line; the value is in the test that proves a dotted
CUBE.MEASUREalias now survives end-to-end and that an alias containing a"is still handled safely rather than breaking the statement. Those two are the pair.STOP conditions
packages/drivers/driver-sql⇒ report; that means analytics: cube query with a time-dimension granularity (month) on a date dimension crashes 500 DATABASE_ERROR #13714 is incomplete.remote-transport.tssince dispatch ⇒ report rather than racing. (Measured at dispatch against the 10 open PRs: none touches it.Verification expected
A test pinning both directions: a dotted
CUBE.MEASUREalias reachingaggregateon the remote transport now succeeds, and an alias containing a"is escaped rather than breaking out. Thefieldposition still refusing what it refused before (that is your regression control). A firing positive control on any zero. Plus a changeset, push, and a DRAFT PR sayingCloses #14113.⛔ Report anything unmeasured as NOT MEASURED. ⛔ No test skipped, disabled, quarantined or re-baselined. ⛔ Never
git stash. ⛔ Never editcontent/docs/releases/. Worktree-first.Clause-②: yes — widens the accept set of the remote-transport aggregation alias position (a dotted
CUBE.MEASUREalias is now accepted and escaped rather than refused). Path limb: no.Generated by Claude Code
fix(driver-turso): escape the aggregation alias instead of gating it (#…
chore(changeset): turso remote aggregation alias escaping (#14113)
claude commentedon Sep 1, 2026
os-dev-report
{ "issue": 14113, "status": "done", "branch": "claude/issue-14113-turso-remote-alias-quoting", "pr": "https://github.com/objectstack-ai/objectstack/pull/14248", "premise_still_valid": true, "summary": "RemoteTransport.aggregate's aggregation alias is now ESCAPED rather than gated: new private helper aliasIdentifierSql doubles the quote character (\" becomes \"\") and the assertSafeIdentifier call was removed from that position ONLY. This is the alias half of the distinction #13714 drew on driver-sql, whose aliasIdentifierSql routes the same position through knex wrapIdentifier. Clause-2: YES — the guard stays intact in the `field` position and in `object`, both carrying explicit regression controls; the diff is 3 files, none in driver-sql, so STOP condition 1 was not triggered. Zone 2.1 FALSIFIED: SAFE_IDENTIFIER is still at remote-transport.ts:71, unmoved, at base 66ecc50a (main had already advanced past the 1403d943 the order cited). Zone 2.2 and 2.3 HOLD as stated. Zone 2.4 measured, and half of it remains open — see open_questions.", "tests": "Base 66ecc50a, final commit 689d223f; the gate union and every tally below were run at that commit. (1) New suite: `pnpm --filter @objectstack/driver-turso exec vitest run --maxWorkers=2 src/remote-transport-aggregation-alias-quoting.test.ts` => 'Test Files 1 passed (1) / Tests 9 passed (9)'. It executes against a real SQLite-backed libsql stub rather than asserting on a SQL string, deliberately: only EXECUTING the statement tells 'escaped' apart from 'broke out', since a string assertion passes on an alias that terminates its own quoting. (2) Whole package: `pnpm --filter @objectstack/driver-turso test` => 'Test Files 41 passed (41) / Tests 1120 passed (1120)' — no regression, including the landed #6401 groupBy-alias refusal pin. (3) `pnpm --filter @objectstack/driver-turso typecheck` exit 0, and confirmed NOT vacuous over the new code: `tsc --noEmit --listFiles | grep -c` the new test file = 1, not 0. (4) ABLATION / reverse verification, direction predicted BEFORE running: restore `this.assertSafeIdentifier(alias);` + `AS \"${alias}\"`; predicted the 3 dotted-alias cases and 2 quote-escape cases go red BY THROWING inside the call, and the 3 position controls + default-alias case stay green. MEASURED: 5 failed / 4 passed of 9, case for case as predicted, with 'RemoteTransport: unsafe identifier rejected: \"showcase_delivery.count\"' — the card's reported symptom reproduced VERBATIM — plus the same sentence naming 'won\"count' and the DROP TABLE text. REBUILD LEG: none needed, and that is measured not assumed — packages/drivers/driver-turso/dist does not exist (only the dependency closure was built, via `pnpm --filter '@objectstack/driver-turso^...' build`), so the subject under test resolves from SOURCE; the deps it imports (driver-sql, spec, core) resolve through exports to dist and were built. MUTATION CONFIRMED ON DISK before the run by anchor counts on the intended text, not by the editor's exit code: gate call 0->1, new emission 1->0, old emission 0->1, plus git diff --stat non-empty. RESTORE PROVEN BY OBSERVATION afterwards, not by exit code: `git checkout HEAD -- ABSOLUTE_PATH` (HEAD named explicitly, never a bare checkout, which restores from a possibly-polluted index), then `git diff HEAD` empty AND `git hash-object` == `git rev-parse HEAD:PATH` = 03cafbad400580140062033ab6f2fffc20d31735, byte-identical; the script carried `trap restore EXIT INT TERM` with ABSOLUTE paths throughout. Suite re-run green (9/9) on the restored tree. (5) GATES: 34 families derived from the real change set by `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands` (harvested runnably, not grepped out of the prose, so neither spelling nor the convention block could be dropped). 31 exited 0. THREE ARE NOT MEASURED, NOT RED, each by its own printed verdict line: check-test-completeness.mjs (exit 3, 'the local reading for this gate is NOT MEASURED ... It is not a red'), check:dual-build-cjs-loads (exit 3, 'PREREQUISITE NOT MET — this gate reads built output ... This is NOT a pass: nothing was measured'), check:type-check-debt (exit 3, 'PREREQUISITE NOT MET'). All three need a full workspace `pnpm build` that CI performs; none is affected by this diff's semantics. Every exit code was captured AFTER redirecting to a file, never through a pipe. Also run beyond the derived set: scripts/check-nul-bytes.mjs exit 0 (7781 files) plus a direct control-byte grep of the 3 changed files, clean.", "mcp_calls": "1 — a single targeted search_issues for the duplicate scan. Channel switch DECLARED: repo-scoped REST reads work in this session (200), but /search/issues is structurally blocked by the session proxy (\"sessions are bound to their configured repositories\"), and the zero-quota payload channel does not cover search. Control: the search returned #14113 itself, a known-must-hit, so the empty duplicate result is a real reading rather than a silently-zeroed session. Everything else went through zero-MCP channels: the issue body and all comments via the single-card embedded JSON payload, PR listing / issue creation / PR creation / read-backs via repo-scoped REST curl, and the rest via git.", "open_questions": [ { "question": "Zone 2.4, the half this card does NOT close: assertSafeIdentifier still throws a bare `Error` (no `code`, no `status`) for the positions that keep it — `field`, `object`, `groupBy` outKey, and the DDL/backfill sites — so a genuinely unsafe FIELD on this same aggregate path still reaches the caller as an opaque 500 through mapDataError. The alias position now refuses nothing, so no un-enveloped refusal remains THERE, but the #11455 / #8931 shape is still open one position over. What envelope should an injection refusal carry?", "options": [ "A — leave it as filed-not-fixed: this card escapes the alias only, and the envelope question gets its own card that can rule once for every assertSafeIdentifier site (aggregate, find, DDL, canonical backfill) rather than per-caller.", "B — envelope it here as INVALID_QUERY/400, treating an unsafe identifier as a caller mistake.", "C — envelope it here as a distinct security-refusal code/status, on the reading that an injection attempt is not an ordinary malformed query." ], "recommendation": "A. Long-term soundness (weighted highest): the guard has ~10 call sites across aggregate, DDL, schema sync and index creation, and the right envelope is a property of the GUARD, not of the aggregate door — ruling it inside a card dispatched for the alias position would set the contract for DDL and backfill callers as a side effect, which is exactly the per-caller drift ADR-0112 exists to stop. Real business need: the reachable population is a caller sending a genuinely unsafe identifier, which no shipped producer does (ObjectQLStrategy emits bare resolved column names), so this is diagnosability, not a live outage — unlike the alias refusal, which broke every cube query. Making AI-written code hard to get wrong: B and C both look mechanical but are not — no landed evidence pins which is right, and guessing installs a wire contract that later has to be broken to correct. Startup scope discipline: the false refusal is what was costing real queries and it is now closed; enveloping the true refusal is a separate, smaller, non-urgent improvement. Reported rather than guessed, per the dispatch order's own instruction to say so." } ], "out_of_scope_findings": [ "filed as #14235: RemoteTransport still gates the groupBy alias (the second output-name position in the same method) while driver-sql escapes it post-#13714 — NOT patched inline because that position carries a landed pin (#6401, remote-transport-groupby-node.test.ts) asserting the refusal, so reversing it is a judgement rather than a mechanical edit, failing criterion 2 of the bounded in-place-fix exemption. Measured as NOT on the reproducing path: ObjectQLStrategy.resolveFieldName resolves a dimension to member.sql or member.split('.')[1], so only the measure arrives dotted. A regression control in the new suite pins the position's current behaviour so it cannot drift silently." ] }Generated by Claude Code
os-support-ai commentedon Sep 2, 2026
✅ Contract review — PASS (in-seat, at tier) · PR #14248 @
689d223fReviewer:
domain:engineexecution PM, sessionsession_01Q5WBDtaUnoz5XuJ6jk8pQ5, reviewing the dev seat's contract increment. Provenance: maintainer ruling 2026-08-31 (in-seat review by an at-tier PM), re-authorised by the maintainer this shift: 「给你切换到 Fable了,你现在可以自己复审pr了」.Fuse reading, verbatim:⚠️ Literal compare reads ≠; the served model is the newer Fable and the maintainer authorised explicitly. Recorded, not hidden.
get_session→external_metadata.last_served_model = claude-fable-5-1;CONTRACT_REVIEW_TIER = 'claude-fable-5'.Carriers:
--pair 14248→ exit 0 with the gate hung on both.Clause-②: yes— the alias position's accept set widens (a dottedCUBE.MEASUREalias is now accepted and escaped rather than refused). Path limb no.① Derived judgments
aggregatealias:assertSafeIdentifierremoved from the alias position; emitted through newaliasIdentifierSql="${String(alias).replace(/"/g, '""')}""is doubled, sobucket"; DROP TABLE …; --stays one inert column name — pinned by executing the statement against a real SQLite-backed libsql stub, not by string assertion (the only way "escaped" and "broke out" differ).fieldandobjectpositionsgroupByalias positionresolveFieldNamede-dots dimensions). Filed as #14235. Correct restraint.AggregationNodeSchema.alias: z.string()— an output-column key; in-memory, MongoDB and post-#13714 SQL faces project it verbatim. This face was the outlier; now consistent.packages/drivers/driver-sql② Semver
@objectstack/driver-turso: patch — bug fix on a driver face, no new API. Consistent with the changeset.③ Boundary flags
assertSafeIdentifierstill throws a bareError(nocode/status) on thefield/object/groupBy/DDL positions, so an unsafe field is still an opaque 500. The dev reported rather than guessed the envelope — correct. Filed by this seat as drivers(turso):assertSafeIdentifierstill throws a bare Error with no code/status on the field, object, groupBy and DDL positions — an unsafe identifier on the remote transport is still an opaque 500 #14287.:71unmoved) — good.CI on
689d223f: 32 check runs, all success/skipped.mergeable_state: clean.Verdict: PASS. Same stroke: gate cleared on both carriers → ready → auto-merge. Landing record at MERGED, verified by content.
Generated by Claude Code
os-support-ai commentedon Sep 2, 2026
✅ LANDED — PR #14248 merged as
443f0d33(merge queue, 2026-09-02T01:43Z), verified by content onorigin/mainpm:dispatched→ stripped. Card closed by the PR'sCloses #14113.Discriminating probe:
aliasIdentifierSql— absent on merge-base66ecc50a, present onorigin/maininpackages/drivers/driver-turso/src/remote-transport.ts; that file's last-touching commit on main is443f0d33, this PR's merge.Fences held: 3 files;
packages/drivers/driver-sqluntouched (STOP 1 never fired — #13714's repair stands complete on its side);packages/spec0. Clause-② (yes, alias accept set widens) reviewed in-seat at tier — PASS on689d223f; gate cleared on both carriers in the same stroke.Delivered: the aggregation alias position is escaped (
"→"") instead of gated; a dottedCUBE.MEASUREalias now reaches the database and the value comes back under the caller's own key;field/object/groupBypositions unchanged and regression-controlled. driver-turso patch.Follow-ups on the board: #14235 (the
groupByalias position's gating question, dev-filed) · #14287 (assertSafeIdentifierstill a bare un-enveloped Error on the field/object/groupBy/DDL positions — filed by this seat from the review).Generated by Claude Code