# Session hooks — the harness layer (SHP)

> **Conformance:** L2 — binds every federation repo that wires a session hook, and the one implementation those repos invoke. The rules bind the **hook layer's behaviour**, which is in force the moment they are enacted; per-repo *enablement* is deliberately **staged** under **SHP-11** and is not a conformance breach while pending.
> **Audience:** whoever builds or changes a session hook in this federation; every repo deciding whether to wire one; the founder ruling the `observe` → `enforce` flip.
> **Propagation (DOC-18):** changing a rule here ripples — walk DOCUMENTATION.md §10.4 and update its dependents in the same commit: README *Index of MUSTs* + `rules.json` (regenerated) + FEDERATION.md §4 family table + SCOPE.md count, and — cross-repo, same change-wave, **supervisor-applied per [AG-13](AGENTS.md#AG-13)** — `mgf-fed`'s [`docs/howto/session-hooks.md`](https://codeberg.org/magogi-admin/mgf-fed/src/branch/main/docs/howto/session-hooks.md), the mechanism's own boundary doc. **That link is staged**: the file lives on `mgf-fed`'s `feat/session-hooks` branch and the URL above resolves **on the S3a merge**, which lands next in the ruled order (this family first, the kit second). Also staged there, in the S3a fold and **not duplicated here**: the `session-hooks` / `hooks` / `shp` aliases for `mgf-fed standards SESSION_HOOKS`, with a `_PENDING_CORPUS` entry so the alias degrades to the ordinary unknown-topic error until a wheel carrying this doc ships. That fold stages the **`REVIEW` aliases in the same change** (its court's F10), which is why both are one cross-repo edit and neither is this document's to make.

> **Scope.** *What a hook that runs at the edge of an agent session is allowed to do, and
> what it must never be able to do.* A session hook is the only channel that reaches
> **every** agent session mechanically — regardless of what any agent remembers, and
> regardless of which model is driving. That reach is exactly why it is dangerous: the
> same channel that can re-teach a rule to every session can also stop every session.
> This family binds both halves.
>
> It binds the **hook layer**. It does not adjudicate correctness, it does not gate CI,
> and it never decides whether a change was *right* — only whether the standing
> obligation was **communicated** and whether the communication **was recorded**.

**THE LAYERING RULE — the governing principle.**

> **CI enforces truth. Documents communicate intent. Hooks guarantee the communication
> happens.**

Anything a reader can mechanically re-derive belongs to **CI** (a gate, a golden, a
validator) — this federation already has four of them plus a supervised test tier.
Anything that lives only in an author's head — intent, ordering, rationale, a deferred
obligation — belongs in a **document**. The **hooks** exist to make *"nobody read the
document"* impossible. Read every rule below through that split: a hook never
adjudicates truth, and CI never carries intent.

The corollary is the whole of **SHP-13**: a layer whose only job is to guarantee
communication has **no business stopping work**. It earns its place by being unable to.

---

## Rules box (read this first)

| ID | Rule |
|---|---|
| <a id="SHP-01"></a>**SHP-01** | **A session hook MUST re-teach the standing session obligations at every session start — mechanically, from one source, within a stated line budget.** Project instructions load every session and the system prompt names the model, so session-start output is the one channel that reaches *every* agent session regardless of what any agent remembers. This rule **mechanises the re-teach that [SEAT-02](SEATS.md#SEAT-02) requires, through the channel class [SEAT-08](SEATS.md#SEAT-08)'s dispatch block already uses** — it does not replace that block and does not become SEAT-02's sole mechanism; SEAT-08 names the dispatch source, and a hook is a second carrier on the same channel. The re-teach prints an **instruction, never a body**: what to do, and the exact command that does it. It MUST NOT grow past a ceiling the implementation states as a constant, because session-start output competes for the very context it is trying to protect. A session that has already satisfied the obligation gets one quiet line — a re-teach that never shuts up is noise, and noise is what a reader learns to scroll past. |
| <a id="SHP-05"></a>**SHP-05** | **Attribution before blame: a hook MUST NOT demand an action for a change it cannot attribute to the session it is running in.** Attribution has exactly two strengths and they MUST be recorded distinctly. A path carried by a structured file tool (`Edit` / `Write` / `MultiEdit` / `NotebookEdit`) is **definitive** — this session wrote that file. A shell command is **evidence a human reads, never an attribution**: it can write anywhere and proves nothing directly, so any finding resting on one MUST quote the implicating command. Anything weaker does **not** block and is still **recorded** — *not blaming is not the same as not saying.* The repo a written path belongs to MUST be **derived** (walk the path to its owning git root), never guessed from a parent-directory glob. Without this rule a hook compares a tree to a baseline and blames this session for every difference — **including a concurrent session's legitimate work** — and blaming an innocent session, then ordering it to document work it never did, is how a discipline gets ripped out. |
| <a id="SHP-06"></a>**SHP-06** | **Nothing blocks without a sanctioned exit: a finding MUST NOT be able to block a session unless its discharge exists, is one command, and is named in the refusal.** The gate blocks **silence**, never **judgment**. A finding whose sanctioned exit this corpus has not ruled MUST stay **advisory in every mode** — recorded, reported, never blocking — and the corpus decision that would create its exit MUST be named as the prerequisite for making it enforceable. Second, a block MUST **self-release**: a session told why it was stopped and stopped again on the retry is trapped, and a gate that can trap a session is a gate that gets ripped out. One refusal, then the session goes through. A gate with no sanctioned exit is a gate that gets bypassed, and a bypassed gate protects nothing while looking like it does. |
| <a id="SHP-07"></a>**SHP-07** | **The protocol MUST be audible in the mode where nobody is watching.** A stop-time hook's output is **discarded by the harness when it does not block** — so a gate that speaks only when blocking is inaudible in exactly the mode built for *"this runs where nobody is watching."* Verdicts MUST therefore be **persisted and replayed at the next session start — once, not forever.** A finding the session went on to resolve MUST NOT leave a stale verdict behind: the persisted verdict is rewritten each run, never only appended to, because a false alarm replayed into the next session is how a reader learns to ignore the channel. A no-op is loud: a run that was switched off, or that evaluated nothing, is **announced**, never assumed healthy. |
| <a id="SHP-08"></a>**SHP-08** | **The kill-switch MUST exist at three scopes, MUST be honoured before any other logic, and MUST be announced.** The scopes: **one session** (an environment variable), **the whole fleet** (a sentinel beside the installed implementation), **one repo** (a sentinel inside the repo). First match wins; the check requires no JSON editing and no git operation; and it **never raises** — a broken switch check must not take the hooks down with it. While disabled, nothing is recorded and **nothing can block, ever**. The disabled state MUST be **announced at session start**, because an OFF protocol that looks healthy is precisely the invisible-failure class this family exists to end. The repo- and fleet-scope sentinels MUST be **git-ignored**: a sweeping `git add` that commits a kill-switch switches the protocol off for **every clone**, permanently and invisibly — the one failure a kill-switch may never have. |
| <a id="SHP-09"></a>**SHP-09** | **One implementation, thin shims: the hook logic MUST live exactly once, and an adopting repo MUST install a shim that invokes it — never a copy of it.** A forked hook is a determinism break by construction: two repos running two divergent copies of one protocol will disagree, and the disagreement is silent. Adoption MUST be **additive and drop-in** — no repo re-architects to take it — and MUST add no runtime dependency the adopting repo does not already have. The installer MUST **merge, never clobber**, the configuration it writes into, and MUST **refuse, writing nothing**, a configuration it cannot parse: silently rewriting a file we could not parse is how a tool destroys hand-tuned settings. Uninstall MUST remove exactly the entry the installer added and leave everything else intact. |
| <a id="SHP-10"></a>**SHP-10** | **Every hook invocation MUST leave one structured record over a CLOSED vocabulary, and a failure to record MUST NOT change behaviour.** The vocabulary is closed and growth is a smell — a log that accepts any event name is a log nobody can query and nobody trusts — so the **refusal path is as much a feature as the append path**, a refused record writes nothing, and widening the set is a corpus decision, not a code decision. Two invariants hold under stress: **a torn write never breaks the reader** (a partial trailing line is skipped, not fatal — an audit trail that becomes unreadable at the first bad byte is not an audit trail), and **a persistence failure never changes a hook's behaviour** (observability must not become the failure). Finally, **no CI job may read, validate, or fail on session-hook state**: the session layer is not the truth layer, and a CI job that gates on a session artifact makes every clone's local state a build input. |
| <a id="SHP-11"></a>**SHP-11** | **Enablement MUST land in `observe`, MUST NOT reach `enforce` except by a founder act, and no step may create a state that is only safe if a later step also happens.** `observe` evaluates everything, blocks nothing, and records what it *would* have done; the flip to `enforce` is taken **per repo**, only after that repo's observe record reads as expected, and it is the founder's alone ([SEAT-01](SEATS.md#SEAT-01) — agents never self-enact). The staging MUST be **structural, not documentary**: two independent keys, one arming the enforcing half at all and one deciding whether an armed half blocks, so the step that installs the hook cannot by itself produce a session that can be blocked. **No unrecognised value for either key may yield a state that can BLOCK**, and an unrecognised **mode** MUST read as `observe` — a typo in a mode must never be read as *"enforce"*. (The obligation is on the *blocking* outcome, not on each key in isolation: a permissive arming key is safe precisely because the mode key still fails to the weaker state, and the two keys are only a guarantee together.) Rollback MUST be one command or one commit. |
| <a id="SHP-12"></a>**SHP-12** | **Determinism and honest failure: identical inputs MUST produce identical output and identical decisions, and every refusal MUST name its locus.** The decision MUST be separable from the process that carries it — a pure function from recorded state to findings, testable without a harness, a subprocess, or an exit code in the way — because a verdict observable only by running a hook cannot be regression-pinned, and an unpinnable gate is one refactor from guarding nothing ([CRT-08](REVIEW.md#CRT-08)). **Every refusal on an operator-invoked surface** — a CLI verb a human or an agent typed, such as emitting an event or installing the shim — names what was refused and why, and **exits non-zero**. On a **harness-invoked** path the refusal is still named, and the exit code is **SHP-13's**, not this rule's: the two are not in tension because they govern different callers, and §6 states the split. A gate whose verdict depends on **when** it ran cannot be trusted to be the reason a session stopped. |
| <a id="SHP-13"></a>**SHP-13** | **Fail-open totality: a session hook MUST NOT be able to stop a session by malfunction — only by a ruled, dischargeable finding.** Every path returns success, *including the paths that exist only because something broke*: a missing state directory, a full disk, an unreadable log, a malformed payload, an exception raised inside the hook's own plumbing. The **single** sanctioned non-zero exit is a blocking finding under a gate that is both armed and enforcing, and that finding is bound by **SHP-06**. The live path is wrapped whole and the wrapper **prints** the failure rather than swallowing it — a silent hook is the failure mode, not the safety. **This rule governs the harness-invoked path only**: an operator-invoked CLI verb that refuses a bad argument exits non-zero under **SHP-12**, and must, because a human or an agent is reading that exit code — fail-open totality is what a hook owes the *harness*, never what a command owes its *caller*. A session that cannot start because observability broke is a worse defect than anything a hook measures: the first time it happens, the hooks come out of every repo in the fleet and never go back in. |

*(Family enacted — Sooth→mgf adoption wave S3, corpus **v3.7.0** EXTEND under [`LOCK.md`](../../LOCK.md) §2, founder-enacted; date recorded at merge.)*

---

## 1. Why this is a family and not only a howto

The mechanism has a good document. `mgf-fed`'s `docs/howto/session-hooks.md` explains
the boundary better than a rules box can, and on any conflict about **mechanism** —
environment-variable names, file paths, wire formats, the exact event set — **that
document wins and this one takes a correction note**.

But a discipline with no rule IDs cannot be cited in a review, cannot appear in the
*Index of MUSTs*, and cannot be checked by a gate, so its adoption stays invisible and
drifts. The source family proved the point against itself: its `shp/SHP.md` spec had
existed since **2026-07-09** and is excellent, and it had nonetheless propagated to
**one repo in ten**. That is the gap this family closes, and it is the whole of what it
closes — **enacting it wires no hooks and flips no repo to enforce.**

The second reason is sharper, and it is why this family binds the *restrictions* more
than the *capabilities*. A session hook is the highest-leverage surface in the
federation and the highest-blast-radius one: it runs before every session, in every
repo, on every model. Nine of the ten rules below exist to bound it — what it may not
block (SHP-06, SHP-13), what it may not invent (SHP-01's instruction-not-body rule and
SHP-05's attribution floor), what it may not silently become (SHP-08, SHP-11), and what
it may not make anyone else's problem (SHP-09, SHP-10). Only **SHP-01** grants
anything.

## 2. The mechanism this family governs — and its staged posture

The obligations above are written against a real implementation, not an aspiration:
**`mgf-fed hooks`**, the session-hook kit ported in adoption wave **S3a**. At the time
this family was written that kit sits on `mgf-fed`'s `feat/session-hooks` branch,
**unmerged and in court** — so every citation below is to *the channel the kit will
provide once it lands*, the same staged posture [SEAT-08](SEATS.md#SEAT-08) uses for the
dispatch block.

Before it, **no repo in the 44-member federation registry had a session hook at all**
(`SOOTH_ADOPTION.md` §1, measured 2026-07-30). The scoping matters: the **`mgf` program
repo — which is not a registry member** — does carry a `SessionStart` hook today, one
that injects its own `.claude/memory/` digest into context. It is the adjacent instance
worth knowing about, because it shares SHP-01's channel and therefore shares its line
budget: two things writing into one session-start window compete, and the ceiling SHP-01
requires is measured against the window, not against one writer's share of it. It is not
governed by this family (which binds registry members), and it is not evidence against
the baseline. The kit's shape, as it stands on that branch:

| This family's rule | The kit's answer |
|---|---|
| SHP-01 | `mgf-fed hooks session-start` — one merged line in `.claude/settings.json`, re-teaching SEAT-02 with the exact declaration command, under a stated `MAX_OUTPUT_LINES` ceiling; a session that already declared gets one quiet line |
| SHP-05 | A per-session write-set recording `kind=exact` (structured file tools) vs `kind=command` (shell, quoted as evidence); the owning repo of a written path derived by walking to its git root |
| SHP-06 | `seat_undeclared` may block — its discharge is one `hooks emit` command. `cross_repo_write` **never** blocks, in either mode |
| SHP-07 | Verdicts persisted to a pending file and replayed by the next session-start; the file is rewritten each run, so a resolved finding leaves nothing behind |
| SHP-08 | `MGF_HOOKS_DISABLED` (session) · a `DISABLED` file beside the installed package (fleet) · `.claude/mgf-hooks-disabled` (repo); both sentinels git-ignored, announced at session start, checked before any other logic |
| SHP-09 | The kit is an **installed package**, so adoption is one line of configuration and nothing is vendored; the installer merges and refuses unparseable JSON |
| SHP-10 | An append-only `.claude/state/agent-events.jsonl` over six event types; refusal is loud and writes nothing; no CI job reads it |
| SHP-11 | Two keys — `MGF_HOOKS_GATE` arms, `MGF_HOOKS_MODE` decides — with an unrecognised **mode** reading as `observe`, so no unrecognised value on either key can produce a state that blocks. The arming key is permissive today (any non-empty value but `0` arms it); a strict `'1'`-only parse is folded into the S3a kit in parallel, which closes SHP-11's letter as well as its outcome |
| SHP-12 | The decision is a pure `evaluate(root, session)` returning findings, tested without a process |
| SHP-13 | Every entry point returns `0`; the gate's own plumbing failure is caught and fails open |

**The `cross_repo_write` ruling is the worked example of SHP-06**, and it is worth
reading as one. The source family's gate *blocks* a session that changed a sibling repo
and left no handoff behind. mgf **has ruled no cross-repo handoff channel** — `FEEDBACK.md`
papers are the nearest thing and their grammar is not gate-shaped — so there is no
one-command discharge for that finding. Blocking on it would produce a gate with no exit
**the moment it fired**, which is the exact failure SHP-06 forbids. The kit therefore
records the finding, reports it, and never blocks on it in either mode. **Ruling that
channel is the named prerequisite for making it enforceable**, and it is a corpus
decision, not a code change (see §3).

That is the whole discipline in one case: the rule did not bend to fit the mechanism,
and the mechanism did not quietly ship an unenforceable gate. The obligation stayed
whole and the *enforcement* stayed staged.

## 3. What this family does NOT adopt — the handoff channel

The source family is a **cross-project handoff protocol**; hooks are how it guarantees
the handoff happens. mgf ported the hooks and **did not port the handoff**. Three of the
source's twelve rules are therefore **not adopted**, named here rather than quietly
omitted:

| Source rule | Its obligation | Disposition |
|---|---|---|
| **SHP-03** | A session that modified a sibling repo does not end without recording a handoff in that sibling | **NOT ADOPTED — no channel exists.** There is no mgf inbox, no acknowledgement ledger, and no handoff schema; an obligation to record into a channel that does not exist is unmeetable, and gating on it would be a gate with no exit (**SHP-06**) |
| **SHP-04** | Handoff well-formedness: pinned schema, sidecar, evidence floor, correct addressing | **NOT ADOPTED — nothing to be well-formed.** The rule is meaningful only once SHP-03's artifact exists |
| **SHP-02** | The sibling set is derived from the federation registry, never from a parent-directory glob | **DISSOLVED BY THE PORT, not declined.** The obligation — *derive, never guess* — survives; the rule does not, because the port made the failure structurally impossible. Walking a written path up to its **owning** git repo is exact rather than approximate: it needs no registry and no root variable, and it cannot produce the "zero siblings resolved, so the gate silently protects nothing" failure the source had to add a tripwire for. The residue is carried in **SHP-05**'s derived-not-guessed clause |

**The numbers 02, 03 and 04 are left vacant, deliberately.** They are not free for
reuse. Every remaining rule in this family keeps the **source family's own number**, and
that alignment is load-bearing in a way a renumbering would destroy — see the
Provenance note. Re-issuing `SHP-03` for some unrelated future obligation would make
every citation dated before that day ambiguous, which is precisely the hazard the
[REVIEW.md](REVIEW.md) family had to document for `CRT-*` after it *did* renumber.

**Reactivation trigger.** SHP-03 and SHP-04 come back — at their own numbers, in this
document, as a normal EXTEND — **if and when this corpus rules a cross-repo handoff
channel**: a named artifact with a schema, a receiver-side home, and an acknowledgement
record. That single ruling is the prerequisite for three things at once, and they should
land together: the two rules above, and the flip of the kit's `cross_repo_write` finding
from advisory to blocking. Until it is ruled, the finding stays advisory (**SHP-06**),
the two rule numbers stay vacant, and **the absence is recorded rather than papered
over** — which is the same honesty SHP-07 requires of the mechanism.

**SHP-13 is mgf's own addition** and has no source number, which is why it sits at the
end of the range rather than in one of the vacancies. The source family's rules require
honest failure (its SHP-12) and forbid observability from changing behaviour (its
SHP-10), but neither states the totality: *no path, for any reason, may return non-zero
except a ruled dischargeable finding.* The mgf port implements exactly that, and an
obligation that strong deserves to be citable on its own.

## 4. Adoption posture (why nothing here fails today)

These rules bind the **hook layer's behaviour** and the standing obligations of a repo
that has one — the single unforked implementation, the closed vocabulary, CI's
indifference to session state, the kill-switch, the fail-open floor.

**Per-repo enablement is deliberately staged by SHP-11 and is not a conformance breach
while pending.** A repo with no session hook wired is conformant; so is a repo that has
one wired in `observe`. This is the federation's standing warn-then-enforce order — the
same staging the **PR-\*** DRAFT marker uses, the same *GATED* class
[`LOCK.md`](../../LOCK.md) §1 defines, and the same shape
[SEAT-08](SEATS.md#SEAT-08)'s dispatch block is already staged behind. It is what keeps
this family gateable on day one instead of declaring 44 repos non-conformant for a
mechanism that has not merged yet.

Two things stand between this family and any repo being held to SHP-01's channel, and
both are **cross-repo and supervisor-applied per [AG-13](AGENTS.md#AG-13)** — mgf-standard
can define the obligation and stage it; it cannot ship the mechanism:

1. **The kit merges in `mgf-fed`** (`feat/session-hooks`, in court at the time of
   writing) and reaches a published wheel a repo can resolve.
2. **A repo runs `mgf-fed hooks install`** — a per-repo act, reversible by one command
   or one commit (SHP-11).

The flip from `observe` to `enforce` is a **third**, separate founder act, taken per
repo after reading that repo's observe record. The source family's evidence for this
order is not an argument against it: its 2026-07-23/24 observe record **caught a real
defect class before any session was ever blocked.**

## 5. Self-review checklist

Before shipping a change to any session hook in this federation:

- [ ] **SHP-13** — every path returns success, including the ones that exist only
      because something broke. I can name the *only* non-zero exit and it is a ruled
      finding.
- [ ] **SHP-06** — every blocking finding has a one-command discharge, named in its own
      refusal text. Any finding without one is advisory in **both** modes.
- [ ] **SHP-08** — the kill-switch is checked before any other logic, at all three
      scopes, and its sentinels are git-ignored.
- [ ] **SHP-01** — the session-start output is under its stated ceiling, prints an
      instruction rather than a body, and is quiet when the obligation is already met.
- [ ] **SHP-05** — nothing is demanded of a session for a change I cannot attribute to
      it; command evidence is quoted, never treated as proof.
- [ ] **SHP-11** — the change cannot, by itself, produce a session that can be blocked.
      Two keys still stand between install and enforcement.
- [ ] **SHP-10** — the event vocabulary did not grow; a persistence failure still
      changes nothing; no CI job reads any of this.
- [ ] **SHP-12** — the decision is still a pure function I can test without a process,
      and every refusal names its locus.
- [ ] **SHP-09** — nothing was vendored, forked, or clobbered.
- [ ] **SHP-07** — a verdict written in observe mode still reaches a human, and a
      resolved finding leaves nothing stale behind.

## 6. Mechanical enforcement

| Rule | Enforcement |
|---|---|
| SHP-01 | **Staged** on the kit — becomes mechanical the day `mgf-fed hooks install` runs in a repo, and the ceiling is a constant a unit test can pin. Until then, review-enforced against the kit's own suite. |
| SHP-05 | **Staged.** The write-set recorder ships disabled (SHP-11's first key); its exact-vs-command distinction is unit-tested in the kit. Review-enforced here. |
| SHP-06 | Review-enforced, with a mechanical tripwire: any finding marked blocking whose text does not contain a command is the reviewer prompt. The `cross_repo_write` ruling (§2) is the standing worked example. |
| SHP-07 | **Staged.** Testable in the kit (a verdict written in observe mode must appear in the next session-start output; a resolved finding must clear). Not observable from this repo. |
| SHP-08 | Partly mechanical **today**: the two sentinel paths must appear in an adopting repo's `.gitignore`, which is a one-line `grep`. The before-any-other-logic ordering is unit-tested in the kit. |
| SHP-09 | Mechanically checkable: a `grep` for vendored hook scripts in any repo's tree returns nothing. Merge-never-clobber and refuse-unparseable are unit-tested in the kit. |
| SHP-10 | The closed vocabulary is a frozen set in the implementation, unit-tested. The CI-isolation half is checkable **today**, in every repo: no pipeline step may reference the hook state directory. |
| SHP-11 | Review-enforced as governance (the flip is a founder act, never a checkable artifact). The two-key structure is unit-tested in the kit, as is the mode key's weaker-on-typo reading. The **outcome** obligation — no unrecognised value can produce a blocking state — holds today through the mode key; the arming key strict-parses `'1'` only (landed with the S3a fold, mgf-fed `6404303`), so each key now fails weaker alone rather than by each key alone. |
| SHP-12 | **Mechanized where it counts** — a pure decision function is a testable one, and the kit's suite is the proof. The operator-surface exit-code half is directly testable per CLI verb (a refused `emit` exits non-zero). Determinism itself is review-enforced. |
| SHP-13 | **Mechanized in the kit**: every *harness-invoked* entry point has a test asserting exit `0` under an induced failure. The boundary is the thing to check on review — a new surface must be classified harness or operator before its exit code means anything, because SHP-12 and SHP-13 give opposite answers and only the caller decides which applies. From this repo, review-enforced. |

---

## Provenance

**Adapted with attribution from the Sooth family's SHP family**
([`sooth-standard`](https://codeberg.org/magogi-admin/sooth-standard)
`docs/standards/HANDOFF.md`, `SHP-01..12`, whose own authority is `shp/SHP.md` v0.1,
founder-decided 2026-07-09, plus that family's two founder-directed review rounds —
attribution, 2026-07-13, and operability, 2026-07-15). The layering rule, the fail-open
discipline, the three-scope kill-switch, the observe→enforce staging with its
"no step may create a state that is only safe if a later step also happens" invariant,
attribution-before-blame, the sanctioned-exit rule, the closed event vocabulary, and the
audibility rule all travelled from there.

**MGF does not depend on Sooth.** Nothing here is an import edge, a submodule, a package
dependency, or a runtime reference (the Sooth family's own **XF-03**): the practices
travelled, the code did not, and each family evolves on its own side. The two families
are independently versioned and may diverge; **mgf's text is authoritative here.** The
shared-philosophy clause of [`LOCK.md`](../../LOCK.md) §5 applies — a semantic change to
this family's fail-open floor or its kill-switch contract should trigger a cross-family
feedback note, as it does for the principles trio.

**The rule IDs DO map 1:1 here — deliberately, and unlike `CRT-*`.** The court family
renumbered (twelve rules against sixteen) and had to document three worked equivalences
plus a date-based resolution rule so that older citations stay readable. This family
takes the opposite decision, for a mechanical reason: **the mgf implementation was
written before this family existed and already cites the source numbers.** The kit's
`_gate.py` emits a finding whose `rule` field is the literal string `SHP-05`; its
docstrings cite SHP-06, SHP-07 and SHP-11 by number; `_state.py` and `_session_start.py`
cite SHP-08; `_install.py` cites SHP-09 and SHP-11. Renumbering would have falsified
every one of those the day this family enacted, across a repo boundary this one may not
edit ([AG-13](AGENTS.md#AG-13)). Keeping the numbers makes them resolve correctly against
**this** document the moment both land. The cost is the three vacancies at 02 / 03 / 04,
documented in §3 with their reactivation trigger, and one number this family adds beyond
the source range (**SHP-13**).

**Ported after the mechanism was built, not before.** The obligation layer follows the
implementation here rather than leading it — the S3a port
(`mgf-fed` `feat/session-hooks`: the `mgf.fed.sessionhooks` package, the `mgf-fed hooks`
CLI, and `docs/howto/session-hooks.md`) was written first, reviewed against the source
family, and is in court as this family is written. Three of its divergences from the
source are recorded there with their reasons, and each shaped a rule above: **one
configuration line instead of four copied shell scripts** (the kit is an installed
package, so SHP-09's "thin shim" collapses to a single command); **owning-repo
resolution instead of a registry glob** (which is what dissolved the source's SHP-02);
and **`session_start` added, `preflight` dropped** (an absence is only visible against a
record that the session existed at all, and an event type nobody emits is dead
vocabulary — the closed-vocabulary discipline of SHP-10 read from both sides).

A **fourth** divergence belongs with them, because it changes an obligation rather than
a mechanism: the source's attribution rule has a **fail-closed command tier** — a
sibling merely implicated by a command this session ran is refused, with the implicating
command quoted. This port **deliberately demotes that tier to evidence-only**: with no
handoff gate to discharge, a fail-closed command tier would block on a finding with no
exit, which is what SHP-06 forbids and what the `cross_repo_write` ruling settles in §2.
SHP-05 therefore says *quoted evidence, never proof* where the source says *refused*.
The tier **reactivates with SHP-03/04**, on the same single ruling — §3's trigger.

**What this family does not claim.** Every rule above is in force as an *obligation* on
enactment; **none of them is running anywhere yet.** No **registry-member** repo has a
session hook wired at the time of this amendment (the non-member `mgf` program repo's
own hook is noted in §2 and is outside this family's scope), the kit that would provide
one is unmerged, and its enforcing half ships switched off behind two keys. That is SHP-11 working as designed,
and saying so plainly is the same honesty [CRT-05](REVIEW.md#CRT-05) requires of a court
round: the gap is recorded, not guessed past.

On any conflict about **mechanism** — variable names, paths, wire formats, the event set
— `mgf-fed`'s `docs/howto/session-hooks.md` at HEAD wins and this document takes a
correction note. On any conflict about **obligation**, these rules are the standard.
