Repository navigation
fix(spec)!: the datasource read redactor covers the declared ssl secret members - #28674
Conversation
…t members DatasourceSchema marks ssl.key as a credential value, and the built-in datasource redactor applies the schema's credential markers beside its config rule, so every metadata read exit names ssl.key in redactedKeys and serves the public ssl members unchanged. The credential-field ratchet also reads 'private key' and 'passphrase' in a field description. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FEkQ5pQZ6YVyVMRMk27xRe
The /meta item, list and layered reads, the sys_metadata data door and the metadata service's served body withhold ssl.key, keep the public ssl members, and a GET-edit-PUT round trip keeps the stored key. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FEkQ5pQZ6YVyVMRMk27xRe
📓 Docs Drift CheckThis PR changes 1 package(s): ⛔ 2 release-owned page(s) name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 168 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin f85395814eda2185bcd090ace6e633d091ba6d1e && git checkout f85395814eda2185bcd090ace6e633d091ba6d1e
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 2678600d57f70f6913384f03f65c12e480781486 a2d8e887a81615b456977522e92d664f280bc80d && git checkout -B drift-repro 2678600d57f70f6913384f03f65c12e480781486 && git merge --no-ff a2d8e887a81615b456977522e92d664f280bc80d
node scripts/docs-audit/affected-docs.mjs --json 2678600d57f70f6913384f03f65c12e480781486
|
|
domain:server#3 · os-project-manager · session_01FEkQ5kE5BrPUPNMMyv6oCH Contract reviewServed-tier: Inputs: card #28633 (body and all 8 comments, including the ruling record 6557788232, claim amendments 6557238812 / 6557268469 and the dev's os-dev-report 6558370233), PR #28674 (body, file list, net diff at Check-runs on the head at the time of reading (41): 13 completed success (Check Changeset, Governed Surface Queue Guard, PR-body guards, Check PR Size, Auto Label, Dogfood Regression Gate, Spec property liveness, Plan Test Core shards, Lint · PM dispatch-gates, Lint · scoped · tooling self-tests, Flag docs affected, filter), 8 skipped (opt-in and matrix shards), and 20 in_progress: Build Core, Build UI Deps, Test Core 1–8, Lint · ESLint / docs & code guards / repo & tooling gates / CI, release & engine gates / scoped ×2, Type Check ×4, Live E2E UI. That is an honest reading, not a verdict on those families: the dev's local run of the 80 derived families (80 × exit 0, 0 NOT-MEASURED) and the full dogfood (VERDICT exit 0) are the only measured answers so far, and queue entry still waits on the CI conclusions. Nothing below depends on them. ① Derived judgments
② Semver level
③ Boundary flagsDev deviations (os-dev-report 6558370233):
Open question (needs_decision): should the spec also refuse an authored inline Answered: A, as this PR does — and the refusal is not owed by this card. Grounds read in the tree, not the PR body: (i) the published spec on ④ Design judgmentJudged over the floor (every non-prose path of the diff, the two schema-face hits first) against the published spec on Does the narrowing take a declared capability away at the consumer? No. The spec declares Trying to break it:
Nothing found at must-fix. Two should-fix findings, both follow-ups that do not change what this head serves. Implemented-by: VERDICT: PASS |
|
domain:server#3 · os-project-manager · session_01FEkQ5kE5BrPUPNMMyv6oCH 席位核对 · ACCEPT · PR #28674 · 2026-11-01T13:31ZHead-sha: 维护者速读(席位终稿)
核对六项
|
Requested by Leehom · project thread
Fixes #28633
Clause-②: no (narrowing)
Metadata-protocol: none
Skills-docs: none — no page under
skills/**orcontent/docsstates what a metadata read serves of a datasource'ssslblock; the TLS bullet incontent/docs/data-modeling/drivers.mdxstays true as written.BREAKING read-answer narrowing (
@objectstack/spec, shipped aspatch): the metadata read exits no longer serve a datasource'sssl.key. No authored value is refused and no stored row is rewritten.维护者速读
ssl块里声明的密钥成员(ssl.key)一起脱敏,做法与config里的凭据一致:不下发,并在redactedKeys里点名ssl.key。DatasourceSchema的ssl.key是客户端私钥,让它和config凭据走同一条声明式脱敏规则。ca/cert/enabled/rejectUnauthorized照常下发。回滚即撤掉 schema 上的credential标记与脱敏器里那一段。packages/spec/src/kernel/metadata-type-redaction.ts(redactDatasourceItem新增的标记字段一段);packages/spec/src/data/datasource.zod.ts(ssl.key的credential标记);packages/spec/src/kernel/metadata-credential-fields.test.ts(守卫多读两个词)。pnpm --filter @objectstack/spec exec vitest run src/kernel/metadata-type-redaction.test.ts,预期全部通过(main 上新增的 4 条为红)。What changes
The datasource read redactor covers the declared
sslsecret members.packages/spec/src/data/datasource.zod.ts:DatasourceSchema.ssl.key(the client private key) is marked.meta({ credential: 'value' }), the schema-declared credential marker from ruling B+ on meta: thewebhookmetadata type has no redactor, so its secret-bearing fields are not masked on the meta read doors (detail withheld under the disclosure ruling) #24966 (kernel/metadata-credential-fields.ts). It is the only secret member of the declaredsslblock (enabled,rejectUnauthorized,ca,cert,key);caandcertare public material and are not marked. The block declares nopassphrase.packages/spec/src/kernel/metadata-type-redaction.ts:redactDatasourceItemkeeps itsconfighalf (redactDatasourceConfig, unchanged) and adds a second half that applies the credential markers on the registereddatasourceschema, through the same derivation the third resolution uses (derivedCredentialRedactorFor). The built-in wins the lookup over the derivation, so without this half a marker onDatasourceSchemawas never read. The result namesssl.keyinredactedKeysin the same dotted shape asconfig.password, sorted together with theconfigpaths. An item with nothing to withhold is still returned by reference.packages/spec/src/kernel/metadata-credential-fields.test.ts: the credential-field ratchet's description rule also readsprivate keyandpassphrase. With it, an unmarkedssl.keyreds the ratchet (datasource:ssl.key), so the marker cannot be dropped silently..changeset/28633-datasource-ssl-key-redaction.md(@objectstack/specpatch).Why the marker, not a second hand list. The order prefers deriving the set from schema credential markers where the module supports it, and it does: the marker is the one declaration, the derivation already exists, and the ratchet beside it now catches a forgotten one. The
configrule indata/datasource-credential-redaction.tsstays the home of driver-config credentials (derived from each driver'sz.never()contract);sslis not a driver-config member, so it is not added there.Consumers inherit it. Every read exit resolves the
datasourceredactor throughgetMetadataTypeRedactor, so none needed a code change. Measured and pinned:/metalist, item, layered (getMetaItems,getMetaItem,getMetaItemLayered)redactMetadataItem→ registrypackages/metadata-protocol/src/protocol.metadata-redaction.test.tssys_metadata/sys_metadata_historythrough the data API (findData,getData)redactStoredMetadataRow→ registrypackages/metadata-protocol/src/protocol.data-door-stored-metadata-redaction.test.tsgetPublished)MetadataManager.redactServedBody→ registrypackages/metadata/src/metadata-service.test.tsredactStoredMetadataRow/redactStoredMetadataBody: the MCP stdio data bridge, the audit writer, the automation CRUD nodes)packages/spec/src/kernel/metadata-type-redaction.test.tsWrite path. The generic carry-forward (
carryForwardRedactedValues) walksredactedKeys, so a save that sends back the served body keeps the storedssl.key; pinned by a GET, edit, PUT round trip.Tests
New and extended cases, all in existing files:
packages/spec/src/kernel/metadata-type-redaction.test.ts, new block (6 cases):ssl.keydropped and named whileenabled/rejectUnauthorized/ca/certstay; withheld with noconfigobject at all;config.passwordandssl.keytogether in one sortedredactedKeys; the stored-row seam on a serialized body. Controls: nosslblock, and ansslblock with nokey, are returned by reference withredactedKeys: []; the cleared form (key: '') is served as written. The existingconfigcases are unchanged and green.packages/metadata-protocol/src/protocol.metadata-redaction.test.ts(3 cases):getMetaItems,getMetaItem,getMetaItemLayered(overlay and effective) servesslwithoutkeyand the stored row keeps it; control with no key; GET, edit, PUT keeps the stored key.packages/metadata-protocol/src/protocol.data-door-stored-metadata-redaction.test.ts(3 cases):findDataandgetDataonsys_metadata, asys_metadata_historysnapshot; control body served as its stored bytes.packages/metadata/src/metadata-service.test.ts(1 case): a code-registered datasource throughgetPublished, with the real built-in redactor.Red first, green, ablation
origin/mainsources, new spec cases in place):src/kernel/metadata-type-redaction.test.tsgaveTests 4 failed | 35 passed (39); the four red cases are the withholding cases, the controls passed.metadata-credential-fields.test.tsplussrc/data/:Test Files 155 passed (155),Tests 4748 passed | 1 todo.node scripts/ablation-replace.mjs(each restoreblob == HEAD,git diff HEADempty):redactDatasourceItemremoved:Tests 4 failed | 44 passed (48)over the redaction and ratchet files; the four withholding cases red, controls green.credentialmarker removed fromssl.key:Tests 5 failed | 43 passed (48): the four withholding cases and the ratchet (expected [ 'datasource:ssl.key' ] to deeply equal []).origin/maintext: ratchetTests 9 passed (9), which is why the rule gainsprivate key/passphrase.dist/: the A1 mutation spelled(globalThis as any).ABLATION_28633,pnpm --filter @objectstack/spec build,ablation-dist-preflight.mjs(dist reading passed), then metadata-protocolTests 4 failed | 66 passed (70)(every new withholding case) and metadata1 failed(the new case). Restore leg rebuilt spec;ablation-dist-preflight.mjs ... --absentexit 0, dist and tree both clean.Acceptance notes
ssl.keyis documented as "PEM format or path to file". A path is not secret, but the redactor cannot tell the two forms apart, so both are withheld. Withholding a path costs a reader nothing it can act on through the read door, and the stored row keeps it for the connect path.packages/services/service-datasource.Verification
All readings on head
a2d8e887(two commits onorigin/main6b9c1ca9; the diff is the eight files above).pnpm --filter PKG test, under the lock, after building their closures):@objectstack/spec782 files / 23128 tests passed;@objectstack/metadata-protocol291 passed, 4 skipped / 21950 passed;@objectstack/metadata65 / 919;@objectstack/rest363 / 6910 (332 skipped);@objectstack/runtime503 / 8171 (19 skipped);@objectstack/service-datasource67 / 1125;@objectstack/mcp48 / 612;@objectstack/plugin-audit68 / 1109;@objectstack/service-automation251 / 3185. All exit 0. These are every package outside spec that importsgetMetadataTypeRedactoror a stored-body seam (redactStoredMetadataBody,redactStoredMetadataRow(s),redactMetadataItem).pnpm --filter @objectstack/spec typecheckexit 0;@objectstack/metadata-protocol+@objectstack/metadatatypecheck exit 0.node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack→ 80 families, each run with its exit captured before any pipe and stdin from/dev/null: 80 × exit 0. Two needed a prerequisite first:check-plugin-teardown-shape.mjs --self-testanswered exit 3 on the shallow clone and exit 0 aftergit fetch --depth=1of its fixture commit;check:dual-build-cjs-loadsansweredPREREQUISITE NOT MET(exit 3) before a full build and exit 0 afterpnpm build --concurrency=2(115 entry points across 70 packages load).--ran:✓ dispatch-gates --ran: 80 derived famil(ies) accounted for — 80 run, 0 NOT-MEASURED. No generated artifact moved (check:docs,check:authorable-surface,check:api-surfaceexit 0, tree clean).node scripts/check-commit-card-trailers.mjs --range origin/main..HEADexit 0;node scripts/check-platform-checklist.mjs --changed-from origin/mainexit 0 (1 anchored file, 2 anchors judged, 0 lost);pnpm exec eslint --no-inline-configon the changed.tsfiles exit 0; control-byte scan of the changed files: no hits. The repo-widepnpm lintis CI's.(narrowing)arm), after the full build, under the lock,pnpm --filter @objectstack/dogfood test→Test Files 369 passed | 3 skipped (372),Tests 2981 passed | 28 skipped (3009),VERDICT command-exit 0.test:repo, the lint farm, the type-check lanes) are read off this PR's check-runs by the seat.Generated by Claude Code