12 KiB
| task_id | title | status | priority | phase | created | references | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| souveraine-settings-authority-001 | Souveraine settings authority — complete IPC, security policy, and named profiles | scoped | high | design | 2026-07-18 |
|
Souveraine settings authority
End goal
Every supported Souveraine setting is discoverable and operable through one
Souveraine-owned settings authority. The settings application, shell surfaces,
profiles, the local human, and agents all use the same schema and mutation
path. There are no UI-only settings, hidden ad-hoc writes to Config.options,
or profiles that bypass the security policy.
This is not a loose settings.set(any.path, any.value) escape hatch. It is a
typed, audited capability surface:
- low-risk settings can be adjusted directly by a human or agent;
- sensitive settings can be proposed by an agent but only committed by a local human after seeing the exact diff and satisfying step-up;
- secret-bearing values are never returned over shell IPC;
- a profile is an atomic settings transaction, not a second configuration mechanism;
- every refusal returns a structured reason instead of silently doing nothing.
Build this authority as original Souveraine AGPL code. Existing ii-derived configuration is input to inventory, not an architecture to preserve.
Why a schema is load-bearing
Config.qml currently defines values and persistence, while individual
settings pages know labels and presentation. That is not enough for an agent
or a profile system: neither can safely infer type, range, risk, device scope,
or whether a value is a credential from an arbitrary QML property path.
Create a Souveraine-owned settings schema. Every supported key has one record:
path lock.security.allowPowerFromLock
type bool
default false
scope common | phone | laptop | device
risk ambient | personal | physical | admin | secret
mutability direct | propose-only | human-only | read-only
profileEligible true
requires [capabilities or companion settings]
conflicts [mutually exclusive settings]
validation enum/range/pattern/custom validator
description human- and agent-readable intent
The registry is authoritative. A config key without schema metadata is treated as internal and is not exposed over IPC. CI fails when a user-facing setting is added to a settings page without a schema record and IPC reachability test.
One settings service, one mutation path
Create an original SettingsService.qml (or equivalent owned service) that
owns schema lookup, validation, authorization, persistence, audit, profiles,
and change notification. Settings pages stop assigning directly to
Config.options.*; they call this service just like agents do.
The service exposes in-process methods for QML and an IPC adapter. Quickshell
IPC only reliably transports declared primitive types, so structured payloads
and results cross as JSON strings, following the proven apps and dock
surfaces.
Proposed IPC target: settings.
settings.schema() -> all non-secret metadata
settings.describe(path) -> metadata + effective value
settings.get(path) -> effective value (redacted if secret)
settings.list(prefix) -> settings under a namespace
settings.set(path, jsonValue, reason) -> direct write or structured refusal
settings.propose(path, jsonValue, reason) -> immutable pending change
settings.pending() -> pending proposals, no secret values
settings.history(limit) -> audit history, redacted
settings.profileList() -> available profiles
settings.profileDescribe(name) -> metadata + expanded diff
settings.profilePropose(name, reason) -> pending atomic profile transaction
Do not expose approve, authenticate, or mintGrant over IPC. Approval is a
local Souveraine surface action. An agent can formulate a change and explain
why; it cannot click its own approval dialog or turn a proposal into authority.
Authorization policy
The settings authority evaluates each key's schema risk. The caller never supplies or self-asserts its risk class.
| Risk | Read | Agent mutation | Human mutation |
|---|---|---|---|
ambient |
allowed | direct, validated, audited | direct |
personal |
value may be redacted while locked | direct or proposal according to key metadata | direct when unlocked |
physical |
allowed unless value itself is sensitive | proposal only | local review + StepUpAuth("physical") |
admin |
metadata allowed; value policy per key | proposal only | local review + StepUpAuth("admin") |
secret |
never return the value | never accept plaintext through shell IPC | local credential surface only |
lock.security.allowPowerFromLock, requirePasswordToPower, step-up enablement
and TTL, suspend/hibernate policy, charge thresholds, and any setting that
widens an agent's own authority are never direct agent writes. They are
proposal-only even if a broad step-up grant happens to be live. This prevents
an unrelated process from racing the IPC socket during a grant window.
Approval binds to the immutable proposal digest: path(s), old value(s), new value(s), reason, requester, device, and expiry. The local approval surface shows the complete diff before PAM. Authentication approves that exact digest, not a reusable boolean and not a caller-provided token. Locking the session, changing any old value, or reaching expiry invalidates the proposal.
Audit and recovery
Every attempted mutation records:
- timestamp and device/profile;
- caller identity when available (
human,agent:<id>, system component); - path and redacted old/new values;
- reason;
- outcome (
applied,proposed,refused,expired,rolled-back); - authorization path (direct policy, physical step-up, admin step-up).
Successful transactions retain an inverse diff so the settings UI can undo a change. Audit is append-only and bounded/rotated; secret material is never written. A failed multi-key transaction rolls back fully and reports the key that failed.
Profiles are policy transactions
Profiles such as the working-name pair “paranoid” and “open” are named, versioned bundles of schema paths and values. Names remain product language to decide later; do not bake those working names into API semantics.
A profile is not a parallel config file that overwrites Config.qml. Applying
one expands it against the current effective configuration, validates every
key, computes one visible diff, evaluates the highest risk in that diff, and
commits atomically through SettingsService.
Profile model:
id / displayName / description / version
deviceScopes: common, phone, laptop, optional explicit device IDs
values: schema path -> typed value
inherits: optional single base profile (cycle rejected)
source: built-in | user | managed
Rules:
- Applying a profile never bypasses per-key authorization. If one key is
physicaloradmin, the whole transaction becomes a reviewed proposal. - Unknown, removed, or device-incompatible keys refuse the transaction; they are not silently ignored.
- Profiles contain no secrets. Credential references are separate and remain local.
- The preview names values that will change, values already satisfied, and unsupported capabilities before authentication.
- A profile applies atomically and records one audit transaction plus its expanded per-key diff.
- The active-profile label is descriptive state, not magical inheritance. A
manual edit may mark the profile
modified; it must not be overwritten by a background reapply. - Agent-created profiles are proposals until a human saves them. Agents may suggest and preview profiles freely.
Device profiles are not security profiles
Do not conflate phone/laptop surface profiles with a security posture.
- A device profile chooses form factor, available surfaces, dimensions, hardware capabilities, and package manifest.
- A settings profile changes policy and preferences on a supported device.
Settings schema records declare their device scope, and profile expansion refuses incompatible keys. A laptop security profile cannot accidentally turn on a phone-only lock keypad or power-button gesture.
Human surfaces
The Souveraine settings application consumes the same schema and service:
- categories and controls are projected from metadata where practical;
- bespoke pages remain valid for rich interactions, but their writes still go
through
SettingsService; - every setting shows whether agents may change it directly, propose it, or never access it;
- pending agent proposals have a first-class review surface;
- profile preview is a real diff, with risk and required authentication shown;
- undo/history is visible rather than hidden in logs.
The lock surface never hosts general settings approval. A physical/admin change waits until the user is securely unlocked, except for a deliberately designed emergency action whose policy explicitly says otherwise.
Implementation shape
Original AGPL components, names adjustable during build:
services/SettingsSchema.qml— metadata and validation definitions.services/SettingsService.qml— effective values, transactions, auth policy, persistence, proposals, history, and profiles.modules/souveraine/settings/SettingsScope.qml—IpcHandleradapter only; JSON-string transport, no policy duplication.modules/souveraine/settings/ProposalReview.qml— local diff + step-up.modules/souveraine/settings/ProfileManager.qml— list, edit, preview, apply, and modified-state presentation.- a profile store under Souveraine-owned user state, written atomically.
The existing Config.qml JsonAdapter may remain the persistence adapter
while the migration is underway, but once SettingsService owns a key no UI
or service may write that key around it. The end state has one authority, not a
permanent split brain.
Migration rule
Inventory all current user-facing Config keys first and classify every one.
Move whole namespaces through the authority, not individual toggle patches.
Security-sensitive lock/session/power settings are the first namespace because
the new allowPowerFromLock toggle demonstrates why a direct property write is
not enough.
The migration is complete only when every visible settings control is backed by schema and uses the authority. Do not call a generic unvalidated setter a finished IPC implementation.
Done when
- Every user-facing setting has schema metadata, device scope, validation, risk, and mutability.
- Every settings UI write and agent mutation uses one SettingsService transaction path.
settingsIPC offers discovery, read, direct low-risk set, proposal, history, and profile preview/proposal with JSON-string results.- Secret values never cross IPC, logs, profile files, or audit history.
- Security-widening changes require local review bound to the exact diff; an agent cannot approve its own proposal.
- Profiles validate and apply atomically and cannot bypass a key's policy.
- Phone/laptop device profiles remain distinct from settings/security profiles.
- Every change is audited and successful transactions can be undone.
- CI catches user-facing Config keys or settings controls missing schema and IPC coverage.