Watch
1
0
Fork
You've already forked SouveraineOS
0

docs: set the shared-state authority spine

This commit is contained in:
Fimeg 2026-08-10 20:09:54 -04:00
commit 9b54eb41eb
5 changed files with 224 additions and 0 deletions

View file

@ -32,6 +32,13 @@ suspend, with receipts.
the threat register), [`souveraine-components/`](souveraine-components/) (the
authority binaries and their audit ladder).
**Shared state.** [`SHARED-STATE-ARCHITECTURE.md`](SHARED-STATE-ARCHITECTURE.md)
is the design of record for household tasks, calendar, contacts, and their
sync. Culver is a Personal-class client; Culver/machined owns identity and
admission; the shared-state service owns the canonical event trail and local
projection; QML only renders it. Loro is an offline merge substrate, never an
authority.
**Designs of record.** [`STORAGE-ENCRYPTION.md`](STORAGE-ENCRYPTION.md),
[`WAYDROID.md`](WAYDROID.md), [`../USB/README.md`](../USB/README.md),
[`FEDERATION.md`](FEDERATION.md),

View file

@ -0,0 +1,130 @@
---
description: Shared household state — authority, replication, and surfaces for tasks, calendar, and contacts
status: Design of record
date: 2026-08-10
---
# Shared state — one authority, many surfaces
SouveraineOS needs a real shared-state system for household tasks, calendar,
contacts, and eventually mail-facing actions. It is not a CalDAV server with a
sidebar taped to it, and it is not a collection of app-local databases that
appear to agree until two people use them at once.
This document owns the cross-cutting shape. Component docs own their concrete
contracts; task files own session-sized implementation work. Where this and
code disagree, the code is right — reconcile one or the other in the same
change.
## The boundary
```
Culver + machined
identity, device enrolment, shared-space membership, revocation,
scoped signing authority
|
shared-state service
admitted event log, local materialized view, replication, audit
|
tasks | calendar | contacts | later: mail-facing actions
|
QML surfaces
sidebar, notification, dedicated application, phone view
```
One layer owns each question:
| Question | Owner | Not the owner |
| --- | --- | --- |
| Who is a person, device, or member of a household space? | Culver/machined | a task document, Loro, a DAV client |
| Which mutation is admissible? | shared-state service, against an enrolled identity and scoped capability | QML, a sync peer, the storage engine |
| What is the current task/calendar/contact view? | shared-state materializer | the sidebar or a cache in an application |
| How do offline changes converge? | Loro or another chosen CRDT substrate | identity/admission policy |
| May this device currently reveal Personal data? | sessiond + the storage/key policy | the application that wants the data |
## People are not devices
A household space has distinct people with distinct enrolled devices. Casey's
wife is not another Casey device and does not inherit his authority by sharing
a list. Membership grants an explicit role in a named space; revocation stops
new admitted events and key wrapping for the removed device.
Every accepted mutation records actor, device, capability, object, and time.
The trail must make it possible to answer who changed a task, not merely what
the current checkbox says.
## Data model
Tasks, calendar events, and contacts are different first-class objects.
- A task has shared definition fields and per-person participation. Completion
is not one shared boolean: an assignee has their own status, completion time,
and note. A household task can therefore remain open for Casey while showing
that another assignee is done.
- A calendar event owns time, recurrence, attendees, reminders, and
availability. It is not a task with a date bolted on.
- A contact is Personal-class identity data. Culver's existing contacts and
messenger UI remain a client of this architecture; they do not become the
cross-household authority by accident.
The first vertical slice is a Household space with tasks: title, notes,
assignees, per-assignee status, due/reminder, subtasks, and links. It must be
pleasant in one dedicated QML application and in the sidebar before calendar
or mail expands the scope.
## Authority and storage
The shared-state service accepts an event only after verifying the actor's
enrolment and operation-scoped capability. This is the same direction as the
component audit ladder:
- machined must mint scoped, expiring capability tokens rather than raw
signatures;
- callers need stable identity beyond uid 1000;
- secrets must release Personal-class material against a caller and scope;
- sessiond remains the live local authority for lock, step-up, and key
eviction.
Personal-class state is unavailable while the local session is locked.
Outbound actions — share, send, export, delete, or add a device — have their
own step-up policy. A successful local merge is never permission to export it.
## Replication
Loro is a candidate replication primitive because it can merge an offline tree
of task data deterministically. It does **not** decide who may submit a change,
enrol a device, or read the resulting state. Changes are encrypted for the
admitted nodes, replicated over an untrusted transport, verified locally, then
materialized into the canonical view.
CalDAV/CardDAV/JMAP are compatibility boundaries. They may import or export
data for other software; none defines the authority, audit trail, or household
membership model.
## Surface contract
QML surfaces subscribe to the service projection. They never store a competing
truth or mutate storage directly. The sidebar asks for the work that needs a
person now; the full application supplies planning and history. Notifications
are projections and obey the existing ambient/personal/step-up policy.
## Sequencing
1. Write the service contract and a session-sized Household-task task.
2. Build the local event trail, materialized task view, and QML sidebar/app
slice.
3. Add enrolled-device replication, using Loro only as the merge layer.
4. Add calendar on the same authority rail.
5. Add DAV/JMAP adapters as explicit compatibility edges.
This sequence does not wait for biometric matching. A fingerprint factor can
improve local step-up once real verification exists; it must not be mistaken
for shared-space admission or remote identity.
## Non-goals
- Replacing Culver's Matrix transport or contacts UI.
- Treating a shared checkbox as multi-person task state.
- Making the sidebar another database.
- Letting a CRDT or a DAV client grant itself authority.
- Shipping an unlabelled hold gesture as biometric authentication.

View file

@ -292,6 +292,32 @@ So the deliverable is **`souveraine-fpd`**: a system daemon owning
`gazed` does, and minting through `souveraine-stepup`. The `fp-*.py` tools are
instruments, not the factor.
### Preview surface — built 2026-08-10, deliberately not a factor
The smaller `blueline-fingerprintd` producer now publishes its own FPC IRQ as
a root-owned, world-readable pulse record at
`/run/blueline-fingerprintd/preview-pulse`. The Souveraine touch lock watches
that record and shows a configurable three-second hold exercise; the settings
page labels it a wiring preview and it is off by default. The daemon is now
`blueline-fingerprintd` package-owned as well as present in the provisioning
overlay, so a normal `pacman -Syu` can update the live device path.
This proves only **FPC daemon → shell preview**. The pulse neither calls
`unlocked()` nor mints a step-up grant, and the hold always says PIN remains
required. The reader's `KEY_WAKEUP` stays mapped to panel wake only: the FTS
touch driver emits the same generic key, so binding that key inside Hyprland
would falsely call a touchscreen double-tap an FPC event. A real factor still
needs `souveraine-fpd`'s verified match result and TASK-41's attested producer
route.
**The intended real flow is PIN after boot, FPC after that.** The PIN performs
the first unlock that makes Personal-class key material and the enrolled FPC
template database available. A verified FPC match may then unlock a later
screen lock or mint a narrow read/step-up grant, according to the requested
operation; it never replaces first-boot storage unlock, household membership,
or remote-device admission. A reboot returns the device to PIN-required. The
raw IRQ and this preview do none of those things.
### Prior art, checked 2026-08-09
| | |

View file

@ -0,0 +1,60 @@
# TASK 68 — Shared household state: tasks first, authority intact
**Status:** planned 2026-08-10. **Size:** begin with one vertical slice.
**Design:** [`../SHARED-STATE-ARCHITECTURE.md`](../SHARED-STATE-ARCHITECTURE.md).
## Goal
Build a shared Household space where distinct people can create and complete
their own task participation without making the sidebar, Culver, or a CRDT the
authority.
## First slice
1. A local service with an append-only admitted event trail and materialized
task projection.
2. Household membership with separate Casey and spouse identities.
3. Tasks with title, notes, assignees, per-assignee status, due/reminder,
subtasks, and links.
4. One full QML application plus a sidebar projection asking what needs the
current person now.
5. No remote replication until the local data model and authority boundary are
pleasant and inspectable.
## Non-negotiable boundaries
- Culver is the Personal-class contacts/messenger client, not household
admission authority.
- Culver/machined owns enrolment, membership, revocation, and scoped signing.
- The service admits mutations; QML surfaces only project them.
- Loro may merge admitted offline state. It never decides identity,
membership, or permission.
- Calendar is a first-class time object, not a task with a date.
- CalDAV/CardDAV/JMAP are compatibility edges, never the authority model.
- A local biometric factor may improve step-up once it verifies a real match;
it does not admit a remote device or replace shared-space identity.
## Dependencies to make explicit, not hide
- `souveraine-machined` needs scoped, expiring capability tokens (component
audit P1).
- Per-agent/account caller identity is needed before a token can name a
meaningful local producer or caller (P3).
- `souveraine-secrets` must scope Personal-class release to caller and item
(P2).
- sessiond supplies live lock state, step-up, and key eviction; it is not the
shared-state database.
## Acceptance
Casey and his wife can each see a Household task, mark only their own
participation complete, and inspect who changed what. The sidebar and full app
show the same materialized state. No widget writes another state store, and no
offline peer becomes a member merely by merging data.
## Connects to
TASK-16 (Culver contacts as Personal-class data), TASK-23 (locked notification
cadence), TASK-30 (enumerable surface verbs), TASK-41 (attested producers),
`souveraine-components/audit-status.md`, and
`SHARED-STATE-ARCHITECTURE.md`.

View file

@ -64,6 +64,7 @@ Four that are cheap relative to what they unblock:
| # | Task | What's left |
|---|------|-------------|
| 68 | [Shared household state: tasks first](68-shared-household-state.md) | Canonical plan landed 2026-08-10. First slice is an admitted local task trail + materialized QML app/sidebar view; Culver is the Personal-class client, not the authority. |
| 67 | [The caching landed blind, and compaction drifted](67-caching-landed-blind-and-compaction-drift.md) | Prompt caching works — one turn shows `cache_write=22585` then `cache_read=22585` — and **nothing reads the counters**: `assistant_with_usage` has zero callers, so the only proof is a log line. Separately, the live config hands the subconscious `sliding_window` where the doc and the code default both say `sliding_reflect`, and the `memory` tool's enum exposes the blind strategy while hiding the safe one. §1 needs Casey's word, not a fix. |
| 64 | [One SouveraineOS build for the iPhone](64-one-iphone-build.md) | Scoped 2026-08-10, not started. `linux-pauli` is not a repository, reaches the phone by `scp`, and CI has never built it. The display work that day is worth more than its code: the raster is **derived, not guessed** — 888×1845 @ 60 off `adp_mipi_regs` — and `aperture_remove_all_conflicting_devices()` is the correct handoff. ADP still shows nothing with everything else correct; the `adp16` kernel is staged and has never booted. |
| 59 | [Her face on the glass: a Live2D presence](59-her-face-on-the-glass.md) | **Phase 1 passed on hardware 2026-08-05: ~58 fps, rig verified drawing (`painted_px` 23.6k/24k), 283 MB WebKit RSS.** Scoped from a Live2D board-girl found in a cloned blog. The reference streams its chat over **SSE terminated by `[DONE]`** — the contract souveraine's server already speaks, so the integration is accidentally done. Base layer is **wry**, not the Chromium already on the phone; the first deliverable is a measured frame rate for the Cubism runtime in WebKit on freedreno, and if that fails the task stops there. Replaces ani-avatar's 2.5 GB of sprite states with one rig. Reference rig ships as the working placeholder and gets edited toward Annie — it just stays out of any public repo. Summoned by app launch and/or the radial dial, not always-on. |