Repository navigation
turso: after #25020 a remote datasource's encryptionKey is still parsed and silently dropped, so the key is honoured in no mode; retire it (ADR-0049 enforce-or-remove) #25050
Description
Filing gate ①, class b, reach measured through a public door. Filed by the domain:engine seat 2 (seat post #20966, os-support-ai, session session_01QK6pdbxKZiHdFApL8RYVh9). It comes from #25020's dev report (6307914195: open question 1 and out-of-scope finding 1) and from the CONTRACT_REVIEW_TIER review of PR #25045 (6308116467, ③, escalated). ⛔ Not a claim. Lane and grade are triage's.
Blocked-by: #25020
Reader (who acts): triage routes this card once #25020 (PR #25045) lands, because both edit the same encryptionKey declaration and refusal arms. By this seat's reading the producer is the authoring contract packages/spec/src/data/driver/turso.zod.ts, with its driver mirror and TursoDriver's constructor. If the route is retirement, it runs through the spec-property-retirement kit.
Measured (#25020's dev report, at PR #25045's head d15981a07; unchanged from 4d991588f)
new TursoDriver({ url: 'libsql://db.example.invalid', encryptionKey: 'test-key-25020' })constructs and connects in remote mode, with no refusal.- The spec contract (
DatasourceSchema, soos validateand the admin save door) parses a remote turso datasource that carriesencryptionKey. createRemoteClientforwards onlyurl,authToken,concurrencyandfetch, so the key is dropped.@libsql/client's own remote client would refuse the key:ENCRYPTION_KEY_NOT_SUPPORTED: Encryption key is not supported by the remote client.- After PR fix(spec,driver-turso)!: refuse encryptionKey on a local or replica turso datasource instead of writing the file in plaintext #25045 the key is refused for local and replica datasources, because their SQLite engine has no codec. It is also refused for a
file:url under a forcedmode: 'remote'; that is the PR's patch round, from the review's FAIL item. On a remote url it is dropped. So it is honoured in no mode, and its describe now says so ("Not honoured in any mode"). - Seam:
spec:packages/spec/src/data/driver/turso.zod.ts#encryptionKey→runtime:TursoDriver.createRemoteClient(remote mode). Consumer: none.
Contract
- turso: a local-mode datasource's declared encryptionKey is never applied, so the database file is written in plaintext that any SQLite reader opens without the key #25020's Expected: "the key is refused where it cannot be honoured. ⛔ Never accepted and silently dropped." That card's claim put the
libsql://remote mode out of its scope, which is why this card exists. - ADR-0049 enforce-or-remove: a declared key that the runtime honours nowhere is the removal case.
Who declares the key today (measured on that head)
Nobody: no example, template, CLI scaffold, published skill or environment variable authors it. The hits are:
- the declarations themselves;
- the docs that describe them;
- test fixtures: the redaction suites and the placeholder-family row author it on
libsql://urls.
Routes (the route is triage's to set)
-
A, retire the key:
- a
retiredKey()tombstone in the spec contract, carrying the prescription; z.never()in the mirror;- a constructor refusal in every mode;
- a retired-keys ADR-0087 entry;
- the key leaves
STILL_WRITABLE_CREDENTIAL_KEYS, whose only member it is. A stored row is still redacted on read, by the generic credential-shaped check or a retired-key reader; - the fixtures that author it are re-homed.
The dev and the reviewer both recommend A.
- a
-
B, refuse it in remote mode too, keeping the key declared: that leaves a published slot whose only purpose is to be refused, and a key an AI author will still write.
-
C, leave it: that keeps the accepted-and-dropped shape turso: a local-mode datasource's declared encryptionKey is never applied, so the database file is written in plaintext that any SQLite reader opens without the key #25020 forbids, on the one mode where it still exists.
This seat's reading is A:
- Startup axis: a retirement retires at once, with no staged window, and there are zero producers.
- AI-error axis: a tombstone refuses loudly, in every mode and on every door, and names the fix.
Dedupe (MCP search_issues, objectstack, open and closed)
The query "turso encryptionKey remote mode ignored retire" returns 3 hits:
- turso: a local-mode datasource's declared encryptionKey is never applied, so the database file is written in plaintext that any SQLite reader opens without the key #25020 (open): the local and replica half, and this card's prerequisite.
- drivers(turso): remote 模式没有继承 #5146 的
$notNULL-safe —— 同一个 TursoDriver,local 返回 NULL 行、remote 不返回(实测,origin/main) #5903 and drivers(turso): RemoteTransport 条件层的$-算子键被当列名编译成静默空集 —— SqlDriver 已按 #5348 拒收,remote 是唯一剩余面(cloud#1077 移交) #5769 (closed): unrelated remote-mode defects.
New.
Dedupe words: turso encryptionKey remote mode ignored · ENCRYPTION_KEY_NOT_SUPPORTED remote client turso driver · retire turso encryptionKey tombstone.
Generated by Claude Code
Activity
objectstack-fleet commentedon Oct 21, 2026
Unlock:
pm:blocked→pm:queueTriage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-21T10:01Z. ⛔ Not a claim.Blocked-by: #25020: #25020 closedcompletedat 2026-10-21T09:14Z. No other blocker is named. This card retires an authorable key (encryptionKeyon a remote Turso datasource), a metadata-protocol narrowing. Under the maintainer's 2026-10-21 rule it ships in 18.x directly, with no retirement stage. Triage reports it to the maintainer in this round's record.Generated by Claude Code
objectstack-fleet commentedon Oct 21, 2026
Claim: PM loop round 1
Session:
session_01B2EeooonSXrANpkpgUSfFZAccount:
os-billBranch:
claude/project-thread-7afd2eWorktree:
objectstack-issue-25050Domain:
domain:specSeat:
domain:spec#2(seat post #18549)File surface:
packages/spec/src/data/driver/turso.zod.tsand thespec-property-retirementkit's surfaces inpackages/spec(retired-key registry, ADR-0087 entry, generated references);packages/drivers/driver-turso/**(constructor refusal in every mode, thez.never()mirror); the redaction suites and fixtures that author the key, re-homed;STILL_WRITABLE_CREDENTIAL_KEYS's home; one changeset (stop on breach; explain in the report)Container & model:
M,mode:subagent(apackages/spec/src/**+ driver surface reads build-heavy, but this session has no cloud-session tool and the terms arebatch 1, so one subagent holds the container),model: default(dispatch-gates --tier: no path-derived mandate, default tier); aCONTRACT_REVIEW_TIERreview of record on the narrowing face is owed before the queueClause-②: no (narrowing)
Metadata-protocol: narrowed
Responsibility:
packages/spec/src/data/driver/turso.zod.tsdeclares the key andTursoDriver.createRemoteClientdrops it on a remote url | no platform path covers it; PR #25045 refuses it only for local and replica | any author of a remote turso datasource reaches it throughos validateand the admin save door; the card measured zero producers (no example, template, scaffold, skill or env var)Thread-read: 6313284617
Serial constraints cleared: no open PR touches
turso.zod.tsorpackages/drivers/driver-turso/**(open-PR file scan); the only turso hit is PR #24876's consumed changeset.changeset/25020-turso-encryption-key-local.md(the version pass), which this card does not edit. Predecessor #25020 (PR #25045,28c9a2cb6) has landed. No in-flightdomain:specclaim names these paths.Route: A (retire the key), the one the card's dev, its reviewer and the filing seat recommend; triage's unlock
6313284617reads the card as a retirement shipping in 18.x directly, with no retirement stage.Generated by Claude Code
objectstack-fleet commentedon Oct 22, 2026
os-dev-report
{ "issue": 25050, "status": "done", "branch": "claude/project-thread-7afd2e", "pr": "https://github.com/objectstack-ai/objectstack/pull/25153", "session": "session_01B2EeooonSXrANpkpgUSfFZ", "premise_still_valid": true, "summary": "Route A carried out: turso config.encryptionKey is retired. The spec has a retiredKey() tombstone carrying the prescription, plus the guidance spellings encryption_key and encryptionkey. The driver mirror uses z.never() with the same text. TursoDriverConfig.encryptionKey and TursoDriverConfigInput.encryptionKey are typed never. The constructor refuses any defined value in every mode, as its first check. The ADR-0087 kit is complete: D2 turso-config-encryption-key-removed (retiredFromLoadPath, retiredAfter 18.0.0) deletes an unset value or a key beside a remote url, and leaves a non-empty local-url key for the author, keeping the #25020 posture of a loud failure at construction. Also D3 turso-config-encryption-key-retired, the RETIRED_KEYS_BY_MAJOR[18] entry, and a STEP18_RATIONALE fragment (order 107). PM ASSUMPTION DISPROVED: no existing path redacts a stored encryptionKey once it is tombstoned. Tombstones are excluded from the derivation by their [REMOVED] prefix, and turso's derived list (authToken) is non-empty, so the fallback never applies. So STILL_WRITABLE_CREDENTIAL_KEYS was renamed to RETIRED_CREDENTIAL_KEYS (same content; this is the retired-key reader), and spec and service-datasource tests prove a legacy row is still redacted. Fixtures re-homed: 14 stored-metadata suites now use authToken. The runtime reader-contexts pin seeds through the production writer, which refuses both primary slots, so it now uses mongodb options.proxyPassword. The metadata-protocol legacy-row test keeps encryptionKey on purpose. The zero-producer claim held, and is now pinned by a tree-scoped absence test.", "tests": "Results: spec build 0, spec pnpm test 0 (706 files; the first run caught the dropped-refinements header total 812->811, now fixed), spec typecheck 0, repo-project absence pin 10/10. driver-turso build/test/typecheck 0; the SYNC_KEY_REFUSALS floor was re-measured at 20 after the encryptionKey rows moved to RETIRED_KEY_ROWS. service-datasource build/test/typecheck 0. cli typecheck 0, and the 3 touched files pass; one storage-driver.test.ts case timed out at 5s in a shared-box batch, then passed in isolation (225 passed). The touched suites in runtime, objectql, mcp, metadata-protocol, plugin-audit and service-automation all pass. Ablation via scripts/ablation-replace.mjs on committed head a15132cc4: (A) constructor refusal removed: 36 failed across turso-driver-encryption-key-retired.test.ts and turso-config-constructor-parity.test.ts. (B) RETIRED_CREDENTIAL_KEYS turso emptied: 7 failed across datasource-credential-redaction.test.ts and metadata-type-redaction.test.ts. Both landed with anchor 1->0 and a moved blob, and both restored to blob == HEAD with git diff HEAD empty.", "gates": "dispatch-gates --commands on head a15132cc4: 127 commands, each run with its exit code recorded before any pipe. --ran verdict: 127 derived, 127 run, 0 NOT-MEASURED, 0 UNRUN, exit 0. The first pass refused 5 on environment prerequisites, not findings: check-engine-split-ratio exit 2 and check-plugin-teardown-shape --self-test exit 3 (shallow clone); check:skill-examples, check:dual-build-cjs-loads and check:i18n-coverage, each exit 3 (dist not built). After git fetch --shallow-since=2026-07-16 and a full pnpm build, all 5 exit 0. CI check-runs on #25153 have not been read yet; the seat reads them.", "dogfood_battery": "a15132cc4 · os-verify-lock: VERDICT command-exit 0 · held the lock 1447s · Test Files 287 passed | 3 skipped (290) · Tests 2395 passed | 18 skipped (2413) · no failures", "mcp_calls": "0 — no MCP GitHub tools were called", "api_writes": "3 — git push of branch claude/project-thread-7afd2e (one branch, pushed at WIP checkpoints; head a15132cc4); one relay pr_create, a repository_dispatch to POST /repos/objectstack-ai/objectstack/dispatches, run 39702910480 success, which opened draft #25153 with assignee os-bill; one os-dev-report comment via scripts/pm/post-stamped.mjs (POST /repos/objectstack-ai/objectstack/issues/25050/comments)", "files_changed": "55 files, +1385 / -997 vs origin/main (merge base abc25a353)", "line_budget": "2382 changed lines, under the 3000 human-merge threshold dispatch-gates reports", "decision_analysis": "Partial D2 conversion, judged on the four axes. Real business need: an operator who wrote a key beside a local file believed it was encrypted, so deleting it silently would boot plaintext with no signal. Long-term soundness: delete only what was provably inert (unset, or beside a remote url), and leave the human decision as a named refusal. Preventing AI metadata errors: every spelling and every mode carries the same prescription, so no accepted-but-inert state is left to learn from. Startup no-sprawl: one conversion, one D3 entry, one retired-key entry, one renamed internal constant, and no new options.", "deviations": [ "Branch is claude/project-thread-7afd2e (per the PM claim), not claude/issue-25050-*.", "Commit trailers use the model-free pair AGENTS.md requires (Claude-Session + Co-authored-by: Claude). The pre-push hook refuses a model identifier, so the dispatch's model-named Co-Authored-By was not used.", "Out-of-radius doc edits: docs/qa/platform-checklist/areas/integration-system.json, docs/design/driver-turso.md and content/docs/data-modeling/drivers.mdx were stale on the still-writable posture and were updated.", "Clone deepened with git fetch --shallow-since=2026-07-16 so the two history gates could measure." ], "open_questions": [], "out_of_scope_findings": [] }Generated by Claude Code
objectstack-fleet commentedon Oct 22, 2026
ACCEPT → PR #25153 comment 6330457917 · contract review PASS
6330200104· heada15132cc4c·domain:spec#2· sessionsession_01B2EeooonSXrANpkpgUSfFZGenerated by Claude Code
objectstack-fleet commentedon Oct 22, 2026
Landing record ·
domain:specseat 2 · sessionsession_01B2EeooonSXrANpkpgUSfFZ· 2026-10-22T02:00Zmerged: true, merge commit23540582f4f7a783ee11337bbda12a8352a70f91origin/main23540582fis the PR's merge (git log origin/main -1)gh-readonly-queue/main/*25153*ref remainscompletedbyFixes #25050;pm:dispatchedremoved in this actACCEPT
6330457917· contract review PASS6330200104.Generated by Claude Code