You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
fix(automation): kill the four-layer silence around record-change flows that never fire (#3099)
2026-07-17 third-party eval: an auto-launched flow that failed to fire
produced ZERO output at every layer. Root causes were two-fold:
1. serve's boot-quiet window swallows stdout entirely, and ObjectLogger
routes debug/info/warn to stdout — so every flow-registration/binding
log (success AND failure) vanished in `os dev`/`os start`. Only
error/fatal (stderr) survive.
2. Each "didn't happen" path was itself silent: a trigger-fired execute()
failure lived only in the run-history row; a flow whose objectName
matches nothing armed a hook that never fires; a stack whose requires
lacked automation/triggers just never bound anything.
Fixes, layer by layer:
- Startup banner `Flows:` section (post-restore, immune to the quiet
window): counts + trigger types + ⚠ lines for engine-not-enabled,
unbound triggered flows, and dead bindings (unknown object).
- Trigger-fired run failures now log at ERROR (stderr): the engine no
longer drops the AutomationResult of a trigger-fired execution.
Condition-not-met skips stay at debug (high-frequency by design).
- RecordChangeTrigger probes object existence at bind time and warns on
an objectName that matches no registered object (exact-name match).
- kernel:bootstrapped binding audit in the automation plugin: warn per
enabled-but-unbound triggered flow with the actionable reason; new
AutomationEngine.getTriggerBindingAudit() + extended
getFlowRuntimeStates() (status/triggerType/object).
- os validate: new lint rule validateFlowTriggerReadiness — warns when a
record-triggered flow targets an object the stack does not define, and
when an auto-triggered flow's status is draft (authored or defaulted;
draft flows still fire — declare active/obsolete). Showcase flows now
declare status: 'active' explicitly.
- Removed the leftover boot-debug stderr writes (registerApp/AppPlugin/
StandaloneStack/AuditPlugin) that earlier debugging of this same
silence left behind.
Verified end-to-end on a real create-objectstack project (published-
package layout): correct flows fire on REST PATCH across os start /
os dev / restart; each mistake variant now produces its explicit signal.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Flow trigger observability — kill the four-layer silence around record-change flows that never fire (2026-07-17 third-party eval).
12
+
13
+
A misauthored auto-launched flow (wrong `objectName`, missing `requires: ['automation','triggers']`, failing start condition) produced ZERO output at every layer: the engine's own registration/binding logs land inside the CLI's boot-quiet stdout window (which swallows debug/info/warn — only error/fatal reach stderr), and each "didn't happen" path was itself silent. Fixes:
14
+
15
+
-**Startup banner `Flows:` section** (`os serve`/`os dev`/`os start`): flow count, bound-to-trigger count, registered trigger types, draft count — plus loud `⚠` lines for flows declared with no automation engine enabled (`requires` missing), flows whose trigger type has no registered trigger, and bound record-change flows targeting an unknown object (dead binding). Printed after stdout is restored, so it is immune to the boot-quiet window.
16
+
-**Trigger-fired run failures now log at ERROR** (stderr — always visible): the automation engine no longer drops the AutomationResult of a trigger-fired execution; condition-evaluation faults and node failures surface with the flow name. Condition-not-met skips stay at debug (high-frequency, intentional).
17
+
-**`RecordChangeTrigger` probes object existence at bind time** and warns when a flow's `objectName` matches no registered object (exact-name matching), instead of silently arming a hook that can never fire.
18
+
-**`kernel:bootstrapped` binding audit** in the automation plugin: warns per enabled-but-unbound triggered flow with the reason, and reports registered/bound/draft counts (`AutomationEngine.getTriggerBindingAudit()`, extended `getFlowRuntimeStates()` with `status`/`triggerType`/`object`).
19
+
-**`os validate` flow-wiring advisories** (`@objectstack/lint``validateFlowTriggerReadiness`): warns when a record-triggered flow targets an object the stack does not define, and when an auto-triggered flow's status is `draft` (authored or defaulted — draft flows still fire; declare `active` or `obsolete`).
20
+
- Removed leftover boot-debug writes (`registerApp`/`AppPlugin`/`StandaloneStack`/`AuditPlugin` stderr noise) that previous debugging of this same silence had left behind.
description: 'On send, requires finance AND legal to both approve via one aggregating approval node — demonstrates parallel approvals without a token tree (ADR-0039 Track A).',
985
998
type: 'autolaunched',
999
+
status: 'active',
986
1000
// The revert-on-reject write is an approval-process outcome, not an act of the
987
1001
// submitter — run it as the system principal so it lands regardless of whether
988
1002
// the submitter still has edit rights on a "sent" invoice (#1888 runAs enforced).
description: 'On health → red, branches on severity then alerts in parallel and pushes to an incident system with try/catch — demonstrates nested construct composition.',
0 commit comments