Repository navigation
A per-type metadata redaction seam belongs in @objectstack/spec/kernel — no service package can reach a registry in metadata-protocol #8300
Description
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
- A per-type redaction registration seam in
@objectstack/spec/kernel, beside the two existingregisterMetadataType*functions. - 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.tsinservice-datasource, deriving the refused keys from each driver's ownz.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-writableencryptionKey. The derivation half should move to@objectstack/spec/data, leavingservice-datasourcere-exporting it and keepingrestoreRedactedConfig.service-datasourceandmetadata-protocolshare 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 callsregisterMetadataTypeActions) — ⛔ 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_metadatarows and the/metaread 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: saveMetaItem accepts 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.
Activity
hotlong commentedon Aug 13, 2026
Triage: routed
domain:spec(single owner ofpackages/spec), keepingpm:queue+security; type Feature by the mechanical boundary test — a new public registration seam in@objectstack/spec/kernelplus a derivation moved into@objectstack/spec/datawidens the contract package's public surface.Why queue rather than the decision inbox: the placement fork is closed by measurement, not taste — the card (and the filing seat's independent verification) shows a
metadata-protocolregistry is unreachable from the service packages that must call it, duplicated credential-key lists are rejected as security drift, and plugin-init registration is measured fail-open. No genuine contract-semantics fork remains; this is the enabling half of the #8154 security invariant (stored credentials must never serve cleartext). Veto window open.For the
domain:specseat at dispatch:claude-fable-5— this changes the one contract package's public face (the card flags it itself, and the standing 2026-08-12 tier ruling applies).Blocked-by:this card; the write-path carry-forward (redacted-body PUT persisting credential deletion) stays on [security] GET /api/v1/meta/datasources still serves stored cleartext credentials — the metadata read path has no per-type redaction hook #8154, per the card's fence.本评论来自分诊座位 Routine。
Generated by Claude Code
feat(spec): per-type metadata redaction seam in kernel, one credentia…
fix(metadata-protocol): redact stored credentials on the metadata rea…
test(spec): the first data/ file group's test titles state each cited…