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

B3 Trust Calibration — Permission Lanes, Not Vibes, For payments-service

AIHC.2.B3Bloom's: ApplyPBL
● Exhibit 7 of 12 — Maya R.'s track

Standard: AIHC.2.B3 · Bloom's: Apply · 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 Apply, and the lesson closes by writing to the ledger.

Learning objective

(Bloom's: Apply) Apply per-task-class trust calibration to your live pipeline by encoding your trust decisions as permission rules in payments-service's settings — an allow lane, an ask lane, and a deny lane, each derived from your lesson-3 reversibility/blast-radius sort and your lesson-6 failure-log evidence, so that what Claude Code may do without you is a written, checkable policy instead of a per-prompt mood — AIHC.2.B3 (Trust calibration).

Work at hand: your pipeline works (lesson 6's at-bat ran a real PR through it), but every session still routes trust through your thumb: approve, approve, approve. Your lesson-3 2×2 already contains the calibration — you made those reversibility calls once, deliberately. Today that sort stops living in a markdown table and starts living where Claude Code actually reads policy: the permissions block.

SSD cycle 1 — count your approvals: trust you re-decide hourly isn't calibrated

  • SAY: Trust calibration (B3) means the amount of checking matches the evidence and the stakes — per class of task, decided once, revised on evidence (AIHC X2: the line moves by policy, not per prompt). A session where you approve pytest eleven times is not eleven trust decisions; it's one decision you refused to write down, taxed eleven times.
  • SEE: Yesterday's session, as the approval prompts actually came (described transcript, condensed): Bash(pytest tests/payments -q) → you approved (1st of 6 today) Read(src/payments/fees.py) → you approved (reads: 9 approvals today) Bash(pytest tests/payments -q) → you approved (same command, again) Edit(src/payments/fees.py) → you approved (each edit reviewed — good) Bash(git push origin fix/fee-rounding) → you approved (barely glanced — bad) Two miscalibrations, opposite directions: the test-run and read approvals are pure tax (your lesson-3 sort already ruled them reversible and diff-checkable), while the git push got less attention than it deserved because it arrived in an approval stream you'd stopped reading. Undifferentiated prompting flattens exactly the distinction B3 exists to make.
  • DO: From your real last two sessions, list every distinct class of action you were prompted for (running tests, reading source, editing payments code, git pushes, network calls, dependency installs — whatever actually occurred). For each class, write its lane from your lesson-3 2×2 verdicts: ALLOW (reversible + verifiable after the fact), ASK (want eyes on each instance), or DENY (never without a human driving). No syntax yet — the calibration first, on your own evidence.

SSD cycle 2 — encode the lanes: the permissions block is where calibration becomes real

  • SAY: Claude Code reads trust policy from the permissions block of a settings file. The rule syntax is "ToolName(resource_pattern)" in three buckets — and the buckets are exactly your three lanes: "allow = runs without prompting; deny = always blocked, takes precedence in a merge; ask = prompts every time" (doc-set §9, Settings).
  • SEE: The doc-set's own example block (§9, verbatim), then your cycle-1 lanes in the same syntax: json {"permissions": {"allow": ["Bash(npm run test *)", "Read(./src/**)"], "deny": ["Bash(curl *)", "Read(./.env)"], "ask": ["Bash(*)"]}} json { "permissions": { "allow": ["Read(./src/**)", "Read(./tests/**)", "Bash(pytest tests/payments *)"], "ask": ["Edit(./src/payments/**)", "Bash(git push *)"], "deny": ["Read(./.env)", "Bash(curl *)", "Edit(./db/migrations/**)"] } } Note what the deny lane encodes: ./.env and curl come straight from the doc-set's own example; db/migrations/** is yours — your lesson-3 sort ruled the ledger migration irreversible-and-high-blast-radius, and a lane is where that verdict stops depending on you remembering it. (Like a bullpen chart taped in the dugout before first pitch — who's allowed to face the ninth was decided in daylight, not by whoever feels warm in the moment.)
  • DO: Write your real permissions block from your cycle-1 lane list, one rule per line, in .claude/settings.local.json (§9: local = you + this repo, gitignored — your personal calibration, not yet team policy). On-demand check per rule: can you name the lesson-3 verdict or lesson-6 failure-log entry that justifies its lane? A rule you can't source to evidence is a vibe with syntax.

SSD cycle 3 — know which file speaks and who wins a disagreement

  • SAY: Lanes live in a file, and files stack. §9: precedence runs Managed → command-line args → Local → Project → User, permission rules merge across scopes rather than override — with deny taking precedence in a merge — and project-level allow rules need a one-time workspace-trust approval; personal local rules don't. Your lesson-6 log already holds the failure-shaped version of this (the teammate-gets-prompts-you-don't surprise); today it becomes placement policy instead of a surprise.
  • SEE: Your three lanes, placed: .claude/settings.local.json your ALLOW + ASK lanes (yours alone; no team approval; gitignored) .claude/settings.json candidate DENY lanes (git-committed; would bind the team; lesson 9 makes this proposal properly) ~/.claude/settings.json nothing from this repo (user scope = every project you touch — payments-specific rules don't belong there) One consequence to sit with: because deny wins merges, a deny you push to project scope binds you too, even where your local file says allow. That is a feature — the migration freeze you want for the team is one you shouldn't be able to vibe your own way past either.
  • DO: Annotate each rule in your cycle-2 block with its destination scope and a one-line why. Flag every rule you believe belongs at project scope — don't move them yet; that flag list is a real input lesson 9 will build the team proposal from. Then verify the local block loaded: §9 — permission edits reload immediately in a running session, and /status verifies what loaded, /doctor checks resolved settings for invalid entries. Run both (described) and fix anything /doctor flags.

SSD cycle 4 — modes and subagents: calibration has two more dials than the block

  • SAY: Two frozen mechanisms can silently re-calibrate trust if you don't set them deliberately. Session mode: §2 — default mode asks before each change; Shift+Tab cycles default → acceptEdits → plan; acceptEdits auto-approves file edits, plan proposes with no edits at all. Parent precedence: your lesson-6 log entry already established §8's rule — a parent session in bypassPermissions/acceptEdits takes precedence over a subagent's own permissionMode and can't be overridden. Put together: running your reviewer from an acceptEdits parent quietly loosens the very lane structure you just wrote. Mode is a trust dial, so it gets a lane policy too.
  • SEE: Your task classes mapped to modes, one line each: ledger-migration review → plan mode (propose-only; edits structurally impossible) routine fix w/ contract → default (ask lane still live per instance) reviewer subagent running → default parent (never acceptEdits — preserves §8 tool-allowlist + lane behavior underneath it)
  • DO: Add a ## Trust lanes section to your payments-service/CLAUDE.md (3–4 lines, concrete per §4's verify-by-looking bar): which mode each task class runs in, and the standing rule that the reviewer never runs under an acceptEdits parent. This line is advisory context, not enforcement (§4) — note in the section which single mode rule you'd want enforced someday, and leave a pointer to the failure log if it's ever violated.

Independent at-bat

Unscaffolded: a task class your lanes don't cover yet arrives this week — dependency bumps (pyproject.toml edits + pip install). Calibrate it end-to-end alone: place it on your 2×2, choose its lane and scope file, write the rule(s) in real syntax, probe your calibration from inside a session (ask Claude to do the thing and observe which lane fires — a lane you never probed is a lane you believe, not one you've checked), and record the probe result as a one-line evidence note next to the rule. No worked mapping this time.

Exit ticket

(Graded against your settings files, your lesson-3 sort, your failure log, and the cited doc-set sections — never against how trustworthy the session "felt.")

  1. (Remember) Name the three permission buckets and, per §9, what each does to a matching action at prompt time.
  2. (Understand) Your local file allows Bash(pytest tests/payments *); a future project file denies it. Which behavior wins under §9's merge rules, and why is that outcome the right default for a trust system?
  3. (Apply) Write the complete lane entry — bucket, rule syntax, destination file — for a new task class: Claude fetching a URL with curl to check an API's response format. Source the lane to a 2×2 verdict in one clause.
  4. (Apply) A teammate watches your session and asks why you still get prompted for edits under src/payments/ when "you obviously trust the pipeline now." Answer in two sentences using the ask lane's purpose and one piece of your own failure-log evidence.
  5. (Evaluate) Judge your own block: which single rule is most likely to be miscalibrated — too loose or too tight — six weeks from now, and what observable evidence (approval counts, log entries, probe results) would trigger you to move it, per X2, by revision rather than by mood?

ledger_write

ledger_write:
  learner_id: L1-DEV-MAYA
  lesson_id: L1-dev-maya-07-b3-trust-calibration
  anchors: [AIHC.2.B3]
  bloom_reached: Apply
  exit_ticket_score: ""
  auto_score: ""
  self_score: ""
  calibration_gap: " — computed once Maya's self_score exists"
  starting_mastery:
    AIHC.2.B3: "not previously evidenced — first measurement this lesson"
  journal_prompt: >
    Count your approval prompts in the first full session after the lanes went live, and put
    the number next to cycle 1's count. Then the harder question: did any surviving ask-lane
    prompt get MORE of your attention than it used to — and is that what the deny/allow lanes
    were actually for?
  structure_used: PBL
  referents_used: [baseball-mets-bullpen-chart]
  next_lesson_seed: >
    Lesson 8 turns to what the pipeline leaves behind: the PR record. The verification record
    (L5) says what was checked; provenance (C1) adds who and what wrote the code — commit
    attribution, the session-to-PR link, and a standing CLAUDE.md convention.

RUBRIC SELF-AUDIT

# Indicator Verdict Evidence
R1 One objective, Bloom's-leveled, named anchor PASS States "Bloom's: Apply," names AIHC.2.B3 only
R2 Every tool claim traces to the doc-set PASS §9 (rule syntax, three buckets verbatim, file locations, precedence/merge, deny-wins, workspace-trust, immediate reload, /status, /doctor), §2 (permission modes, Shift+Tab cycle), §8 (parent-mode precedence — introduced in L6, re-cited here), §4 (CLAUDE.md concreteness, context-not-enforcement) all cited at point of use
R3 3–6 cycles, one point each PASS 4 cycles: undifferentiated prompting defeats calibration; lanes encoded in the permissions block; scope placement and merge behavior; modes + parent precedence as trust dials
R4 SEE verified against ground truth PASS §9's example block quoted verbatim before her adaptation; her block uses only the documented ToolName(resource_pattern) grammar; the approval transcript is her own session described, with her real commands/paths from L1–L6
R5 Every DO on real work-at-hand PASS DOs mine her real sessions, write her real settings.local.json, annotate scopes for her real rules, and amend her real CLAUDE.md; the at-bat calibrates a real incoming task class
R6 Media doctrine honored PASS Static transcript/JSON/table SEEs only; nothing is a motion/process point
R7 Exit ticket 3–5 Qs, Bloom's-climbing PASS 5 Qs, Remember→Understand→Apply→Apply→Evaluate, graded against her files + doc-set, never against felt trust
R8 Ledger write complete PASS auto_score/self_score = ""; journal prompt present; next-lesson seed recorded
R9 Scaffolding fades; at-bat genuinely independent PASS Cycle 2 shows the doc-set example before her adaptation; cycle 3 gives placement only as a sketch; cycle 4's DO is unmodeled; the at-bat covers a new task class with no worked mapping — consistent with the part-faded band-2 arc position
R10 Referents elected-only, flavor-only PASS One Mets bullpen-chart referent (cycle 2), decorative; the lanes stand entirely on §9
R11 Info-at-point-of-use honored PASS Each §-citation sits inside the cycle that needs it; /status and /doctor introduced at the exact DO where she runs them; no front-loaded reading
R12 No replication PASS L6 used permission mechanics as failure evidence; this lesson writes the proactive policy — different artifacts (settings.local.json lanes, CLAUDE.md trust section, probe notes) and all-new DOs/exit Qs; no overlap with the exemplar (which has no settings content)

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