Watch
1
0
Fork
You've already forked souveraine
0

settings: the device state machine gets a surface

TASK-08(f)/TASK-19: state, panel, evidence, confidence, per-source health, the
sensors_degraded flag and the recent decision trail were legible only through
forensic.jsonl. DeviceEvidence gains a read path (polled only while watched);
the Device page renders it. Readout only — the confidence gates are still
computed and never branched on, so controls over them would lie.
This commit is contained in:
Fimeg 2026-07-31 13:11:43 -04:00
commit 1c90d7f76a
2 changed files with 335 additions and 0 deletions

View file

@ -29,6 +29,216 @@ ContentPage {
}
}
// Proprioception
// TASK-08(f) / TASK-19: the state machine computes its state, its
// evidence, its confidence and its per-source health, and until now none
// of it reached a screen. `forensic.jsonl` knew; the device could not tell
// you. A body that cannot feel itself is the thing this OS is not
// supposed to be.
//
// READOUT ONLY, deliberately. TASK-19: the confidence gates are computed,
// logged and never branched on, so a control over them "would be lying"
// showing a threshold slider nothing consults breaks this page's own rule
// against success-shaped switches. Observations can be shown honestly
// today; controls wait on TASK-08(g).
property bool _watchingEvidence: false
function _startWatching() {
if (_watchingEvidence) return;
_watchingEvidence = true;
DeviceEvidence.watch();
}
function _stopWatching() {
if (!_watchingEvidence) return;
_watchingEvidence = false;
DeviceEvidence.unwatch();
}
Component.onCompleted: _startWatching()
Component.onDestruction: _stopWatching()
ContentSection {
icon: "monitor_heart"
title: Translation.tr("Device state")
// The laptop has no sessiond. Say so, rather than rendering zeroes
// that look like a healthy reading (§10: "no evidence" and "evidence
// says nothing is happening" must not be the same state).
StyledText {
Layout.fillWidth: true
visible: !DeviceEvidence.available
text: Translation.tr("sessiond is not answering on this device — no state to report.")
color: Appearance.colors.colSubtext
font.pixelSize: Appearance.font.pixelSize.smaller
wrapMode: Text.WordWrap
}
Repeater {
model: DeviceEvidence.available ? [
{ k: Translation.tr("State"), v: DeviceEvidence.state.device_state ?? "—" },
{ k: Translation.tr("Lock phase"), v: DeviceEvidence.state.phase ?? "—" },
{ k: Translation.tr("Locked"), v: (DeviceEvidence.state.locked ?? false) ? Translation.tr("yes") : Translation.tr("no") },
{ k: Translation.tr("Panel"), v: (DeviceEvidence.state.panel_on ?? false) ? Translation.tr("on") : Translation.tr("off") },
{ k: Translation.tr("Dimmed"), v: (DeviceEvidence.state.dimmed ?? false) ? Translation.tr("yes") : Translation.tr("no") },
{ k: Translation.tr("Display active"), v: (DeviceEvidence.state.display_active ?? false) ? Translation.tr("yes") : Translation.tr("no") },
{ k: Translation.tr("Idle"), v: (DeviceEvidence.state.idle_secs ?? 0) + "s" },
{ k: Translation.tr("Observed"), v: (DeviceEvidence.state.observed ?? false) ? Translation.tr("yes") : Translation.tr("no") },
{ k: Translation.tr("Confidence"), v: Number(DeviceEvidence.state.observed_confidence ?? 0).toFixed(2) },
{ k: Translation.tr("Wake suppressed"), v: (DeviceEvidence.state.suppress_dpms_wake ?? false) ? Translation.tr("yes") : Translation.tr("no") },
{ k: Translation.tr("Shell alive"), v: (DeviceEvidence.state.shell_alive ?? false) ? Translation.tr("yes") : Translation.tr("no") }
] : []
delegate: RowLayout {
required property var modelData
Layout.fillWidth: true
spacing: 8
StyledText {
Layout.fillWidth: true
text: modelData.k
color: Appearance.colors.colSubtext
font.pixelSize: Appearance.font.pixelSize.smaller
}
StyledText {
text: String(modelData.v)
color: Appearance.colors.colOnLayer1
font.pixelSize: Appearance.font.pixelSize.smaller
}
}
}
}
ContentSection {
icon: "sensors"
title: Translation.tr("Evidence sources")
// The flag §10 was built for. It rides every forensic snapshot and had
// nowhere to appear: the SLPI outage on 2026-07-25 killed every sensor
// for four hours and exited status 0, so the crash reporter
// structurally could not help.
StyledText {
Layout.fillWidth: true
visible: DeviceEvidence.available && (DeviceEvidence.state.sensors_degraded ?? false)
text: Translation.tr("A source reported and then went silent. Readings below are not trustworthy.")
color: Appearance.colors.colError
font.pixelSize: Appearance.font.pixelSize.smaller
wrapMode: Text.WordWrap
}
StyledText {
Layout.fillWidth: true
visible: DeviceEvidence.available
text: Translation.tr("live = reporting · unknown = never heard from (no reporter wired) · down = spoke, then stopped")
color: Appearance.colors.colSubtext
font.pixelSize: Appearance.font.pixelSize.smaller
wrapMode: Text.WordWrap
}
Repeater {
model: {
if (!DeviceEvidence.available) return [];
const health = DeviceEvidence.state.sensor_health ?? {};
const fresh = DeviceEvidence.state.evidence_fresh ?? {};
return Object.keys(health).map(name => ({
name: name,
health: health[name],
fresh: fresh[name] === true
}));
}
delegate: RowLayout {
required property var modelData
Layout.fillWidth: true
spacing: 8
MaterialSymbol {
iconSize: Appearance.font.pixelSize.normal
text: modelData.health === "live" ? "sensors"
: modelData.health === "down" ? "sensors_off"
: "help"
color: modelData.health === "down" ? Appearance.colors.colError
: modelData.health === "live" ? Appearance.colors.colOnLayer1
: Appearance.colors.colSubtext
}
StyledText {
Layout.fillWidth: true
text: modelData.name
color: Appearance.colors.colOnLayer1
font.pixelSize: Appearance.font.pixelSize.smaller
}
StyledText {
// "unknown" is not a failure accel, light and touch have
// no reporter on this device and correctly sit there
// forever. Only a source that spoke and then stopped failed.
text: modelData.health + (modelData.fresh ? Translation.tr(" · fresh") : "")
color: modelData.health === "down" ? Appearance.colors.colError
: Appearance.colors.colSubtext
font.pixelSize: Appearance.font.pixelSize.smaller
}
}
}
}
ContentSection {
icon: "history"
title: Translation.tr("Recent decisions")
StyledText {
Layout.fillWidth: true
text: Translation.tr("What the machine last decided, and what it decided it from. The same entries the forensic trail hash-chains.")
color: Appearance.colors.colSubtext
font.pixelSize: Appearance.font.pixelSize.smaller
wrapMode: Text.WordWrap
}
Repeater {
// Newest first, by seq independent of the order the buffer
// happens to return.
model: (DeviceEvidence.recentDecisions ?? []).slice()
.sort((a, b) => (b.seq ?? 0) - (a.seq ?? 0))
.slice(0, 12)
delegate: ColumnLayout {
required property var modelData
Layout.fillWidth: true
spacing: 1
RowLayout {
Layout.fillWidth: true
spacing: 6
StyledText {
text: {
// `event` is a tagged union (decision,
// state-transition, sensor-input, error-*). Take
// whichever key it carries rather than assuming.
const ev = modelData.event ?? {};
const kind = Object.keys(ev)[0] ?? "event";
const body = ev[kind] ?? {};
return body.decision ?? body.to ?? kind;
}
color: Appearance.colors.colOnLayer1
font.pixelSize: Appearance.font.pixelSize.smaller
font.bold: true
}
Item { Layout.fillWidth: true }
StyledText {
text: "#" + (modelData.seq ?? "?")
color: Appearance.colors.colSubtext
font.pixelSize: Appearance.font.pixelSize.smaller
}
}
StyledText {
Layout.fillWidth: true
visible: (modelData.reason ?? "").length > 0
text: modelData.reason ?? ""
color: Appearance.colors.colSubtext
font.pixelSize: Appearance.font.pixelSize.smaller
wrapMode: Text.WordWrap
}
}
}
}
ContentSection {
icon: "tune"
title: Translation.tr("Device overrides")

View file

@ -95,6 +95,131 @@ Singleton {
sock.connected = true;
}
// Ingress: the machine's own account of itself
//
// Everything above is egress the shell telling sessiond that something
// happened. This half is the other direction, and until now it did not
// exist: the state machine computes its state, its evidence, its
// confidence and its per-source health, and **no surface could see any of
// it** (TASK-08(f), TASK-19). The trail knew and the glass did not.
//
// Strictly a projection. It reads `device_state`, holds nothing the
// protocol owns, and decides nothing DEVICE-STATE-MACHINE §1's whole
// complaint is actors that saw one facet and acted on it, and a readout
// that started branching would be the eighth. Doctrine §4: read the
// authority, never mirror it into a second source of truth.
//
// Polled only while a surface is actually looking (watch/unwatch). A
// settings page open on the desk should not cost a request per second for
// the rest of the day.
/// True once sessiond has answered at least once. False on the laptop,
/// where there is no daemon surfaces must render that as "unavailable",
/// never as healthy-looking zeroes.
property bool available: false
/// The last `device_state` reply, verbatim. Read-only to every consumer.
property var state: ({})
/// Recent forensic entries (the decision trail), newest last.
property var recentDecisions: []
/// ms epoch of the last successful read; 0 = never.
property double lastReadAt: 0
property int _watchers: 0
/** Begin polling. Pair every call with unwatch(). */
function watch() {
root._watchers += 1;
if (root._watchers === 1) {
readTimer.running = true;
root._query();
}
}
function unwatch() {
root._watchers = Math.max(0, root._watchers - 1);
if (root._watchers === 0) {
readTimer.running = false;
readSock.connected = false;
}
}
/** One-shot refresh, whether or not anything is watching. */
function refresh() {
root._query();
}
property bool _queryPending: false
function _query() {
if (readSock.connected) {
readSock.write(JSON.stringify({ op: "device_state" }) + "\n");
readSock.write(JSON.stringify({ op: "forensic_log", count: 20 }) + "\n");
return;
}
root._queryPending = true;
readSock.connected = true;
}
Timer {
id: readTimer
interval: 2000
repeat: true
running: false
onTriggered: root._query()
}
// A SECOND connection, deliberately. The egress socket above is
// fire-and-forget and throttled; interleaving request/response traffic on
// it would mean correlating replies to writes that may never come. This
// one only ever asks questions. It does NOT register shell authority
// that is SessiondBridge's job, and a second registration is what
// deadlocks the lease.
Socket {
id: readSock
path: Quickshell.env("XDG_RUNTIME_DIR") + "/souveraine/sessiond.sock"
onConnectionStateChanged: {
if (connected && root._queryPending) {
root._queryPending = false;
readSock.write(JSON.stringify({ op: "device_state" }) + "\n");
readSock.write(JSON.stringify({ op: "forensic_log", count: 20 }) + "\n");
} else if (!connected) {
root._queryPending = false;
// No daemon is the laptop's normal state. Say unavailable and
// let the surface show that, rather than leaving stale values
// on screen that look current.
root.available = false;
}
}
parser: SplitParser {
splitMarker: "\n"
onRead: message => {
let reply;
try {
reply = JSON.parse(message);
} catch (e) {
return;
}
if (reply.ok !== true) {
console.log("[device-evidence] read refused:",
reply.code ?? "?", reply.reason ?? "");
return;
}
// device_state carries the state field; forensic_log carries
// entries. One parser, two shapes, told apart by content
// rather than by a correlation id the protocol does not have.
if (reply.device_state !== undefined) {
root.state = reply;
root.available = true;
root.lastReadAt = Date.now();
} else if (reply.entries !== undefined) {
root.recentDecisions = reply.entries;
}
}
}
}
Socket {
id: sock
path: Quickshell.env("XDG_RUNTIME_DIR") + "/souveraine/sessiond.sock"