# Account Filesystem Schema Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Seed a canonical filesystem schema into every account dir and enforce it through a durable prompt directive plus a PreToolUse Write-path hook, without removing the main agent's Bash/Write tools.

**Architecture:** Three layers. (A) `provision_account_dir` seeds the operator-data + tool-owned skeleton and copies a shipped `SCHEMA.md` (the single source of truth for the allowed top-level set). (B) A `DATA_STEWARDSHIP` constant in the session manager's system-prompt is rendered on every spawn. (C) A bash PreToolUse hook (`fs-schema-guard.sh`) inspects Write/Edit/NotebookEdit target paths and blocks novel top-level dirs and over-deep operator-data paths, parsing the allowed set from the account's seeded `SCHEMA.md`. Decision C1 (chattr OS-lock) is dropped — it cannot run under the unprivileged provision path.

**Tech Stack:** Bash (provision + hook), python3 (JSON-aware hook parsing, project standard), TypeScript (session-manager system prompt + self-test), shell test harness (`__tests__/*.test.sh`).

---

## File Structure

- **Create** `platform/templates/account-schema/SCHEMA.md` — shipped template. Human-readable file-ontology doc + a fenced ` ```allowed-top-level ` block (the single machine-readable source of the allowed top-level set). Read at provision (copied into the account) and at runtime (parsed by the hook from the account copy).
- **Modify** `platform/scripts/lib/provision-account-dir.sh` — add the seed step (mkdir skeleton, copy SCHEMA.md, log line) and add `fs-schema-guard.sh` to the settings.json PreToolUse Write/Edit/NotebookEdit matchers.
- **Create** `platform/plugins/admin/hooks/fs-schema-guard.sh` — the PreToolUse hook.
- **Create** `platform/plugins/admin/hooks/__tests__/fs-schema-guard.test.sh` — committed regression test (matches the existing `__tests__/` convention).
- **Modify** `platform/services/claude-session-manager/src/system-prompt.ts` — `DATA_STEWARDSHIP` const, `<data-stewardship>` section in `renderAppendBlock`, self-test assertion.

The allowed top-level set is defined **once**, in the template's fenced block. The hook parses it from the account copy; the test asserts the parse matches the documented set so the doc and enforcement never drift.

---

### Task 1: Ship the SCHEMA.md template (single source of the allowed set)

**Files:**
- Create: `platform/templates/account-schema/SCHEMA.md`

- [ ] **Step 1: Write the template file**

Create `platform/templates/account-schema/SCHEMA.md` with this exact content:

````markdown
# Account data directory — file schema

This directory is a projection of the graph ontology. Its top-level layout is
fixed. Do not author new top-level folders, and do not nest subtrees under an
entity folder. Reorganisation and file moves go through the `data-manager`
specialist; the directory layout below is the standard it keeps you consistent
with.

## Operator-data buckets (entity-anchored, flat)

- `projects/<name>/` — one flat subfolder per Project node. Files live directly
  inside `projects/<name>/`. No deeper subtree.
- `contacts/<name>/` — one flat subfolder per Person or Organization node.
- `documents/` — KnowledgeDocument files and loose uploads. Files live directly
  in `documents/`, or one document-folder deep (`documents/<doc>/file`).

## Tool-owned (off-limits — never reorganise)

The internal structure of these is owned by the writing tool and is recreated on
the next write: `url-get/`, `output/`, `generated/`, `extracted/`, `uploads/`,
any published/served tree (`sites/`, `public/`), `agents/`, `specialists/`, and
platform-internal areas (`cache/`, `secrets/`, `state/`, `logs/`, `tmp/`).

## Allowed top-level entries

The set below is the complete list of permitted top-level entries. A write whose
first path segment is not in this set is blocked. A write under an operator-data
bucket deeper than the flatness rule above is blocked. The list is enforced by a
write-path guard and reconciled periodically.

```allowed-top-level
projects
contacts
documents
url-get
output
generated
extracted
uploads
agents
specialists
sites
public
cache
secrets
state
logs
tmp
SCHEMA.md
account.json
AGENTS.md
.claude
.git
.quarantine
```
````

- [ ] **Step 2: Verify the fenced block parses to the expected set**

Run:
```bash
cd platform/templates/account-schema
awk '/^```allowed-top-level$/{f=1;next} /^```$/{f=0} f' SCHEMA.md | tr '\n' ' '
```
Expected output (exact):
`projects contacts documents url-get output generated extracted uploads agents specialists sites public cache secrets state logs tmp SCHEMA.md account.json AGENTS.md .claude .git .quarantine `

- [ ] **Step 3: Commit**

```bash
git add platform/templates/account-schema/SCHEMA.md
git commit -m "feat(account-schema): ship account-dir SCHEMA.md template"
```

---

### Task 2: Seed the skeleton + SCHEMA.md in provision_account_dir

**Files:**
- Modify: `platform/scripts/lib/provision-account-dir.sh`

- [ ] **Step 1: Add the seed step**

In `provision-account-dir.sh`, immediately after the existing first `mkdir -p`
line (the one creating `agents/admin`, `.claude`, `specialists/...`), insert:

```bash
  # --- Account filesystem schema seed (idempotent) ---------------------------
  # Operator-data buckets (entity-anchored, flat) + tool-owned dirs. agents/ and
  # specialists/ are created above. The seeded SCHEMA.md is the single source of
  # the allowed top-level set, parsed at runtime by the fs-schema write guard.
  local _seed_dirs=(projects contacts documents url-get output generated extracted uploads)
  local _d _seeded=0
  for _d in "${_seed_dirs[@]}"; do
    [ -d "$ACCOUNT_DIR/$_d" ] || { mkdir -p "$ACCOUNT_DIR/$_d" && _seeded=$((_seeded+1)); }
  done
  if [ -f "$TEMPLATES_DIR/account-schema/SCHEMA.md" ]; then
    cp "$TEMPLATES_DIR/account-schema/SCHEMA.md" "$ACCOUNT_DIR/SCHEMA.md"
  else
    echo "  [acct-schema] WARNING: template missing at $TEMPLATES_DIR/account-schema/SCHEMA.md — SCHEMA.md not seeded" >&2
  fi
  echo "  [acct-schema] seeded dirs=$_seeded"
```

- [ ] **Step 2: Verify the seed runs idempotently against a scratch account**

Run:
```bash
TMP=$(mktemp -d); export TEMPLATES_DIR="$PWD/platform/templates" PROJECT_DIR="$PWD/platform" SCRIPT_DIR="$PWD/platform/scripts" USERS_FILE="$TMP/users.json"
bash -c '. platform/scripts/lib/provision-account-dir.sh; provision_account_dir "'"$TMP"'/acct" testid house' 2>&1 | grep acct-schema
ls "$TMP/acct" | sort | tr '\n' ' '; echo
test -f "$TMP/acct/SCHEMA.md" && echo "SCHEMA.md OK"
# second run is idempotent (seeded dirs=0)
bash -c '. platform/scripts/lib/provision-account-dir.sh; provision_account_dir "'"$TMP"'/acct" testid house' 2>&1 | grep acct-schema
rm -rf "$TMP"
```
Expected: first run logs `[acct-schema] seeded dirs=8`; `ls` includes
`contacts documents extracted generated output projects uploads url-get` plus
`SCHEMA.md`; `SCHEMA.md OK`; second run logs `[acct-schema] seeded dirs=0`.

- [ ] **Step 3: Commit**

```bash
git add platform/scripts/lib/provision-account-dir.sh
git commit -m "feat(account-schema): seed skeleton + SCHEMA.md in provision_account_dir"
```

---

### Task 3: Write the fs-schema-guard hook (failing test first)

**Files:**
- Create: `platform/plugins/admin/hooks/__tests__/fs-schema-guard.test.sh`
- Create: `platform/plugins/admin/hooks/fs-schema-guard.sh`

- [ ] **Step 1: Write the failing test**

Create `platform/plugins/admin/hooks/__tests__/fs-schema-guard.test.sh`:

```bash
#!/usr/bin/env bash
# Regression test for fs-schema-guard.sh.
#
# Covers:
#   1. Non-Write/Edit/NotebookEdit tool                    → ALLOW (early exit)
#   2. Write to projects/Acme/a.txt (flat)                 → ALLOW
#   3. Write to documents/a.pdf (loose)                    → ALLOW
#   4. Write to output/deep/nested/x.png (tool-owned)      → ALLOW (any depth)
#   5. Write to projects/Acme/deploy/sources/a.txt         → BLOCK over-deep
#   6. Write to projects/Acme/deploy/a.txt                 → BLOCK over-deep
#   7. Write to whim/x.txt (novel top-level)               → BLOCK top-level
#   8. Write to an absolute path outside the account dir   → ALLOW (not our concern)
#   9. NotebookEdit over-deep (notebook_path)              → BLOCK over-deep
#  10. Terminal stdin (no envelope)                        → BLOCK fail-closed
#  11. Allowed-set parse matches the documented set        → assertion
set -u

HOOK="$(cd "$(dirname "$0")/.." && pwd)/fs-schema-guard.sh"
[ -x "$HOOK" ] || { echo "FAIL: $HOOK not executable" >&2; exit 1; }

# A scratch account dir with a seeded SCHEMA.md (copied from the shipped template).
REPO_ROOT="$(cd "$(dirname "$0")/../../../.." && pwd)"
TEMPLATE="$REPO_ROOT/templates/account-schema/SCHEMA.md"
[ -f "$TEMPLATE" ] || { echo "FAIL: template missing at $TEMPLATE" >&2; exit 1; }
ACCT=$(mktemp -d)
trap 'rm -rf "$ACCT"' EXIT
cp "$TEMPLATE" "$ACCT/SCHEMA.md"
mkdir -p "$ACCT/projects/Acme" "$ACCT/documents" "$ACCT/output"

PASS=0; FAIL=0
# Run one case in the account dir as cwd.
#   $1 name  $2 stdin  $3 expected_exit  $4 expected_log_re ("" = none)  $5 terminal(1=attach tty)
run_case() {
  local name="$1" stdin="$2" exp="$3" re="$4" term="${5:-0}" ef actual
  ef=$(mktemp)
  if [ "$term" = "1" ]; then
    ( cd "$ACCT" && bash "$HOOK" >/dev/null 2>"$ef" </dev/null )  # /dev/null is not a tty; see note
    actual=$?
  else
    ( cd "$ACCT" && printf '%s' "$stdin" | bash "$HOOK" >/dev/null 2>"$ef" )
    actual=$?
  fi
  local sc; sc=$(cat "$ef"); rm -f "$ef"
  local ok=1
  [ "$actual" -eq "$exp" ] || ok=0
  if [ -n "$re" ] && ! printf '%s' "$sc" | grep -Eq "$re"; then ok=0; fi
  if [ $ok -eq 1 ]; then echo "PASS: $name (exit=$actual)"; PASS=$((PASS+1));
  else echo "FAIL: $name (exit=$actual, want $exp; log want /$re/)" >&2; FAIL=$((FAIL+1)); fi
}

env() { printf '{"hook_event_name":"PreToolUse","tool_name":"%s","tool_input":{"%s":"%s"}}' "$1" "$2" "$3"; }

run_case "non-write allow"      "$(env Bash command 'ls')"                          0 "" 
run_case "flat project allow"   "$(env Write file_path 'projects/Acme/a.txt')"      0 ""
run_case "loose document allow" "$(env Write file_path 'documents/a.pdf')"          0 ""
run_case "tool-owned deep allow" "$(env Write file_path 'output/deep/nested/x.png')" 0 ""
run_case "over-deep block"      "$(env Write file_path 'projects/Acme/deploy/sources/a.txt')" 2 "fs-guard. blocked path=projects/Acme/deploy/sources/a.txt reason=over-deep"
run_case "over-deep one block"  "$(env Write file_path 'projects/Acme/deploy/a.txt')" 2 "reason=over-deep"
run_case "top-level block"      "$(env Write file_path 'whim/x.txt')"               2 "fs-guard. blocked path=whim/x.txt reason=top-level"
run_case "outside acct allow"   "$(env Write file_path '/etc/passwd')"              0 ""
run_case "notebook over-deep"   "$(env NotebookEdit notebook_path 'projects/Acme/deploy/n.ipynb')" 2 "reason=over-deep"

# Terminal stdin → fail closed. printf nothing, attach /dev/null which the hook treats as non-tty;
# emulate true terminal via the -t test is impractical in CI, so assert the empty-input branch:
run_case "empty stdin block"    ""                                                  2 "no stdin"

# Allowed-set parse == documented set.
EXPECT="projects contacts documents url-get output generated extracted uploads agents specialists sites public cache secrets state logs tmp SCHEMA.md account.json AGENTS.md .claude .git .quarantine"
GOT=$(awk '/^```allowed-top-level$/{f=1;next} /^```$/{f=0} f' "$ACCT/SCHEMA.md" | tr '\n' ' ' | sed 's/ *$//')
if [ "$GOT" = "$EXPECT" ]; then echo "PASS: allowed-set parse"; PASS=$((PASS+1));
else echo "FAIL: allowed-set parse: got [$GOT]" >&2; FAIL=$((FAIL+1)); fi

echo "----- $PASS passed, $FAIL failed -----"
[ $FAIL -eq 0 ]
```

Note on the "empty stdin block" case: the hook fails closed on empty input as
well as a tty (a non-write hook should never silently allow a write it cannot
inspect). The hook treats empty `INPUT` as an inspect-failure → block with
`no stdin` in the message.

- [ ] **Step 2: Run the test to verify it fails**

Run: `bash platform/plugins/admin/hooks/__tests__/fs-schema-guard.test.sh`
Expected: FAIL — `fs-schema-guard.sh not executable` (file does not exist yet).

- [ ] **Step 3: Write the hook**

Create `platform/plugins/admin/hooks/fs-schema-guard.sh`:

```bash
#!/usr/bin/env bash
# fs-schema-guard — PreToolUse Write/Edit/NotebookEdit path guard.
#
# Enforces the seeded account-dir file schema (see <accountDir>/SCHEMA.md):
#   - the target's first path segment must be in the allowed top-level set
#     (parsed from the fenced ```allowed-top-level block of the account's
#     SCHEMA.md — our own structured data, not CLI prose);
#   - a target under an operator-data bucket (projects/ contacts/) may be at
#     most <bucket>/<entity>/<file> deep; documents/ may be at most
#     <bucket>/<folder>/<file> deep. Tool-owned dirs pass at any depth.
#
# The hook governs the account dir only. A path resolving outside the account
# dir (cwd) is allowed — platform-source writes are PLATFORM_BOUNDARY's concern.
#
# Exit codes: 0 = allow, 2 = block (stderr shown to the agent). Fail closed when
# the tool call cannot be inspected (tty or empty stdin). Every block logs
#   [fs-guard] blocked path=<rel> reason=<top-level|over-deep>
# No task numbers / internal refs in any operator-visible string.

set -uo pipefail

# Fail closed if attached to a terminal (no JSON envelope coming).
if [ -t 0 ]; then
  echo "Blocked: fs-schema-guard received no stdin (cannot inspect the write). Failing closed." >&2
  exit 2
fi
INPUT=$(cat)
if [ -z "$INPUT" ]; then
  echo "Blocked: fs-schema-guard received no stdin (cannot inspect the write). Failing closed." >&2
  exit 2
fi

field() {
  printf '%s' "$INPUT" | python3 -c "
import sys, json
try:
    d = json.load(sys.stdin)
    print(d.get('$1', {}).get('$2', '') if '$1' else d.get('$2', ''))
except Exception:
    print('')
" 2>/dev/null || echo ""
}

TOOL_NAME=$(field '' tool_name)
case "$TOOL_NAME" in
  Write|Edit) FILE_PATH=$(field tool_input file_path) ;;
  NotebookEdit) FILE_PATH=$(field tool_input notebook_path) ;;
  *) exit 0 ;;
esac
[ -z "$FILE_PATH" ] && exit 0

ACCOUNT_DIR="$PWD"

# Resolve to an account-relative path; allow anything outside the account dir.
REL=$(FILE_PATH="$FILE_PATH" ACCOUNT_DIR="$ACCOUNT_DIR" python3 -c '
import os, sys
fp = os.environ["FILE_PATH"]; acct = os.environ["ACCOUNT_DIR"]
ab = fp if os.path.isabs(fp) else os.path.join(acct, fp)
ab = os.path.normpath(ab); acct = os.path.normpath(acct)
if ab == acct or not ab.startswith(acct + os.sep):
    print("")  # outside the account dir (or the dir itself)
else:
    print(os.path.relpath(ab, acct))
' 2>/dev/null || echo "")
[ -z "$REL" ] && exit 0

# Parse the allowed top-level set from the account's seeded SCHEMA.md.
ALLOWED=""
if [ -f "$ACCOUNT_DIR/SCHEMA.md" ]; then
  ALLOWED=$(awk '/^```allowed-top-level$/{f=1;next} /^```$/{f=0} f' "$ACCOUNT_DIR/SCHEMA.md")
fi
# Fail open on a missing/empty schema (an un-seeded legacy account must not have
# every write blocked); the periodic reconcile is the backstop there.
[ -z "$ALLOWED" ] && exit 0

SEG0=${REL%%/*}

# Top-level check.
if ! printf '%s\n' "$ALLOWED" | grep -qxF "$SEG0"; then
  echo "[fs-guard] blocked path=$REL reason=top-level" >&2
  echo "Blocked: '$SEG0' is not an allowed top-level entry in this account's data directory. The layout is fixed by SCHEMA.md — place operator data under projects/, contacts/, or documents/, and route any reorganisation through the data-manager specialist." >&2
  exit 2
fi

# Over-deep check for operator-data buckets. Count path segments.
depth=$(printf '%s' "$REL" | awk -F/ '{print NF}')
case "$SEG0" in
  projects|contacts)
    # Allowed: <bucket>/<entity>/<file> = 3 segments max; <bucket>/<file> = 2 ok.
    if [ "$depth" -gt 3 ]; then
      echo "[fs-guard] blocked path=$REL reason=over-deep" >&2
      echo "Blocked: $SEG0/ is flat — one folder per entity, then files. '$REL' nests a subtree under an entity folder, which has no basis in the graph ontology. Keep files directly under $SEG0/<name>/, or route a reorganisation through the data-manager specialist." >&2
      exit 2
    fi
    ;;
  documents)
    # Allowed: documents/<file> = 2, documents/<folder>/<file> = 3 max.
    if [ "$depth" -gt 3 ]; then
      echo "[fs-guard] blocked path=$REL reason=over-deep" >&2
      echo "Blocked: documents/ holds files or one folder deep. '$REL' nests deeper, which has no basis in the graph ontology. Route a reorganisation through the data-manager specialist." >&2
      exit 2
    fi
    ;;
esac

exit 0
```

- [ ] **Step 4: Make the hook executable and run the test**

Run:
```bash
chmod +x platform/plugins/admin/hooks/fs-schema-guard.sh
bash platform/plugins/admin/hooks/__tests__/fs-schema-guard.test.sh
```
Expected: `----- 12 passed, 0 failed -----` and overall exit 0.

- [ ] **Step 5: Commit**

```bash
git add platform/plugins/admin/hooks/fs-schema-guard.sh platform/plugins/admin/hooks/__tests__/fs-schema-guard.test.sh
git commit -m "feat(account-schema): add fs-schema-guard PreToolUse write hook"
```

---

### Task 4: Seed the hook into the account settings.json

**Files:**
- Modify: `platform/scripts/lib/provision-account-dir.sh` (the `SETTINGS_EOF` heredoc)

- [ ] **Step 1: Add the hook to the three PreToolUse matchers**

In the `cat > "$ACCOUNT_SETTINGS"` heredoc, the `PreToolUse` array has three
blocks matching `Write`, `Edit`, and `NotebookEdit`, each with one hook running
`archive-ingest-surface-gate.sh`. Add a second hook command to each of those
three blocks so the block reads (Write shown; apply the same to Edit and
NotebookEdit):

```json
      {
        "matcher": "Write",
        "hooks": [
          { "type": "command", "command": "bash $HOOKS_PATH/archive-ingest-surface-gate.sh" },
          { "type": "command", "command": "bash $HOOKS_PATH/fs-schema-guard.sh" }
        ]
      },
```

- [ ] **Step 2: Verify the seeded settings.json is valid JSON and references the hook**

Run:
```bash
TMP=$(mktemp -d); export TEMPLATES_DIR="$PWD/platform/templates" PROJECT_DIR="$PWD/platform" SCRIPT_DIR="$PWD/platform/scripts" USERS_FILE="$TMP/users.json"
bash -c '. platform/scripts/lib/provision-account-dir.sh; provision_account_dir "'"$TMP"'/acct" testid house' >/dev/null 2>&1
python3 -c "import json;d=json.load(open('$TMP/acct/.claude/settings.json'));print('valid json')"
grep -c "fs-schema-guard.sh" "$TMP/acct/.claude/settings.json"
rm -rf "$TMP"
```
Expected: `valid json`; grep count `3` (one per Write/Edit/NotebookEdit matcher).

- [ ] **Step 3: Commit**

```bash
git add platform/scripts/lib/provision-account-dir.sh
git commit -m "feat(account-schema): seed fs-schema-guard into account settings"
```

---

### Task 5: Add the DATA_STEWARDSHIP directive + self-test

**Files:**
- Modify: `platform/services/claude-session-manager/src/system-prompt.ts`

- [ ] **Step 1: Add the DATA_STEWARDSHIP constant**

After the `PLATFORM_BOUNDARY` const (ends at the `].join(' ')` around line 78),
add:

```typescript
// The account data directory is a projection of the graph ontology with a fixed
// layout seeded as SCHEMA.md. Like PLATFORM_BOUNDARY this lives here because the
// append-system-prompt block is the only doctrine surface that survives SDK
// upgrades and per-account IDENTITY edits, so the directive holds on every spawn
// and every role. The seeded SCHEMA.md and a PreToolUse write guard enforce the
// layout; this directive names the rule so the hard layer is not a surprise.
export const DATA_STEWARDSHIP = [
  'Your account data directory has a fixed layout, recorded in its SCHEMA.md: operator data lives under projects/, contacts/, and documents/ (one flat folder per entity, then files); other top-level directories are owned by tools and are off-limits to reorganisation.',
  'Never author a new top-level folder, and never nest a subtree under an entity folder (e.g. projects/<name>/deploy/). A write that does will be blocked.',
  'Route directory reorganisation and file moves through the data-manager specialist, and non-trivial graph writes through database-operator. A one-line update against an in-context, unambiguously-classified node stays direct.',
].join(' ')
```

- [ ] **Step 2: Render the section in renderAppendBlock**

In the `sections` array, after the `<platform-boundary>` block (the three lines
`'<platform-boundary>', PLATFORM_BOUNDARY, '</platform-boundary>',`), add:

```typescript
    '<data-stewardship>',
    DATA_STEWARDSHIP,
    '</data-stewardship>',
```

- [ ] **Step 3: Add the self-test assertion**

In `runSystemPromptSelfTest`, add `'<data-stewardship>'` and
`'</data-stewardship>'` to the `required` tags array (e.g. right after the
`</soul>` entry, or anywhere in the list).

- [ ] **Step 4: Build and run the self-test**

Run:
```bash
cd platform/services/claude-session-manager
npm run build 2>&1 | tail -5
node -e "const m=require('./dist/system-prompt.js'); const r=m.runSystemPromptSelfTest(); if(!r.ok){console.error('SELF-TEST FAIL',r.missing);process.exit(1)} const b=m.DATA_STEWARDSHIP; if(!b||!b.includes('SCHEMA.md')){console.error('DATA_STEWARDSHIP missing');process.exit(1)} console.log('self-test ok, DATA_STEWARDSHIP present')"
cd "$(git rev-parse --show-toplevel)/maxy-code" 2>/dev/null || cd ../../..
```
Expected: build succeeds; `self-test ok, DATA_STEWARDSHIP present`.

(If `npm run build` needs deps, run `npm install` in
`platform/services/claude-session-manager` first. If the package emits ESM, use
the import form the package's other test scripts use instead of `require`.)

- [ ] **Step 5: Commit**

```bash
git add platform/services/claude-session-manager/src/system-prompt.ts
git commit -m "feat(account-schema): add DATA_STEWARDSHIP directive + self-test"
```

---

### Task 6: Confirm the template ships in the installer payload

**Files:**
- Inspect only (no code change unless the payload excludes templates/).

- [ ] **Step 1: Verify templates/ is bundled into the installer payload**

Run:
```bash
cd "$(git rev-parse --show-toplevel)/maxy-code"
grep -rn "templates" packages/create-maxy-code/scripts/bundle.js | head
ls packages/create-maxy-code/payload/platform/templates/account-schema 2>/dev/null || echo "payload not built yet (built at bundle time)"
```
Expected: the bundle copies `platform/templates/` recursively (so
`account-schema/SCHEMA.md` is included). If the bundle uses an explicit
allow-list that names specific template subdirs, add `account-schema` to it and
commit; otherwise no change is needed.

- [ ] **Step 2: Commit only if the bundle needed an edit**

```bash
git add packages/create-maxy-code/scripts/bundle.js
git commit -m "chore(account-schema): include account-schema template in payload"
```

---

## Self-Review

**Spec coverage:**
- A. Seeded schema + skeleton → Tasks 1, 2 (+ Task 6 ships it). ✓
- B. DATA_STEWARDSHIP directive + self-test → Task 5. ✓
- C. PreToolUse Write-hook + seeding into settings → Tasks 3, 4. ✓
- C1 chattr → dropped (gating decision); no task. ✓
- D. Backstop reconcile → Task 1092, out of scope. ✓
- Observability `[acct-schema] seeded dirs=<n>` → Task 2; `[fs-guard] blocked …` → Task 3. ✓
- Allowed top-level set single-sourced + drift test → Tasks 1, 3. ✓

**Placeholder scan:** none — every step carries real content and exact commands.

**Type/name consistency:** hook filename `fs-schema-guard.sh`, log tags
`[acct-schema]` / `[fs-guard]`, fence label `allowed-top-level`, const
`DATA_STEWARDSHIP`, section tag `<data-stewardship>` — used identically across
all tasks.
