Watch
1
0
Fork
You've already forked SouveraineOS
0
SouveraineOS/docs/SETTINGS-AUTHORITY.md

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
surfaces/quickshell/modules/common/Config.qml
surfaces/quickshell/modules/settings/
surfaces/quickshell/services/StepUpAuth.qml
surfaces/quickshell/modules/common/functions/Session.qml
docs/tasks/souveraine-shell-ecosystem.md
docs/tasks/device-specific-quickshell-profiles.md
docs/tasks/power-indication.md
docs/DECISIONS.md

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:

  1. Applying a profile never bypasses per-key authorization. If one key is physical or admin, the whole transaction becomes a reviewed proposal.
  2. Unknown, removed, or device-incompatible keys refuse the transaction; they are not silently ignored.
  3. Profiles contain no secrets. Credential references are separate and remain local.
  4. The preview names values that will change, values already satisfied, and unsupported capabilities before authentication.
  5. A profile applies atomically and records one audit transaction plus its expanded per-key diff.
  6. 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.
  7. 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.qmlIpcHandler adapter 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.
  • settings IPC 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.