Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,7 @@ Thumbs.db

.sentrux/cache.bin
.sentrux/churn.bin

# Crash dumps must never enter source control.
core
core.*
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ BIOS changes, voltage changes, raw MSR/PCI/EC access, firmware flashing, and arb
## Repository status

FPSMaxxing is a Rust 2024 Cargo workspace with OSS governance, a security policy, issue templates, CI, and an organized [documentation index](docs/README.md) covering architecture, plans, threat model, broker operations, and provider guides.
Every stage of the closed loop above has a working implementation on the mock path, except what the walkthrough marks as future.
Every stage of the closed loop above has a working implementation on the mock path, except what the closed-loop walkthrough above marks as future.
What ships today, by capability:

- **Capabilities and providers.** Shared capability and provider contracts, a provider SDK lifecycle, and a mock provider covering snapshot, preview, apply, verify, and rollback under test.
Expand All @@ -94,7 +94,7 @@ What ships today, by capability:
- **Crash and lease recovery.** An independent watchdog that restores prior state from the journal after a crash or a lease expiry, on the Linux-safe mock path.
- **Measurement and decision.** A deterministic experiment runner that gates measured trials through an immutable evaluator and replays them from the journal alone.

Everything the walkthrough above states in the future tense is still ahead of us.
Everything the closed-loop walkthrough above states in the future tense is still ahead of us.
So are real hardware providers and live frame-time measurement.

Try the read-only alpha:
Expand Down Expand Up @@ -124,7 +124,7 @@ It is a demonstration binary rather than an MCP tool, takes no arguments, and jo

`fpsmaxxing-broker` is the trusted side of the local IPC boundary: it serves capability discovery and the bounded provider lifecycle to authenticated local peers over a Unix domain socket, and refuses to run on Windows because the Windows named-pipe transport is not yet available.
The gateway does not connect to it yet, so the broker path is exercised by the `BrokerClient` in `crates/ipc` and its end-to-end tests in `apps/broker/tests/integration.rs` rather than by the MCP command above.
[Broker operations and deployment](docs/BROKER_OPERATIONS.md) covers running it directly: socket, journal, and lock paths, private-directory ownership rules, and systemd units.
[Broker operations and deployment](docs/BROKER_OPERATIONS.md) covers running it directly: socket, journal, and lock paths, private-directory ownership rules, and systemd RuntimeDirectory settings.

```bash
cargo run -p fpsmaxxing-broker
Expand Down
15 changes: 7 additions & 8 deletions apps/broker/src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@
//! shell, Registry path, or hardware primitive crosses this boundary.
//!
//! Only the Unix domain socket transport is implemented; the Windows named-pipe
//! transport is not yet available, so the binary refuses to run there.
//! transport is deliberately out of scope, so the binary refuses to run there.
//!
//! The broker always establishes one owner-only private directory of its own,
//! under `$XDG_RUNTIME_DIR` (or `/run`). Unless `--socket`/`--journal` or their
Expand Down Expand Up @@ -98,10 +98,10 @@ or the broker refuses to start.";
/// Environment override for `--journal`.
///
/// Deliberately distinct from the `FPSMAXXING_JOURNAL_PATH` the unprivileged
/// gateway, CLI, and watchdog read. Sharing that variable would let an
/// operator who exported it for the CLI silently move the privileged
/// broker's audit journal out of its owner-only directory and into a file
/// the gateway is writing concurrently.
/// gateway and CLI read. Sharing that variable would let an operator who
/// exported it for the CLI silently move the privileged broker's audit
/// journal out of its owner-only directory and into a file the gateway is
/// writing concurrently.
const JOURNAL_ENV: &str = "FPSMAXXING_BROKER_JOURNAL_PATH";

/// Directory the broker keeps its socket and journal in by default.
Expand Down Expand Up @@ -259,7 +259,7 @@ or the broker refuses to start.";
/// [`resolve_paths`] against an arbitrary runtime base and environment.
///
/// Only [`SOCKET_ENV`] and [`JOURNAL_ENV`] are ever consulted; the broker
/// shares no path variable with the unprivileged gateway, CLI, or watchdog.
/// shares no path variable with the unprivileged gateway or CLI.
///
/// The private directory under `base` is created and vetted whatever the
/// overrides say, because the single-instance lock lives in it and must not
Expand Down Expand Up @@ -692,8 +692,7 @@ or the broker refuses to start.";
resolve_paths_from, restrict_journal, runtime_base,
};

/// The variable the unprivileged gateway, CLI, and watchdog use for
/// their journal.
/// The variable the unprivileged gateway and CLI use for their journal.
const GATEWAY_JOURNAL_ENV: &str = "FPSMAXXING_JOURNAL_PATH";

/// An unprivileged uid, so `XDG_RUNTIME_DIR` is eligible at all.
Expand Down
1 change: 1 addition & 0 deletions docs/ARCHITECTURE.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ The audit journal is different: it holds every `apply-intent` record and outlive
The broker reads only broker-specific overrides (`FPSMAXXING_BROKER_SOCKET`, `FPSMAXXING_BROKER_JOURNAL_PATH`) and never the `FPSMAXXING_JOURNAL_PATH` the unprivileged gateway, CLI, and watchdog use, so an operator who exported that variable cannot move the privileged audit journal.
Both are read as raw `OsString` values, so a path that is not UTF-8 relocates the socket or journal as configured rather than being silently dropped back to the default.
The command line is read as `OsString` too, but it is matched against flag names rather than used verbatim, so an argument that is not UTF-8 is a typed fatal parse error naming it - not the mid-iteration panic `env::args` would raise, and not a silent fallback either.
[Broker operations and deployment](BROKER_OPERATIONS.md) turns these rules into the procedure for running the broker, down to the systemd `RuntimeDirectory` settings a unit needs.

The broker fails fast rather than degrading.
Losing the control-plane worker thread - including to a panic mid-lifecycle, which may leave provider state applied and un-rolled-back - stops the serve loop and exits the process non-zero instead of answering later requests with an internal fault.
Expand Down
32 changes: 16 additions & 16 deletions docs/BROKER_OPERATIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ For why the boundary is shaped this way, read [Architecture](ARCHITECTURE.md); f
The `fpsmaxxing-broker` binary is the trusted side of the local IPC boundary.
It owns the control plane and serves capability discovery and the bounded provider lifecycle to authenticated local peers over a Unix domain socket.
Only the Unix domain socket transport is implemented, and the Windows named-pipe transport is not yet available, so the binary refuses to run there.
The gateway does not connect to it yet - it still opens an in-process control plane of its own - so the broker path is exercised by the `BrokerClient` in `crates/ipc` and its end-to-end tests in `apps/broker/tests/integration.rs` rather than by an MCP client.
For what currently drives that boundary, see [Privileged broker](../README.md#privileged-broker) in the project README.

## Running the broker

Expand All @@ -26,6 +26,19 @@ cargo run -p fpsmaxxing-broker -- \
--journal "$HOME/.local/state/fpsmaxxing/journal.sqlite"
```

## The private directory

The private directory is `$XDG_RUNTIME_DIR/fpsmaxxing`, or `/run/fpsmaxxing` when `XDG_RUNTIME_DIR` is unset, is not absolute, or the broker runs as root.
The broker creates it mode `0700` on every start, whether or not an override moved the socket and the journal out of it, because the single-instance lock lives there.
It refuses to start unless that directory is owned by the broker's own uid and has mode exactly `0700`, and unless every directory above it is owned by the broker or root and is not writable by anyone else except under a sticky bit.

Do not put your own directory at `/run/fpsmaxxing`.
That is the privileged broker's own private directory.
Creating `/run/fpsmaxxing` as your user therefore leaves a later root broker refusing to start until it is chowned to root or removed.
A root broker creates and vets it on its own.

A systemd unit needs both `RuntimeDirectory=fpsmaxxing` and `RuntimeDirectoryMode=0700`: `RuntimeDirectoryMode` defaults to `0755`, and the broker validates an existing private directory rather than correcting its mode, so a unit that omits the mode is refused on every start.

## Socket and journal paths

| Setting | Flag | Environment variable | Default |
Expand All @@ -35,28 +48,15 @@ cargo run -p fpsmaxxing-broker -- \

A flag wins over its environment variable, and both are broker-specific so nothing the gateway, the CLI, or the watchdog exports can move the privileged journal.

A path from a flag or an environment variable is held to the same bar as the default: it must be absolute, the directory holding it must exist, and the whole chain above it is vetted, so an override cannot place a privileged socket or audit journal somewhere another user can reach it.
Give the socket and the journal a directory of their own at mode `0700`, owned by the broker or root - the default private directory already is one.
A path from a flag or an environment variable is vetted as well: it must be absolute, the directory holding it must exist, and the whole chain above it is vetted, so an override cannot place a privileged socket or audit journal somewhere another user can reach it.
Give the socket and the journal a directory of their own at mode `0700`, owned by the broker or root, which is the looser of the two ownership rules: the default private directory is accepted only when the broker owns it itself.

That directory is held higher than the ancestors above it, in two ways.
The sticky bit does not excuse group or world write there: sticky stops another user removing the broker's socket or journal, but not creating either one first and keeping ownership of it, so a shared root like `/tmp` is refused.
Nor is group or world traversal excused: the socket's own mode cannot be pinned, so a merely traversable directory like `/run` would put every local user in front of it, and it is refused too.

The journal file itself is created mode `0600`, and SQLite's rollback journal and write-ahead log inherit that.

## The private directory

The private directory is `$XDG_RUNTIME_DIR/fpsmaxxing`, or `/run/fpsmaxxing` when `XDG_RUNTIME_DIR` is unset, is not absolute, or the broker runs as root.
The broker creates it mode `0700` whether or not an override moved the socket and the journal out of it, because the single-instance lock lives there, and refuses to start unless it and every directory above it are owned by the broker or root and are not writable by anyone else.
It still establishes that directory even when both paths are given, so it also needs to be able to create `$XDG_RUNTIME_DIR/fpsmaxxing` - or `/run/fpsmaxxing`, when that variable is unset - on every start.

Do not put your own directory at `/run/fpsmaxxing`.
That is the privileged broker's own private directory, and it is the one directory held to exact ownership: root ownership satisfies an explicit `--socket` or `--journal` parent, but a broker accepts its private directory only when it owns that itself.
Creating `/run/fpsmaxxing` as your user therefore leaves a later root broker refusing to start until it is chowned to root or removed.
A root broker creates and vets it on its own.

A systemd unit needs both `RuntimeDirectory=fpsmaxxing` and `RuntimeDirectoryMode=0700`: `RuntimeDirectoryMode` defaults to `0755`, and the broker validates an existing private directory rather than correcting its mode, so a unit that omits the mode is refused on every start.

## Single-instance lock

Only one broker may run per user: it takes an exclusive lock on `<private dir>/broker.lock` before the journal is opened and before the socket is bound, so a second broker exits non-zero without having touched either.
Expand Down
2 changes: 1 addition & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ Use this page as the documentation entry point. The Markdown documents are canon

| Document | Purpose |
| --- | --- |
| [Broker operations and deployment](BROKER_OPERATIONS.md) | Running the privileged broker: socket, journal, and lock paths, private-directory ownership rules, and systemd units |
| [Broker operations and deployment](BROKER_OPERATIONS.md) | Running the privileged broker: socket, journal, and lock paths, private-directory ownership rules, and systemd RuntimeDirectory settings |

## Extension guides

Expand Down