@inventarch/steward-hook
v3.7.0
Published
[](https://www.npmjs.com/package/@inventarch/steward-hook) [](https://gi
Readme
@inventarch/steward-hook
The edit guard IA registers in Claude Code. For each authored
@hook, it refuses a file edit the hook selects when a@mandategoverns the path and none grants the acting agent Synthesis (an edit no mandate governs is allowed), and it protects the files IA generates and owns.ia host clauderegisters it through@inventarch/distribution; you do not run it by hand.
⚡ Quick start
Declare what to guard in .ia/src/hook.ia. A @hook is an owner edit in source, so write the file by hand; ia author refuses hook records:
#! ia 1.0
@hook guard-records
meaning
says "Guard edits to this workspace's IA records."
answers "Which edits to .ia/src does the edit guard decide?"
hook
event PreToolUse
tools [Write, Edit, MultiEdit]
paths [".ia/src/**"]
message "Edits to .ia/src need a mandate that grants Synthesis."Check the workspace, preview the registration, then apply it:
ia validate
ia host claude
ia host claude --apply --yesThe plan lists hook-authoring-system/binding/hook/guard-records hook group create. The apply adds one PreToolUse group to .claude/settings.local.json that matches Write|Edit|MultiEdit and runs the installed launcher as guard --root <root> --hook <identity>. ia host claude --remove --apply --yes removes every group and restores editing.
To check a decision without Claude Code, install the package in the project and call the same entry from the workspace root:
import { runHook } from '@inventarch/steward-hook';
const root = process.cwd();
const event = {
hook_event_name: 'PreToolUse',
tool_name: 'Edit',
cwd: root,
tool_input: { file_path: `${root}/.ia/src/decision.ia` },
};
const args = ['--root', root, '--hook', 'hook-authoring-system/binding/hook/guard-records'];
const decision = runHook(args, JSON.stringify(event));
console.log(decision.hookSpecificOutput?.permissionDecisionReason ?? 'allowed');In the root quick start's my-project workspace, whose starter mandate grants Synthesis, this prints allowed. With that mandate's moves cut to [Observation, Verification], it prints a refusal that begins IA-HOOK-NOT-MANDATED: Edits to .ia/src need a mandate that grants Synthesis. and names ia position --seat agent-system/policy/mandate/my-project-mandate --shape governance as the next command.
🧭 What it does
| Part | Behavior |
| --- | --- |
| Selection | A registration for one @hook decides an edit only when the hook's tools name the tool and its paths select the file. Any other edit is allowed; nothing is decided just because it is under .ia/src. |
| Mandate decision | A selected edit is governed by the mandates whose authority.covers claims the path or that are scoped to its @workspace. With none it is allowed. Otherwise the acting agent needs Synthesis, else IA-HOOK-NOT-MANDATED with the hook's message. |
| Acting agent | A subagent acts as the composition.agent of the one authored @agent-profile its agent_type names; the main session acts as the workspace's steward, else its single participant. An unknown subagent or an ambiguous default actor is IA-HOOK-IDENTITY-UNAVAILABLE. |
| Projection protection | Runs first, whatever the hook selects. Refuses edits to .ia/distributions.lock.json, .ia/distributions/, the packet and generated files a projection state owns (with no state, the packet's files, CLAUDE.md and both ia-authoring skills) and, while a hook registration is owned, both Claude settings files and the launcher they name (IA-HOOK-PROJECTION-MANAGED). |
| Path safety | Refuses a selected path reached through a link, junction or short name, any dangling link or Windows alternate data stream, and in --hook mode any file under the root with more than one hard link (IA-HOOK-PATH-UNSAFE). |
| Failing closed | When the hook is not an admitted authored @hook or the records cannot be read, refuses edits under the selection last registered for it, or every edit under the root when none is recorded (IA-HOOK-DECLARATION-UNAVAILABLE, IA-HOOK-MANDATE-UNAVAILABLE). While an authored @mandate fails admission, refuses every edit the hook selects. Each reason names commands that work from a shell in that state. |
| Legacy 1.x guard | Without --hook, applies managed-state protection and the retired steward rule for .ia/src/systems/<name>/, for a 1.x registration that survives. A 1.x steward file under .claude/agents/ stays protected until removed. ia host claude --apply retires the 1.x registration and removes the marked steward files it owns. |
| Exports | runHook, evaluateMandateHook, evaluateHook, printable, deny and streamed; printable strips terminal escapes and control characters from every refusal reason. ./context holds the lifecycle context hook; ia host claude --context refuses in this release. |
⚖️ What it does not do
- Not authentication or a sandbox. The guard is a cooperative restriction. It grants no host permission and no filesystem isolation, and anyone who can edit
.ia/srccan change the mandates it reads. - Only file-tool edits. It sees the
Write,EditandMultiEditcalls Claude Code sends it. A shell command or another program that writes a file is not checked. - Only Synthesis. It checks the move, not a mandate's excluded words, and reads records, never the bytes being written. Record authoring with
ia previewandia authorenforces excluded words. - Only declared hooks. A workspace with no authored
@hookgets no guard group. A@hookrecord is a declaration;ia doctorreports whether its group is registered. - No Codex hooks.
ia host codexreports each@hookas not taken. WheredisableAllHooksturns hooks off,ia hostwrites no new group. - Fails open on a missing Node. On Windows, Claude Code runs the guard's direct form, which fails open when its Node cannot start.
📚 Learn more
- Package contract · language summary
- Root README · architecture reference · CLI guide
@inventarch/distribution, which registers, owns and recovers the hook groups@inventarch/runtime, which decides a selected edit against the governing mandates- hook-authoring-system, owner of
@hook, and agent-system, owner of@agentand@mandate
