Repository navigation
Releases: albertorsesc/planeops
Release list
v0.11.2
Added
- Every drift item carries the
intentits entry was declared with, and the
kill_criteriawhere one is set, rendered under the item inDRIFT.mdand
as fields inDRIFT.json. The registry has always required a reason per
entry and no output had ever shown one, so a report could name a missing
secret while the sentence explaining what it is for, and that the remedy is
migrating it into the sops store, sat two files away. An item the ungoverned
pass built from an observation has no declaration behind it and carries
neither.DRIFT.jsonmoves to schema 3 for the new fields; the terminal view
is unchanged, since packing same-message groups onto shared rows is what
keeps it scannable.
v0.11.1
Added
-
unmanaged.yamltakes apublishers:list besideglobs:, so a vendor can
be exempted once instead of one agent at a time. A publisher matches an
identity the adapter attested rather than a name the subject chose: on macOS
thelaunchdadapter reports the Team ID its program is signed with, so a
single line covers everything that vendor ships, including agents that do not
exist yet. That is also why it is safe where acom.vendor.*pattern is not,
and why it may cover a service that runs at login.An agent running
/bin/sh -c ...cannot inherit one: the OS signs its own
binaries without a Team ID, so no interpreter carries a publisher and no
publisher rule can silence one. Exceptions need no syntax, since a declared
entry is never exempt: declare the one asset you do want governed.A rule covers what can be attested, so an agent that declares no program has
nothing signed and still needs a glob: on one real machine two of a vendor's
four agents were covered by one publisher line, and the two on-demand helper
plists with no program were not.Publishers are macOS-only for now. systemd units are not signed, and package
ownership does not carry the same guarantee, so thesystemdadapter attests
nothing and apublishers:rule simply never matches there.
v0.11.0
Security
- BREAKING: An
unmanagedglob could hide a service that runs code at
login. Matching items were dropped before the snapshot was written, so the
check that alerts on an ungoverned always-on service never saw them. The
identity a launchd glob matches is theLabelinside the plist, which
whoever writes the file chooses, so a wildcard exemption such as
launchd/com.vendor.*was a name anything could adopt to run at login
unmentioned. A glob now withholds the report's question and not the
observation, which is what SPEC section 2 already described, and a pattern no
longer covers an always-on service at all. Naming one exactly still does, so
a vendor updater you have looked at stays quiet while the name space around
it does not. Migration: an always-on service a pattern used to cover now
alerts until it is named exactly inunmanagedor declared in the registry.
The snapshot moves to schema 2, which addsunmanaged;plane observe
rewrites it, so nothing carries over.
Changed
- Exempted items now reach the snapshot, so
plane observeand the MCP
inventory tool count them: their totals rise by however many anunmanaged
glob covers. The inventory gainedunmanaged_countso the number explains
itself rather than looking like a jump in what the machine runs.
Fixed
- A glob that also matched a declared entry reported an installed asset as
missing. Exemption is skipped for anything declared, so the two registry
files can no longer contradict each other into a false alert. plane mcpcounted an exempted server as ungoverned, and
plane import observedproposed exempted items now that they reach the
snapshot. Both honour the exemption, except for an always-on item, which
import observedstill proposes because declaring it is the remedy the
report names.- A backup file stopped being debris the moment it was dated.
footprint
skipped.zshrc.bakbut asked about.zshrc.bak-20260727,
.zshrc.bak.pre-qwen8b, and.openclaw.archive-20260722, because the
default patterns matched the bare suffix and nothing after it, while the way
anyone actually names a backup is to append what it was taken for. Those
three spellings are filtered now, each pattern anchored on the separator
that follows the backup word so a real tool calledbakeryorarchivebox
is never silently skipped.
v0.10.4
Fixed
- An adapter that misspells one of the six facts the triage reads (
present,
drifted,always_on,stale,configured,governed_by) no longer scans
clean. The facts map is open by design, soalwaysonwas not an error, it
was simply a fact nobody reads: the service still ran at login and nothing
said so. Adapters now state those six as named arguments toObserved.of,
where a misspelling is a type error at the line that wrote it, and observe
re-checks them as it collects, which is what covers an adapter the engine
never type-checked. A general fact of the wrong type is refused for the same
reason, sincebool("no")is true and apresentof"no"reads as the
opposite of what it says. Everything an adapter records of its own is
untouched,present_atandconfigured_byincluded, and a refusal lands in
the snapshot'sfailedlist, so one bad adapter cannot sink a scan.
v0.10.3
Added
plane init --sectionsprints the documented instance.yaml sections your
instance does not set, ready to append (>> instance.yaml). An instance
created before an adapter existed never heard that the adapter has a
section, because the starter file is written once and then left alone; the
only hint was diffing the shipped example by eye. Re-runningplane init
on an existing instance now names the unadopted sections too, so the
command you already know is what tells you. It writes nothing: the file is
yours, and a config that edits itself is one you no longer know the shape
of.
Fixed
- Every prompt now answers the same way when there is nobody to ask. Nine
sites each caught their own idea of a missing stdin, and none caught the
RuntimeErrorPython raises when the descriptor itself is gone, so a verb
run without stdin ended in a traceback rather than a decline. They share
one helper: an answer comes back from a terminal or a pipe, andNone
comes back when stdin has ended, is detached, or is closed, which each
caller turns into its conservative branch. Piped answers keep working
deliberately, sinceplane applyhas no flag that would replace them.
v0.10.2
Added
- A harness adapter: code plugged into an AI harness, starting with hooks.
Profiles are configuration (harness.profilesin instance.yaml); which
file holds a tool's settings, and how that file's schema names its hooks,
lives in a harness leaf, so the adapter names no tool and supporting
another is dropping a module in. A hook runs code unprompted, so it
observesalways_onand an undeclared one alerts through the pass that
already catches self-installing daemons. Identity is kind, event, and the
script it runs; the command string is never recorded, because a shell line
can carry a token and the script path is the identity anyway.present
means the hook will actually run, so one whose script is gone is wired but
broken, and resolvability is the test rather than the executable bit (a
script passed to a runtime is normally not executable). Each hook records
the harness that runs it and the config dirs that wire it, so several
profiles of the same tool stay distinguishable.
v0.10.1
Changed
- CI checks a release's version bump against its own changelog section, so
the numbering rule is enforced rather than remembered: a BREAKING entry
must move the slot reserved for it (the minor while 0.x, the major from
1.0), and moving that slot without one is refused.scripts/check_version_bump.py
runs the same check locally. Releases through 0.10.0 bumped the minor for
features before the rule was written down; they are grandfathered, and the
check applies from 0.10.1 on.
Fixed
- The version guard reads a declared break, not the word: it looks for the
bolded entry prefix CONTRIBUTING mandates (or the commit-footer spelling)
rather than any occurrence of "BREAKING". A bare substring match meant
release notes that merely described the rule were read as declaring a
break, which refused the very release introducing the guard. - An active entry whose adapter reports
present: Falsenow alerts "expected
present, not observed", the same as observing nothing at all. Only the
retired path consulted that fact, so a booted-out service the adapter had
looked at produced a soft report at most, and withtolerance: autoit was
folded into silence: the one thing the loop exists to catch. The alert is
structural, so tolerance cannot fold it, and the soft signals below it are
skipped because they describe a thing that is not there. A parked entry
stays silent (dormancy is its expected state) and an unconfigured active
secret keeps its own more specific message.
v0.10.0
Added
plane driftgroups each section by the observing adapter, states a shared
message once on the group header, and packs the bare ids into columns sized
per column. A 16-item section reads as four rows instead of sixteen, and
nothing is truncated where it used to be. The grouping comes from the id's
ownadapter/native_idshape, so every adapter, present and future, gets it
without the renderer naming any of them.
Changed
- The footprint conventions the example instance recommends no longer include
macOS's~/Library/Application Support. Measured on a real machine, 37 of
its 79 entries were the system's own and most of the rest were desktop apps
and derived caches of tools a package manager already governs, so the root
produced rows nobody can declare, apply, or remove. docs/footprint.md
records why the alternative, teaching the tool to filter the OS's own
directories back out, was rejected: that filter would key on names its
subject chooses.
Fixed
- The footprint noise defaults skip shell completion caches
(.zcompdump,.zcompdump-<host>-<version>), found by running the
adapter against a real machine. Two rules now bound what ships as a
default: a pattern must match something real on a scanned machine, and no
pattern may match a directory where software wires itself to run at login
(systemd,autostart,LaunchAgents), which a test enforces.
v0.9.0
Added
- A footprint adapter: tools discovered by the config traces they leave.
Roots are configuration (footprint.rootsin instance.yaml: XDG config,
data, and state, home dotfiles viadot_only, per-OS conventions via
os:); the same tool across roots merges into one observation with a
footprint per trace, and a configured root is never itself a tool. A tool
whose name matches another adapter's declared entry is attributed to it
(governed_by, a new general fact the triage honors) instead of listing
as ungoverned, so discovery only asks about tools with no decision on
record. Debris never becomes a question: OS artifacts, shell and editor
state, backup copies, and the cache dir are skipped by name
(ignore:extends the list,ignore_defaults: falsedrops it). All of
it stat-only: nothing is ever opened, so credential-bearing configs
contribute their name and shape, never their contents; a root the scan
cannot list refuses loudly into the failed-scan alert, naming itself,
instead of silently shrinking coverage. Opt-in: no section, no scan.
v0.8.0
Added
- The mcp adapter grows its write side: with
manage: trueundermcp:in
instance.yaml,plane applyproposes removing a retired server's block
from each client config that still wires it, behind the usual per-change
confirmation. The edit is digest-guarded (refused if the file changed since
the preview, or if its formatting does not round-trip byte-identically),
atomic through symlinks with permissions preserved, and preceded by a
0600 backup of the removed block under~/.local/state/planeops/backups/.
Project- and repo-scoped wirings are skipped by name; previews, results,
and the journal never carryenvvalues. Wiring servers in stays manual:
envblocks hold secrets. - Observation records structured wirings per server (client + scope), so the
write side targets exactly what was seen instead of re-deriving it.
Fixed
- A parked entry no longer reports the
driftedfact: a parked RunAtLoad
service that is unloaded deviates from its own definition on purpose, and
apply plans nothing for parked, so the report could only nag forever. - Changes executed under a standing
a(all in domain) render their diffs
before executing, instead of mutating with the diff only journaled after
the fact.aanswers the question for the domain, never the showing.