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

2.8 KiB

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.