policies/<agent_id>.json inside the config directory.
Both the filename and the keys inside are ids, not names. A name can be
reused — remove an agent or a server, add another with the same name, and it is
a different principal. Keying on the immutable id means a grant cannot survive
the thing it was written for and reattach to whatever takes the name next. Read
grants back in name form with holm show agent <name>; holm show server <name>
prints a server’s id.
Example
Schema
Top level
ToolPermission
ArgumentPolicy
Match types
Glob
Shell-style wildcards.
* and ? match within one level, ** matches across levels. A pattern without wildcards is an exact match. A value containing a .. segment never matches unless the pattern contains .. itself, so a path pattern cannot be walked out of.Regex
Full regular expression, matched from the start of the value. Use
re: prefix in CLI.Lifecycle
A grant links two ids, so it lives and dies with both ends:- Removing an agent deletes its policy file.
- Removing a server revokes every grant on it;
holm remove servernames the agents it affected. holm modify serverchanges a server in place and keeps its id, so grants survive the edit.holm rotateissues a new access key but keeps the agent’s id, so grants survive rotation.
Upgrading from an older Steerholm
Policies written before this format were keyed by name —policies/<agent name>.json, with server names as the permission keys. The installer migrates
them: it stops the daemon, replaces the binary, brings the state up to the
current schema, and starts the daemon again.
The state directory records the schema it is in, at migrations/version.
Steerholm refuses to run against a schema it does not understand rather than
risk misreading it — an unmigrated policy would read as the agent having no
grants at all.
The state as it was before the migration is kept inside the config directory as
.pre-migration. It holds a copy of config.json, including any --env
values, which is why it lives there rather than beside the config directory: it
inherits the same owner-only permissions and moves with your state. There is
only ever one, and you do not have to clean it up — the next migration replaces
it rather than adding to it.
The migration also tidies what can no longer apply. A policy file for an agent
that is not in config.json any more — or whose file and contents name different
agents — is moved to policies/unmigrated/ rather than guessed at, and grants on
servers that no longer exist are dropped; both are logged.
It is there so you can see what a migration did. If grants look wrong
afterwards, comparing .pre-migration/policies/ against policies/ shows
exactly what changed, and that comparison is the most useful thing to attach to
a bug report.