Skip to content

Verified: Alarms

Each claim the Alarms page makes, with the tests that prove it in the last green CI run: 18 of 41 verified, 3 with no test yet. The claims live in docs/claims/alarms.yaml.

Last green CI run 16 Oct 2026, 14:48 UTC on c7618d3 · all runtime claims

ALM-001 verified

A project with no alarms section behaves exactly as before: there is no alarm engine, all five /api/alarms routes answer 404, /api/meta reports alarms false, and stream frames carry no alarms field.

where the docs say so · "Naming the manifest key" dropped (the page does not say it). TestNoAlarmRetainerLeavesTheStateAlone removed: retain contents are not one of the listed promises.

  • pass TestNoAlarmsSectionMeansNoEngine in internal/project
  • pass TestAlarmRoutes404WithoutAnEngine in server
  • pass TestFrameCarriesAlarmSummary in server

ALM-002 partly verified

match takes type, member and tag, all globs; a rule with a member matches struct members, and a rule without one matches flat BOOL tags.

where the docs say so · Member and tag globs and the struct-versus-flat split are asserted. A glob in type is never tested (every case spells the type literally), and no test shows a member-less rule skipping a flat non-BOOL tag. "Case-insensitive" dropped (not on the page).

  • pass TestMatchMemberDiscriminatesStructFromFlat in alarm

ALM-003 verified

Rules are tried in file order and the first matching rule wins.

where the docs say so · "A member no rule claims produces no definition" dropped (not on the page), and with it TestRulesExpandAgainstComposedTags, which asserted only that clause.

  • pass TestExpandCoversTheFleetFromFewRules in alarm

ALM-005 partly verified

An unknown placeholder in an id, name, site, area, display or enable template is a load error, not literal braces on an operator screen.

where the docs say so · Both tests put the bad placeholder in a name template; id, site, area, display and enable templates are not tested.

ALM-006 partly verified

An alarm id declared in two sources (two rules, a rule and an explicit definition, or a manifest and an alarm file) is an error naming both sources, never last-wins.

where the docs say so · Error-not-last-wins is asserted for all three pairs, but naming both sources only for two rules ("rule 0" and "rule 1"). The rule-versus-entry test checks only the id. The manifest-versus-alarm-file test checks only the substring "declared by both", and the message actually reads `alarm "X" is declared by both alarm "X" and alarm "X"`: it names neither the file nor the manifest.

  • pass TestExpandRejectsDuplicateIDs in alarm
  • pass TestExpandRejectsRuleCollidingWithExplicitAlarm in alarm
  • pass TestAlarmFilesDuplicateIDIsAnError in internal/project

ALM-007 verified

Alarm files are bare YAML lists, composed with the manifest's own alarms section.

where the docs say so · Unknown-key rejection dropped (the page does not promise it), and with it TestLoadRejectsUnknownKey, TestLoadRejectsUnknownKeyInsideMatch and TestManifestAlarmsDecodeStrictly.

ALM-008 verified

site-from and area-from patterns set each definition's site and area from the tag name (RTU9_ZONE15_FIT_001 gives site RTU9, area ZONE15).

where the docs say so · Rewritten to the page's example. The capture-group-required load error (TestSiteFromNeedsACaptureGroup) is not a promise of the page.

ALM-009 verified

A condition becomes Unack-Active only after it has held for on-delay; a bit that drops before the delay elapses never qualifies and journals nothing.

where the docs say so

  • pass TestOnDelayQualifiesAgainstTheClock in alarm
  • pass TestAlarmOnDelayWalksInVirtualTime in acceptance

ALM-010 partly verified

An active alarm whose condition goes false returns to Unack-RTN only after it has held false for off-delay.

where the docs say so · Only t=0 and t=31s against a 30 s delay are checked: nothing just under the delay, and a bounce restarting the hold is never shown.

  • pass TestOffDelayHoldsTheAlarmUp in alarm

ALM-011 verified

The ISA-18.2 transition table holds: Unack-Active acks to Ack-Active, Ack-Active returns to Normal on clear, Unack-RTN needs an ack to reach Normal, and Unack-RTN going true again becomes Unack-Active.

where the docs say so

  • pass TestLifecycleActiveAckRTN in alarm
  • pass TestStateMachineTable in alarm (7 of 7 passed)
  • pass TestUnackRTNRequiresAnAck in alarm

ALM-012 verified

ack-required false collapses Unack-Active to Ack-Active and Unack-RTN to Normal on arrival; auto-clear false latches: an acked alarm whose condition clears goes to Unack-RTN, not Normal.

where the docs say so · Reworded to the page's table row (Ack-Active, condition false → Unack-RTN with auto-clear false). The earlier "until acked" was not asserted for a latched alarm: no case acks it again after the latch.

  • pass TestStateMachineTable in alarm (7 of 7 passed)

ALM-013 verified

A shelf always expires (there is no permanent shelf), and the alarm then returns to the state it interrupted rather than dropping to Normal.

where the docs say so · The 400 status and "permanent shelf" message dropped from the claim (not on the page); the tests still show both a past shelf and an open-ended request refused.

  • pass TestShelveExpires in alarm
  • pass TestShelveRejectsThePastAndTheUnshelvable in alarm
  • pass TestAckShelveAndUnshelve in server
  • pass a shelved high-level alarm is silent and always expires in examples/lift-station

ALM-014 verified

A shelved alarm is silent: off the active list, and journalling nothing while its condition cycles.

where the docs say so · "Condition still tracked" dropped: not on the page, and the test cannot show it (the condition ends true and the interrupted state was already Unack-Active, so unshelve gives the same answer either way). The non-shelvable refusal is not a page promise, so TestShelveRejectsThePastAndTheUnshelvable was removed here.

  • pass TestShelveIsSilentButStillTracks in alarm
  • pass TestShelveExpires in alarm
  • pass a shelved high-level alarm is silent and always expires in examples/lift-station

ALM-016 verified

When the enable path goes false the alarm becomes Suppressed (leaving the active list), and returns to its live state when enable goes true again, with suppress and unsuppress journalled.

where the docs say so

ALM-017 verified

A definition whose tag does not resolve (a site that has never birthed) is Suppressed with a reason, never an error that faults the engine.

where the docs say so · The non-BOOL case is not on the page; TestNonBoolConditionIsSuppressedNotFatal was removed.

  • pass TestMissingTagSuppressesWithAReason in alarm

ALM-018 verified

Acknowledgement and shelf persist through the retain store, and a restarted or failed-over engine restores them: an acked alarm is Ack-Active again rather than unacked, and a shelved alarm stays shelved.

where the docs say so · The engine's snapshot and restore (ack and shelf) and the runtime's retain plumbing are tested apart; TestRunWiresAlarmAcksThroughRetain runs naut run's startAlarms wiring with the real engine and restores an ack across a restart.

  • pass TestRetainedAckSurvivesRestart in alarm
  • pass TestAlarmStateRoundTripsThroughRetain in runtime
  • pass TestRunWiresAlarmAcksThroughRetain in cmd/naut

ALM-019 partly verified

A standby re-reads the retain store on the leadership edge and restores alarm state, so a failover does not resurrect acked alarms as unacked.

where the docs say so · Engine-level restore is tested with a fake alarm engine; there is no end-to-end two-node failover test with a real engine.

  • pass TestTakeoverRestoresAlarmsOnTheLeadershipEdge in runtime

ALM-020 partly verified

Only ack and shelf persist; active and RTN are not retained but re-derive from the field.

where the docs say so · The snapshot holds only ack and shelf (the retained record has no field for anything else). No test restores an ack while the field is false to show the state re-derives rather than coming back active. The unknown-id and expired-shelf cases are not on the page, so TestRestoreIgnoresUnknownAndExpired was removed.

  • pass TestRetainedAckSurvivesRestart in alarm

ALM-021 partly verified

Acknowledgement and shelf are never written back to the edge device's tags.

where the docs say so · An ack with no scan after it leaves the alarm's bit and the PV, HH and Online tags unchanged. Shelve and unshelve writing nothing are not asserted, and only those four tags are checked.

ALM-022 verified

GET /api/alarms returns a timestamp, the summary and the active alarm list.

where the docs say so

  • pass TestAlarmsListAndSummary in server

ALM-023 verified

POST /api/alarms/ack acknowledges the listed ids, or all of them in one request.

where the docs say so · The returned count, the 404 for an unknown id and the no-half-apply rule are code details, not page claims.

  • pass TestAckShelveAndUnshelve in server
  • pass TestAckAllAndUnknownID in alarm

ALM-024 partly verified

POST /api/alarms/shelve shelves an alarm for the given seconds and records who (by); POST /api/alarms/unshelve takes it off the shelf.

where the docs say so · The shelf's expiry is asserted only to be non-zero, not to be now plus the requested seconds.

  • pass TestAckShelveAndUnshelve in server

ALM-025 partly verified

GET /api/alarms/journal filters by from, to, site, priority, id, kind, state, free text q and limit.

where the docs say so · Over HTTP only kind, site and q are asserted (plus a malformed from rejected). Priority, id, the from/to range and limit are asserted only on the ring journal. The state filter is not asserted anywhere. TestParseTimeAcceptsBothForms removed (a parse helper, not a filter).

  • pass TestJournalQuery in server
  • pass TestJournalFiltering in alarm (9 of 9 passed)

ALM-026 partly verified

Alarm writes (ack, shelve, unshelve) require the same authorization as a tag write (Bearer or X-Nautilus-Token), while reads stay ungated.

where the docs say so · No token gives 401 and X-Nautilus-Token gives 200; an Authorization: Bearer token is not tried, and the same-origin default for browser writes is not asserted.

  • pass TestAlarmWritesHonorAuth in server

ALM-027 no test yet

A standby forwards alarm writes to the leader the same way it forwards a tag write.

where the docs say so · No test covers forwarding of /api/alarms writes from a standby.

ALM-028 partly verified

Each stream frame carries a counts-only alarms summary (active, unacked, shelved, suppressed, byPriority, worst, newest, rev), never alarm rows.

where the docs say so · The frame test checks active and worst; the engine test adds unacked, shelved, byPriority and newest. Suppressed and rev are not checked in these tests, and the frame's JSON field names are not asserted.

  • pass TestFrameCarriesAlarmSummary in server
  • pass TestSummaryCounts in alarm

ALM-029 partly verified

The summary rev bumps on any state change and stays constant across quiet scans.

where the docs say so · Bumps are asserted for activation and ack only; rtn, shelve, unshelve, suppress and unsuppress are not checked.

  • pass TestSummaryRevBumpsOnlyOnChange in alarm

ALM-030 verified

/api/meta reports whether the alarm engine is present and the offered shelve times in seconds.

where the docs say so · TestConfigShelveDurationsFallBack removed (default shelve times are not a page claim). TestAlarmRoutes404WithoutAnEngine added: it is the only test asserting the alarms false half of "whether".

  • pass TestMetaReportsAlarmsAndShelveTimes in server
  • pass TestAlarmRoutes404WithoutAnEngine in server

ALM-032 verified

Every state change produces exactly one journal event of kind active, rtn, ack, shelve, unshelve, suppress or unsuppress.

where the docs say so · TestEventJSONShape removed (it serialises a hand-built event; no state change is involved).

  • pass TestStateMachineTable in alarm (7 of 7 passed)
  • pass TestSuppressedWhenEnableFalse in alarm
  • pass TestShelveExpires in alarm

ALM-033 partly verified

An in-memory journal ring is always on and bounded by its keep depth, dropping the oldest events, so the journal works without any database.

where the docs say so · The always-on default (no sink configured) and the keep config key are not asserted at the project level.

  • pass TestRingJournalRoundTrip in alarm

ALM-034 partly verified

The file journal sink is a durable journal written as rotated JSONL.

where the docs say so · Durable (events survive a reopen) and rotated (a .1 generation appears) are asserted. JSONL is not: both tests read the file back only through the sink's own Query, so nothing inspects that what is written is one JSON object per line. Querying across generations and skipping a torn line are code details, not page claims; TestFileJournalSkipsATornLine was removed.

  • pass TestFileJournalRoundTrip in alarm
  • pass TestFileJournalRotatesAndStillQueriesAcross in alarm

ALM-035 partly verified

The postgres journal sink creates its table idempotently at startup.

where the docs say so · Skips unless NAUTILUS_TEST_DATABASE_URL points at a real Postgres (no CI job sets it); idempotence shows only on a second run against the same database. "Queries newest first" dropped (not on the page).

  • pass TestPGJournal in alarm

ALM-036 partly verified

The postgres sink reads its connection string from the environment variable dsn-env names, defaulting to NAUTILUS_ALARM_DB_URL and falling back to DATABASE_URL.

where the docs say so · Only the both-unset case is tested (the error names NAUTILUS_ALARM_DB_URL). That a set dsn-env, the default or the DATABASE_URL fallback is actually read is not asserted.

ALM-038 partly verified

Notifiers (log, and webhook with a header) receive each event and run off the scan goroutine.

where the docs say so · Asserted: each event reaches the notifier, the log notifier writes one record carrying the event, the webhook posts it with a custom header, and Evaluate returns while a notifier is blocked (off the scan goroutine). `header-env:` (the key read from the environment) has no test.

  • pass TestWebhookPostsTheEventAsJSON in alarm
  • pass TestEngineNotifiesOffTheScanGoroutine in alarm
  • pass TestLogNotifier in alarm

ALM-039 no test yet

A slow notifier drops events with a counter on a bounded queue instead of stalling the scan.

where the docs say so · Only the idle-notifier path (Dropped is 0) is tested; no test saturates the queue.

ALM-040 partly verified

The engine evaluates once per main scan on the post-scan hook, so one scan over a tripped limit is enough to raise the alarm.

where the docs say so · One scan is shown to be enough (the bit the logic sets in that scan is seen); that evaluation happens exactly once per main scan is not asserted.

ALM-041 partly verified

A standby never evaluates alarms because it skips Scan.

where the docs say so · Asserts the standby does not scan; does not assert on the alarm engine directly.

  • pass TestStandbyDoesNotScan in runtime

ALM-042 verified

The same engine works on a single controller against a BOOL its own logic computes, on a one-box memory-driver project.

where the docs say so · TestLiveSealInAndAlarm removed (it checks a tag on a fake live controller; no alarm engine is involved).

ALM-043 partly verified

naut check reports a condition path naming a missing member or a non-BOOL as an error, an undeclared tag and a rule matching nothing as warnings.

where the docs say so · The error/warning split is asserted on Project.CheckAlarms, the pass naut check calls. No test runs naut check on a project with alarm mistakes, so the command printing them, and counting the first two as errors and the last two as warnings, is not asserted.

ALM-044 verified

Acceptance tests assert alarm state with the alarms matchers (active, unacked, shelved, state, priority, journal) and drive it with the ack, shelve and unshelve verbs, in virtual time so an on-delay is exact.

where the docs say so · TestAlarmFailureNamesWhatItSaw has every matcher (active, unacked, shelved, state, priority, journal) fail on a value wrong in one way and say what it saw.

  • pass TestAlarmOnDelayWalksInVirtualTime in acceptance
  • pass TestAlarmAckVerb in acceptance
  • pass TestAlarmShelveAndUnshelveVerbs in acceptance
  • pass TestAlarmFailureNamesWhatItSaw in acceptance (7 of 7 passed)

ALM-045 no test yet

naut alarms list dumps the expanded alarm set (-o yaml re-loadable, with -count, -site and -priority narrowing it).

where the docs say so · No test found for the naut alarms list command.