device-state: §11 — where the trail lives, what bounds it, what chains it
This commit is contained in:
parent
f8afc4896b
commit
eaa5e2923d
1 changed files with 56 additions and 6 deletions
|
|
@ -60,12 +60,12 @@ positive debounce and **250 ms negative** — near suppresses immediately, far
|
|||
waits before being believed. Every flap writes a forensic entry, so this is
|
||||
also most of the log volume below.
|
||||
|
||||
**OPEN — the forensic trail is neither durable nor bounded.**
|
||||
`$XDG_RUNTIME_DIR/souveraine/forensic.jsonl` is on tmpfs: it is RAM on a 3.5 GB
|
||||
phone and it is erased on every reboot. 104 KB accumulated in 40 minutes
|
||||
(~3.6 MB/day) with no rotation. §5 calls this trail "tamper-evident" and pairs
|
||||
it with the SessionAudit hash chain, which a file that evaporates on reboot
|
||||
cannot be. Two separate decisions are owed: where it lives, and what bounds it.
|
||||
**FIXED 2026-07-26 — the trail was on tmpfs, unbounded, and chained to
|
||||
nothing.** `$XDG_RUNTIME_DIR/souveraine/forensic.jsonl` was RAM on a 3.5 GB
|
||||
phone, erased every reboot, growing without limit — 104 KB in this audit's
|
||||
first 40 minutes. §5 called it tamper-evident; it carried a sequence number,
|
||||
which catches a deleted line and not an edited one. Both decisions the audit
|
||||
owed are taken, and the third it implied. See §11.
|
||||
|
||||
Two divergences to settle when wiring, code-first per doc precedence:
|
||||
- The `Locked → Observed` transition keys on **raw `proximity_near`**, not on
|
||||
|
|
@ -712,3 +712,53 @@ Health answers "is the source there". It does not answer "is the source telling
|
|||
the truth" — a proximity sensor stuck reporting `near` heartbeats perfectly and
|
||||
reads as `Live`. §4's cross-sensor disagreement machinery is where that would
|
||||
live, and it is still computed and logged without being branched on.
|
||||
|
||||
---
|
||||
|
||||
## 11. The trail itself (built 2026-07-26)
|
||||
|
||||
§5 said what goes in the trail. §10 made the entries diagnostic. This is about
|
||||
the file.
|
||||
|
||||
**Where.** `$XDG_STATE_HOME/souveraine/forensic.jsonl` — `~/.local/state`, next
|
||||
to `crashes.log`. Durable, and inside the encrypted home. No fallback path: with
|
||||
no home the trail is memory-only and says so at startup. The old code fell
|
||||
through to `/tmp`, which nobody would look in. Nothing is migrated; there was
|
||||
never anything in the tmpfs file to migrate.
|
||||
|
||||
**Bounds.** 4 MiB a file, two rotated generations, 12 MiB in all. At §0's
|
||||
3.6 MB/day that is three days; at the 0.5 MB/day measured since §10's heartbeat
|
||||
dedupe, three weeks. Proximity debounce (§9.5) cuts it again — the flap is most
|
||||
of what is left.
|
||||
|
||||
**The chain.** Each entry carries `prev`, the SHA-256 of the one before it, and
|
||||
each written line ends with its own `hash`. Same contract as
|
||||
`SessionAudit.qml`, so one verifier reads both: strip the trailing
|
||||
`,"hash":"<hex>"`, close the object, hash it, compare.
|
||||
|
||||
Three properties, each a test:
|
||||
|
||||
- Rotation does not break it. The new file's first entry carries the old file's
|
||||
last hash. A file read alone shows a non-empty `prev` on line 1 — correct, its
|
||||
predecessor is the next file along.
|
||||
- Restart does not break it. The daemon reads the tail and continues the seq and
|
||||
the hash. Every run opens with `trail-opened`, which names the path, says
|
||||
whether the chain resumed, and proves the file is writable before anything
|
||||
needs it to be.
|
||||
- A tail we cannot parse is rotated aside, never appended to. Chaining onto a
|
||||
torn line makes the file fail verification forever, which reads as tampering.
|
||||
The damaged file is kept — it is evidence — and a clean chain starts, loudly.
|
||||
|
||||
Seq, chain head and write now sit under one lock. Two locks and a later write
|
||||
was survivable for a counter; two threads taking the same `prev` verifies as
|
||||
tampered.
|
||||
|
||||
**Failed writes are loud.** Every file call was `let _ = ...`. Now the first
|
||||
failure warns with path and error, and recovery says so — one line per edge, as
|
||||
§10 does for source-down. The chain head advances only on a line that landed.
|
||||
Advance it on a failed write and the next entry points at a `prev` no file
|
||||
holds, which looks exactly like deletion.
|
||||
|
||||
**Owed:** nothing verifies the chain yet. The contract above is four lines of
|
||||
shell, and the same verifier answers `SessionAudit.qml`, which has had this gap
|
||||
since it was written.
|
||||
|
|
|
|||
Loading…
Reference in a new issue