@rubytech/create-maxy-code 0.1.349 → 0.1.350

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (22) hide show
  1. package/package.json +1 -1
  2. package/payload/platform/docs/superpowers/plans/2026-06-23-account-filesystem-schema.md +544 -0
  3. package/payload/platform/docs/superpowers/specs/2026-06-23-account-filesystem-schema-design.md +170 -0
  4. package/payload/platform/plugins/admin/hooks/__tests__/fs-schema-guard.test.sh +68 -0
  5. package/payload/platform/plugins/admin/hooks/fs-schema-guard.sh +108 -0
  6. package/payload/platform/scripts/lib/provision-account-dir.sh +22 -3
  7. package/payload/platform/services/claude-session-manager/dist/http-server.d.ts.map +1 -1
  8. package/payload/platform/services/claude-session-manager/dist/http-server.js +45 -1
  9. package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
  10. package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts +11 -0
  11. package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts.map +1 -1
  12. package/payload/platform/services/claude-session-manager/dist/pty-spawner.js +18 -0
  13. package/payload/platform/services/claude-session-manager/dist/pty-spawner.js.map +1 -1
  14. package/payload/platform/services/claude-session-manager/dist/system-prompt.d.ts +1 -0
  15. package/payload/platform/services/claude-session-manager/dist/system-prompt.d.ts.map +1 -1
  16. package/payload/platform/services/claude-session-manager/dist/system-prompt.js +16 -0
  17. package/payload/platform/services/claude-session-manager/dist/system-prompt.js.map +1 -1
  18. package/payload/platform/templates/account-schema/SCHEMA.md +54 -0
  19. package/payload/server/public/assets/data-CBrgiyPM.js +1 -0
  20. package/payload/server/public/data.html +1 -1
  21. package/payload/server/server.js +60 -16
  22. package/payload/server/public/assets/data-CH6GNBO3.js +0 -1
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@rubytech/create-maxy-code",
3
- "version": "0.1.349",
3
+ "version": "0.1.350",
4
4
  "description": "Install Maxy — AI for Productive People",
5
5
  "bin": {
6
6
  "create-maxy-code": "./dist/index.js"
@@ -0,0 +1,544 @@
1
+ # Account Filesystem Schema Implementation Plan
2
+
3
+ > **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.
4
+
5
+ **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.
6
+
7
+ **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.
8
+
9
+ **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`).
10
+
11
+ ---
12
+
13
+ ## File Structure
14
+
15
+ - **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).
16
+ - **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.
17
+ - **Create** `platform/plugins/admin/hooks/fs-schema-guard.sh` — the PreToolUse hook.
18
+ - **Create** `platform/plugins/admin/hooks/__tests__/fs-schema-guard.test.sh` — committed regression test (matches the existing `__tests__/` convention).
19
+ - **Modify** `platform/services/claude-session-manager/src/system-prompt.ts` — `DATA_STEWARDSHIP` const, `<data-stewardship>` section in `renderAppendBlock`, self-test assertion.
20
+
21
+ 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.
22
+
23
+ ---
24
+
25
+ ### Task 1: Ship the SCHEMA.md template (single source of the allowed set)
26
+
27
+ **Files:**
28
+ - Create: `platform/templates/account-schema/SCHEMA.md`
29
+
30
+ - [ ] **Step 1: Write the template file**
31
+
32
+ Create `platform/templates/account-schema/SCHEMA.md` with this exact content:
33
+
34
+ ````markdown
35
+ # Account data directory — file schema
36
+
37
+ This directory is a projection of the graph ontology. Its top-level layout is
38
+ fixed. Do not author new top-level folders, and do not nest subtrees under an
39
+ entity folder. Reorganisation and file moves go through the `data-manager`
40
+ specialist; the directory layout below is the standard it keeps you consistent
41
+ with.
42
+
43
+ ## Operator-data buckets (entity-anchored, flat)
44
+
45
+ - `projects/<name>/` — one flat subfolder per Project node. Files live directly
46
+ inside `projects/<name>/`. No deeper subtree.
47
+ - `contacts/<name>/` — one flat subfolder per Person or Organization node.
48
+ - `documents/` — KnowledgeDocument files and loose uploads. Files live directly
49
+ in `documents/`, or one document-folder deep (`documents/<doc>/file`).
50
+
51
+ ## Tool-owned (off-limits — never reorganise)
52
+
53
+ The internal structure of these is owned by the writing tool and is recreated on
54
+ the next write: `url-get/`, `output/`, `generated/`, `extracted/`, `uploads/`,
55
+ any published/served tree (`sites/`, `public/`), `agents/`, `specialists/`, and
56
+ platform-internal areas (`cache/`, `secrets/`, `state/`, `logs/`, `tmp/`).
57
+
58
+ ## Allowed top-level entries
59
+
60
+ The set below is the complete list of permitted top-level entries. A write whose
61
+ first path segment is not in this set is blocked. A write under an operator-data
62
+ bucket deeper than the flatness rule above is blocked. The list is enforced by a
63
+ write-path guard and reconciled periodically.
64
+
65
+ ```allowed-top-level
66
+ projects
67
+ contacts
68
+ documents
69
+ url-get
70
+ output
71
+ generated
72
+ extracted
73
+ uploads
74
+ agents
75
+ specialists
76
+ sites
77
+ public
78
+ cache
79
+ secrets
80
+ state
81
+ logs
82
+ tmp
83
+ SCHEMA.md
84
+ account.json
85
+ AGENTS.md
86
+ .claude
87
+ .git
88
+ .quarantine
89
+ ```
90
+ ````
91
+
92
+ - [ ] **Step 2: Verify the fenced block parses to the expected set**
93
+
94
+ Run:
95
+ ```bash
96
+ cd platform/templates/account-schema
97
+ awk '/^```allowed-top-level$/{f=1;next} /^```$/{f=0} f' SCHEMA.md | tr '\n' ' '
98
+ ```
99
+ Expected output (exact):
100
+ `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 `
101
+
102
+ - [ ] **Step 3: Commit**
103
+
104
+ ```bash
105
+ git add platform/templates/account-schema/SCHEMA.md
106
+ git commit -m "feat(account-schema): ship account-dir SCHEMA.md template"
107
+ ```
108
+
109
+ ---
110
+
111
+ ### Task 2: Seed the skeleton + SCHEMA.md in provision_account_dir
112
+
113
+ **Files:**
114
+ - Modify: `platform/scripts/lib/provision-account-dir.sh`
115
+
116
+ - [ ] **Step 1: Add the seed step**
117
+
118
+ In `provision-account-dir.sh`, immediately after the existing first `mkdir -p`
119
+ line (the one creating `agents/admin`, `.claude`, `specialists/...`), insert:
120
+
121
+ ```bash
122
+ # --- Account filesystem schema seed (idempotent) ---------------------------
123
+ # Operator-data buckets (entity-anchored, flat) + tool-owned dirs. agents/ and
124
+ # specialists/ are created above. The seeded SCHEMA.md is the single source of
125
+ # the allowed top-level set, parsed at runtime by the fs-schema write guard.
126
+ local _seed_dirs=(projects contacts documents url-get output generated extracted uploads)
127
+ local _d _seeded=0
128
+ for _d in "${_seed_dirs[@]}"; do
129
+ [ -d "$ACCOUNT_DIR/$_d" ] || { mkdir -p "$ACCOUNT_DIR/$_d" && _seeded=$((_seeded+1)); }
130
+ done
131
+ if [ -f "$TEMPLATES_DIR/account-schema/SCHEMA.md" ]; then
132
+ cp "$TEMPLATES_DIR/account-schema/SCHEMA.md" "$ACCOUNT_DIR/SCHEMA.md"
133
+ else
134
+ echo " [acct-schema] WARNING: template missing at $TEMPLATES_DIR/account-schema/SCHEMA.md — SCHEMA.md not seeded" >&2
135
+ fi
136
+ echo " [acct-schema] seeded dirs=$_seeded"
137
+ ```
138
+
139
+ - [ ] **Step 2: Verify the seed runs idempotently against a scratch account**
140
+
141
+ Run:
142
+ ```bash
143
+ TMP=$(mktemp -d); export TEMPLATES_DIR="$PWD/platform/templates" PROJECT_DIR="$PWD/platform" SCRIPT_DIR="$PWD/platform/scripts" USERS_FILE="$TMP/users.json"
144
+ bash -c '. platform/scripts/lib/provision-account-dir.sh; provision_account_dir "'"$TMP"'/acct" testid house' 2>&1 | grep acct-schema
145
+ ls "$TMP/acct" | sort | tr '\n' ' '; echo
146
+ test -f "$TMP/acct/SCHEMA.md" && echo "SCHEMA.md OK"
147
+ # second run is idempotent (seeded dirs=0)
148
+ bash -c '. platform/scripts/lib/provision-account-dir.sh; provision_account_dir "'"$TMP"'/acct" testid house' 2>&1 | grep acct-schema
149
+ rm -rf "$TMP"
150
+ ```
151
+ Expected: first run logs `[acct-schema] seeded dirs=8`; `ls` includes
152
+ `contacts documents extracted generated output projects uploads url-get` plus
153
+ `SCHEMA.md`; `SCHEMA.md OK`; second run logs `[acct-schema] seeded dirs=0`.
154
+
155
+ - [ ] **Step 3: Commit**
156
+
157
+ ```bash
158
+ git add platform/scripts/lib/provision-account-dir.sh
159
+ git commit -m "feat(account-schema): seed skeleton + SCHEMA.md in provision_account_dir"
160
+ ```
161
+
162
+ ---
163
+
164
+ ### Task 3: Write the fs-schema-guard hook (failing test first)
165
+
166
+ **Files:**
167
+ - Create: `platform/plugins/admin/hooks/__tests__/fs-schema-guard.test.sh`
168
+ - Create: `platform/plugins/admin/hooks/fs-schema-guard.sh`
169
+
170
+ - [ ] **Step 1: Write the failing test**
171
+
172
+ Create `platform/plugins/admin/hooks/__tests__/fs-schema-guard.test.sh`:
173
+
174
+ ```bash
175
+ #!/usr/bin/env bash
176
+ # Regression test for fs-schema-guard.sh.
177
+ #
178
+ # Covers:
179
+ # 1. Non-Write/Edit/NotebookEdit tool → ALLOW (early exit)
180
+ # 2. Write to projects/Acme/a.txt (flat) → ALLOW
181
+ # 3. Write to documents/a.pdf (loose) → ALLOW
182
+ # 4. Write to output/deep/nested/x.png (tool-owned) → ALLOW (any depth)
183
+ # 5. Write to projects/Acme/deploy/sources/a.txt → BLOCK over-deep
184
+ # 6. Write to projects/Acme/deploy/a.txt → BLOCK over-deep
185
+ # 7. Write to whim/x.txt (novel top-level) → BLOCK top-level
186
+ # 8. Write to an absolute path outside the account dir → ALLOW (not our concern)
187
+ # 9. NotebookEdit over-deep (notebook_path) → BLOCK over-deep
188
+ # 10. Terminal stdin (no envelope) → BLOCK fail-closed
189
+ # 11. Allowed-set parse matches the documented set → assertion
190
+ set -u
191
+
192
+ HOOK="$(cd "$(dirname "$0")/.." && pwd)/fs-schema-guard.sh"
193
+ [ -x "$HOOK" ] || { echo "FAIL: $HOOK not executable" >&2; exit 1; }
194
+
195
+ # A scratch account dir with a seeded SCHEMA.md (copied from the shipped template).
196
+ REPO_ROOT="$(cd "$(dirname "$0")/../../../.." && pwd)"
197
+ TEMPLATE="$REPO_ROOT/templates/account-schema/SCHEMA.md"
198
+ [ -f "$TEMPLATE" ] || { echo "FAIL: template missing at $TEMPLATE" >&2; exit 1; }
199
+ ACCT=$(mktemp -d)
200
+ trap 'rm -rf "$ACCT"' EXIT
201
+ cp "$TEMPLATE" "$ACCT/SCHEMA.md"
202
+ mkdir -p "$ACCT/projects/Acme" "$ACCT/documents" "$ACCT/output"
203
+
204
+ PASS=0; FAIL=0
205
+ # Run one case in the account dir as cwd.
206
+ # $1 name $2 stdin $3 expected_exit $4 expected_log_re ("" = none) $5 terminal(1=attach tty)
207
+ run_case() {
208
+ local name="$1" stdin="$2" exp="$3" re="$4" term="${5:-0}" ef actual
209
+ ef=$(mktemp)
210
+ if [ "$term" = "1" ]; then
211
+ ( cd "$ACCT" && bash "$HOOK" >/dev/null 2>"$ef" </dev/null ) # /dev/null is not a tty; see note
212
+ actual=$?
213
+ else
214
+ ( cd "$ACCT" && printf '%s' "$stdin" | bash "$HOOK" >/dev/null 2>"$ef" )
215
+ actual=$?
216
+ fi
217
+ local sc; sc=$(cat "$ef"); rm -f "$ef"
218
+ local ok=1
219
+ [ "$actual" -eq "$exp" ] || ok=0
220
+ if [ -n "$re" ] && ! printf '%s' "$sc" | grep -Eq "$re"; then ok=0; fi
221
+ if [ $ok -eq 1 ]; then echo "PASS: $name (exit=$actual)"; PASS=$((PASS+1));
222
+ else echo "FAIL: $name (exit=$actual, want $exp; log want /$re/)" >&2; FAIL=$((FAIL+1)); fi
223
+ }
224
+
225
+ env() { printf '{"hook_event_name":"PreToolUse","tool_name":"%s","tool_input":{"%s":"%s"}}' "$1" "$2" "$3"; }
226
+
227
+ run_case "non-write allow" "$(env Bash command 'ls')" 0 ""
228
+ run_case "flat project allow" "$(env Write file_path 'projects/Acme/a.txt')" 0 ""
229
+ run_case "loose document allow" "$(env Write file_path 'documents/a.pdf')" 0 ""
230
+ run_case "tool-owned deep allow" "$(env Write file_path 'output/deep/nested/x.png')" 0 ""
231
+ 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"
232
+ run_case "over-deep one block" "$(env Write file_path 'projects/Acme/deploy/a.txt')" 2 "reason=over-deep"
233
+ run_case "top-level block" "$(env Write file_path 'whim/x.txt')" 2 "fs-guard. blocked path=whim/x.txt reason=top-level"
234
+ run_case "outside acct allow" "$(env Write file_path '/etc/passwd')" 0 ""
235
+ run_case "notebook over-deep" "$(env NotebookEdit notebook_path 'projects/Acme/deploy/n.ipynb')" 2 "reason=over-deep"
236
+
237
+ # Terminal stdin → fail closed. printf nothing, attach /dev/null which the hook treats as non-tty;
238
+ # emulate true terminal via the -t test is impractical in CI, so assert the empty-input branch:
239
+ run_case "empty stdin block" "" 2 "no stdin"
240
+
241
+ # Allowed-set parse == documented set.
242
+ 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"
243
+ GOT=$(awk '/^```allowed-top-level$/{f=1;next} /^```$/{f=0} f' "$ACCT/SCHEMA.md" | tr '\n' ' ' | sed 's/ *$//')
244
+ if [ "$GOT" = "$EXPECT" ]; then echo "PASS: allowed-set parse"; PASS=$((PASS+1));
245
+ else echo "FAIL: allowed-set parse: got [$GOT]" >&2; FAIL=$((FAIL+1)); fi
246
+
247
+ echo "----- $PASS passed, $FAIL failed -----"
248
+ [ $FAIL -eq 0 ]
249
+ ```
250
+
251
+ Note on the "empty stdin block" case: the hook fails closed on empty input as
252
+ well as a tty (a non-write hook should never silently allow a write it cannot
253
+ inspect). The hook treats empty `INPUT` as an inspect-failure → block with
254
+ `no stdin` in the message.
255
+
256
+ - [ ] **Step 2: Run the test to verify it fails**
257
+
258
+ Run: `bash platform/plugins/admin/hooks/__tests__/fs-schema-guard.test.sh`
259
+ Expected: FAIL — `fs-schema-guard.sh not executable` (file does not exist yet).
260
+
261
+ - [ ] **Step 3: Write the hook**
262
+
263
+ Create `platform/plugins/admin/hooks/fs-schema-guard.sh`:
264
+
265
+ ```bash
266
+ #!/usr/bin/env bash
267
+ # fs-schema-guard — PreToolUse Write/Edit/NotebookEdit path guard.
268
+ #
269
+ # Enforces the seeded account-dir file schema (see <accountDir>/SCHEMA.md):
270
+ # - the target's first path segment must be in the allowed top-level set
271
+ # (parsed from the fenced ```allowed-top-level block of the account's
272
+ # SCHEMA.md — our own structured data, not CLI prose);
273
+ # - a target under an operator-data bucket (projects/ contacts/) may be at
274
+ # most <bucket>/<entity>/<file> deep; documents/ may be at most
275
+ # <bucket>/<folder>/<file> deep. Tool-owned dirs pass at any depth.
276
+ #
277
+ # The hook governs the account dir only. A path resolving outside the account
278
+ # dir (cwd) is allowed — platform-source writes are PLATFORM_BOUNDARY's concern.
279
+ #
280
+ # Exit codes: 0 = allow, 2 = block (stderr shown to the agent). Fail closed when
281
+ # the tool call cannot be inspected (tty or empty stdin). Every block logs
282
+ # [fs-guard] blocked path=<rel> reason=<top-level|over-deep>
283
+ # No task numbers / internal refs in any operator-visible string.
284
+
285
+ set -uo pipefail
286
+
287
+ # Fail closed if attached to a terminal (no JSON envelope coming).
288
+ if [ -t 0 ]; then
289
+ echo "Blocked: fs-schema-guard received no stdin (cannot inspect the write). Failing closed." >&2
290
+ exit 2
291
+ fi
292
+ INPUT=$(cat)
293
+ if [ -z "$INPUT" ]; then
294
+ echo "Blocked: fs-schema-guard received no stdin (cannot inspect the write). Failing closed." >&2
295
+ exit 2
296
+ fi
297
+
298
+ field() {
299
+ printf '%s' "$INPUT" | python3 -c "
300
+ import sys, json
301
+ try:
302
+ d = json.load(sys.stdin)
303
+ print(d.get('$1', {}).get('$2', '') if '$1' else d.get('$2', ''))
304
+ except Exception:
305
+ print('')
306
+ " 2>/dev/null || echo ""
307
+ }
308
+
309
+ TOOL_NAME=$(field '' tool_name)
310
+ case "$TOOL_NAME" in
311
+ Write|Edit) FILE_PATH=$(field tool_input file_path) ;;
312
+ NotebookEdit) FILE_PATH=$(field tool_input notebook_path) ;;
313
+ *) exit 0 ;;
314
+ esac
315
+ [ -z "$FILE_PATH" ] && exit 0
316
+
317
+ ACCOUNT_DIR="$PWD"
318
+
319
+ # Resolve to an account-relative path; allow anything outside the account dir.
320
+ REL=$(FILE_PATH="$FILE_PATH" ACCOUNT_DIR="$ACCOUNT_DIR" python3 -c '
321
+ import os, sys
322
+ fp = os.environ["FILE_PATH"]; acct = os.environ["ACCOUNT_DIR"]
323
+ ab = fp if os.path.isabs(fp) else os.path.join(acct, fp)
324
+ ab = os.path.normpath(ab); acct = os.path.normpath(acct)
325
+ if ab == acct or not ab.startswith(acct + os.sep):
326
+ print("") # outside the account dir (or the dir itself)
327
+ else:
328
+ print(os.path.relpath(ab, acct))
329
+ ' 2>/dev/null || echo "")
330
+ [ -z "$REL" ] && exit 0
331
+
332
+ # Parse the allowed top-level set from the account's seeded SCHEMA.md.
333
+ ALLOWED=""
334
+ if [ -f "$ACCOUNT_DIR/SCHEMA.md" ]; then
335
+ ALLOWED=$(awk '/^```allowed-top-level$/{f=1;next} /^```$/{f=0} f' "$ACCOUNT_DIR/SCHEMA.md")
336
+ fi
337
+ # Fail open on a missing/empty schema (an un-seeded legacy account must not have
338
+ # every write blocked); the periodic reconcile is the backstop there.
339
+ [ -z "$ALLOWED" ] && exit 0
340
+
341
+ SEG0=${REL%%/*}
342
+
343
+ # Top-level check.
344
+ if ! printf '%s\n' "$ALLOWED" | grep -qxF "$SEG0"; then
345
+ echo "[fs-guard] blocked path=$REL reason=top-level" >&2
346
+ 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
347
+ exit 2
348
+ fi
349
+
350
+ # Over-deep check for operator-data buckets. Count path segments.
351
+ depth=$(printf '%s' "$REL" | awk -F/ '{print NF}')
352
+ case "$SEG0" in
353
+ projects|contacts)
354
+ # Allowed: <bucket>/<entity>/<file> = 3 segments max; <bucket>/<file> = 2 ok.
355
+ if [ "$depth" -gt 3 ]; then
356
+ echo "[fs-guard] blocked path=$REL reason=over-deep" >&2
357
+ 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
358
+ exit 2
359
+ fi
360
+ ;;
361
+ documents)
362
+ # Allowed: documents/<file> = 2, documents/<folder>/<file> = 3 max.
363
+ if [ "$depth" -gt 3 ]; then
364
+ echo "[fs-guard] blocked path=$REL reason=over-deep" >&2
365
+ 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
366
+ exit 2
367
+ fi
368
+ ;;
369
+ esac
370
+
371
+ exit 0
372
+ ```
373
+
374
+ - [ ] **Step 4: Make the hook executable and run the test**
375
+
376
+ Run:
377
+ ```bash
378
+ chmod +x platform/plugins/admin/hooks/fs-schema-guard.sh
379
+ bash platform/plugins/admin/hooks/__tests__/fs-schema-guard.test.sh
380
+ ```
381
+ Expected: `----- 12 passed, 0 failed -----` and overall exit 0.
382
+
383
+ - [ ] **Step 5: Commit**
384
+
385
+ ```bash
386
+ git add platform/plugins/admin/hooks/fs-schema-guard.sh platform/plugins/admin/hooks/__tests__/fs-schema-guard.test.sh
387
+ git commit -m "feat(account-schema): add fs-schema-guard PreToolUse write hook"
388
+ ```
389
+
390
+ ---
391
+
392
+ ### Task 4: Seed the hook into the account settings.json
393
+
394
+ **Files:**
395
+ - Modify: `platform/scripts/lib/provision-account-dir.sh` (the `SETTINGS_EOF` heredoc)
396
+
397
+ - [ ] **Step 1: Add the hook to the three PreToolUse matchers**
398
+
399
+ In the `cat > "$ACCOUNT_SETTINGS"` heredoc, the `PreToolUse` array has three
400
+ blocks matching `Write`, `Edit`, and `NotebookEdit`, each with one hook running
401
+ `archive-ingest-surface-gate.sh`. Add a second hook command to each of those
402
+ three blocks so the block reads (Write shown; apply the same to Edit and
403
+ NotebookEdit):
404
+
405
+ ```json
406
+ {
407
+ "matcher": "Write",
408
+ "hooks": [
409
+ { "type": "command", "command": "bash $HOOKS_PATH/archive-ingest-surface-gate.sh" },
410
+ { "type": "command", "command": "bash $HOOKS_PATH/fs-schema-guard.sh" }
411
+ ]
412
+ },
413
+ ```
414
+
415
+ - [ ] **Step 2: Verify the seeded settings.json is valid JSON and references the hook**
416
+
417
+ Run:
418
+ ```bash
419
+ TMP=$(mktemp -d); export TEMPLATES_DIR="$PWD/platform/templates" PROJECT_DIR="$PWD/platform" SCRIPT_DIR="$PWD/platform/scripts" USERS_FILE="$TMP/users.json"
420
+ bash -c '. platform/scripts/lib/provision-account-dir.sh; provision_account_dir "'"$TMP"'/acct" testid house' >/dev/null 2>&1
421
+ python3 -c "import json;d=json.load(open('$TMP/acct/.claude/settings.json'));print('valid json')"
422
+ grep -c "fs-schema-guard.sh" "$TMP/acct/.claude/settings.json"
423
+ rm -rf "$TMP"
424
+ ```
425
+ Expected: `valid json`; grep count `3` (one per Write/Edit/NotebookEdit matcher).
426
+
427
+ - [ ] **Step 3: Commit**
428
+
429
+ ```bash
430
+ git add platform/scripts/lib/provision-account-dir.sh
431
+ git commit -m "feat(account-schema): seed fs-schema-guard into account settings"
432
+ ```
433
+
434
+ ---
435
+
436
+ ### Task 5: Add the DATA_STEWARDSHIP directive + self-test
437
+
438
+ **Files:**
439
+ - Modify: `platform/services/claude-session-manager/src/system-prompt.ts`
440
+
441
+ - [ ] **Step 1: Add the DATA_STEWARDSHIP constant**
442
+
443
+ After the `PLATFORM_BOUNDARY` const (ends at the `].join(' ')` around line 78),
444
+ add:
445
+
446
+ ```typescript
447
+ // The account data directory is a projection of the graph ontology with a fixed
448
+ // layout seeded as SCHEMA.md. Like PLATFORM_BOUNDARY this lives here because the
449
+ // append-system-prompt block is the only doctrine surface that survives SDK
450
+ // upgrades and per-account IDENTITY edits, so the directive holds on every spawn
451
+ // and every role. The seeded SCHEMA.md and a PreToolUse write guard enforce the
452
+ // layout; this directive names the rule so the hard layer is not a surprise.
453
+ export const DATA_STEWARDSHIP = [
454
+ '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.',
455
+ '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.',
456
+ '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.',
457
+ ].join(' ')
458
+ ```
459
+
460
+ - [ ] **Step 2: Render the section in renderAppendBlock**
461
+
462
+ In the `sections` array, after the `<platform-boundary>` block (the three lines
463
+ `'<platform-boundary>', PLATFORM_BOUNDARY, '</platform-boundary>',`), add:
464
+
465
+ ```typescript
466
+ '<data-stewardship>',
467
+ DATA_STEWARDSHIP,
468
+ '</data-stewardship>',
469
+ ```
470
+
471
+ - [ ] **Step 3: Add the self-test assertion**
472
+
473
+ In `runSystemPromptSelfTest`, add `'<data-stewardship>'` and
474
+ `'</data-stewardship>'` to the `required` tags array (e.g. right after the
475
+ `</soul>` entry, or anywhere in the list).
476
+
477
+ - [ ] **Step 4: Build and run the self-test**
478
+
479
+ Run:
480
+ ```bash
481
+ cd platform/services/claude-session-manager
482
+ npm run build 2>&1 | tail -5
483
+ 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')"
484
+ cd "$(git rev-parse --show-toplevel)/maxy-code" 2>/dev/null || cd ../../..
485
+ ```
486
+ Expected: build succeeds; `self-test ok, DATA_STEWARDSHIP present`.
487
+
488
+ (If `npm run build` needs deps, run `npm install` in
489
+ `platform/services/claude-session-manager` first. If the package emits ESM, use
490
+ the import form the package's other test scripts use instead of `require`.)
491
+
492
+ - [ ] **Step 5: Commit**
493
+
494
+ ```bash
495
+ git add platform/services/claude-session-manager/src/system-prompt.ts
496
+ git commit -m "feat(account-schema): add DATA_STEWARDSHIP directive + self-test"
497
+ ```
498
+
499
+ ---
500
+
501
+ ### Task 6: Confirm the template ships in the installer payload
502
+
503
+ **Files:**
504
+ - Inspect only (no code change unless the payload excludes templates/).
505
+
506
+ - [ ] **Step 1: Verify templates/ is bundled into the installer payload**
507
+
508
+ Run:
509
+ ```bash
510
+ cd "$(git rev-parse --show-toplevel)/maxy-code"
511
+ grep -rn "templates" packages/create-maxy-code/scripts/bundle.js | head
512
+ ls packages/create-maxy-code/payload/platform/templates/account-schema 2>/dev/null || echo "payload not built yet (built at bundle time)"
513
+ ```
514
+ Expected: the bundle copies `platform/templates/` recursively (so
515
+ `account-schema/SCHEMA.md` is included). If the bundle uses an explicit
516
+ allow-list that names specific template subdirs, add `account-schema` to it and
517
+ commit; otherwise no change is needed.
518
+
519
+ - [ ] **Step 2: Commit only if the bundle needed an edit**
520
+
521
+ ```bash
522
+ git add packages/create-maxy-code/scripts/bundle.js
523
+ git commit -m "chore(account-schema): include account-schema template in payload"
524
+ ```
525
+
526
+ ---
527
+
528
+ ## Self-Review
529
+
530
+ **Spec coverage:**
531
+ - A. Seeded schema + skeleton → Tasks 1, 2 (+ Task 6 ships it). ✓
532
+ - B. DATA_STEWARDSHIP directive + self-test → Task 5. ✓
533
+ - C. PreToolUse Write-hook + seeding into settings → Tasks 3, 4. ✓
534
+ - C1 chattr → dropped (gating decision); no task. ✓
535
+ - D. Backstop reconcile → Task 1092, out of scope. ✓
536
+ - Observability `[acct-schema] seeded dirs=<n>` → Task 2; `[fs-guard] blocked …` → Task 3. ✓
537
+ - Allowed top-level set single-sourced + drift test → Tasks 1, 3. ✓
538
+
539
+ **Placeholder scan:** none — every step carries real content and exact commands.
540
+
541
+ **Type/name consistency:** hook filename `fs-schema-guard.sh`, log tags
542
+ `[acct-schema]` / `[fs-guard]`, fence label `allowed-top-level`, const
543
+ `DATA_STEWARDSHIP`, section tag `<data-stewardship>` — used identically across
544
+ all tasks.