Files
ruvnet--RuView/docs/adr/ADR-295-source-provenance-state-machine.md
T
2026-08-11 13:04:00 -04:00

65 lines
2.8 KiB
Markdown

# ADR-295: Source provenance state machine — synthetic can never present as live
- **Status**: Accepted — initial implementation (this PR)
- **Date**: 2026-08-11
- **Deciders**: ruv
- **Tags**: provenance, honesty, ui, sensing-server, security
## Context
An August 2026 external review found two provenance defects on the release
path:
1. The pose-fusion simulator starts in demo mode; on any page port other than
3000 the WebSocket target falls back to `localhost:8765`, and if the
connection fails the simulator keeps running while the status still reads
"ready" — producing a convincing moving visualization with no live CSI
(issue 1557).
2. The main sensing client labels the source **live** when the authenticated
status endpoint returns an error for lack of authorization, until a real
frame happens to correct it (issue 1526).
The common root cause: source state is a boolean (live vs not), so "unknown"
collapses to "live". CLAUDE.md requires MEASURED/CLAIMED/SYNTHETIC labeling
and forbids presenting synthetic output as real.
## Decision
Define one canonical, mutually exclusive `SourceState` enum shared by the
sensing server and every UI/client that renders a source:
- `Synthetic` — generated data (simulator/replay of synthetic fixtures).
- `LiveVerified` — frames from an authenticated, attested source.
- `LiveUnverified` — frames arriving but provenance not yet confirmed.
- `Stale` — last frame older than a configured freshness window.
- `Disconnected` — no source.
Rules enforced structurally:
- **`Unknown` is not a state.** Any ambiguous condition resolves to
`LiveUnverified`, `Stale`, or `Disconnected` — never `LiveVerified`.
- A status-endpoint error resolves to `Disconnected`/`LiveUnverified`, never
live-verified.
- The simulator constructs `Synthetic` and cannot transition to any `Live*`
state without a verified frame.
- `Synthetic` is watermarked in every view and every export.
- Transitions are a pure function of (last-frame-age, auth-status,
source-kind) so they are unit-testable without a clock or a socket.
Scope of this PR: the shared `SourceState` type + transition function + tests
in the sensing server, and wiring of the two identified surfaces (pose-fusion
simulator status, sensing client source label). Broader UI adoption follows.
## Consequences
- Closes the "synthetic shown as live" and "unknown shown as live" classes.
- A small breaking change to any consumer currently reading a boolean source
flag; mitigated by exposing a compatibility accessor during migration.
## Validation
- Unit tests for every transition, especially: auth-error → not-live;
simulator → never live without a verified frame; freshness expiry → `Stale`;
watermark present on synthetic export.
- `cargo test -p wifi-densepose-sensing-server`.