fix(audit): populate ParentExecutionId on alarm-triggered script runs
M5.4 T4 threaded a `parentExecutionId` parameter through
AlarmActor.SpawnAlarmExecution → AlarmExecutionActor → ScriptRuntimeContext,
but every call site passed null — so alarm on-trigger runs were silently always
execution-tree roots, contradicting the "tag-cascade coverage is complete"
claim in CLAUDE.md and Component-AuditLog.md.
Source the id where a spawner genuinely exists: a static attribute write issued
by a site script (`Instance.SetAttribute`) or by an inbound API request
(`Route.To(...).SetAttributes(...)`, whose ParentExecutionId was already carried
to the site and then dropped). The id rides site-locally through three additive,
nullable fields — no wire, proto or central schema change:
ScriptRuntimeContext.SetAttribute / RouteToSetAttributesRequest.ParentExecutionId
→ SetStaticAttributeCommand.SourceExecutionId
→ AttributeValueChanged.SourceExecutionId (InstanceActor static-write path)
→ AlarmActor.SpawnAlarmExecution → AlarmExecutionActor → ScriptRuntimeContext
All four computed trigger types participate. Expression triggers evaluate off
the dispatcher, so the writer of the newest value folded into the snapshot is
captured *with* the snapshot and echoed home on ExpressionEvalResult /
ExpressionEvalFailed — a change arriving mid-flight cannot mis-attribute the
raise.
Deliberately still roots (documented, not deferred): alarms fired by Data
Connection Layer values (external device data has no spawning execution — this
includes the device echo of a script write to a *data-sourced* attribute, so
only static writes cascade), and ScriptActor value-change/conditional/
expression/timer trigger runs (a timer tick has no spawner; a WhileTrue/interval
run has no single identifiable write).
Tests: new SiteRuntime.Tests/Actors/AlarmCascadeParentExecutionTests pins all
three hops — SetAttribute stamps the run's ExecutionId, InstanceActor publishes
it on the change (and publishes null when absent), and ValueMatch/HiLo/
Expression alarms parent the on-trigger run to the writer while a DCL-originated
change leaves it a root.
Docs: CLAUDE.md and Component-AuditLog.md corrected from "complete" to the true
behaviour; Component-SiteRuntime.md gains an "Audit correlation of an on-trigger
run" section with the hop table and the by-design root cases.
This commit is contained in:
@@ -259,6 +259,41 @@ When the Instance Actor is stopped (due to disable, delete, or redeployment), Ak
|
||||
- **Can** call instance scripts via `Instance.CallScript()` — sends an ask message to the appropriate sibling Script Actor.
|
||||
- Instance scripts **cannot** call alarm on-trigger scripts — the call direction is one-way.
|
||||
|
||||
### Audit correlation of an on-trigger run (`ParentExecutionId` tag-cascade)
|
||||
|
||||
An alarm fired by a **script- or inbound-API-initiated static attribute write**
|
||||
is a genuine execution spawn, and the on-trigger run records the writing
|
||||
execution as its parent in the central Audit Log (#23). The originating id rides
|
||||
site-locally through three additive, nullable fields — no wire, proto or schema
|
||||
change:
|
||||
|
||||
| Hop | Field |
|
||||
|---|---|
|
||||
| `ScriptRuntimeContext.SetAttribute` (this run's `ExecutionId`) / `RouteToSetAttributesRequest.ParentExecutionId` (the inbound request's) | → `SetStaticAttributeCommand.SourceExecutionId` |
|
||||
| Instance Actor static-write path (`HandleSetStaticAttributeCore`) | → `AttributeValueChanged.SourceExecutionId` |
|
||||
| Alarm Actor trigger evaluation → `SpawnAlarmExecution` | → `AlarmExecutionActor` → `ScriptRuntimeContext.ParentExecutionId` |
|
||||
|
||||
All four computed trigger types participate. `Expression` triggers evaluate a
|
||||
whole attribute snapshot **off the dispatcher**, so the firing change is no
|
||||
longer in scope when the boolean returns: the writer of the newest value folded
|
||||
into the snapshot is captured *with* the snapshot and echoed back on the
|
||||
evaluation result, so a change arriving mid-flight cannot mis-attribute the
|
||||
raise.
|
||||
|
||||
The on-trigger run is correctly a **tree root** (`ParentExecutionId` NULL) when
|
||||
the firing value has no originating execution:
|
||||
|
||||
- Values from the **Data Connection Layer** — external device data has no
|
||||
spawning execution. This includes the confirmed value of a write to a
|
||||
**data-sourced** attribute: that write is forwarded to the device and the echo
|
||||
arrives on the subscription long after the writing execution ended, so only
|
||||
**static** attribute writes cascade.
|
||||
- Deploy-time seeding and central Test Run / tooling writes.
|
||||
|
||||
`AttributeValueChanged.SourceExecutionId` is deliberately **not** projected onto
|
||||
the gRPC debug/stream wire shapes — the cascade is resolved inside the site node
|
||||
that owns the Instance Actor, and central reads the linkage from audit rows.
|
||||
|
||||
---
|
||||
|
||||
## Native Alarm Actor
|
||||
|
||||
Reference in New Issue
Block a user