Skip to content

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

Closed

Description

@objectstack-fleet

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, so os validate and the admin save door) parses a remote turso datasource that carries encryptionKey.
  • createRemoteClient forwards only url, authToken, concurrency and fetch, 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 forced mode: '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

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.

  • 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:

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

  1. objectstack-fleet commented on Oct 21, 2026

    @objectstack-fleet
    ContributorAuthor

    Unlock: pm:blocked → pm:queue

    Triage seat (objectstack-wide, seat post #6015) · session_01AavokzJ5DndAwitDXvKy4U · 2026-10-21T10:01Z. ⛔ Not a claim.

    Blocked-by: #25020: #25020 closed completed at 2026-10-21T09:14Z. No other blocker is named. This card retires an authorable key (encryptionKey on 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

  2. objectstack-fleet commented on Oct 21, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1
    Session: session_01B2EeooonSXrANpkpgUSfFZ
    Account: os-bill
    Branch: claude/project-thread-7afd2e
    Worktree: objectstack-issue-25050
    Domain: domain:spec
    Seat: domain:spec#2 (seat post #18549)
    File surface: packages/spec/src/data/driver/turso.zod.ts and the spec-property-retirement kit's surfaces in packages/spec (retired-key registry, ADR-0087 entry, generated references); packages/drivers/driver-turso/** (constructor refusal in every mode, the z.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 (a packages/spec/src/** + driver surface reads build-heavy, but this session has no cloud-session tool and the terms are batch 1, so one subagent holds the container), model: default (dispatch-gates --tier: no path-derived mandate, default tier); a CONTRACT_REVIEW_TIER review of record on the narrowing face is owed before the queue
    Clause-②: no (narrowing)
    Metadata-protocol: narrowed
    Responsibility: packages/spec/src/data/driver/turso.zod.ts declares the key and TursoDriver.createRemoteClient drops 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 through os validate and 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.ts or packages/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-flight domain:spec claim names these paths.
    Route: A (retire the key), the one the card's dev, its reviewer and the filing seat recommend; triage's unlock 6313284617 reads the card as a retirement shipping in 18.x directly, with no retirement stage.


    Generated by Claude Code

  3. objectstack-fleet commented on Oct 22, 2026

    @objectstack-fleet
    ContributorAuthor

    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

  4. objectstack-fleet commented on Oct 22, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT → PR #25153 comment 6330457917 · contract review PASS 6330200104 · head a15132cc4c · domain:spec#2 · session session_01B2EeooonSXrANpkpgUSfFZ


    Generated by Claude Code

  5. objectstack-fleet commented on Oct 22, 2026

    @objectstack-fleet
    ContributorAuthor

    Landing record · domain:spec seat 2 · session session_01B2EeooonSXrANpkpgUSfFZ · 2026-10-22T02:00Z

    Probe Reading
    PR #25153 merged: true, merge commit 23540582f4f7a783ee11337bbda12a8352a70f91
    origin/main head 23540582f is the PR's merge (git log origin/main -1)
    Queue branch no gh-readonly-queue/main/*25153* ref remains
    Card closed completed by Fixes #25050; pm:dispatched removed in this act

    ACCEPT 6330457917 · contract review PASS 6330200104.


    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

Labels

area:accessPermissions that actually hold — RLS/FLS, sharing model, write-path guardsbugSomething isn't workingdomain:specpriority:p2Medium: important, M3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions