100 lines
3.0 KiB
Markdown
100 lines
3.0 KiB
Markdown
# Runtime Flows And Invariants
|
|
|
|
## Event Time Source Priority
|
|
|
|
Use component event time over ingest time whenever possible.
|
|
|
|
- `eventFallbackTime(actual, ingestedAt, collectedAt)`:
|
|
1. `actual`
|
|
2. `ingestedAt`
|
|
3. `collectedAt`
|
|
|
|
- `collectedFallbackTime(collectedAt, ingestedAt)`:
|
|
1. `collectedAt`
|
|
2. `ingestedAt`
|
|
|
|
## Status Event Time Parsing Order
|
|
|
|
`parseComponentStatusEventTime` resolves time in this order:
|
|
|
|
1. `status_changed_at`
|
|
2. Latest matching `status_history` item for current status
|
|
3. Latest parseable `status_history` item
|
|
4. `status_checked_at`
|
|
|
|
## Failure Event Rules
|
|
|
|
For critical components:
|
|
|
|
- Timeline event type: `COMPONENT_FAILED`
|
|
- `failure_events.failure_time` uses resolved failure time (not raw ingest time)
|
|
- `failure_events.external_id` includes the same failure timestamp
|
|
|
|
## First Seen Rules
|
|
|
|
`parts.first_seen_at` must be the earliest known ingest-derived component time.
|
|
|
|
Candidate sources:
|
|
|
|
1. Parseable `status_history[].changed_at`
|
|
2. `status_changed_at`
|
|
3. `status_checked_at`
|
|
4. `eventFallbackTime(nil, ingestedAt, collectedAt)`
|
|
|
|
Persistence rule:
|
|
|
|
- Keep the minimum value over time.
|
|
- If incoming is earlier than stored value, overwrite with incoming value.
|
|
|
|
## Duplicate Component Serial Rules (CSV + JSON Ingest)
|
|
|
|
If serial numbers are not unique within the same `p/n` (`model`) inside one ingest payload:
|
|
|
|
- First occurrence keeps original `vendor_serial`.
|
|
- Each next duplicate occurrence is assigned a service serial placeholder:
|
|
- Format: `NO_SN-XXXXXXXX` (8-digit zero-padded global counter).
|
|
- If `vendor_serial` is empty, a service serial placeholder is assigned as well.
|
|
- Counter is global for the whole application and stored in `id_sequences` under `entity_type = 'no_sn_placeholder'`.
|
|
|
|
## Component Health Computation In UI
|
|
|
|
Component health is derived only from the latest status event among:
|
|
|
|
- `COMPONENT_FAILED`
|
|
- `COMPONENT_WARNING`
|
|
- `COMPONENT_OK`
|
|
|
|
Non-status timeline events (`INSTALLED`, `REMOVED`, `FIRMWARE_CHANGED`, `FIRMWARE_INSTALLED`, etc.) must not change health status.
|
|
|
|
## Firmware Timeline Rules
|
|
|
|
For component firmware observations:
|
|
|
|
- First observed version -> `FIRMWARE_INSTALLED` (asset + component timeline pair)
|
|
- Later version change -> `FIRMWARE_CHANGED` (asset + component timeline pair)
|
|
|
|
Storage details:
|
|
|
|
- `FIRMWARE_INSTALLED` stores transition string in `timeline_events.firmware_version`: `- -> <installed_version>`
|
|
- `FIRMWARE_CHANGED` stores installed/new firmware value
|
|
|
|
Detection details:
|
|
|
|
- Previous observation lookup: `ORDER BY observed_at DESC, id DESC LIMIT 1 OFFSET 1`
|
|
|
|
## Timeline Color Semantics
|
|
|
|
- `REMOVED` -> yellow
|
|
- `COMPONENT_FAILED` -> red
|
|
- `COMPONENT_WARNING` and related warning semantics follow `timelineEventClass`
|
|
|
|
## Regression Guardrails
|
|
|
|
Do not reintroduce these regressions:
|
|
|
|
- Using ingest timestamp when payload provides better event/failure timestamp
|
|
- Letting `INSTALLED` mark failed components as healthy
|
|
- Missing `Previous Components` section on asset page
|
|
- Missing installation history on component page
|
|
- Missing firmware information on component page timeline
|