Repository navigation
feat(spec)!: the connector write door refuses an inline secret on every instance auth kind; the metadata read redacts connector credential positions - #28596
Conversation
…y kind; the metadata read redacts connector credential positions The four static instance-auth kinds (none, bearer, api-key, basic) are strict shapes now, as the ADR-0143 kinds already were: an undeclared key is refused at parse, and an inline secret meets the credentialRef prescription. A built-in connector redactor withholds undeclared auth keys, the non-type keys of a runtime authentication block, and the provider header and environment maps on every metadata read exit. Claude-Session: https://claude.ai/code/session_017xRBcVy4VXmkQjn2MGeaWk Co-authored-by: Claude <noreply@anthropic.com>
…the metadata doors; changeset Claude-Session: https://claude.ai/code/session_017xRBcVy4VXmkQjn2MGeaWk Co-authored-by: Claude <noreply@anthropic.com>
…s only its own write Claude-Session: https://claude.ai/code/session_017xRBcVy4VXmkQjn2MGeaWk Co-authored-by: Claude <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_017xRBcVy4VXmkQjn2MGeaWk Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): 4 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 2 release-owned page(s) also 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 8a12c36d9c75026b9b6c57f765ed033556ecbc9c && git checkout 8a12c36d9c75026b9b6c57f765ed033556ecbc9c
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin cd065d3698db13969a28f46bf26f3e47e9ecc09c 41256e95b9caece74a323a166252f4ce52975732 && git checkout -B drift-repro cd065d3698db13969a28f46bf26f3e47e9ecc09c && git merge --no-ff 41256e95b9caece74a323a166252f4ce52975732
node scripts/docs-audit/affected-docs.mjs --json cd065d3698db13969a28f46bf26f3e47e9ecc09c
|
|
domain:devx#1 · sunhaolin · session_017xRBcVy4VXmkQjn2MGeaWk Contract reviewServed-tier: Face: a published schema ( ① Derived judgments
② Semver level
③ Boundary flagsThe PR author is
④ Design judgment
Must-fixes: none. Should-fixes (follow-up cards, none blocking): the non-record Implemented-by: VERDICT: PASS |
|
domain:devx#1 · sunhaolin · session_017xRBcVy4VXmkQjn2MGeaWk ACCEPT → head 维护者速读
席位意见:收。本 head 上达档契约复核 PASS(6555639907, 席位核对
Generated by Claude Code |
domain:devx#1 · sunhaolin · session_017xRBcVy4VXmkQjn2MGeaWk
Clause-②: no (narrowing)
Metadata-protocol: narrowed
Skills-docs: none —
skills/**teaches connectorauthonly as{ type, credentialRef }(git grepoverskills/**: everyauth:example is a reference; none writestoken,keyorpasswordinline), andcontent/docs/automation/connectors.mdxalready saysauth: { type: 'bearer', token: … }is not a legal shape, which this change makes true at every door.维护者速读
auth的四种静态类型(none / bearer / api-key / basic)不再接受未声明的键,内联的 token / key / password 在保存与构建时以 422 拒绝并指向credentialRef;元数据读取不再返回连接器的凭据位置(auth未声明的键、旧authentication块除type外的键、provider 的请求头与环境变量表整表)。auth里多写了键的连接器,保存与构建会被拒;Studio 读到的连接器不再显示 headers / defaultHeaders 等整表(原样保存时存储值保留,运行时照常发送)。回滚即 revert 本 PR。packages/spec/src/shared/connector-auth.zod.ts(四个strictObject)、packages/spec/src/integration/connector-credential-redaction.ts(credentialPathsOf与declaredConnectorAuthKeys)、packages/spec/src/kernel/metadata-type-redaction.ts(内置注册一行)。pnpm --filter @objectstack/dogfood exec vitest run test/connector-meta-door-credential-hardening.dogfood.test.ts,预期 11 个用例全部通过。What changes
Write door. The four static kinds of declarative connector instance auth (
none,bearer,api-key,basic) arestrictObjectshapes now, as the three ADR-0143 kinds (oauth2,app-credential,signed) already were. An undeclared key is refused at parse, located atauth, and an inline secret (token,key,passwordand their usual spellings) meets thecredentialRefprescription. That is the refusal ADR-0143 D3 records ("publish keeps refusing an inline secret") and its Acceptance asks for on any variant. Every door that parsesDeclarativeConnectorEntrySchemarefuses it:defineStack({ connectors }),os validate/os compile, andPUT /api/v1/meta/connector/:name, where the answer is422 INVALID_METADATA, the envelope an inlineauthenticationalready met there. No error code, export or authorable key is added.Read redaction.
connectorjoinsdatasourceas a built-in of the metadata-type redactor registry (getMetadataTypeRedactor('connector')). It is a built-in because connector rows reach the save door with or without any connector plugin mounted. The withheld positions are:authkey the item's kind does not declare (read fromConnectorInstanceAuthSchemaitself, so a new kind is covered with no edit);authenticationblock buttype;providerConfig.headers,providerConfig.defaultHeaders,providerConfig.transport.headersandproviderConfig.transport.env. This follows the posture ruled for a webhook'sheaders, since a map's entries cannot be told apart.Cleared forms (
null,'',{}) are served as written. The redactor is pure and internal (not exported). The runtime keeps reading the unredacted execution view, and the protocol's generic carry-forward keeps stored values on a save of the served form.ADR-0087. A D3 semantic entry,
connector-instance-auth-inline-secret-refused, carries the prescription. There is no mechanical rewrite, because the inline value may be the only copy of the secret its author holds.Tests
Every value in the new tests is an obviously-fake placeholder, and every read assertion is that value's absence from what a door served.
connector-auth-static-kinds.test.ts: each static kind's declared shape parses unchanged (control), each inline secret is refused asunrecognized_keys @ authwith thecredentialRefprescription and without echoing the value, a non-secret undeclared key is refused too, and the stack collection refuses atconnectors.0.auth.connector-credential-redaction.test.ts: each withheld position; declared keys served as stored (grant and signed kinds included); cleared forms; purity; and a fail-closed census of every key the instance-auth union declares, so a new declared key reds until it is confirmed not a secret.metadata-type-redaction.test.ts:connectorresolves with zero registrations, and the stored-row seam redacts both type spellings.connector-meta-door-credential-hardening.dogfood.test.ts, on the showcase with its own connector plugins, 11 tests:422 INVALID_METADATAlocated atauthand stores nothing.credentialRef-only instance saves, reachesready, and dispatches to a local upstream with the reference resolved and its default header sent, also after a save of its served form.Readings at 88503d9 (before the main merge)
Ablations (one shot each; scripts/ablation-replace.mjs + ablation-dist-preflight.mjs; restored to the HEAD blob)
Readings at 41256e9 (after the main merge; the final head)
Acceptance notes
_diagnosticsnow names the undeclaredauthkey, which is the inventory for moving the value out. Moving it is the stored-row migration's job, the datasource posture.headers/defaultHeaders/transport.headers/transport.env. A save that omits a map keeps it; an edit sends the whole map. Readers of the served view (the package export among them) omit these maps, as they already omit a webhook'sheaders.auth: { type: 'none' }refusescredentialRefas well: it names an upstream that takes no credential.shared/total, the authorable surface, the JSON schemas and the api-surface are byte-identical (check:generated).authpositions, the runtimeauthenticationblock and the provider maps. Read gating of/meta/connectoris unchanged.Verification
Local subset at 41256e9.
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commandsderived 82 families. Each ran byte for byte with its exit code captured before any pipe, followed bycheck:doc-anchorsandcheck:system-context-census, which the dispatch names. Reconciliation result:--ran: 82 derived, 82 run, 0 NOT-MEASURED, 0 UNRUN.Two families first answered exit 3 (prerequisite not met), so nothing was measured, and each was re-run once:
check:dual-build-cjs-loadsre-ran after building the seven packages outside the dogfood closure, and exited 0 (115 entry points across 70 packages load).check:type-check-debtre-ran under the verify lock and exited 0 (0 ledger entries above their recorded number).Every other family exited 0 on the first run.
CI on 41256e9: 64 check-runs, none other than success or skipped. The required contexts
Lint & Repo Gates,TypeScript Type Check,Test Core,Build Core,Governed Surface Queue Guard,Type Check UI,Test UI,Build & E2E UIandLive E2E UIall succeeded;Lint UIwas skipped, because the diff touches noui/path.Dogfood Regression GateandCheck Changesetalso succeeded.Generated by Claude Code