Repository navigation
Commit 6c4c43a
fix(service-automation,mcp): type a time-relative flow's conditions against the object its sweep scans (#30453)
<!-- claude-projects-attribution: {"github_login":"os-bill"} -->
_Requested by **Bill** · [project
thread](https://claude.ai/code/project/chan_016iMXWSZDzqWRWvWzdNo8wg?thread=cmsg_016iMXWSZDzqWRWvWzdNo8wgJQxgN9AS3TCTXHT5xvkdBN)_
domain:server#2 · os-bill · session_01Utp6CbRQNg8jRbhpBF2rFY
Fixes #30396
Clause-②: no — a logged registration advisory (never thrown, never a
refusal) now types a time-relative flow's conditions against the object
the run binds, and the MCP `validate_expression` prose stops presenting
a start the publish door refuses as a supported shape; the tool's input
schema and verdicts are unchanged.
Metadata-protocol: none
## 维护者速读
-
改了什么:时间相对(定时扫描)流程注册时的字段提示,现在按真正被扫描的对象(`timeRelative.object`)检查条件,不再按开始节点的
`objectName`;MCP `validate_expression` 的说明和 SKILL.md 改为「把被扫描对象作为
objectName 传入,开始节点写别的对象会在发布时被拦(`flow-time-relative-object-mismatch`)」。
- 为什么改:实测旧行为两头都错:发布规则上线前存下的两对象流程,注册日志对被扫描对象的正确字段报 unknown
field,对运行时必失败的字段一声不吭;只写 `timeRelative.object` 不写 `objectName`
的流程完全不做字段检查。MCP 文案还在教 agent 写一种现在发布会被拒的流程。
-
风险与回滚:注册这一步只是日志提示,从不抛错、不拦注册;对象取自触发绑定本身(`deriveTriggerBinding`),与运行时同一答案;非时间相对流程不变;MCP
只改文字,输入 schema 与判决不变。回滚即撤 PR。
-
先看:`packages/services/service-automation/src/flow-registration-checks.ts`
的 `validateFlowExpressions` 取对象那几行;`engine.ts` 宿主里新增的一行
`deriveTriggerBinding`;`packages/mcp/src/mcp-http-tools.ts` 的
`sweepObject` 说明。
- 两分钟验证:`pnpm --filter @objectstack/service-automation exec vitest run
--maxWorkers=2 src/flow-registration-checks.test.ts`,预期 4 个用例通过。
- 需要你做的:无,席位落地。
## What changes
- **Registration advisory (`flow-registration-checks.ts`,
`validateFlowExpressions`).** The schema-aware pass (#1928: logged
`unknown field` / typo / type-mismatch warnings, never thrown) took its
object from the start node's `config.objectName`. It now takes it from
the flow's trigger binding when that binding is `time_relative`:
`binding.object`, which `deriveTriggerBinding` resolves as
`timeRelative.object` when it is a string, `config.objectName` only
otherwise — the object whose row the sweep hands each run. The rule is
reused, not restated: the pass calls the engine's own
`deriveTriggerBinding` (the same method `validateApiTriggerSecret`
already calls at registration). Every other flow keeps
`config.objectName`. The binding is derived only on the advisory pass
with a resolver wired; the stored pass (`advisory: false`) never reads
the hint.
- **Engine host (`engine.ts`).** One line: the `validateFlowExpressions`
host now passes `deriveTriggerBinding`, plus a sentence in that method's
docblock. Outside the claim's expected file list (not a banned path);
see Acceptance notes.
- **MCP text (`mcp-http-tools.ts`, `skill-md.ts`).**
`validate_expression`'s description, its `sweepObject` input text and
the SKILL.md line no longer advise passing a `sweepObject` other than
`objectName`. They say a time-relative flow's conditions read rows of
the swept object, so pass it as `objectName`, and that a start whose
`objectName` names another object is refused at publish
(`flow-time-relative-object-mismatch`). The `sweepObject` text keeps
what the tool answers when the two differ (the gate's advisory
type-mismatch warning). No schema change: the other-object arm still
mirrors what `@objectstack/lint` grades for such a flow's expressions,
so retiring it would move the tool off the gate.
- **Tests (`flow-registration-checks.test.ts`, new).** On a stored
two-object flow (start `objectName: crm_account`, sweep `crm_contract`):
a field of the swept object passes; a field only `crm_account` has warns
`unknown field`. A one-object flow is unchanged (clean, and a typo still
warns). A sweep-only flow (no `objectName`) is typed against the swept
object.
- **Changeset** `.changeset/30396-time-relative-condition-object.md`:
`patch` on `@objectstack/service-automation` and `@objectstack/mcp`.
## Acceptance notes
- **Measurement (on `09720e2c`, before the fix).** The four tests above
against the old code: 3 red. The two-object flow logged `[flow
'renewal_alert'] node 'check' (decision) condition: unknown field
\`renewal_status\` on \`crm_account\`` for the swept object's real
field, and nothing for `record.industry` (a field that fails every run);
the sweep-only flow logged nothing for `record.industry` because no
object was typed at all. The one-object case was already green.
- **Behaviour widening, advisory only.** A sweep-only time-relative flow
(start names no `objectName`) now gets the schema advisory where it got
none. It is a log line at registration; nothing is refused.
- **File surface.** `engine.ts` is outside the claim's expected list:
`validateFlowExpressions` receives its host members from the engine
(`RegistrationCheckHost` Pick), so reusing the binding means one wiring
line there. The alternatives were a third copy of the binding rule
beside `deriveTriggerBinding` and `@objectstack/lint`'s, which the order
rules out, or calling `deriveTriggerBinding` with a stand-in host.
- **Text pins.** The package pins no tool-description text
(`skill-md-surface-guard.test.ts` checks tool names only), and hint text
is not pinned by convention; no text pin added.
- **Restatements elsewhere.** `git grep sweepObject` over `skills/`,
`content/`, `docs/`, `apps/`, `ui/`: no hit; no skill page restates the
MCP advice.
## Verification
Readings on head `9fe18676` (`fa901c10` the change, `35374475` merged
with `main` at `fdd06415`, `9fe18676` a comment-only edit) unless named.
<details><summary>Build, typecheck, tests</summary>
- Build: `pnpm --filter '@objectstack/service-automation^...' --filter
'@objectstack/mcp^...' build` under the lock, VERDICT command-exit 0;
later `pnpm --filter '@objectstack/dogfood^...' build` on `35374475`,
exit 0.
- `@objectstack/service-automation`: `pnpm test` Test Files 264/264,
Tests 3418/3418 (on `fa901c10`); `pnpm typecheck` 0.
- `@objectstack/mcp`: `pnpm test` Test Files 56/56, Tests 678/678 (on
`fa901c10`); `pnpm typecheck` 0.
- `pnpm lint` (`eslint . --no-inline-config`) on `9fe18676`: exit 0.
</details>
<details><summary>Ablation (scripts/ablation-replace.mjs; the suite
imports `src`, no dist leg)</summary>
- The binding read gated off, so the pass types against
`config.objectName` again (`const objectName = 'ABLATION_30396' &&
bound?.triggerType === 'never'`): mutation landed (anchor 1→0, blob
`c53973cf` → `488218fa`); 3 red, among them `a stored two-object flow: a
field of the swept object passes` with `+ "[flow 'renewal_alert'] node
'check' (decision) condition: unknown field \`renewal_status\` on
\`crm_account\`"`; restored, blob == HEAD, `git diff HEAD` empty.
</details>
<details><summary>Derived gates</summary>
- `node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack` on `9fe18676`: 62 families (the same list as
on `35374475`), all 62 exit 0 (exit code captured per line before any
pipe).
- One was not measured on the first pass and is recorded at its re-run:
`check:dual-build-cjs-loads` (PREREQUISITE NOT MET: seven packages
outside the dogfood closure had no `dist/`; built under the lock, then
exit 0). `check-plugin-teardown-shape --self-test`'s pinned fixture
commit was fetched into this shallow clone before the run.
- `--ran` reconciliation: "62 derived famil(ies) accounted for — 62 run,
0 NOT-MEASURED (a DERIVED zero …)".
</details>
<details><summary>Dogfood (full battery, run as the order asks; no
narrowing arm)</summary>
- `pnpm --filter '@objectstack/dogfood^...' build && pnpm --filter
@objectstack/dogfood test` under the lock, started on `35374475` (no
shard, no path): Test Files 419 passed, 3 skipped (422); Tests 3492
passed, 36 skipped (3528); VERDICT command-exit 0. `9fe18676` changes
only a code comment in `flow-registration-checks.ts`, written while that
build was running.
</details>
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01Utp6CbRQNg8jRbhpBF2rFY
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent eea2603 commit 6c4c43a
6 files changed
Lines changed: 131 additions & 16 deletions
File tree
- .changeset
- packages
- mcp/src
- services/service-automation/src
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
1099 | 1099 | | |
1100 | 1100 | | |
1101 | 1101 | | |
1102 | | - | |
| 1102 | + | |
1103 | 1103 | | |
1104 | 1104 | | |
1105 | | - | |
1106 | | - | |
1107 | | - | |
| 1105 | + | |
| 1106 | + | |
| 1107 | + | |
| 1108 | + | |
1108 | 1109 | | |
1109 | 1110 | | |
1110 | 1111 | | |
| |||
1145 | 1146 | | |
1146 | 1147 | | |
1147 | 1148 | | |
1148 | | - | |
1149 | | - | |
1150 | | - | |
1151 | | - | |
| 1149 | + | |
| 1150 | + | |
| 1151 | + | |
| 1152 | + | |
| 1153 | + | |
1152 | 1154 | | |
1153 | 1155 | | |
1154 | 1156 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
275 | 275 | | |
276 | 276 | | |
277 | 277 | | |
278 | | - | |
279 | | - | |
| 278 | + | |
| 279 | + | |
| 280 | + | |
| 281 | + | |
280 | 282 | | |
281 | 283 | | |
282 | 284 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
9003 | 9003 | | |
9004 | 9004 | | |
9005 | 9005 | | |
| 9006 | + | |
| 9007 | + | |
| 9008 | + | |
9006 | 9009 | | |
9007 | 9010 | | |
9008 | 9011 | | |
| |||
9022 | 9025 | | |
9023 | 9026 | | |
9024 | 9027 | | |
| 9028 | + | |
9025 | 9029 | | |
9026 | 9030 | | |
9027 | 9031 | | |
| |||
| 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 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
435 | 435 | | |
436 | 436 | | |
437 | 437 | | |
438 | | - | |
| 438 | + | |
439 | 439 | | |
440 | 440 | | |
441 | 441 | | |
| |||
448 | 448 | | |
449 | 449 | | |
450 | 450 | | |
451 | | - | |
452 | | - | |
453 | | - | |
454 | | - | |
| 451 | + | |
| 452 | + | |
| 453 | + | |
| 454 | + | |
| 455 | + | |
| 456 | + | |
| 457 | + | |
| 458 | + | |
| 459 | + | |
| 460 | + | |
| 461 | + | |
| 462 | + | |
455 | 463 | | |
456 | | - | |
| 464 | + | |
| 465 | + | |
| 466 | + | |
| 467 | + | |
457 | 468 | | |
458 | 469 | | |
459 | 470 | | |
| |||
0 commit comments