@rubytech/create-maxy-code 0.1.348 → 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.
- package/package.json +1 -1
- package/payload/platform/docs/superpowers/plans/2026-06-23-account-filesystem-schema.md +544 -0
- package/payload/platform/docs/superpowers/specs/2026-06-23-account-filesystem-schema-design.md +170 -0
- package/payload/platform/lib/graph-style/dist/index.d.ts +1 -1
- package/payload/platform/lib/graph-style/dist/index.d.ts.map +1 -1
- package/payload/platform/lib/graph-style/dist/index.js +20 -12
- package/payload/platform/lib/graph-style/dist/index.js.map +1 -1
- package/payload/platform/lib/graph-style/src/__tests__/parity.test.ts +9 -9
- package/payload/platform/lib/graph-style/src/index.ts +20 -12
- package/payload/platform/lib/graph-write/dist/index.d.ts.map +1 -1
- package/payload/platform/lib/graph-write/dist/index.js +3 -2
- package/payload/platform/lib/graph-write/dist/index.js.map +1 -1
- package/payload/platform/lib/graph-write/src/index.ts +3 -2
- package/payload/platform/neo4j/schema.cypher +110 -31
- package/payload/platform/plugins/.claude-plugin/marketplace.json +5 -0
- package/payload/platform/plugins/admin/hooks/__tests__/fs-schema-guard.test.sh +68 -0
- package/payload/platform/plugins/admin/hooks/fs-schema-guard.sh +108 -0
- package/payload/platform/plugins/admin/hooks/lib/maxy-mcp-plugins.txt +1 -0
- package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +36 -3
- package/payload/platform/plugins/docs/references/calendar-booking.md +1 -1
- package/payload/platform/plugins/docs/references/joblogic.md +28 -0
- package/payload/platform/plugins/docs/references/memory-guide.md +1 -1
- package/payload/platform/plugins/joblogic/.claude-plugin/plugin.json +21 -0
- package/payload/platform/plugins/joblogic/PLUGIN.md +182 -0
- package/payload/platform/plugins/joblogic/lib/mcp-spawn-tee/index.js +193 -0
- package/payload/platform/plugins/joblogic/lib/mcp-spawn-tee/package.json +3 -0
- package/payload/platform/plugins/joblogic/mcp/dist/index.d.ts +2 -0
- package/payload/platform/plugins/joblogic/mcp/dist/index.d.ts.map +1 -0
- package/payload/platform/plugins/joblogic/mcp/dist/index.js +229 -0
- package/payload/platform/plugins/joblogic/mcp/dist/index.js.map +1 -0
- package/payload/platform/plugins/joblogic/mcp/dist/lib/auth.d.ts +31 -0
- package/payload/platform/plugins/joblogic/mcp/dist/lib/auth.d.ts.map +1 -0
- package/payload/platform/plugins/joblogic/mcp/dist/lib/auth.js +78 -0
- package/payload/platform/plugins/joblogic/mcp/dist/lib/auth.js.map +1 -0
- package/payload/platform/plugins/joblogic/mcp/dist/lib/client.d.ts +35 -0
- package/payload/platform/plugins/joblogic/mcp/dist/lib/client.d.ts.map +1 -0
- package/payload/platform/plugins/joblogic/mcp/dist/lib/client.js +106 -0
- package/payload/platform/plugins/joblogic/mcp/dist/lib/client.js.map +1 -0
- package/payload/platform/plugins/joblogic/mcp/dist/lib/idempotency.d.ts +8 -0
- package/payload/platform/plugins/joblogic/mcp/dist/lib/idempotency.d.ts.map +1 -0
- package/payload/platform/plugins/joblogic/mcp/dist/lib/idempotency.js +41 -0
- package/payload/platform/plugins/joblogic/mcp/dist/lib/idempotency.js.map +1 -0
- package/payload/platform/plugins/joblogic/mcp/dist/lib/secrets.d.ts +21 -0
- package/payload/platform/plugins/joblogic/mcp/dist/lib/secrets.d.ts.map +1 -0
- package/payload/platform/plugins/joblogic/mcp/dist/lib/secrets.js +47 -0
- package/payload/platform/plugins/joblogic/mcp/dist/lib/secrets.js.map +1 -0
- package/payload/platform/plugins/joblogic/mcp/package.json +10 -0
- package/payload/platform/plugins/joblogic/skills/joblogic/SKILL.md +32 -0
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/relationship-patterns.test.js +28 -22
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/relationship-patterns.test.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-cypher-drift.test.js +8 -4
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-cypher-drift.test.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-write-path-task802.test.js +39 -10
- package/payload/platform/plugins/memory/mcp/dist/lib/__tests__/schema-write-path-task802.test.js.map +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/relationship-patterns.d.ts +1 -1
- package/payload/platform/plugins/memory/mcp/dist/lib/relationship-patterns.js +1 -1
- package/payload/platform/plugins/memory/references/schema-construction.md +152 -57
- package/payload/platform/plugins/memory/references/schema-trades.md +22 -24
- package/payload/platform/scripts/lib/provision-account-dir.sh +22 -3
- package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js +46 -0
- package/payload/platform/services/claude-session-manager/dist/canonical-tool-names.generated.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/http-server.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/http-server.js +45 -1
- package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts +11 -0
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.js +18 -0
- package/payload/platform/services/claude-session-manager/dist/pty-spawner.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/system-prompt.d.ts +1 -0
- package/payload/platform/services/claude-session-manager/dist/system-prompt.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/system-prompt.js +16 -0
- package/payload/platform/services/claude-session-manager/dist/system-prompt.js.map +1 -1
- package/payload/platform/templates/account-schema/SCHEMA.md +54 -0
- package/payload/server/public/assets/{AdminLoginScreens-B_5wYcR-.js → AdminLoginScreens-BEzoTk56.js} +1 -1
- package/payload/server/public/assets/{AdminShell-CMV3XY-s.js → AdminShell-BG7YD4UO.js} +1 -1
- package/payload/server/public/assets/{Checkbox-LplFUKpb.js → Checkbox-CvTFOczj.js} +1 -1
- package/payload/server/public/assets/{OperatorConversations-C4Cx814-.css → OperatorConversations-CnP9Y6g2.css} +1 -1
- package/payload/server/public/assets/{admin-3QQtd7j2.js → admin-Bhxa1HOy.js} +1 -1
- package/payload/server/public/assets/{browser-D5q26eZL.js → browser-BAQFuyma.js} +1 -1
- package/payload/server/public/assets/calendar-sSr6zUjW.js +1 -0
- package/payload/server/public/assets/chat-ZH4Fsyu0.js +1 -0
- package/payload/server/public/assets/data-CBrgiyPM.js +1 -0
- package/payload/server/public/assets/{graph-DFQbqmi9.js → graph-DbGjFYPS.js} +1 -1
- package/payload/server/public/assets/graph-labels-IGIEr-uc.js +1 -0
- package/payload/server/public/assets/{operator-BlCyq4q1.js → operator-Ba5afDyM.js} +1 -1
- package/payload/server/public/assets/{page-drg1V-yA.js → page-D7uwMUOy.js} +1 -1
- package/payload/server/public/assets/{public-BVjeYfsD.js → public-DikYWzOd.js} +1 -1
- package/payload/server/public/browser.html +4 -4
- package/payload/server/public/calendar.html +4 -4
- package/payload/server/public/chat.html +5 -5
- package/payload/server/public/data.html +4 -4
- package/payload/server/public/graph.html +6 -6
- package/payload/server/public/index.html +6 -6
- package/payload/server/public/operator.html +7 -7
- package/payload/server/public/public.html +5 -5
- package/payload/server/server.js +92 -31
- package/payload/server/public/assets/calendar-Csbdj7rp.js +0 -1
- package/payload/server/public/assets/chat-Ct86qtMI.js +0 -1
- package/payload/server/public/assets/data-HwT5wPmj.js +0 -1
- package/payload/server/public/assets/graph-labels-kCti2CaC.js +0 -1
- /package/payload/server/public/assets/{OperatorConversations-Bp-g80PM.js → OperatorConversations-DU8CqO-z.js} +0 -0
package/package.json
CHANGED
|
@@ -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.
|