Watch
1
0
Fork
You've already forked souveraine
0
Commit graph souveraine/src
Author SHA1 Message Date
Fimeg
4fd3d244ab sessiond: bearer posture as evidence and an Action (TASK-49)
Nothing owned which link carries traffic. It was the residue of a DHCP metric,
a NetworkManager penalty and a shell script's assumption, and when those
disagreed the phone silently lost the network - every IPv4 destination leaving
over a degraded clat while wifi sat associated.

The thing this replaces, wiuf-vpn-gate, is worth stating because its three
defects are the design constraints:

- it was driven by the NM dispatcher AND a 90s timer while calling nmcli
  up/down, which is itself an NM event. 652 tunnel recycles in 90 minutes,
  ~7/min - a loop, not a bad interval. Here the probe only reads, the decision
  is a pure function, and the only egress is an Action from tick().
- its home test was ' inet 10.10.' - a /16 match that reads the foreign network
  this phone lives on (10.10.30.213) as home. home_ssids is identity; an empty
  list means never claim to be home.
- it managed CONN=wiuf while two profiles answer to that name.

LinkHealth keeps 'associated' apart from 'carrying', which is DEVICE-STATE-
MACHINE §10's source-health lesson applied to links: a wifi link that
associates and cannot route is a sensor that heartbeats and lies, and it is
what NM's +20000 penalty mislabels. A carrying cellular link therefore beats a
merely associated wifi one; wifi only wins ties.

TunnelHealth reads wg's received-byte counter, which nothing read before. Up
and deaf is a real state - measured over clat as 368 B received against 5.30
KiB sent - and every other readout on the device calls it connected.

Anti-flap is a settling window, not a rate limit: a changed preference must
hold BEARER_SETTLE (20s) before it produces a command, and the clock restarts
whenever the candidate itself changes. A steady state emits nothing, so it is
safe to evaluate every tick. Tests cover the flap that never settles, the
settled state that must not re-emit, and the dead network that must not be
flailed at.

NetworkManager stays the single writer of routes (acceptance #6): preference
is expressed as ipv4.route-metric plus device reapply, never ip route, and
reapply rather than up/down so a correction is not itself an NM event. The one
route sessiond owns is the WireGuard endpoint /32, which NM has no opinion
about.

The tunnel toggle stays the user's. tunnel_underlay() returns None when the
tunnel is off - this decides how traffic is carried, not whether the tunnel
exists.

Probed off the existing clock every 5th tick, with the lock released; no new
timer (acceptance #6) and no subprocess spawned under the state mutex.
2026-08-01 14:08:14 -04:00
Fimeg
86afe00dba sessiond: the power button reports edges; the machine recognises gestures
blueline-power-button decided policy in shell script — read its own panel
state, asked the shell to lock over qs ipc, polled, then blanked the panel
itself. That was a path to a dark panel outside request_blank(), so §1's
lock-then-blank invariant had a hole in the most-used control on the device.

Edges in, gestures out: tap, double, triple, hold, long-hold, per button.
Only the power tap is bound; the rest are recognised, recorded and inert
until there is a binding table to point them at.
2026-07-31 21:45:47 -04:00
Fimeg
1eb50a1817 sessiond: subscribe — the body tells the mind when a belief changes
A held connection receives one trail entry per notable edge: transitions,
source-down/recovered, violated guarantees. Readings, wakes and heartbeats
never cross — the filter is the point, not the transport.
2026-07-31 21:07:19 -04:00
Fimeg
539df90b95 sessiond: peer pid via getsockopt, and put sessiond under test
UnixStream::peer_cred is still unstable, so it only failed at the aarch64
build after rust-test had gone green. Use the same getsockopt machined
uses.

rust-test never built souveraine-sessiond at all — it is
required-features and the feature is not default. Build and test it.
2026-07-31 18:52:43 -04:00
Fimeg
efe2d0d1e0 lock: adopt the session lock across a scene reload
sessiond admits shell_ready from the lease holder's own pid; the shell
carries its lock request through the reload so WlSessionLock adopts the
existing lock instead of taking the unlock branch on it.
2026-07-31 14:32:29 -04:00
Fimeg
4caca36859 fix: preserve the full reply (not the streamed partial) on surface-drop
Revises the prior fix: the model's reply is already complete in
final_content before the paced stream loop, so truncating to
needlessly dropped the unstreamed tail. On SSE receiver-drop, stop
streaming but commit the FULL reply (interrupt flag skips the pointless
N+1 and marks the turn); the surface recovers the whole reply on resume.
2026-07-30 15:22:12 -04:00
Fimeg
426dcf6363 fix: commit partial reply when the SSE surface drops mid-stream
turn.rs streaming loop returned early on tx.send failure (receiver gone)
before the session commit, orphaning the turn: the assistant's partial
reply never landed, so the next turn's history snapshot read the user's
follow-up with no preceding reply -> a user->user gap. The model then
answered as if from nowhere (the 'whatever it was...' non-sequitur),
model-independent and intermittent. Commit the partial like an interrupt
instead, the way the cancel branch already does, so the thread links.
2026-07-30 15:17:55 -04:00
Fimeg
e9bf33e64a agent: per-agent voice_id on the public list
AgentSummary carries voice_id, sourced from agent.json (_souveraine) via
get() rather than the DB config_json column, which is write-once at create
and drifts on later edits. The list endpoint is the surface's only
unauthenticated view of an agent, so voice travels there; /v1/agents/:id
is token-gated.
2026-07-30 09:18:38 -04:00
Fimeg
c8507e924b sessiond: delete the fallback login; this daemon locks and never unlocks
It authenticated through PAM, called unlock_and_destroy(), and returned
Unlocked — and then the shell, whose GlobalStates.screenLocked is a separate
bool, re-registered, was told must_lock=true, and locked again. The protocol
has a lock directive and no unlock directive, so the fallback could open the
compositor's lock and had no way to tell the session it had. Casey hit it
exactly that way: "it logs in, and then qs says Locked still."

It was also not a last resort in practice. It is raised whenever shell_alive is
false, and that flag lied for 90 minutes on 07-29 because a scene reload's
re-registration was refused and the shell gave up (45fbbea). So it was the
first thing reached, on a bad signal, to do a job it could not finish — the
fallback shape the doctrine forbids.

Gone: auth.rs, the PIN pad, the keypad geometry and digit font, the
failed-attempt counter, SessionOutcome::Unlocked, Msg::AuthDone, and the
--pam-service plumbing down to the CLI.

Kept: acquiring ext-session-lock before any shell exists, which was never a
fallback but the ordering invariant from LOCK-DPMS-LESSONS.md §1, and the
Released handoff. A locked session with no shell now stays locked and says so
in the journal. Recovery is the shell returning, or a reboot.

79 tests pass.
2026-07-29 11:37:08 -04:00
Fimeg
c48737620e sessiond: squeeze is input, and a pocket can veto it
Active Edge becomes InputTrigger::Squeeze. It resets the idle budget
and lands in the trail like any real input, but unlike a power button
it is a sensor reading and a tight pocket is a squeezed chassis, so it
carries the same proximity veto as tap-to-wake (DEVICE-STATE-MACHINE
4).

note_input_gated applies the veto and records the refusal via
record_decision — a silently dropped report is what 10 exists to
prevent. The input op now advertises refused_by_state.

Producers cooperate rather than being enforced; TASK-41 is the gate.
2026-07-28 13:03:53 -04:00
Fimeg
45fc69b56a sensord: decay accel motion, debounce light in both directions
Accel inferred moving from orientation edges, so Moving(false) was only
reachable from the startup banner — boot 0 carried 165 true to 8 false and the
keepalive re-sent a stuck true every 30s, pinning +0.3 into observed_confidence
for the life of the process. Motion now decays over MOTION_WINDOW and the
keepalive re-derives it.

Light crossed the lux threshold on one sample and fell back on the next,
pulsing confidence 0.50->0.70->0.50 in 1.2s and toggling promote_idle across
the 0.6 band. An excursion must now persist: 2000ms brightening, 4000ms
darkening, taken from config_autoBrightness*LightDebounce in blueline's vendor
RRO. Reversal restarts the clock.

ParserState is shared and survives monitor-sensor respawns.
2026-07-27 21:21:16 -04:00
Fimeg
3d9340057f sessiond: an expected source that never reports is Absent, not silent
sensord hung off graphical-session.target, which nothing on this device
starts, so it was enabled and dead from every boot. Down starts from a
last-seen stamp and structurally cannot see that. Bind the reporter to
sessiond and make the machine say so.
2026-07-27 15:34:09 -04:00
Fimeg
2c1ee8bc00 sessiond: gate the blank actuator on logind LockedHint 2026-07-26 20:16:58 -04:00
Fimeg
29a3c472f5 sensord: buffer every source, and make the light threshold real
Two halves of the same defect. The lux threshold compared each sample to the
previous sample, so a slow drift never tripped it — it now compares to the
last value actually reported. And no source had any rate floor at all, which
is how light managed ~8 reports/sec; 250ms per source, enforced separately so
a busy light sensor cannot delay a proximity edge.
2026-07-26 20:07:31 -04:00
Fimeg
de3a332513 sensord: report edges, not every sample
Light drifts continuously; monitor-sensor emitted ~8 lines/sec and each was
a socket round trip re-asserting the same Changing(false). The keepalive is
what keeps silence meaningful, so only changes need reporting.
2026-07-26 20:03:21 -04:00
Fimeg
28e8b78284 sensord: needless_bool 2026-07-26 19:16:46 -04:00
Fimeg
83fab1a1b6 sensord: one reporter for every sensor, replacing the per-sensor scripts
blueline-proximity-lock is 113 lines of shell implementing a subtle contract
(heartbeat inside SOURCE_DOWN_AFTER, seed from the probe banner, report both
edges) that would have been hand-copied for light and again for accel. One
binary, one contract, shipped in the package instead of the rootfs overlay.
2026-07-26 18:46:27 -04:00
Fimeg
4d652bbbc6 sessiond: fix the self-deadlock that made the daemon stop answering
Action::Restore bound the brightness inside an if-let, so the MutexGuard
lived for the whole body and run_executor locked again. Non-reentrant: the
state lock was held forever and every later request thread queued behind it
(measured: 347 threads in futex_wait, 691/1024 fds, accept loop healthy, no
request answered in 3h). Bind first, then branch.

Also close connections idle for 120s, exempting the shell heartbeat.
2026-07-26 18:26:31 -04:00
Fimeg
f2a4b93ee3 sessiond: a torn tail is a hard reboot, not tampering — resume the chain
Measured: the hard reboot left the trail at exactly 4096 bytes with the last
entry cut in half. Rotating that aside cost chain continuity at the one moment
it is worth most.
2026-07-26 14:49:00 -04:00
Fimeg
43eee1e3c5 sessiond: carry the caller's intent into the trail 2026-07-26 14:38:40 -04:00
Fimeg
c44deda7c2 sessiond: restore the exact brightness the dim captured, not a guessed floor 2026-07-26 14:00:37 -04:00
Fimeg
18bd4a0974 sessiond: make the verb surface enumerable and its refusals branchable 2026-07-26 13:00:58 -04:00
Fimeg
4cd7ddd046 sessiond: debounce the proximity edge that actually flaps
Measured: 44 near-episodes in 2.4h, median dwell 1s, 15 sub-second. The
negative debounce §9.5 specified would have delayed each blip, not removed it.
2026-07-26 12:50:26 -04:00
Fimeg
813010dbb6 sessiond: proximity stops actuating; it vetoes tap-to-wake and nothing else 2026-07-26 12:19:52 -04:00
Fimeg
4d0c7a1c0b sessiond: the forensic trail leaves tmpfs, gains bounds and a hash chain 2026-07-26 11:24:41 -04:00
Fimeg
01fda8ab87 sessiond: persist the device-state policy
SetPolicy only mutated memory. Every lock-screen timer set in Settings
reverted to the built-in 15s on the next restart, silently — the page was
honest about reading the daemon, the daemon was forgetting.

Saved to $XDG_CONFIG_HOME/souveraine/device-state-policy.json, write-then-
rename. serde(default) on the container so an older file loads with new
fields defaulted instead of dropping the user's settings on upgrade.

A failed save returns ok:false rather than a success-shaped switch.
2026-07-26 09:01:36 -04:00
Fimeg
862d1d7bba sessiond: evidence-source health as device state
A source that reported and then went silent past 90s is DOWN, not quiet,
and says so as an error-security entry. Sources that never reported stay
unknown — a light sensor with no reporter is correctly silent forever.

Every forensic snapshot now carries sensors_degraded, so a decision taken
during an outage doesn't read like a healthy one. This is the 2026-07-25
SLPI case: hexagonrpcd-sdsp exited 0, every sensor dead for hours, nothing
in any log.

Health is not read from SensorProxy Has* — measured lying both ways.

Also: evidence_fresh meant "ever seen" and read true through the outage;
repeated readings no longer append to the trail, since reporters heartbeat.

55 sessiond tests.
2026-07-26 08:30:07 -04:00
Fimeg
bd8a4eca50 sessiond: refuse half-applied unlocks; log the real sensor source
set_session_locked(false) discarded transition()'s return. There is no
Suspending/Asleep -> Active edge, so an unlock reported while asleep left the
state put but voided pending_blank, blank_requested and dimmed anyway. Now
refused, loudly, with the state intact.

update_sensors hardcoded SensorSource::Proximity in the forensic entry, so
accel/light/touch readings were written to the trail labelled as proximity.
Takes the source now; update_sensors() stays as a proximity wrapper.

47 tests, 1 new.
2026-07-26 07:30:06 -04:00
Fimeg
70a05819e8 settings: presets instead of seconds; two ordering fixes
IdleConfig: ConfigSelectionArray presets (30s/1/2/5/10 min for the shell's
timers, 15s/30s/1/5 min/Never for the lock screen) instead of seconds
spinboxes. Nobody reasons about a lock screen in 5s increments; the policy
struct already said "iOS Auto-Lock shape". Wire values stay seconds.
Header comment corrected — hypridle no longer owns screen-off.

set_panel(false) clears pending_blank. A panel darkened by any other route
left it armed, so the next tick emitted a second Blank or hit the
blank-without-lock error path for a blank nobody was waiting on.

SetPolicy refuses dim_grace >= lock_blank_after. dim_at saturates to zero, so
the panel dimmed the instant it went idle with no lit period.

Verified by rendering the page, not by parse alone.
2026-07-26 07:24:28 -04:00
Fimeg
df654e6bee sessiond: take lock truth from logind, not from our own history
Measured on device 40min after boot: device_state=locked, panel_on=false, while
LockedHint=no. Screen dark, session unlocked.

locked_ack was the only lock ingress and there was no unlock one outside the
fallback PIN surface, so after the first unlock the machine believed it was
locked forever. Every is_locked() rule then fired against an in-use phone —
proximity blanked it, and blanks never asked for a lock because one was
"already held". Doctrine §4: never hold state the protocol owns.

New lockhint.rs watches LockedHint via gdbus monitor (§8's pattern, not
polling). Resolve via User.Display, NOT GetSession(auto) — auto returns the
CALLER's session and sessiond is a session-less user unit; over ssh it answered
_310 while the graphical session was _31.

set_session_locked() moves the base tier and voids lock-screen intent on
unlock: pending blank cleared, dim restored.

46 tests, 8 new.
2026-07-26 07:18:02 -04:00
Fimeg
15a32796ba sessiond: make lock-before-blank an invariant, not a timer coincidence
LOCK-DPMS-LESSONS §1 is "Ordering: lock, then off". It held only because
hypridle's 300s lock listener fired before its own 600s screen-off listener —
two numbers in a config file, not a guarantee. Anything that skipped the lock
(idle inhibitor, native coordinator off, dead shell) still met the blank, and
the panel went dark on an unlocked session with nothing said about it.

Every path to a dark panel now routes through request_blank():

- locked  -> Blank, unchanged, no added round trip
- unlocked -> Action::Lock, blank withheld, pending_blank deadline armed
- ack lands -> Blank
- budget expires -> Blank anyway + error-security in the forensic trail

The panel fails open because a lit unlocked phone in a pocket is worse than a
dark one (§1); the *claim* fails closed because doctrine §8 forbids pretending
the session locked. Input inside the ack window cancels the blank outright.

Action::Lock does not go through the executor table — the lock is not a shell
command. While a live shell owns steady state the directive goes down the
heartbeat connection and the shell raises its rich surface; with no shell alive
sessiond raises its own. Same relationship DEVICE-STATE-MACHINE §6 gives the
DPMS executor: the authority decides, the surface executes. A directive that
cannot be delivered takes the lock here rather than timing out into a dark
unlocked screen, and is recorded either way.

Policy gains lock_ack_budget (2s, refuses 0) and unlocked_blank_after (None by
default — the shell's IdleCoordinator owns the unlocked idle timer through
ext-idle-notify per doctrine §5, and a second one here would recreate the
competing-owner disease). Both are on the GetPolicy/SetPolicy seam so Settings
is a view over the owning daemon, per TASK-19.

Unknown directives are loud on the shell side: an older shell silently
dropping one would leave the daemon waiting out its budget and blanking
unlocked.

38 tests pass, 5 new covering the ordering.
2026-07-25 18:38:32 -04:00
Fimeg
9d8bce2a08 sessiond: log transitions prev → next (args were next, next, prev) 2026-07-25 15:08:47 -04:00
Fimeg
aa3027845d sessiond: give the device state machine a clock and real actuators
The state machine landed in b4b30b1 and never ran. Verified on the phone
after 2.5h of use: device_state=active, observed_confidence=0.0,
forensic_log count=0, forensic.jsonl never created. Not one transition.

Root cause was not a missing caller. The daemon had no timebase at all —
every Duration in it was a one-shot handshake deadline — so the machine
could answer requests but nothing ever told it time had passed. It could
not be the proactive half of anything.

- tick loop (1s) drives every timed rule; tick_at() takes the clock so the
  rules are testable without sleeping
- panel is a field, not a ninth state: the enum had no cell for "locked,
  screen dark", and DozeLight (frozen apps, Wi-Fi save) is not a dark glance
- lock-blank rule, two stages: dim as a visible pre-warning, blank one grace
  window later. Nothing owned this before — the only backstop was hypridle's
  600s screen-off timer shared with the desktop case, so glancing at the
  clock lit the panel for ten minutes
- dim/restore via brightnessctl + blueline-undim. The dim listener was
  dropped from hypridle.conf by accident and never restored; it now lives
  here, so brightness has one writer and the save happens exactly once —
  which is the actual fix for the 10/255 wedge the old -s/-r pair hit
- input truth from ext-idle-notify (new idle.rs), not inferred. Tracking the
  compositor's active/quiet boolean instead of stamping last-input is what
  keeps a long swipe from being blanked mid-gesture: continuous input emits
  no events at all
- evidence staleness, 30s TTL per source, clearing toward safety: a stale
  proximity stops suppressing wake, a stale accelerometer stops buying the
  longer budget. Measured need — hexagonrpcd-sdsp exited 0, systemd left it
  dead, and iio-sensor-proxy went on reporting HasProximity=true for hours
- proximity-near while locked blanks now, taking that decision back from
  blueline-proximity-lock
- get_policy/set_policy IPC, Auto-Lock shaped with 0 = never, so Settings can
  be a view over the owning daemon instead of a switch that looks like it worked
- fix: PAM unlock was refused as an illegal transition from Observed,
  DozeLight and DozeDeep. Unlocking a phone whose proximity had fired hit it

33 tests. aarch64 cross-build verified on archdev.
2026-07-25 14:23:07 -04:00
Fimeg
687cfeb5fb memory tests: mkdir -p before seeding fixture files
init() scaffolds only system/; the other schema roots are created on
first write by the real write path. Three tests seeded fixtures directly
into issues/, projects/ and reference/ and died on ENOENT, which failed
cargo test and skipped the packaging job that publishes the pacman repo.
2026-07-24 20:10:28 -04:00
Fimeg
b4b30b124d Unified device state machine + security hardening
sessiond:
- device_state.rs: 8-state unified device state machine with legal
  transition table, sensor evidence model (proximity/accel/light/touch),
  confidence scoring, cross-sensor disagreement detection, and forensic
  logging with full state snapshots at every decision point.
- protocol.rs: extended with DeviceState, SensorInput, and ForensicLog
  IPC requests. SensorSource/SensorValue types for proximity, accel,
  light, touch.
- server.rs: DeviceStateMachine integrated into Daemon struct. Handlers
  for DeviceState (full state query), SensorInput (sensor evidence +
  Observed transitions), ForensicLog (recent entries query).
- lock.rs: Failed attempt counter on PIN surface (advisory, PAM owns
  lockout policy). Rendered as red digit glyphs below PIN dots.
- draw.rs: Attempt counter rendering + test.
- auth.rs: PAM config docs reference.
- mod.rs: device_state module added.

shell:
- IdleCoordinator.qml: Legal transition table with runtime enforcement.
  setState() refuses illegal transitions with warning. returnActive()
  explicitly only allows Dimmed/Waking.
- GlobalStates.qml: Write authority comments on every property (// WRITER:).
- SessionAudit.qml: SHA-256 replaces MD5 for hash chain. Forensic event
  wiring (device-state-transition, device-error, sensor-input, wake-event).
  logDeviceError/logSensorInput/logWakeEvent functions for QML callers.

Design doc: SouveraineOS/docs/DEVICE-STATE-MACHINE.md (separate repo).

Tests: 22 passing (was 7). Full lifecycle test exercises Active → Dimmed →
Locked → Observed → DozeLight → DozeDeep → Suspending → Asleep → Locked
with 23 forensic entries.
2026-07-24 16:29:04 -04:00
Fimeg
0b13e2d347 fix: speech prefetch flag, agent identity on restore, workspace selector
Speech: prefetch() now sets synthProc._prefetch=true before voiceLookup
so the background synth doesn't auto-play. speak() skips voiceLookup
when the endpoint+voice are already resolved (faster repeat taps).

Ai: addMessage defaults model to Souveraine.currentAgentId so restored
messages (/resume, new conversations) show the agent icon/name instead
of a generic placeholder.

Session: create_with_messages_and_timestamps preserves persisted
created_at/updated_at through load_persisted so /resume sorts by
actual activity time, not server restart time.

TTS: ttsClean strips unclosed think blocks, fixes regex dotAll flag
for older Qt/V8 (phone), adds pronunciation map.

SubconsciousTicker: tap psychology icon to expand inline mini-log,
long-press to open the event panel overlay.

New: workspace-selector.sh + .desktop — wofi dmenu grid for quick
workspace 1-10 jump from the app drawer. Installed to phone via
deploy.sh symlink.
2026-07-23 16:47:00 -04:00
Fimeg
19fe6b1d0e shell: dock reorder, fullscreen detection fix, idle-power, sessiond, misc shell work
- Dock drag-to-reorder for pinned apps (insertion gap, quick-slide vs dwell)
- Fullscreen detection: scan all windows via HyprlandData.windowList
- IdleCoordinator, GlobalStates, Session.qml updates
- Deploy script, qmldir, settings, wallpaper, visualizer fixes
- sessiond server, memory module updates

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-22 22:18:20 -04:00
Fimeg
d824ca7f81 secrets: at-rest encryption, passphrase wrap, phone packaging
Seal items AES-256-GCM under a random store key (item id as AAD).
Machine wrap: HKDF over a deterministic machined signature (domain
secrets-store-key, socket /run/souveraine/machined.sock; legacy
seed-id fallback, same framing) — private key never enters the daemon.
Argon2id passphrase wrap slot (64MiB/3, HKDF-mixed with the machine
signature) managed via org.souveraine.Secrets.Manage: SetPassphrase /
VerifyPassphrase / HasPassphrase; rotation re-wraps only.

Client-found spec fixes: DH prime was the 2048-bit group-14 constant,
not the 1024-bit Second Oakley group libsecret uses (pinned by test);
item ids now simple UUIDs (hyphens illegal in object paths); collection
also served at /aliases/default; interfaces registered before name
claim. Added spec signals, real timestamps, content types, atomic 0600
store writes.

packaging: systemd user unit, D-Bus activation shadow, idempotent
phone deploy script (masks gnome-keyring, verifies round-trip).
2026-07-20 21:16:32 -04:00
Fimeg
5cb1be06cd lock primitives: power-from-lock, login1 polkit rule, gesture rail progress
seat0-active polkit rule so power off/reboot/suspend stop returning
challenge into a session with no polkit agent on the lock surface.
Lock-surface buttons stay gated behind allowPowerFromLock (opt-in) and
requirePasswordToPower. SystemGestureRail progressive gesture + mission
control nav flag.
2026-07-19 22:13:08 -04:00
Fimeg
c91c3a7796 sessiond: quiet fallback until first input, dismissnotify on handoff
fallback surface renders only the dark field until touched, so a normal
boot no longer flashes a second PIN style under the shell lockscreen.
hyprctl dismissnotify fires once the shell confirms its lock, clearing
the bogus lockscreen-crashed banner from the handoff connection drop.
2026-07-17 10:48:06 -04:00
Fimeg
50530b1159 fix lock feedback loop + impatient sessiond boot lock
Session.lock() notified logind unconditionally; hypridle's lock_cmd fires
on logind's Lock signal, so every lock echoed back through logind ->
hypridle -> lock() forever. Observed: ~450 lock requests/min for 7 minutes,
re-locking the screen seconds after each unlock. Notify only on the
unlocked->locked edge.

sessiond gave up waiting for the compositor lock ack after one dispatch —
at boot Hyprland defers the ack while the splash holds DRM, so the initial
lock silently failed (journal: 'never acknowledged', 0.7s in) and boot ran
on the legacy lock path. Wait up to 60s for locked/finished.
2026-07-17 06:01:36 -04:00
Fimeg
e10efbbd56 lock surface: restore redacted chat on unlock + announce replies
Redacted messages stayed '[content hidden until unlock]' forever — nothing
restored them on unlock. Redaction now records the message and unlock
restores content from rawContent. Turns finishing while locked are also
redacted and surfaced. New LockAgentCard shows a one-line preview of
replies that landed during the lock window on the lock glance, like a
notification; body stays hidden until unlock.

sessiond auth: getpwuid_r buffer is u8 with a c_char cast so it compiles
on aarch64 (c_char = u8 there) and x86 (c_char = i8).
2026-07-16 21:16:28 -04:00
Fimeg
dc9fe900ec conversation seeding + memfs frontmatter fixes
API-created conversations (shell /agent, /new) never got a system prompt —
the agent booted amnesiac with no constitution, memories, or skills. Prompt
seeding now lives on SouveraineServer and every entry point shares it.

Memory frontmatter: stop serializing None as 'key: null', round-trip
unknown keys instead of destroying them, merge agent-supplied frontmatter
instead of nesting a second block in the body, and make reads tolerant so
a nonconforming file can still be read and healed.
2026-07-16 20:57:41 -04:00
Fimeg
856c6e5576 sessiond: session authority daemon — lock-before-shell, lock-past-shell-death
ext-session-lock client that locks at session start, hands the lock to the
shell over a runtime socket without an unlocked instant (abandon + restore),
and retakes it the moment the shell heartbeat drops. Spartan PIN fallback
surface, PAM via hand-rolled libpam FFI, machined-style sync server.
Shell side: SessiondBridge singleton + LockScreen initIfReady routes the
startup lock decision through the handshake.
2026-07-16 20:32:46 -04:00
Fimeg
b4dc39e501 parse reasoning_content alias (deepseek/z.ai reasoning field name) 2026-07-16 13:03:51 -04:00
Fimeg
33068d69ff turn errors reach every surface: Error event end-to-end, no more silent empty replies 2026-07-16 12:55:49 -04:00
Fimeg
da410b1e6a hosted agents: read seeds from the live agents tree, not server/agents 2026-07-16 12:20:56 -04:00
Fimeg
8e540c773d instance awareness: device_announce carries hosted_agents
The full server's bridge now announces which agent pubkeys this box
hosts (read fresh per reconnect), same list the lite listener already
computed — loader moved to federation/mod.rs and shared. DeviceRegistry
records it (serde default keeps old known_peers.json parsing) and
souveraine peers shows a hosts: line. Durable memfs-side instance log
still to come.
2026-07-16 11:17:47 -04:00
Fimeg
a62379ad8a memory sync: manual push/fetch against the shared memfs remote
Per-instance branch (instance/{machine-pubkey-prefix} until commission
labels exist) on the origin remote. Fetch + push only — never touches
the working tree; merge policy is the next step. Shells out to system
git so creds work like everywhere else. Loud without a remote;
provisioning stays explicit.
2026-07-16 11:11:49 -04:00
Fimeg
8cfa992da2 memfs_commit presence signal on the bus after memory mutations
One event per memfs write/append/delete, replacing the three bespoke
memory_* events nobody consumed. Payload carries op, agent pubkey (seed
beside memfs), machine instance (machined-first), paths, head commit,
branch — enough for a federated peer to know to fetch. Best-effort:
missing identity warns and ships null, never blocks the write.
MemoryRepo stays a pure store; grew head_commit_hex/current_branch.
2026-07-16 11:07:32 -04:00