Skip to content

A non-record--prefixed flow triggerType silently degrades the flow to manual — no lint, no runtime signal (spun out of #5957) #6637

Description

@os-project-manager

Observation-class. Spun out of #5957 / PR #6636, whose ruling put this family explicitly out of scope: it is a different shape and a different question (a lint-capability judgement), so it is filed standalone rather than folded into that card. Unassigned, for triage. Nobody is hitting it today.

The shape

flow-trigger-unknown-event only ever looks at tokens that already start with record-. The engine's binding resolver decides that first:

// packages/services/service-automation/src/engine.ts, resolveTriggerBinding
const triggerType = typeof config.triggerType === 'string' ? config.triggerType : undefined;
if (triggerType && triggerType.startsWith('record-')) {
  return { triggerType: 'record_change', binding: { /* ... event: triggerType */ } };
}
// ... array form, time_relative, schedule, api ...
// falls through: the flow is simply not record-triggered

So the two families fail differently:

authored token engine verdict diagnostic today
record-created (in-prefix, out-of-grammar) binds to record-change, maps to zero hook events, never fires flow-trigger-unknown-event, error since #5762
onCreate (out-of-prefix) never binds at all — silently a manual flow none, at any layer

The second row is the strictly quieter failure of the two, and it is the one with no diagnostic. An author who writes onCreate gets a flow that looks authored, validates clean, boots clean, and simply never runs.

Measured census (origin/main at the time of filing)

git grep over the tracked tree finds 4 non-record--prefixed triggerType tokens. They sit in three different positions, and only one is the shape above — worth stating, because #5957's body counted them as one group and only a third of that count is actionable:

  1. A genuine flow start node — the real instance.
    packages/cli/test/authoring-rule-command-parity.test.ts:60
    config: { objectName: 'parity_task', triggerType: 'onCreate' } on a type: 'record_change' flow. This is exactly the silent-degradation shape. (The fixture's subject is approval-expression-invalid parity, so its own assertions do not depend on the token.)

  2. The FlowRuntimeState inventory shape — a different field that happens to share the name. packages/runtime/src/domain-handler-registry.test.ts:663 and :670, triggerType: 'on_create' inside { name, enabled, bound, status, triggerType, object }. That position carries the resolved trigger kind (record_change / schedule / time_relative / api), not an authored token. The test is a pass-through assertion (toEqual against the same literal), so the value is arbitrary by construction.

  3. A deliberately-invalid literal — correct as written, do not touch. packages/spec/src/data/object.test.ts:1003, workflows: [{ name: 'stamp', triggerType: 'on_update', actions: [] }]. This is the Object-level workflows: [...] (and any unknown ObjectSchema key) is silently stripped at build — no error/warning (ADR-0032 'no silent failure', metadata layer) #1535 unknown-key rejection test; the whole workflows key is rejected on purpose and the literal carries a @ts-expect-error. Not a flow start node at all.

So: 1 site of the described shape, not 4.

The question for triage

Should a non-record--prefixed triggerType on a type: 'record_change' flow produce a diagnostic?

The tension is real in both directions, which is why this is filed rather than fixed:

  • For: it is a strictly quieter failure than the one lint never-fire 族 severity 复审:两条 warning 实感更接近 error(#5496/#5647 dev 报告建议)—— 同批升、单独 PR,不逐条搭车 #5762 raised to error, on the same authoring surface, and "declared = enforced" says a flow that declares a trigger and binds to nothing should say so. A record_change flow whose start node names a trigger the engine cannot route is not an ambiguous intent.
  • Against: the prefix check is also the mechanism by which a flow legitimately is manual, and lint-flow-patterns.test.ts already carries a deliberate does NOT flag record_change — undecidable at authoring time, caught at run time case. A new rule here risks flagging the intentional shape. There may also be no business pull: nothing in the example apps authors this, and the only in-repo instance is one CLI test fixture.

A narrower framing worth considering instead of a new rule: the check could be scoped to flows that declare type: 'record_change' and a start-node triggerType that resolves to no binding at all — which is decidable at authoring time and cannot catch a genuinely manual flow, because a manual flow does not declare record_change.

If triage decides yes, the runtime half (#3481 established the precedent that a silently-dead trigger form deserves a signal at some layer) and the lint half should be decided together.

Related: #5957 / PR #6636 (origin), #5762 (the severity upgrade that made the in-prefix family loud), #3427 / #3457 / #3481 (the three sources of flow-trigger-unknown-event).

Metadata

Metadata

Type

No type

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions