Skip to content

fix(spec): datasource read redaction resolves a driver's identity the way its sibling helper does - #21963

Merged
objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-21955-redaction-driver-identity
Oct 6, 2026
Merged

objectstack-fleet[bot] merged 3 commits into
mainfrom
claude/issue-21955-redaction-driver-identity

Conversation

@objectstack-fleet

Copy link
Copy Markdown
Contributor

Fixes #21955
Clause-②: no

What changed

The per-driver half of redactableConfigKeys in packages/spec/src/data/datasource-credential-redaction.ts now resolves a driver's identity through resolveDriverId. That is the resolver its sibling helper passthroughSecretPaths already uses, and the one the write door's contract lookup (getDriverConfigSchema) uses. There is no second identity resolver: the fix replaces one lookup line with the sibling's two-line shape.

  • Every spelling the write door judges against a builtin driver's contract is now redacted as that driver. The still-writable credential key is withheld on the datasource admin item read and on the metadata read under every accepted spelling, and redactedConfigKeys names it.
  • A crafted driver id now has a defined answer. It resolves to no builtin, so it answers as a driver the platform ships no contract for: the canonical credential spellings and the former aliases are withheld, and the read succeeds. It is never served and never throws.
  • No service-datasource source line moves. getDatasource(), restoreRedactedConfig and the credential migration all reach the fix through the spec export (measured below).

Measured reach, before the fix (BASE 76fec88b16, spec dist built from BASE)

A unit-level harness of service-datasource's DatasourceAdminService, with an in-memory record store, ran every spelling the alias table and the resolver's folding accept. The harness is scratch and is not committed.

  • Write door: every accepted spelling was accepted by DatasourceSchema.safeParse and by createDatasource, and was judged against the builtin contract (validateDriverConfig(...).known === true).
  • Admin item read (getDatasource, behind GET /api/v1/datasources/:name): for each accepted spelling other than the canonical one, the still-writable credential key came back in config and redactedConfigKeys was []. The canonical spelling withheld it.
  • Metadata read (the built-in datasource redactor, used by the /meta item and list reads): the same answer as the item read for every spelling.
  • Admin list read (listDatasources): it serves no config at all, by its contract (DatasourceSummary), so it carries no exposure for any spelling.
  • Crafted driver id: the write door accepted the row as an unknown plugin driver. Then getDatasource, the metadata redactor, restoreRedactedConfig and planCredentialMigration each threw an unhandled TypeError. So the item read failed, an edit with a config patch failed, and the migration planner failed. listDatasources was unaffected.

After the fix, the same harness reads every accepted spelling as withheld and named, and every crafted id as read without a throw.

Exposure stays admin-only: the datasource read doors require the admin capability.

Pins

  • packages/spec/src/data/datasource-credential-redaction.test.ts, a new block. Every spelling is derived from DRIVER_ID_ALIASES plus the resolver's own folding (as-is, upper, capitalised, padded), never listed by hand.
    • Premise: each accepted spelling resolves to its builtin and is judged by the write door against that contract.
    • Each accepted spelling answers its canonical driver's redactable set, byte-equal.
    • The still-writable credential key is withheld under each accepted spelling and named as withheld. It has a population floor, so a derivation that finds nothing fails.
    • Control: a canonical spelling withholds it exactly as before.
    • A crafted driver id answers as a driver with no shipped contract, with credentials withheld and no throw.
  • Lockstep in service-datasource (declared on [PM seat] domain:services — ⏳ vacant (signed off 2026-10-06 by session_011K3zqE8Pv1Evw5hc8tZCnN) #6021):
    • datasource-config-redaction.test.ts: the admin item read withholds the key under each accepted spelling. An untouched Save restores the stored value under each accepted spelling, so redaction never turns a save into deletion. A crafted id is read with its credentials withheld, and an untouched Save keeps them.
    • datasource-credential-migration.test.ts: the planner reads the same list under each accepted spelling. A bindable row names the still-writable key as residue, byte-equal to the canonical spelling's plan. A row holding only that key is refused with it named.

Reverse verification (fix committed first, then reverted with scripts/ablation-replace.mjs, then restored)

  • Spec pin. The subject is imported relatively from src, so no build leg applies. Predicted three red, with the premise and the control staying green. Observed Tests 3 failed | 41 passed (44): the byte-equal set, the withheld key, and the crafted id. Restore proof: blob b543590328ef equals HEAD, and git diff HEAD is empty.
  • service-datasource pins. These resolve @objectstack/spec/data through dist, so each leg rebuilt spec.
    • Mutate leg: ablation-dist-preflight found the reverted line in 8 built files. Predicted five red. Observed Tests 5 failed | 61 passed (66).
    • Restore leg: rebuild, then --absent reported the marker absent from all 228 built files and the tree clean against HEAD. Then Tests 66 passed (66).

Clause-② (measured)

no. The fix narrows what the read path serves, not what @objectstack/spec accepts.

  • No schema file changed. DatasourceSchema.safeParse gave the same answer before and after for every spelling and every crafted id in the harness.
  • check:api-surface: public API surface + factory signatures unchanged.
  • check:authorable-surface and check:export-origins are green.

The changeset is @objectstack/spec patch. service-datasource takes none, because no source line in it moved.

Tests and gates (at HEAD cbea11408e)

  • pnpm --filter @objectstack/spec exec vitest run --project local --maxWorkers=2: Test Files 619 passed (619), Tests 18476 passed | 1 todo.
  • pnpm --filter @objectstack/service-datasource exec vitest run --maxWorkers=2: Test Files 41 passed (41), Tests 748 passed (748).
  • @objectstack/metadata-protocol, which reaches the datasource redactor: protocol.metadata-redaction.test.ts and stored-metadata-body-family.pin.test.ts gave Tests 56 passed (56).
  • pnpm --filter @objectstack/service-datasource typecheck and pnpm --filter @objectstack/spec typecheck (including check:test-typecheck) both exit 0. tsc --listFiles confirms that both edited service-datasource test files are in the program.
  • node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack: 87 derived, 86 run with exit 0, 1 NOT MEASURED, 0 unrun (--ran reconciliation).
    • NOT MEASURED: pnpm check:dual-build-cjs-loads, reason: PREREQUISITE NOT MET (exit 3). It reads every package's built dist/, and this worktree built only the closure it needed. Declared to CI.
  • Lint is a proven narrowing, not the repo-wide run.
    • ① The checked population is the four touched .ts files. Each resolves to a config under eslint --print-config, so none is ignored.
    • ② eslint --no-inline-config --format json linted 4 files with 0 errors and 0 warnings.
    • ③ Invariance: eslint.config.mjs enables no type-aware linting (zero project / projectService entries, and none in the resolved parserOptions). So this diff cannot move any untouched file's verdict.
    • The repo-wide pnpm lint stays CI's.
  • origin/main was re-fetched before this PR opened (80f9f7e6ba). Nothing that landed since BASE touches these files, so there was no merge.

Acceptance notes

  • STILL_WRITABLE_CREDENTIAL_KEYS and PASSTHROUGH_SECRET_PATHS are typed as string-keyed records. Typing them by the builtin id union would make a raw-string index a compile error, closing this defect class at tsc. That is not done here, to keep the fix to the sibling's shape. Carrier: the at-tier contract review of this PR.

Generated by Claude Code

claude added 3 commits October 6, 2026 06:16
… way its sibling helper does

The per-driver half of `redactableConfigKeys` now looks a driver up through
`resolveDriverId`, the resolver `passthroughSecretPaths` and the write door's
contract lookup already use. Every spelling the write door judges against a
builtin driver's contract is redacted as that driver, and a crafted driver id
answers as a driver with no shipped contract instead of throwing.

Claude-Session: https://claude.ai/code/session_01T9u38rswFp5Rw8DswRUReJ
Co-authored-by: Claude <noreply@anthropic.com>
… credential migration hold under every accepted spelling of a builtin driver

Lockstep pins for the spec redaction fix: the item read withholds the
still-writable credential key under each accepted spelling, an untouched Save
restores the stored value, a crafted driver id is read with its credentials
withheld, and the migration planner reads the same list as under the canonical
spelling. No source line in this package moves.

Claude-Session: https://claude.ai/code/session_01T9u38rswFp5Rw8DswRUReJ
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added the size/m label Oct 6, 2026
@github-actions

github-actions Bot commented Oct 6, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

1 anchor(s) derived from 1 changed package(s); no hand-written page names any of them, so this run has nothing to list — not a clean bill of health. This check sees only pages that NAME a derived anchor: one that documents this change in prose, or enumerates it in an authoring dialect, names none and stays invisible to it on every run.

What this run could not see
  • 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 — 138 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 80f9f7e6ba5d2097a4cb32ca696908dcf9678102 → packageMentionDocs.

Which tree this was computed on

This run read content/docs from 9c12aae5c41bf6212c21662baad61cf30fc6a747 — the merge of head cbea11408e36bb46b64f71d04cd367a442ef4777 into base 80f9f7e6ba5d2097a4cb32ca696908dcf9678102, 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 9c12aae5c41bf6212c21662baad61cf30fc6a747 && git checkout 9c12aae5c41bf6212c21662baad61cf30fc6a747
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 80f9f7e6ba5d2097a4cb32ca696908dcf9678102 cbea11408e36bb46b64f71d04cd367a442ef4777 && git checkout -B drift-repro 80f9f7e6ba5d2097a4cb32ca696908dcf9678102 && git merge --no-ff cbea11408e36bb46b64f71d04cd367a442ef4777

node scripts/docs-audit/affected-docs.mjs --json 80f9f7e6ba5d2097a4cb32ca696908dcf9678102

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

@github-actions github-actions Bot added documentation Improvements or additions to documentation protocol:data tests tooling labels Oct 6, 2026
@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 6, 2026 07:53
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 6, 2026 07:53
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 6, 2026
Merged via the queue into main with commit fb69825 Oct 6, 2026
37 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-21955-redaction-driver-identity branch October 6, 2026 08:24
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/m tests tooling

Projects

None yet

2 participants