Repository navigation
Commit 3cc45cc
fix(spec,service-automation,lint)!: registration and validate judge a connector_action's connectorConfig.input value slot (#28150)
domain:spec#1 · os-tesla · session_01L9qibwb6hBqszPTYJobitm
<!-- claude-projects-attribution: {"github_login":"os-tesla"} -->
_Requested by **Tesla** · [project
thread](https://claude.ai/code/project/chan_01AXsQkLFKRdQCqQ3RTr6Jao?thread=cmsg_01AXsQkLFKRdQCqQ3RTr6Jao8zjUCX5w2bY9jeptPaF4yU)_
Fixes #27968
## 维护者速读
- 改了什么:流程里"调用连接器"节点的输入值,现在在注册流程和 `objectstack validate`
时就会被检查,和子流程的输入用同一条规则。
- 为什么改:之前写错的计算表达式、旧的 `{…}` 模板写法能顺利保存和上线,要等流程真正跑到这一步才失败,或者把
`{record.title}` 这样的原文直接发给外部系统。
- 风险与回滚:已经存在、带这三种写法的流程会被拒绝注册(启动时打一条 warn 并跳过该流程,其他流程照常);仓库内只有示例应用的一处
Slack 文案命中,已改写。回滚只需 revert 本 PR,不涉及数据迁移。
- 先看:`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`,三种写法都被拒绝,合法表达式照常注册并运行。
- 需要你做的:无。席内契约复审 PASS(评论 6525595405),由席位落地;skills 下的过时说明已立卡 #28162 交
skills 车道。
## What changes
- **The ledger gains a sibling-block root**
(`packages/spec/src/automation/flow-node-expression-paths.ts`). A
`FlowNodeExpressionPath` may now name `block`, the node property its
`path` is rooted in. It defaults to `config`, so every existing entry
keeps its meaning. `FLOW_NODE_SIBLING_EXPRESSION_BLOCKS` lists the
addressable blocks (today only `connectorConfig` → `connector_action`).
`resolveFlowNodeExpressions` and `resolveFlowNodeValueSlots` take the
node's sibling blocks as an optional third argument, and each result
carries its `block`. The new entry is `connector_action` ·
`connectorConfig` · `input.*` · `value`.
- **One judge, no second list.** `flowNodeValueTemplateRefusals` (in
`flow-value-slot-template.ts`) reads the same entry and reports `block`.
A sibling-block slot takes the generic value-slot remedy, without the
callee sentence a child flow's input adds.
`flow-node-config-refusals.ts` skips block-rooted entries, because its
walk addresses only `config`.
- **Both doors pass the node.** In `service-automation`,
`flow-registration-checks.ts` locates a refusal at
`connectorConfig.input.<key>`. In `lint`, `validate-expressions.ts` does
the same, so `objectstack validate` and the runtime authoring gate judge
the map too. No service-automation-only judge was added (triage
6487582764).
- **Declared in the schema.** `FlowNodeSchema.connectorConfig.input`
values carry `.meta({ xExpression: 'value' })` and stay `z.unknown()`,
so `FlowSchema.parse` is unchanged. The describe text now says what a
value is.
- **The ratchet reads a third channel.**
`getFlowNodeSiblingBlockJsonSchemas()` hands the sibling-block JSON
Schema to `config-expression-ledger.test.ts`. A marker on a block the
ledger cannot address fails the ratchet.
- **Declared rider.** The stale doc comment in the `FlowSchema` example
(formerly `flow.zod.ts:950–951`, "handed to the action as written: no
template is read there") now reads "Each `connectorConfig.input` value
is a literal, handed to the action as written, or a CEL value envelope
evaluated in the run's scope before dispatch".
- **Other changes.**
- The `engine.ts` `evaluateValueEnvelope` docblock is updated (comment
only).
- The ADR-0087 D3 entry is
`18.connector-action-input-value-slot-judged`.
- There are three changesets, one per package.
- The regenerated artefacts are `api-surface/automation.json`,
`export-origins/automation.json` and
`content/docs/references/automation/flow.mdx`.
**File-surface amendments (producer side, named per the order):**
- `examples/app-showcase/src/automation/flows/index.ts`:
`TaskCompletedSlackFlow`'s `text: '✅ 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.ts`
and `packages/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 outside `node.config`.
Under `connectorConfig.input`, `registerFlow` and `objectstack validate`
accepted three shapes that they refuse under a `subflow`'s
`config.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 with `origin/main` `91a423ef`). The suites
and the gate sweep ran on `0a88f925` (`origin/main` `6b125a7a`). 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:generated` and full dogfood were re-run.
- **Rejection tests.**
- `service-automation` `connector-input-registration.test.ts` has 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 asserts `node 'call'
(connector_action) connector_action input value at
connectorConfig.input.customerId: <refusal>`, checks the literal key
beside it is not named, and compares against a `subflow` `config.input`
control 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
no `input` registers.
- `lint` `validate-expressions.connector-input.test.ts` has 5 tests: the
same three shapes at `connectorConfig.input.<key>`, a valid envelope
passing, and the runtime authoring gate refusing with rule
`expression-invalid`.
- **Ablation** (`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.
- registration `resolveFlowNodeExpressions` → 2 envelope cells red;
- registration `flowNodeValueTemplateRefusals` → token cell and
not-registered test red;
- lint resolve → 2 envelope cells red;
- lint template → token cell and runtime-gate test red.
- **Suites.**
- `service-automation`: 226 files / 2865 tests green.
- `lint`: 179 files / 7649 tests green.
- `spec`: full suite 756 files / 22516 tests green.
- `metadata-protocol` credential-literal gate: 3/3.
- After the merge: `connector-input-schema.test.ts` (from #27995),
`connector-openapi` `input-schema-dispatch.test.ts`, the ledger ratchet
and both new files are green.
- `tsc --noEmit` is clean for spec, service-automation and lint.
- **Full dogfood** (owed by the narrowing arm, under the verify lock,
closure built): 358 files / 2883 tests green, 20 skipped, on `6a06a420`.
An earlier run on `0a88f925` had 1 red (`api-action-door-forward`, the
main defect #28082 fixed by #28095), which is why main was merged again.
- **Gates.** `dispatch-gates --commands` derived 112 commands, each run
`</dev/null` with its exit recorded. `--ran` reconcile: 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-examples` and
`check: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.ts` is
integration tier and is declared to CI; it was not run locally.
## Acceptance notes
- **In-tree survey of `connectorConfig.input`.** There are 6 authored
connector inputs outside tests: 4 in `examples/app-showcase` flows, 1 in
`ui/apps/console/src/preview-samples.ts:248` and 1 in
`content/docs/automation/connectors.mdx`. The new judge refuses exactly
1: the showcase `post_to_slack` text, rewritten above. Two test fixtures
(the DARK credential variants) were refused and rewritten. The
conversion fixture `packages/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.
- **Not narrowed.** `FlowSchema.parse` is unchanged: the map stays
`z.unknown()`, as every value slot in an open `config` does. `input`
stays optional.
- **Existing rows.** A stored flow carrying one of the three shapes is
skipped at boot with the existing `failed to register flow` warn, and
the flows beside it still register. ADR-0087 D3 entry
`connector-action-input-value-slot-judged` carries the prescription. No
D2 conversion exists, because no token has a lossless CEL spelling and a
malformed envelope carries no source to correct.
- **Noted, not changed.**
- `conversions/refused-shapes.ts` detector
`flow-value-slot-template-dialect-refused` still walks `config` only.
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.
- `ui` `flow-value-envelope.ts` `isValueEnvelopeSlot` ignores `block`.
Its one false-positive path, `config.input` on `connector_action`, is
refused by the parse already. `ui/` is untouched.
- `lint` also applies its unbound-root check to connector input
envelopes, as it does for every value slot.
- **Contract review** is owed (narrowing) and is run by the triage seat.
## Human Minimum
- [x] 1. Schemas start as Zod; `packages/spec` holds no runtime logic.
- [x] 2. Every name I added uses the casing for its kind.
- [x] 3. No formatter ran; eslint over the changed files passes (the
whole-tree `pnpm lint` runs in CI).
- [x] 4. One issue, one branch off an up-to-date `main`, one pull
request.
- [x] 5. A change that publishes carries a changeset, and both
declaration lines are filled in here and in the changeset.
- [x] 6. `packages/spec` touched: its generated artifacts are
regenerated and committed.
- [x] 7. No hand edits to `content/docs/references/`,
`packages/*/CHANGELOG.md` or `content/docs/releases/`.
- [x] 8. Title, body and comments are in English.
- [x] The *Before Submitting* list in CONTRIBUTING.md is done.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01L9qibzGExkj8nxREmV7FjX
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 9f13ee8 commit 3cc45cc
22 files changed
Lines changed: 782 additions & 61 deletions
File tree
- .changeset
- content/docs/references/automation
- examples/app-showcase/src/automation/flows
- packages
- cli/test
- lint/src
- metadata-protocol/src
- services/service-automation/src
- builtin
- spec
- api-surface
- export-origins
- src
- automation
- migrations/entries/semantic
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
150 | 150 | | |
151 | 151 | | |
152 | 152 | | |
153 | | - | |
| 153 | + | |
154 | 154 | | |
155 | 155 | | |
156 | 156 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
349 | 349 | | |
350 | 350 | | |
351 | 351 | | |
352 | | - | |
| 352 | + | |
| 353 | + | |
| 354 | + | |
| 355 | + | |
353 | 356 | | |
354 | 357 | | |
355 | 358 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
15 | 15 | | |
16 | 16 | | |
17 | 17 | | |
18 | | - | |
| 18 | + | |
| 19 | + | |
19 | 20 | | |
20 | 21 | | |
21 | 22 | | |
| |||
85 | 86 | | |
86 | 87 | | |
87 | 88 | | |
88 | | - | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
89 | 94 | | |
90 | 95 | | |
91 | 96 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| 23 | + | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
| 31 | + | |
| 32 | + | |
| 33 | + | |
| 34 | + | |
| 35 | + | |
| 36 | + | |
| 37 | + | |
| 38 | + | |
| 39 | + | |
| 40 | + | |
| 41 | + | |
| 42 | + | |
| 43 | + | |
| 44 | + | |
| 45 | + | |
| 46 | + | |
| 47 | + | |
| 48 | + | |
| 49 | + | |
| 50 | + | |
| 51 | + | |
| 52 | + | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
2769 | 2769 | | |
2770 | 2770 | | |
2771 | 2771 | | |
2772 | | - | |
2773 | | - | |
| 2772 | + | |
| 2773 | + | |
| 2774 | + | |
| 2775 | + | |
| 2776 | + | |
2774 | 2777 | | |
2775 | 2778 | | |
2776 | 2779 | | |
| |||
2828 | 2831 | | |
2829 | 2832 | | |
2830 | 2833 | | |
2831 | | - | |
| 2834 | + | |
2832 | 2835 | | |
2833 | | - | |
| 2836 | + | |
2834 | 2837 | | |
2835 | 2838 | | |
2836 | 2839 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
24 | 24 | | |
25 | 25 | | |
26 | 26 | | |
27 | | - | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
28 | 30 | | |
29 | 31 | | |
30 | 32 | | |
| |||
55 | 57 | | |
56 | 58 | | |
57 | 59 | | |
58 | | - | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
| 64 | + | |
59 | 65 | | |
60 | 66 | | |
61 | 67 | | |
| |||
0 commit comments