Task #245 — exposes each scripted alarm's current ActiveState as IReadable so
OPC UA variable reads on Source=ScriptedAlarm nodes return the live predicate
truth instead of BadNotFound.
## ScriptedAlarmReadable
Wraps ScriptedAlarmEngine + implements IReadable:
- Known alarm + Active → DataValueSnapshot(true, Good)
- Known alarm + Inactive → DataValueSnapshot(false, Good)
- Unknown alarm id → DataValueSnapshot(null, BadNodeIdUnknown) — surfaces
misconfiguration rather than silently reading false
- Batch reads preserve request order
Phase7EngineComposer.Compose now returns this as ScriptedAlarmReadable when
ScriptedAlarm rows are present. ScriptedAlarmSource (IAlarmSource for the event
stream) stays in place — the IReadable is a separate adapter over the same engine.
## Tests — 6 new + 1 updated composer test = 19 total Phase 7 tests
ScriptedAlarmReadableTests covers: inactive + active predicate → bool snapshot,
unknown alarm id → BadNodeIdUnknown, batch order preservation, null-engine +
null-fullReferences guards. The active-predicate test uses ctx.GetTag on a seeded
upstream value to drive a real cascade through the engine.
Updated Phase7EngineComposerTests to assert ScriptedAlarmReadable is non-null
when alarms compose, null when only virtual tags.
## Follow-ups remaining
- #244 — driver-bridge feed populating CachedTagUpstreamSource
- #246 — Program.cs Compose call + SqliteStoreAndForwardSink lifecycle