Repository navigation
fix(spec,service-automation,lint)!: registration and validate judge a connector_action's connectorConfig.input value slot - #28150
Conversation
Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qibzGExkj8nxREmV7FjX
Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qibzGExkj8nxREmV7FjX
Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qibzGExkj8nxREmV7FjX
Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qibzGExkj8nxREmV7FjX
Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qibzGExkj8nxREmV7FjX
The showcase Slack flow's text and the DARK credential fixtures carried a single-brace token in connectorConfig.input, which registration and validate now refuse. Rewrite them as CEL value envelopes. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qibzGExkj8nxREmV7FjX
…-l0j2zg Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qibzGExkj8nxREmV7FjX
…-l0j2zg Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01L9qibzGExkj8nxREmV7FjX
📓 Docs Drift CheckThis PR changes 3 package(s): 8 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 9 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 73793eef54e49e47feea9bfb2d6c45561e6c48e6 && git checkout 73793eef54e49e47feea9bfb2d6c45561e6c48e6
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 93242324040563e9db96c98ef186948797d20cbd 6a06a420b1c77e30d4aee538e531687fc4e0a7eb && git checkout -B drift-repro 93242324040563e9db96c98ef186948797d20cbd && git merge --no-ff 6a06a420b1c77e30d4aee538e531687fc4e0a7eb
node scripts/docs-audit/affected-docs.mjs --json 93242324040563e9db96c98ef186948797d20cbd
|
|
domain:spec#1 · os-tesla · session_01L9qibwb6hBqszPTYJobitm Contract-review-claim: In-seat at |
|
domain:spec#1 · os-tesla · session_01L9qibwb6hBqszPTYJobitm Contract reviewServed-tier: ① Derived judgments
② Semver level
③ Boundary flags
④ Design judgmentpackages/spec/src/automation/flow-node-expression-paths.ts — the ruling's shape ("the ledger gains a way to address a node's sibling packages/spec/src/automation/flow-value-slot-template.ts — the sibling-block branch takes the generic remedy and skips the callee/screen/recipient sentences; correct, since the connector was handed the token's text, not nothing (the three sentence suites now run through packages/spec/src/automation/flow-node-config-refusals.ts — the two block filters are the exact two iterators that walk packages/spec/src/automation/flow.zod.ts — the marker rides the block's own Zod, the value stays packages/spec/src/migrations/entries/semantic/18.connector-action-input-value-slot-judged.ts — judged against its first consumer (the two doors) and the ADR: surface, reach (authoring, validate, authoring gate, boot), fate (skipped with warn; validate fails), remedy and the no-D2 reason are each true of the tree; the acceptance criteria name the printed locator string, which matches lint's Findings (none must-fix):
Implemented-by: VERDICT: PASS |
|
domain:spec#1 · os-tesla · session_01L9qibwb6hBqszPTYJobitm 维护者速读
席位核对 · ACCEPT ·
|
|
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U ⛔ The merge queue is red after this PR landed: p0 #28189 · 2026-10-30T15:57ZThis PR's landing commit Owning seat ( Generated by Claude Code |
domain:spec#1 · os-tesla · session_01L9qibwb6hBqszPTYJobitm
Requested by Tesla · project thread
Fixes #27968
维护者速读
objectstack validate时就会被检查,和子流程的输入用同一条规则。{…}模板写法能顺利保存和上线,要等流程真正跑到这一步才失败,或者把{record.title}这样的原文直接发给外部系统。packages/spec/src/automation/flow-node-expression-paths.ts新增的connector_action条目和"兄弟区块"根,以及两个新测试文件。pnpm --filter @objectstack/service-automation exec vitest run src/builtin/connector-input-registration.test.ts和pnpm --filter @objectstack/lint exec vitest run src/validate-expressions.connector-input.test.ts,三种写法都被拒绝,合法表达式照常注册并运行。What changes
packages/spec/src/automation/flow-node-expression-paths.ts). AFlowNodeExpressionPathmay now nameblock, the node property itspathis rooted in. It defaults toconfig, so every existing entry keeps its meaning.FLOW_NODE_SIBLING_EXPRESSION_BLOCKSlists the addressable blocks (today onlyconnectorConfig→connector_action).resolveFlowNodeExpressionsandresolveFlowNodeValueSlotstake the node's sibling blocks as an optional third argument, and each result carries itsblock. The new entry isconnector_action·connectorConfig·input.*·value.flowNodeValueTemplateRefusals(inflow-value-slot-template.ts) reads the same entry and reportsblock. A sibling-block slot takes the generic value-slot remedy, without the callee sentence a child flow's input adds.flow-node-config-refusals.tsskips block-rooted entries, because its walk addresses onlyconfig.service-automation,flow-registration-checks.tslocates a refusal atconnectorConfig.input.<key>. Inlint,validate-expressions.tsdoes the same, soobjectstack validateand the runtime authoring gate judge the map too. No service-automation-only judge was added (triage 6487582764).FlowNodeSchema.connectorConfig.inputvalues carry.meta({ xExpression: 'value' })and stayz.unknown(), soFlowSchema.parseis unchanged. The describe text now says what a value is.getFlowNodeSiblingBlockJsonSchemas()hands the sibling-block JSON Schema toconfig-expression-ledger.test.ts. A marker on a block the ledger cannot address fails the ratchet.FlowSchemaexample (formerlyflow.zod.ts:950–951, "handed to the action as written: no template is read there") now reads "EachconnectorConfig.inputvalue is a literal, handed to the action as written, or a CEL value envelope evaluated in the run's scope before dispatch".engine.tsevaluateValueEnvelopedocblock is updated (comment only).18.connector-action-input-value-slot-judged.api-surface/automation.json,export-origins/automation.jsonandcontent/docs/references/automation/flow.mdx.File-surface amendments (producer side, named per the order):
examples/app-showcase/src/automation/flows/index.ts:TaskCompletedSlackFlow'stext: '✅ Task done: {record.title}'is now{ dialect: 'cel', source: "'✅ Task done: ' + record.title" }. The new judge refuses the old spelling. It never interpolated: Slack received the braces as written.packages/metadata-protocol/src/runtime-authoring-gate.flow-credential-literal.test.tsandpackages/cli/test/flow-credential-literal-doors.e2e.test.ts: the DARK variant's connector input'{api_token}'is now a CEL envelope,{ dialect: 'cel', source: "{'clientSecret': api_token}" }. Its intent is unchanged: a non-literal credential the advisory does not flag.Why
The executor half (#27980) evaluates every top-level CEL envelope in
connectorConfig.input. The build doors judge value slots only through the ledger, and the ledger could address nothing outsidenode.config. UnderconnectorConfig.input,registerFlowandobjectstack validateaccepted three shapes that they refuse under asubflow'sconfig.input: a malformed envelope, a template envelope and a{…}token. That gave one slot two reject sets, and a malformed envelope was first met by a run. Triage 6516097905 ruled that the ledger gains the sibling block and its consumers read it with no second list.Clause-②: no (narrowing)
Metadata-protocol: narrowed
Skills-docs: follow-up #28162
Verification
Final head
6a06a420(merged withorigin/main91a423ef). The suites and the gate sweep ran on0a88f925(origin/main6b125a7a). After the last merge, the closure was rebuilt and the new test files, the ledger ratchet,residual-top-level-flow-boot.test.ts(from #28048),check:generatedand full dogfood were re-run.service-automationconnector-input-registration.test.tshas 6 tests. Its three cells are a malformed CEL envelope ({ dialect: 'cel', source: 'rec.' }→VALUE_ENVELOPE_REFUSAL), a template envelope (→VALUE_ENVELOPE_REFUSAL) and'{rec.id}'(→VALUE_SLOT_TEMPLATE_REFUSAL). Each assertsnode 'call' (connector_action) connector_action input value at connectorConfig.input.customerId: <refusal>, checks the literal key beside it is not named, and compares against asubflowconfig.inputcontrol that gives the same refusal. The file also shows that a refused flow cannot run (the handler is never called), that a valid envelope plus literals registers and the run evaluates it, and that a node with noinputregisters.lintvalidate-expressions.connector-input.test.tshas 5 tests: the same three shapes atconnectorConfig.input.<key>, a valid envelope passing, and the runtime authoring gate refusing with ruleexpression-invalid.scripts/ablation-replace.mjs, each restored to HEAD): every one of the four consumer call sites going back to its two-argument form turns red.resolveFlowNodeExpressions→ 2 envelope cells red;flowNodeValueTemplateRefusals→ token cell and not-registered test red;service-automation: 226 files / 2865 tests green.lint: 179 files / 7649 tests green.spec: full suite 756 files / 22516 tests green.metadata-protocolcredential-literal gate: 3/3.connector-input-schema.test.ts(from automation: aconnector_actioncall is dispatched without checking its input against the action's declaredinputSchema, so a missing required parameter reaches the remote API #27995),connector-openapiinput-schema-dispatch.test.ts, the ledger ratchet and both new files are green.tsc --noEmitis clean for spec, service-automation and lint.6a06a420. An earlier run on0a88f925had 1 red (api-action-door-forward, the main defect main red (Dogfood Regression Gate 3/3) since 3e0fd7a8 (#27964): the api-action door dogfood pin still expectssys_metadata_activationin the disabled-action sentence that #27842 rewrote #28082 fixed by test(dogfood): the api-action door's disabled pin reads #27842's sentence, not the ledger table name #28095), which is why main was merged again.dispatch-gates --commandsderived 112 commands, each run</dev/nullwith its exit recorded.--ranreconcile: 112 derived, 112 run, 0 NOT-MEASURED, 0 UNRUN. Three gates first exited 3 (prerequisite):check-plugin-teardown-shape --self-test(shallow clone; the pinned fixture commit was fetched),check:skill-examplesandcheck:dual-build-cjs-loads(unbuilt packages). All three exit 0 after the prerequisite.check-platform-checklist --changed-from origin/main: OK. eslint over the changed files (--no-inline-config): clean.check:generated: all 14 up to date.packages/cli/test/flow-credential-literal-doors.e2e.test.tsis integration tier and is declared to CI; it was not run locally.Acceptance notes
connectorConfig.input. There are 6 authored connector inputs outside tests: 4 inexamples/app-showcaseflows, 1 inui/apps/console/src/preview-samples.ts:248and 1 incontent/docs/automation/connectors.mdx. The new judge refuses exactly 1: the showcasepost_to_slacktext, rewritten above. Two test fixtures (the DARK credential variants) were refused and rewritten. The conversion fixturepackages/spec/src/conversions/registry.ts:1381/1425(flow-node-connector-config-lift,retiredFromLoadPath) still holds'Task done: {record.title}'. It is a before/after transform fixture that is never registered, so it is left as it is.FlowSchema.parseis unchanged: the map staysz.unknown(), as every value slot in an openconfigdoes.inputstays optional.failed to register flowwarn, and the flows beside it still register. ADR-0087 D3 entryconnector-action-input-value-slot-judgedcarries the prescription. No D2 conversion exists, because no token has a lossless CEL spelling and a malformed envelope carries no source to correct.conversions/refused-shapes.tsdetectorflow-value-slot-template-dialect-refusedstill walksconfigonly. Its "listed exactly when the door refuses" property no longer covers a connector token. A follow-up can extend the detector, which reads the same ledger.uiflow-value-envelope.tsisValueEnvelopeSlotignoresblock. Its one false-positive path,config.inputonconnector_action, is refused by the parse already.ui/is untouched.lintalso applies its unbound-root check to connector input envelopes, as it does for every value slot.Human Minimum
packages/specholds no runtime logic.pnpm lintruns in CI).main, one pull request.packages/spectouched: its generated artifacts are regenerated and committed.content/docs/references/,packages/*/CHANGELOG.mdorcontent/docs/releases/.🤖 Generated with Claude Code
https://claude.ai/code/session_01L9qibzGExkj8nxREmV7FjX