Every command, every check, grounded in the source.
Install, verify the install, write a profile, and read what each of the six reconciler checks declares and proves. Nothing below is illustrative unless it says so, every command and output is real.
pip install orrery && orrery doctor
Install
The engines (reconciler, context) are stdlib-only: zero runtime dependencies, Python 3.11 or newer. pyproject.toml declares dependencies = []; the only optional extra is tui, the themed terminal deck (textual>=8,<9), so the core install never has to pull in a TUI framework it doesn't need. Current version 0.1.0; the console entry point is orrery = "orrery.cli:main".
$ pip install orrery
$ pip install 'orrery[tui]' # themed terminal deck (textual)
$ pip install . # core, from the repository root
$ pip install '.[tui]' # core plus the deck extra
Either path installs the same thing: a package sourced from PyPI and a package built from a local checkout behave identically, because the core carries no identity of its own. See the licensing model: the core is AGPL-3.0-or-later.
Quickstart
Two commands cover the whole loop: verify the install, then measure a box against a declared profile.
1. Let it check its own install
doctor confirms the right Python is present, the package imports, every reconciler check is registered, and every subcommand is reachable. It never asks you to trust a note that says the install is fine.
$ orrery doctor
Real output, captured from a fresh install (your Python and package versions will differ):
[ok ] python>=3.11: 3.12
[ok ] stdlib tomllib: present
[ok ] import orrery: 0.1.0
[ok ] reconciler checks: all present
[ok ] import context engine: ok
[ok ] cli entry point: orrery.cli:main
[ok ] tui extra (optional): installed
healthy
Exit code is 0 when every probe passes ("healthy"), 1 if any fails ("PROBLEMS FOUND"). The tui probe is the one exception: "not installed (optional)" still counts as a pass. Add --json for a machine-readable result a CI job can check.
2. Reconcile a box against a profile
A fact here is not "declared", it is declared and its probe passes, continuously. Reconcile is report-only: it measures and prints findings, it never changes a box. Run it against the example profile shipped in the repo, unedited: every path in it is a placeholder, so every check reports drift or failure, which is the honest, correct behavior.
$ orrery reconcile --profile profiles/example.toml
Real output, unedited, run against the example profile as shipped:
reconciler: example
[declared-presence]
FAIL /path/that/must/exist: required member is missing (expected 'present', observed 'absent')
info /path/that/should/not/exist/yet: absent as planned
[fleet-reach]
FAIL some-box: declared-reachable link is DOWN (expected 'reachable', observed 'unreachable')
ok a-deliberately-closed-box: correctly denied
[floors]
DRIFT example content floor: floor path is missing (expected 'present', observed 'absent')
DRIFT example digest budget: floor path is missing (expected 'present', observed 'absent')
[managed-settings]
FAIL /path/to/an/enforcement/file: required enforcement file is missing (expected 'present', observed 'absent')
info /path/to/a/planned/managed-settings/file: not yet in place (planned)
[org-map]
warn org-map: source of truth unreadable: /path/to/orgs.tsv
[secret-edges]
DRIFT some-store/tokens/example -> /path/to/a/consumer/that/references/it: declared consumer file is missing or unreadable (expected 'references the secret', observed 'file absent')
warn some-store/tokens/example: discover_root does not exist: /path/to/scan/for/undeclared/references
summary: OK=1 INFO=2 WARN=2 DRIFT=3 FAIL=3 -> DRIFT DETECTED (exit 1)
Every finding came from a live probe, not from a note that once said something was true. Once your own profile names real paths and real boxes, point at it the same way: orrery reconcile --profile my-fleet.toml. Full flags and exit codes are in the CLI reference below.
Writing a profile
A profile is a TOML file that declares what a box should be. It is per-installation PROFILE data, not part of the core, and it never leaves your box: write it locally, keep real profiles out of git, since they carry install-specific identity (real hosts, real paths). The engine and its checks do not change between installs, only the profile does. The full, commented reference ships in the repo at profiles/example.toml; everything below is that file, explained field by field.
| schema | Integer format marker for the profile file itself. The example ships schema = 1. |
|---|---|
| box | A string naming the installation this profile declares, for example box = "example". Printed as the run's identity in output (reconciler: <box>). |
| [[checks]] | One array-of-tables entry per check. Each entry needs an id matching a registered check (org-map, fleet-reach, declared-presence, secret-edges, floors, managed-settings); the rest of its keys are that check's own options. |
org-map
Declares one canonical bucket-to-account table and the consumer files that re-encode it in three supported shapes.
[[checks]]
id = "org-map"
source = "/path/to/orgs.tsv" # tab-separated, header row with bucket/host/account
[[checks.consumers]]
path = "/path/to/a/python/consumer.py"
parser = "python-dict" # ast-parse a named dict literal
symbol = "BUCKET_ACCT" # {bucket: (host, account)}
[[checks.consumers]]
path = "/path/to/a/bash/consumer"
parser = "bash-assoc" # declare -A NAME=( [bucket]=host/account )
name = "ACCT"
[[checks.consumers]]
path = "/path/to/another/bash/consumer"
parser = "bash-case" # func(){ case "$1" in bucket) echo account;; esac }
func = "org_of"
implied_host = "github.com" # this encoding stores account only; host is implied
- source
- Path to the source-of-truth TSV. Must have a header row containing
bucket,host, andaccountcolumns; a header missing any of the three makes the check WARN, not crash. - checks.consumers[].path
- File that re-encodes the mapping.
- checks.consumers[].parser
- One of
python-dict(ast-parses a named dict literal, no code executed),bash-assoc(adeclare -Ablock), orbash-case(a shell function with acasestatement). - symbol / name / func
- The parser-specific handle to look for: the dict variable name, the associative-array name, or the function name.
- implied_host
- Optional,
bash-caseonly: a host to assume when that encoding stores only the account.
fleet-reach
Declares the ssh edges FROM this box and what each should be.
[[checks]]
id = "fleet-reach"
timeout = 5
[[checks.edges]]
to = "some-box"
expect = "ok" # reachable as declared; unreachable -> FAIL
[[checks.edges]]
to = "a-deliberately-closed-box"
expect = "denied" # unreachable is correct; reachable -> DRIFT (regression)
- timeout
- Seconds allowed per ssh probe. Defaults to 5 if omitted.
- checks.edges[].to
- An ssh alias or
user@host. Must match a plain-alias shape (no leading dash, no shell metacharacters); anything else is refused, never probed. - checks.edges[].expect
"ok"(must be reachable) or"denied"(must stay unreachable).
secret-edges
Declares each secret by its non-secret identity and the files that should reference it. Never a value.
[[checks]]
id = "secret-edges"
discover_root = "/path/to/scan/for/undeclared/references" # optional
[[checks.secrets]]
ref = "some-store/tokens/example" # a vault path or logical name, not the value
[[checks.secrets.consumers]]
path = "/path/to/a/consumer/that/references/it"
- ref
- The secret's identity: a slash-separated vault path (up to 8 short segments) or an
UPPER_SNAKEenv var name. Checked against a fail-closed allowlist plus a denylist of known secret-value shapes (private keys, GitHub and AWS tokens, Slack, Vault, JWT, Stripe, Resend, Google API keys); anything that doesn't clear the gate is refused and never scanned or echoed. - name
- Optional label. When set, it replaces
refin every output line, so even the identity stays out of the report. - checks.secrets[].consumers[].path
- A file expected to contain the literal
ref. - discover_root
- Optional directory to scan (up to 2000 files) for files that reference the secret but were never declared as consumers.
floors
Declares a calibrated floor and/or ceiling for a measured count.
[[checks]]
id = "floors"
[[checks.floors]]
name = "example content floor"
path = "/path/to/a/directory"
kind = "file_count" # file_count (a dir) | line_count (a file)
min = 100
recalibrate_ratio = 3.0 # actual > min*ratio -> INFO, floor is stale
[[checks.floors]]
name = "example digest budget"
path = "/path/to/a/file"
kind = "line_count"
max = 250 # above this -> WARN (ceiling exceeded)
- kind
file_countcounts files under a directory (capped at 100,000);line_countcounts lines in a file.- min
- The floor. Below it is FAIL, treated as real data loss.
- max
- The ceiling. Above it is WARN, a budget exceeded.
- recalibrate_ratio
- Optional. When the actual count exceeds
min × recalibrate_ratio, the finding is INFO: the floor is stale and worth resetting.
managed-settings
Declares that an enforcement file must stay root-owned and unwritable by anyone but its owner.
[[checks]]
id = "managed-settings"
[[checks.files]]
path = "/path/to/an/enforcement/file"
require_owner_uid = 0
max_mode = "0644"
[[checks.files]]
path = "/path/to/a/planned/managed-settings/file"
require_owner_uid = 0
max_mode = "0644"
planned = true
- require_owner_uid
- The uid that must own the file, usually
0(root). - max_mode
- The most permissive octal mode allowed.
"0644"means no group or other write bit. - planned
- When
true, an absent file reports INFO (a promotion not yet done) instead of FAIL.
declared-presence
Declares required members that must exist, and paths that should stay absent until a planned change lands.
[[checks]]
id = "declared-presence"
required = [
"/path/that/must/exist",
]
planned_absent = [
"/path/that/should/not/exist/yet", # absent -> INFO; present -> WARN (profile stale)
]
- required
- A list of paths that must exist. Missing is FAIL.
- planned_absent
- A list of paths that should stay absent, a hardening step not yet taken. Absent is INFO; present is WARN, because it means the profile itself has gone stale.
The six checks
One idea generalized six ways: a thing is not "declared", it is declared AND its probe passes, continuously. Every check is read-only. Findings carry a severity (ok, info, warn, drift, fail); the worst finding across a run decides the exit code.
org-map
Declares one source-of-truth table (a TSV of bucket, host, account) plus every consumer file that re-encodes that mapping.
Proves each consumer agrees with the source. A bucket a consumer maps that the source doesn't list is DRIFT; account comparison is case-insensitive on purpose (different consumers store case differently by design), host is compared only when a consumer encodes one; buckets a consumer legitimately doesn't cover are reported INFO, not a problem.
fleet-reach
Declares the ssh edges that should exist from this box, and whether each should be reachable or deliberately denied.
Proves the live matrix, probed over ssh with a timeout, matches what was declared. A declared-reachable link that's down is FAIL; a deliberately-closed link that has reopened is DRIFT, a security regression. A closed edge going quiet is the failure mode nothing else watches for.
declared-presence
Declares required members that must exist, and paths declared planned-absent, not yet in place by design.
Proves required members are present (FAIL if missing) and planned-absent paths stay absent (INFO while absent, WARN if they appear, since their appearance means the profile is now stale).
secret-edges
Declares secrets by non-secret identity only (a vault path or an UPPER_SNAKE env var name, never a value) and the consumer files that should reference each.
Proves each declared edge holds; a consumer that no longer references the secret, or is missing or unreadable, is DRIFT. With discover_root set, it also surfaces undeclared references: a file that mentions the secret but was never declared as a consumer, also DRIFT. Value-blind by construction: a fail-closed identity gate (an allowlist of vault-path and env-name shapes, backed by a denylist of known secret-value patterns) refuses to scan or print anything that doesn't clear it, so a misdeclared secret value is never grepped across the box or echoed into a report.
floors
Declares a floor and/or ceiling for a measured count: files under a directory, or lines in a file.
Proves the count stays in bounds. Below the floor is FAIL, treated as real data loss; above the ceiling is WARN, a budget exceeded; far above the floor (past min × recalibrate_ratio) is INFO, a floor that has gone stale and is worth resetting. A missing measured path is DRIFT, never reported as a false zero.
managed-settings
Declares each enforcement file's expected owner uid and the most permissive mode it may carry.
Proves the file is present, owned by that uid, and not writable by anyone but the owner, no group or other write bit, so the guard layer cannot be quietly disabled from inside. A file marked planned that isn't in place yet reports INFO, not FAIL, keeping a promotion path visible without failing the run.
CLI reference
The whole surface is five subcommands. orrery with no arguments, or orrery --help, prints this same list; each subcommand hands its remaining arguments straight to an existing module, so the CLI itself adds no logic of its own.
| Command | What it does |
|---|---|
| reconcile | Run a reconciler profile (drift report). |
| context | Resolve the context for a working directory. |
| deck | Launch the themed terminal deck (needs the tui extra). |
| doctor | Verify this installation. |
| version | Print the version. |
reconcile
python -m orrery.reconciler --profile PATH [--ssh HOST] [--json] [--check ID], reached as orrery reconcile .... Report-only: it never changes a box.
| Flag | Meaning |
|---|---|
| --profile PATH | Required. Path to a TOML profile. |
| --json | Emit JSON instead of the human-readable text report. |
| --check ID | Run only the named check instead of every check in the profile. |
| --ssh HOST | Measure a remote host (an ssh alias) read-only, instead of the local filesystem. |
| Code | Meaning |
|---|---|
| 0 | Clean: the worst finding is at or below info. |
| 1 | Drift or worse: the worst finding is DRIFT or FAIL. |
| 2 | The profile itself could not be loaded (a TOML syntax error or a bad path). Printed as profile error: ... to stderr, never a raw traceback. |
The severity ladder
Ordered worst-last, so the worst finding across a run decides the verdict and the exit code.
| Severity | Meaning |
|---|---|
| ok | Declared and observed agree. |
| info | An acknowledged, expected state that is not a problem, for example a planned-absent path that is still absent. |
| warn | A soft issue: a consumer could not be parsed, so it was skipped, not passed. |
| DRIFT | Declared and observed disagree. The core product signal. |
| FAIL | A declared invariant is broken, for example a required member is missing. |
context
python -m orrery.context --config C [--cwd D] [--json], reached as orrery context .... Prints the context block to inject for a working directory; exits 2 with context config error: ... if the config cannot be loaded, 0 otherwise. --config is required; --cwd defaults to the process's own working directory.
doctor
orrery doctor [--json]. Verifies this installation (see Quickstart). Exit 0 when every probe passes, 1 otherwise.
deck
orrery deck --profile PATH --context-config PATH. Launches the themed terminal deck. Needs the tui extra; without it, the CLI never imports Textual at all (a lazy import), so the core install runs every other command regardless. The exact message if it's missing: the deck needs the 'tui' extra (textual not installed). install with: pip install 'orrery[tui]', exit 2.
version
orrery version. Prints orrery 0.1.0 and exits 0.
Remote reconcile, and the context engine
Reconcile a box over ssh, read-only
Add --ssh HOST to measure a remote box instead of the local filesystem, so the same profile and the same checks run unchanged against any reachable box:
$ orrery reconcile --profile my-fleet.toml --ssh runtime-01
Every remote command is a read (test, head, find, stat), run under ssh -o BatchMode=yes -o ConnectTimeout=<timeout>, and never raises: a failure returns nothing found rather than crashing, exactly like a local run. Paths must be absolute and are shell-quoted, so a value from the profile can never become a remote flag or get injected into the remote shell. Reads are bounded (large files are capped, file listings are capped) so a large or unusual file cannot stall a run. HOST is an ssh alias; your own ssh config resolves the address, user, and key, so Orrery itself never holds or needs a credential.
The context engine: per-directory isolation
orrery context --config C [--cwd D] [--json] resolves what context should be injected for a given working directory, and the isolation rule is the whole point: a global baseline is always included; when the working directory is outside the declared projects root ("ops" scope), a fleet-wide digest is added; when the working directory is inside it, at least two levels down ("project" scope), only that one project's own state is added, never another project's and never the fleet digest. A project directory that would resolve outside the declared root, for example through a symlink, is refused and never read, not silently followed. That single rule is what stops project context from bleeding across projects.
The context engine, the guided install walkthrough, and the licensing model live on their own pages: see Get started for a step-by-step first run, and the open-core model for what is AGPL and what is commercial.