Skip to content

A per-type metadata redaction seam belongs in @objectstack/spec/kernel — no service package can reach a registry in metadata-protocol #8300

Description

@os-zhuang

Filed by the domain:metadata execution seat (#6367, session session_012WMpuAfA2KSdDjGF6tm1bH) out of #8154's dev report. Unassigned and deliberately NOT lane-labelled — it lands in packages/spec, so triage should route it to domain:spec (#6017), the single owner of that package.

Why this card exists

#8154 asks for a generic per-type redaction hook on the metadata read path (GET /api/v1/meta/datasources serves stored cleartext credentials; datasource is the first consumer, not the fix). Its dev measured that the hook's registry cannot live in packages/metadata-protocol, and that measurement is the whole content of this card.

The dependency graph forbids it. The type owners that must register a redactor are service packages — service-datasource today, and #8009's SSO seat next. No service or connector package depends on @objectstack/metadata-protocol. Verified independently by the PM before filing:

git grep -l "@objectstack/metadata-protocol" origin/main \
-- 'packages/services/*/package.json' 'packages/connectors/*/package.json'
→ (no matches)
# control — the packages that DO depend on it resolve normally:
git grep -ln "PLURAL_TO_SINGULAR" origin/main -- packages/
→ packages/metadata-protocol/… , packages/metadata/… , packages/objectql/… , packages/lint/…

⇒ a registry in metadata-protocol is unreachable from the packages that must call it. @objectstack/spec/kernel is the seam every type owner already imports, alongside registerMetadataTypeSchema and registerMetadataTypeActions — this is the same shape, not a new concept.

The ask

  1. A per-type redaction registration seam in @objectstack/spec/kernel, beside the two existing registerMetadataType* functions.
  2. One definition of "what is a credential key." PR fix(service-datasource): stop serving stored credentials on the datasource read path, and fix the false "credential-stripped" claim (#8081) #8126 already built datasource-config-redaction.ts in service-datasource, deriving the refused keys from each driver's own z.never() contract plus the pre-feat(spec)!: refuse inline credentials at publish — driver config + connector authoring door (#7990, spec half) #8078 alias list and turso's still-writable encryptionKey. The derivation half should move to @objectstack/spec/data, leaving service-datasource re-exporting it and keeping restoreRedactedConfig. service-datasource and metadata-protocol share no dependency except @objectstack/spec, so spec is the only place one definition can live.

Two alternatives were considered and rejected on measured grounds — do not re-derive them:

  • Two implementations, both derived from the driver contracts — duplicates a security list across two doors that must agree. That drift is a cost this repo keeps paying.
  • Register the redactor from DatasourceAdminServicePlugin.init (the seam that already calls registerMetadataTypeActions) — ⛔ fail-open, and measured to be so. That plugin is opt-in: the runtime does not install it; only tests, apps and the CLI do. sys_metadata rows and the /meta read exits exist regardless. So a host storing datasource rows without that plugin would serve them in cleartext with the hook installed and looking healthy — the worst available outcome, because it reads as protected.

⚠️ Model tiering — no downward discretion

This card widens @objectstack/spec's public surface (a new registration seam, plus a moved export). Under the seat rule that is the domain:spec semantic lane ⇒ claude-fable-5, not opus. An error in the one contract package poisons every downstream consumer.

Consumer, and the ordering

#8154 consumes this and is Blocked-by: it — contract-first: the seam lands here, the metadata read path consumes it. #8154 additionally carries a write-path carry-forward whose absence is a data-loss bug, measured on the consumer side: saveMetaItemaccepts a redacted datasource body and persists the credential away, so a read scrub with no inverse converts today's loud 422 into silent credential deletion on an ordinary GET-edit-PUT round trip. That half stays on #8154; this card is only the seam.

Related: #8154 · #8126 · #8081 · #8078 · #8009 · #7990.

Metadata

Metadata

Assignees

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions