feat(automation): a flow's credentials live in a write-only channel on the secret seam, not in its stored definition (#20790) - #21377
Conversation
…he secret seam Channel object, the save-door step, the gate reads, the restore strip, the engine's verification-time resolution, the clone door refusal and the one-time migration. Tests follow. Claude-Session: https://claude.ai/code/session_01DiCSbmJrkzNhuEAier4VoJ Co-authored-by: Claude <noreply@anthropic.com>
…nnel Claude-Session: https://claude.ai/code/session_01DiCSbmJrkzNhuEAier4VoJ Co-authored-by: Claude <noreply@anthropic.com>
…body shape in the pin Claude-Session: https://claude.ai/code/session_01DiCSbmJrkzNhuEAier4VoJ Co-authored-by: Claude <noreply@anthropic.com>
…nt the new object and write sites Claude-Session: https://claude.ai/code/session_01DiCSbmJrkzNhuEAier4VoJ Co-authored-by: Claude <noreply@anthropic.com>
…bles meet the engine contract gates Claude-Session: https://claude.ai/code/session_01DiCSbmJrkzNhuEAier4VoJ Co-authored-by: Claude <noreply@anthropic.com>
…pin's find double refuses operators Claude-Session: https://claude.ai/code/session_01DiCSbmJrkzNhuEAier4VoJ Co-authored-by: Claude <noreply@anthropic.com>
…ow-hook-secret-seam
…credentials now live Claude-Session: https://claude.ai/code/session_01DiCSbmJrkzNhuEAier4VoJ Co-authored-by: Claude <noreply@anthropic.com>
…registration; the test crypto fake meets the provider contract Claude-Session: https://claude.ai/code/session_01DiCSbmJrkzNhuEAier4VoJ Co-authored-by: Claude <noreply@anthropic.com>
…ow-hook-secret-seam
…f a credential-holding source Claude-Session: https://claude.ai/code/session_01DiCSbmJrkzNhuEAier4VoJ Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 6 package(s): 65 hand-written doc(s) name something this change touched — list omitted above 15 rows. Re-derive on the tree named below: ⛔ 13 release-owned page(s) also affected — read-only, see AGENTS.md Documentation Guardrails. What this run could not see
Coarse fallback — 144 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 fe35e66ada5e2c9e828ce9ea7029ca1578f02a95 && git checkout fe35e66ada5e2c9e828ce9ea7029ca1578f02a95
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 85986144c2ef6f379955137677c5cbfb00e194d2 417ba1fa6fc6399a6bc077459c45d239542245e1 && git checkout -B drift-repro 85986144c2ef6f379955137677c5cbfb00e194d2 && git merge --no-ff 417ba1fa6fc6399a6bc077459c45d239542245e1
node scripts/docs-audit/affected-docs.mjs --json 85986144c2ef6f379955137677c5cbfb00e194d2
|
…through the metadata API; name both triggers of the one-time move Claude-Session: https://claude.ai/code/session_01DiCSbmJrkzNhuEAier4VoJ Co-authored-by: Claude <noreply@anthropic.com>
…ow-hook-secret-seam
…only for a position it holds The inbound reader now has the http node's shape: with a literal start-node secret it asks the channel only when the channel's index holds that position, and otherwise answers the literal without touching the channel. A held secret that does not come back still rejects and is never verified against the literal. Claude-Session: https://claude.ai/code/session_01DiCSbmJrkzNhuEAier4VoJ Co-authored-by: Claude <noreply@anthropic.com>
…ow-hook-secret-seam
⛔ merge queue 构建失败 — 先分诊,再决定要不要重排队列构建 37006699795 红了。队列跑的是全量套件(PR 侧 CI 只跑 affected 子集), 失败的 job(日志抽取,best effort):
跨 PR 相同签名(24h,按失败测试文件聚合):
历史信号:
分诊清单:
Generated by Claude Code · merge-queue-triage workflow (#4859) |
|
Queue failure triaged: not this PR's; one re-queue ·
Generated by Claude Code · https://claude.ai/code/session_01DiCSbmJrkzNhuEAier4VoJ |
Fixes #20790
Clause-②: yes (widening)
This PR carries out ruling record 5942356310 (letter A, R2 and C1, the maintainer's 「同意264」) under claim 5935167060 and its revision 5942559287. It names classes and positions only: no request, header, route, field path or value.
Cross-lane files (named before the change list)
domain:enginepackages/metadata-protocol/src/protocol.ts: the per-type credential-channel registration, the save door's channel step (after the carry-forward, before the put), the publish gate's read of held positions, and the rollback and revert callers that pass the strip.packages/metadata-protocol/src/sys-metadata-repository.ts: the restore verb gains a body-derivation option shaped like the promote verb's (R2).domain:spec:packages/spec/src/system/constants/platform-object-names.ts, one registry line.domain:clipackages/runtime/src/flow-clone.ts: the C1 refusal.packages/runtime/src/domains/automation.ts: the clone handler consults the refusal. This file is in claim 5935167060 but not in revision 5942559287's list (see Acceptance notes).packages/qa/dogfood/(one pin, one dev dependency, one source alias) andpnpm-lock.yaml.--write.What changed
1. A write-only channel on the existing secret seam (
service-automation).sys_flow_credentialholds one row per credential position of a flow, per lifecycle state (draft or active).FlowCredentialChannelhandles five operations:2. The save door stores the body the channel returns (
metadata-protocol).registerCredentialChannel(type, channel)registers a channel.saveMetaItemruns it after the carry-forward and before the put, so the stored row, every new history row and the content hash carry no credential.restoreVersiontakesderiveRestoredBody. Rollback and revert pass the strip. A restore past the move therefore never puts a credential back at rest, and the channel keeps its current one. No new history copy is written.3. Credentials are read at use time (
service-automation,trigger-api).SERVICE_UNAVAILABLE. It is never verified against nothing, and nothing is enqueued.4. C1: the clone door refuses a credential-holding source (
runtime).RESOURCE_CONFLICT, names the classes, and gives Q2 A's prescription: author the copy as a new flow with its own secret.5. A one-time move with a receipt (
service-automation).sys_migrationasflow-credential-channel, with counts and names only.6. No provider means no write. With no crypto provider, a save that would land a credential is refused (503) before any row is written.
7. Spec and docs.
minorfor five packages. It carries the rotation instruction and the accepted cost, and the ADR-0087 gate reads it as non-breaking.Evidence (head
417ba1fa6)Pins (the ruling's list plus the card's four and Q4's outbound set):
The end-to-end pin is
packages/qa/dogfood/test/flow-credential-channel.dogfood.test.ts(8/8).Every negative pin was ablated:
a38db79ecthroughscripts/ablation-replace.mjs. Each anchor hit, each pin turned red, and after each restore the tree read clean againstHEAD. Red counts ranged from 1 to 6 per ablation, across the channel, trigger, http-node, migration, clone and protocol pins.457f43476:dist/by preflight, and 6 of 8 tests went red. Tests 6 and 7 stayed green, as expected, because they do not read the ablated step.--absentpassed, 8/8 green, and the diff againstHEADwas empty.Suites at
80b4647b2, each in the foreground under the shared verify lock:service-automation: 166 files, 2051 passed.metadata-protocol: 2 shards, 202 files plus 3 skipped; 2985 passed, 19 skipped.trigger-api: 2 files, 30 passed.runtimelocalproject: 3 shards, 304 files; 4335 passed, 11 skipped.speclocalproject: 2 shards, 598 files; 17512 passed, 1 todo.service-automation,metadata-protocol,trigger-api,runtime,dogfood) andspectsc: all exit 0.Gates:
node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commandsderived 129 commands at80b4647b2, and all 129 exited 0. The--ranreconciliation answered:129 derived, 129 run, 0 NOT-MEASURED, 0 UNRUN.Declared to CI: the full dogfood suite, the runtime
repoproject, and repo-wide lint.Contract review round 1
The contract review of record found one wrong judgment: Q3 A at the inbound door. With a literal start-node secret, the hook reader asked the credential channel on every post, and the channel throws when it has no reachable store. A packaged inbound flow on a composition with no data engine therefore armed on its literal and was answered 503 on every post.
Commit
84d3d297afixes it in the http node's shape, so both doors read one rule:Pins:
Ablations at
84d3d297a. Each anchor hit; each run rebuilt and the dist preflight found the marker; each restore was proven by the file equalling its HEAD blob, the--absentpreflight passed, and both pins went green again:At
417ba1fa6, after mergingorigin/main:The size is now 3646 changed lines (+3557 / −89), of which 1,678 are added test lines.
Acceptance notes
mainand still holds: the stored row and the history row carried both credentials in cleartext, and an administrator's engine read returned them. The MCP stdio door had already stopped serving them by the time of this build (the fix(mcp)!: the MCP stdio engine-only reader joins the stored-metadata-body family (exit one) #21228 change). [security] Stored-metadata-body family: two exits #21120 did not reach. An engine-only door serves an administrator credential material in cleartext, and the data door serves a content hash computed over the withheld credentials #21207 remains open. For flows only, this PR also removes the credential from what that card's checksum exit covers.packages/runtime/src/domains/automation.tsis outside revision 5942559287's list and inside claim 5935167060. The clone handler there is where the C1 refusal is consulted.origin/mainmoved at least 10 commits after the gate derivation at80b4647b2. One derivation input changed (a release script, outside this diff), and a test merge against currentmainis clean.Generated by Claude Code