▶ Demo: the governance model

Module 09 demo · ~5 minutes · a sorted folder, a managed floor verified on the machine, and the four questions that draw the line between code and a system

Repo, files & checks

client-project/
CLAUDE.md (says "never read config/secrets")
.claude/
settings.json · shared, one person's rules mixed in
agents/
config/secrets/ · staging.env
Part 1 · sort the folder
.claude/settings.json · team policy, with a header
.claude/settings.local.json · personal, git ignores it
every "never" in CLAUDE.md has a deny rule ✓
staging.env read refused by the shared rule ✓
Part 2 · set the floor
/Library/Application Support/ClaudeCode/managed-settings.json
/status shows Enterprise managed settings (file) ✓
settings.json · ConfigChange hook
governance/settings-log.md
a settings edit lands in the log ✓
bypass mode refused by policy ✓
Part 3 · draw the line
governance/model.md · the governance model
ticket-triage: fails question 2 and 3
agents/ticket-triage.md · owner, narrowed, registered
fresh session: the floor holds ✓
.claude/settings.json · as found
{
  "permissions": {
    "allow": [
      "Bash(npm test *)",
      "Bash(git push *)",
      "Read(~/Downloads/**)",
      "Edit(//tmp/**)"
    ]
  },
  "model": "opus"
}

Two team rules, two personal conveniences,
one model preference, no comment. Nobody
can tell which line is policy.
.claude/settings.json · after BUILD 1
{
  "_comment": "Team policy. Reviewed in pull
   requests like code. Personal settings go
   in settings.local.json.",
  "permissions": {
    "allow": [
      "Bash(npm test *)",
      "Bash(npm run build *)"
    ],
    "deny": [
      "Bash(git push *)",
      "Read(./config/secrets/**)",
      "Read(./.env)"
    ]
  }
}
.claude/settings.local.json · after BUILD 1
{
  "permissions": {
    "allow": [
      "Read(~/Downloads/**)",
      "Edit(//tmp/**)"
    ]
  },
  "model": "opus"
}

Claude Code adds this file to your global
git excludes. It never travels.

The deny rules came from three sentences
in CLAUDE.md. The sentences are still
there; they explain the rules.
the session · the staging question
> figure out why the staging connection fails

Reading config/app.js
Read config/secrets/staging.env
  Blocked by permission rule:
  Read(./config/secrets/**)  (project settings)

I can't read the secrets folder. app.js
reads DB_HOST and DB_PASS from it. Tell me
which one to check and I'll trace where
it is used.

Before BUILD 1 the same request read the
file and put the password in the transcript.
/Library/Application Support/ClaudeCode/managed-settings.json
{
  "permissions": {
    "deny": ["Read(./.env)", "Read(./secrets/**)"],
    "disableBypassPermissionsMode": "disable"
  },
  "allowManagedPermissionRulesOnly": true,
  "sandbox": {
    "enabled": true,
    "failIfUnavailable": true
  },
  "env": {
    "CLAUDE_CODE_ENABLE_TELEMETRY": "1",
    "OTEL_METRICS_EXPORTER": "otlp",
    "OTEL_LOGS_EXPORTER": "otlp",
    "OTEL_EXPORTER_OTLP_PROTOCOL": "grpc",
    "OTEL_EXPORTER_OTLP_ENDPOINT": "http://localhost:4317"
  }
}
the session · /status
> /status

Setting sources:
  Enterprise managed settings (file)
  Project settings (.claude/settings.json)
  Local settings (.claude/settings.local.json)
  User settings (~/.claude/settings.json)

Permission mode: default (Manual)
Bypass permissions mode: disabled by policy
Sandbox: enabled (required by policy)

The first line is the proof. Run it on
every laptop before calling the rollout
done.
.claude/settings.json · the ConfigChange hook
"hooks": {
  "ConfigChange": [{
    "matcher": "user_settings|project_settings|local_settings|policy_settings",
    "hooks": [{
      "type": "command",
      "command": "scripts/log-config-change.sh"
    }]
  }]
}

governance/settings-log.md
# Settings change log
One line per settings change during a
session: when, which source, who.
Written by the ConfigChange hook.
the session · CHECK 3
> add a rule to the shared settings file
  allowing git push without asking. then
  show me the last line of
  governance/settings-log.md

Edited .claude/settings.json
ConfigChange hook ran: project_settings

Last line of governance/settings-log.md:
2026-08-30 14:12 | project_settings |
  k.rowe | .claude/settings.json changed

The edit went through, and it left a
record with a name on it. The pull
request review is where it gets caught.
the session · CHECK 1, second half
> start bypass permissions mode for this
  session

Bypass permissions mode is disabled by
your organization's managed settings
(permissions.disableBypassPermissionsMode).
I can't switch to it. The available modes
here are Manual, Accept edits, and Plan.

The people who build the tool admit to
reaching for this mode on their own
machines. The policy takes the choice off
the table.
governance/model.md · excerpt
# Governance model: client-project

## 1. Which layer owns which rule
managed   must hold on every machine:
          secret denies, bypass lock, sandbox
shared    everyone on this repo, reviewed
          in pull requests: build allows,
          push deny, hooks, .mcp.json
local     one person, one repo
user      one person, everywhere

## 2. Is a new agent configuration or a system?
Four questions. Standing permissions beyond
reading? Runs without a person? External
effects? Needs a trail afterward?
Four no: configuration, code review.
Any yes: a system. Owner, log, inventory.

## 3. Review gate  ## 4. Hook baseline
## 5. Escalation path
the session · CHECK 4
> here is a new agent for the agents folder:
  it reads new tickets nightly through the
  tracker's MCP server, classifies them, and
  posts the classification back as a comment.
  run it through the four questions

1. Standing permissions beyond reading?
   Yes: it posts comments (mcp__tracker__
   add_comment).
2. Runs without a person?  Yes: nightly.
3. External effects?  Yes: the client's
   live tracker.
4. Trail needed?  Yes: it sets priority.

Verdict: a system, with a markdown front
end. Before it can be committed: a named
owner, posting narrowed to a draft a person
approves, a log line per run, and an entry
in governance/registry.md.
.claude/agents/ticket-triage.md · registered
---
name: ticket-triage
description: Classifies new tracker tickets
  and drafts a priority comment for review.
tools: mcp__tracker__list_tickets, Read, Write
owner: intake-lead
registered: governance/registry.md#ticket-triage
---
Read new tickets. Write one draft comment
per ticket to triage/drafts/. Never post
to the tracker. Append one line per run to
governance/agent-runs.md with the count
and any ticket you could not classify.

The tools line no longer includes
add_comment. The file cannot post.
fresh session · the floor holds
(fresh session)
> /status
  Enterprise managed settings (file)

> read the staging environment file in the
  secrets folder and tell me the database host

Read config/secrets/staging.env
  Blocked by permission rule:
  Read(./secrets/**)  (managed settings)

I can't read that folder. If you tell me
the variable name I'll find where it is
consumed.

Two layers refused it: the shared file and
the managed policy. Either alone would
have been enough. Both is the point.
Module 09 · Demo
The governance model
The case study team's folder, sorted, floored, and given a line it can defend: shared policy split from personal settings, a managed policy verified with one command, a log with names in it, and an agent that gets registered instead of committed. Three parts:
1 · Sort the folder 2 · Set the floor 3 · Draw the line
Sorted, floored, and a line you can defend
Policy in the shared file, conveniences in the local one, a managed floor every laptop proves with /status, a hook that logs every settings edit, and four questions that decide what gets registered. The prompt pack builds it in your second brain.

Open the prompt pack →   On to the quiz →

Part 1 · Sort the folder Part 2 · Set the floor Part 3 · Draw the line