← Maya R.'s track Lesson 09 / 12 · Maya R.

C2 Governance — Hooks Enforce What The Checklist Only Advised

AIHC.2.C2Bloom's: AnalyzePBL
● Exhibit 9 of 12 — Maya R.'s track

Standard: AIHC.2.C2 · Bloom's: Analyze · Structure: PBL.

Notice the Say-See-Do cycles running on Maya R.'s actual work — not a canned exercise — and that every capability claim is cited to the frozen doc-set. The exit ticket climbs Bloom's to Analyze, and the lesson closes by writing to the ledger.

Learning objective

(Bloom's: Analyze) Analyze every rule your team asks payments-service contributors to follow — the six checklist items, the L7 lane flags, the L8 provenance convention — to determine which governance layer each belongs to (advisory CLAUDE.md · permissioned settings · enforced hooks), and assemble the one-page, team-facing governance proposal that says so, with each placement defended by the mechanism that makes it hold — AIHC.2.C2 (Governance).

Work at hand: you now govern yourself well — lanes (L7), a convention (L8), one hook draft (L6). But your team still runs on the paste-the-checklist ritual you diagnosed in lesson 1, and three of your artifacts are flagged "belongs at project scope" with no vehicle to get there. Today you build the vehicle: the proposal you'd actually put in front of your team, where every rule sits in the weakest layer that genuinely holds it (X4's question: shaped, or guaranteed?).

SSD cycle 1 — three layers, one sorting question: what happens when it's ignored

  • SAY: Your repo now has three places a rule can live, and they differ in exactly one governance-relevant way — what happens when the rule is not followed. CLAUDE.md: §4, "context, not enforced config," "no compliance guarantee" — ignoring it produces nothing. permissions: §9 — a deny is "always blocked"; ignoring it is impossible for the blocked action, but it can only speak in tool-and-pattern grammar. hooks: §6, verbatim — "deterministic control over Claude Code's behavior, ensuring certain actions always happen rather than relying on the LLM to choose to run them" — and unlike a deny rule, a hook can run logic at a lifecycle moment. The sorting question for every team rule is the failure question, not the importance question [instructor synthesis — the layer taxonomy assembles §4/§6/§9's frozen claims].
  • SEE: Three of your real rules, one per layer, sorted by the failure question: "Prefer small, testable increments" → ignored: mild waste → CLAUDE.md (advise) "Never read .env / never edit migrations" → ignored: incident → permissions (deny) "Contract test runs after payments edits" → skipped: silent decay → hook (guarantee) The middle row can't be a hook-first choice (a deny rule already says it, cheaper); the last row can't be CLAUDE.md (your L6 failure-log entry 1 is the evidence it gets skipped when merely advised). Placement is an analysis of mechanism × failure cost, not a ranking of how much you care. (The foul line is painted; the take-a-strike custom is coached. A team that paints its customs onto the field has a silly field — and one that only coaches its foul line has arguments instead of calls.)
  • DO: Sort all of it — the six review-checklist.md items, your L7 project-scope flag list, and the L8 convention's five lines — into the three layers. For each: the layer, the one-line failure-question answer that justifies it, and any rule that needs two layers (an advisory half and an enforced floor) split explicitly into its halves.

SSD cycle 2 — the enforcement floor: PreToolUse can say no

  • SAY: L6's hook reacted (PostToolUse — after the fact, §6: "can't undo, but can react"). Governance also needs the blocking shape: §6 — PreToolUse fires "before a tool call — can block it," and a hook communicates by exit code or JSON: "Exit 2 = block the action, with stderr fed back to Claude as a reason it can act on," or exit 0 with "permissionDecision": "deny" and a permissionDecisionReason. The reason string is load-bearing governance: it's the difference between a wall and a sign on a wall — Claude gets told why, and can route around the blocked path legitimately instead of retrying it.
  • SEE: Your migration-freeze rule, in the blocking shape (draft, adapted from §6's JSON contract — config lives in a settings file's hooks block per §6): json { "hooks": { "PreToolUse": [ { "matcher": "Edit|Write", "hooks": [ { "type": "command", "command": "jq -r '.tool_input.file_path' | grep -q '^db/migrations/' && \ echo '{\"hookSpecificOutput\":{\"hookEventName\":\"PreToolUse\",\ \"permissionDecision\":\"deny\",\ \"permissionDecisionReason\":\"migrations are review-gated: see docs/governance.md\"}}' \ || true" } ] } ] } } Belt and suspenders with your L7 deny lane is intentional: §6 — for PreToolUse, "the most restrictive decision wins (deny > defer > ask > allow)" — and §9's deny already wins merges. Two mechanisms, one policy, no single point of quiet failure.
  • DO: Pick the one rule from your cycle-1 sort whose failure cost is highest among the hook-layer rules (defend the pick in a line). Draft its PreToolUse block: event, matcher, decision path, and — mandatory — the permissionDecisionReason a confused teammate's session would actually be helped by. Note in a comment which failure-log entry or 2×2 verdict this hook descends from; governance without a paper trail to evidence is taste with a JSON syntax.

SSD cycle 3 — checks that need judgment: the prompt and agent hook types

  • SAY: Some checklist items can't be decided by grep — item 4 ("no PII in added log lines") needs reading. §6's frozen inventory covers this: a hook's type can also be "prompt" ("single-turn LLM judgment, Haiku by default") or "agent" ("experimental — a subagent verifies a condition, e.g. 'did tests actually pass,' before returning ok/not-ok"). Governance analysis includes an honesty line the team deserves: a judgment-type hook makes the check always run; it does not make the judgment always right — determinism of occurrence, not of verdict [instructor synthesis]. Your L5 rule (a claim gets a check) still applies above any judgment hook's output.
  • SEE: Item 4's governance stack, assembled from your existing artifacts: advisory CLAUDE.md: "no PII in log lines; fields named in docs/pii-fields.md" (shapes) guarantee PostToolUse prompt-hook on Edit|Write under src/: "do added log lines include fields from docs/pii-fields.md? verdict + line numbers" (always runs) backstop reviewer subagent owns item 4 (L3 scope) + your L5-style spot check (verdict quality)
  • DO: For each judgment-shaped rule in your cycle-1 sort, decide: prompt hook, agent hook, or keep-it-human — and write the honesty line for your proposal stating what the chosen hook does and does not guarantee. (Where you keep it human, say why the judgment can't yet be delegated — that's an X2 line your team will re-derive when models move.)

SSD cycle 4 — the proposal itself: rollout is part of governance

  • SAY: A governance design nobody can adopt is a memo. The adoption mechanics are all frozen facts: project .claude/settings.json is "shareable project hook" config (§6) and git-committed for "all repo collaborators" (§9); project-level allow rules require each teammate's "one-time workspace-trust approval" (§9); /hooks lets anyone browse configured hooks read-only (§6); permission and hook edits "reload immediately in a running session" (§9). So the proposal must say: what lands in which committed file, what each teammate will be prompted to approve and why that's consent rather than friction, how anyone inspects what's governing them, and how a rule gets changed (X2: the line moves by proposal-and-revision, exactly like this one).
  • SEE: The one-page shape, filled with your first three rows:
Rule Layer File Teammate sees Change process
Decimal/rounding (item 1) hook (PostToolUse test-run, L6) + CLAUDE.md .claude/settings.json + CLAUDE.md hook runs on payments edits; /hooks to inspect PR against settings + failure-log evidence
migration freeze deny lane + PreToolUse (cycle 2) .claude/settings.json blocked, with reason string PR + 2×2 re-sort
provenance convention (L8) CLAUDE.md (advisory) CLAUDE.md convention text; spot-check ritual PR against CLAUDE.md
  • DO: Finish the table for every rule from cycle 1, add a five-line preamble in your own voice (what this replaces — the paste-ritual — and what evidence produced each row: your failure log, not your feelings), and a closing line naming the review date when the team re-sorts the table. That's the artifact you bring to your team; this lesson doesn't ship it for you.

Independent at-bat

Unscaffolded: a governance request arrives from outside your own head — a teammate asks, "can we make Claude stop touching tests/fixtures/golden/ files? It keeps regenerating them." Take it through the full pipeline alone: failure question → layer(s) → concrete draft (rule syntax or hook JSON with its reason string) → proposal row with evidence line and teammate-visible behavior → and an explicit note on what you'd measure to know the rule is working rather than just existing. (That last clause is lesson 10's doorway; leave it as one honest sentence.)

Exit ticket

(Graded against your proposal table, your hook drafts, and the cited doc-set sections — never against how governed the repo "feels.")

  1. (Remember) Per §6, name the two ways a hook communicates its decision, and what exit code 2 does with stderr.
  2. (Understand) Why is permissionDecisionReason governance content rather than a nicety? Answer in terms of what the blocked session does next with and without it.
  3. (Apply) Sort this new rule with the failure question and write its proposal row: "docstrings in Google style." Layer, file, teammate-visible behavior, change process.
  4. (Analyze) Your migration freeze exists as both an L7 deny rule and a cycle-2 PreToolUse hook. Trace what each mechanism alone would miss (§9 merge rules vs. §6 decision flow), and state the one scenario where the redundancy pays.
  5. (Analyze — objective level) A senior teammate counters: "put the whole table in CLAUDE.md; hooks and denies are bureaucracy." Using your failure-log evidence and the §4 no-compliance-guarantee line, analyze which rows of your table genuinely could live as advisory-only — and which row's failure history refutes the counter on its own.

ledger_write

ledger_write:
  learner_id: L1-DEV-MAYA
  lesson_id: L1-dev-maya-09-c2-governance
  anchors: [AIHC.2.C2]
  bloom_reached: Analyze
  exit_ticket_score: ""
  auto_score: ""
  self_score: ""
  calibration_gap: " — computed once Maya's self_score exists"
  starting_mastery:
    AIHC.2.C2: "not previously evidenced — first measurement this lesson"
  journal_prompt: >
    Your proposal replaces a ritual you personally maintained for the team (the pasted
    checklist). Write two honest sentences: one on what the paste-ritual gave you that the
    table doesn't, and one on which single row you most expect a teammate to push back on —
    and whether your evidence line for that row is actually strong enough.
  structure_used: PBL
  referents_used: [baseball-mets-foul-line]
  next_lesson_seed: >
    Lesson 10 answers the at-bat's dangling sentence: is any of this working? C3 —
    defect catch-rate on her real pipeline, her own review minutes, seeded-defect drills —
    outcomes, not completion.

RUBRIC SELF-AUDIT

# Indicator Verdict Evidence
R1 One objective, Bloom's-leveled, named anchor PASS States "Bloom's: Analyze," names AIHC.2.C2 only
R2 Every tool claim traces to the doc-set PASS §4 (no-compliance-guarantee, quoted), §6 (hooks definition verbatim; PreToolUse blocks; exit-code/JSON contract with permissionDecision values; most-restrictive-wins; prompt/agent hook types quoted; /hooks read-only; config locations), §9 (deny always blocked/wins merge; project settings git-committed; workspace-trust; immediate reload) all cited at point of use; the three-layer taxonomy and the occurrence-vs-verdict distinction are flagged [instructor synthesis]
R3 3–6 cycles, one point each PASS 4 cycles: the failure question sorts layers; the blocking shape with reason strings; judgment-type checks and their honesty line; rollout mechanics as governance content
R4 SEE verified against ground truth PASS Every quoted mechanism matches §4/§6/§9 verbatim; the JSON drafts follow §6's documented contract and are presented as drafts to verify, not as tested facts; all rules sorted are her real artifacts from L1–L8
R5 Every DO on real work-at-hand PASS DOs sort her real checklist/lanes/convention, draft the hook for her real migration freeze, decide her real item-4 stack, and assemble the actual proposal she'd table
R6 Media doctrine honored PASS Static text/JSON/tables; no motion/process content
R7 Exit ticket 3–5 Qs, Bloom's-climbing PASS 5 Qs, Remember→Understand→Apply→Analyze→Analyze(objective level), graded against her artifacts + doc-set
R8 Ledger write complete PASS auto_score/self_score = ""; journal prompt present; seed recorded
R9 Scaffolding fades; at-bat genuinely independent PASS Cycle 1 models the sort on three rows only; cycle 4 seeds three table rows and she completes the rest; the at-bat is an externally-sourced request through the whole pipeline with no model — part-faded per arc position
R10 Referents elected-only, flavor-only PASS One Mets foul-line referent (cycle 1), decorative; the sorting question stands on §4/§6/§9
R11 Info-at-point-of-use honored PASS Exit-code contract appears at the blocking-hook DO; rollout facts appear at the proposal DO; no front-loaded reading
R12 No replication PASS L6 drafted one reactive hook as failure-crystallization; this lesson's content — the layer sort, blocking hooks with reason strings, judgment-hook honesty, rollout/adoption table — is new, as are all DOs/exit Qs; no exemplar overlap (exemplar has no hooks/governance content)

Escalation: all load-bearing indicators PASS → auto-ships (Playbook §5, AIHC X2/B3).