@rubytech/create-maxy-code 0.1.561 → 0.1.562
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/plugins/admin/hooks/__tests__/datetime-inject.test.sh +10 -0
- package/payload/platform/plugins/admin/hooks/__tests__/mailbox-inject.test.sh +15 -2
- package/payload/platform/plugins/admin/hooks/__tests__/preference-consult-directive.test.sh +6 -0
- package/payload/platform/plugins/admin/hooks/__tests__/prompt-optimiser-directive.test.sh +10 -0
- package/payload/platform/plugins/admin/hooks/datetime-inject.sh +10 -0
- package/payload/platform/plugins/admin/hooks/mailbox-inject.sh +14 -0
- package/payload/platform/plugins/admin/hooks/preference-consult-directive.sh +9 -0
- package/payload/platform/plugins/admin/hooks/prompt-optimiser-directive.sh +10 -0
- package/payload/platform/plugins/admin/skills/platform-architecture/SKILL.md +3 -1
- package/payload/platform/plugins/admin/skills/whats-new/SKILL.md +5 -0
- package/payload/platform/plugins/docs/references/internals.md +2 -0
- package/payload/platform/scripts/__tests__/public-prompt-gate-registered.test.sh +114 -0
- package/payload/platform/scripts/smoke-boot-services.sh +14 -0
- 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 +26 -10
- package/payload/platform/services/claude-session-manager/dist/http-server.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/index.js +16 -0
- package/payload/platform/services/claude-session-manager/dist/index.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/public-prompt-surface.d.ts +56 -0
- package/payload/platform/services/claude-session-manager/dist/public-prompt-surface.d.ts.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/public-prompt-surface.js +155 -0
- package/payload/platform/services/claude-session-manager/dist/public-prompt-surface.js.map +1 -0
- package/payload/platform/services/claude-session-manager/dist/rc-public-lockdown.d.ts +6 -0
- package/payload/platform/services/claude-session-manager/dist/rc-public-lockdown.d.ts.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/rc-public-lockdown.js +1 -1
- package/payload/platform/services/claude-session-manager/dist/rc-public-lockdown.js.map +1 -1
- package/payload/platform/services/claude-session-manager/dist/system-prompt.d.ts +4 -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 +45 -27
- package/payload/platform/services/claude-session-manager/dist/system-prompt.js.map +1 -1
package/package.json
CHANGED
|
@@ -114,6 +114,16 @@ else
|
|
|
114
114
|
fi
|
|
115
115
|
rm -rf "$FAKEBIN2"
|
|
116
116
|
|
|
117
|
+
# Task 2424 — a public visitor's turn carries only its agent's authored
|
|
118
|
+
# IDENTITY/SOUL/KNOWLEDGE. MAXY_PUBLIC_ALLOWED_TOOLS is stamped for role=public
|
|
119
|
+
# spawns only, by both spawn paths.
|
|
120
|
+
pub_out=$(printf '%s' '{"session_id":"x","prompt":"hi"}' | MAXY_PUBLIC_ALLOWED_TOOLS='mcp__channel__reply' bash "$HOOK" 2>/dev/null); pub_rc=$?
|
|
121
|
+
if [[ "$pub_rc" -eq 0 && -z "$pub_out" ]]; then
|
|
122
|
+
echo "PASS: public turn exits 0 with no stdout"; PASS=$((PASS+1))
|
|
123
|
+
else
|
|
124
|
+
echo "FAIL: public turn injected (exit=$pub_rc stdout=$pub_out)" >&2; FAIL=$((FAIL+1))
|
|
125
|
+
fi
|
|
126
|
+
|
|
117
127
|
echo "----"
|
|
118
128
|
echo "PASS=$PASS FAIL=$FAIL"
|
|
119
129
|
[[ "$FAIL" -eq 0 ]]
|
|
@@ -493,13 +493,26 @@ else
|
|
|
493
493
|
fi
|
|
494
494
|
rm -rf "$A"
|
|
495
495
|
|
|
496
|
+
# Task 2424 — a public visitor's turn must never carry the operator's mailbox
|
|
497
|
+
# list. This hook's only role gate was "is ACCOUNT_DIR set", which held on the
|
|
498
|
+
# pty-spawner path but not on the rc-spawn path, where buildRcChildEnv stamps it
|
|
499
|
+
# unconditionally. MAXY_PUBLIC_ALLOWED_TOOLS is stamped for role=public only.
|
|
500
|
+
PUB_A=$(mktemp -d)
|
|
501
|
+
PUB_OUT=$(printf '%s' '{"session_id":"x","prompt":"hi"}' | ACCOUNT_DIR="$PUB_A" MAXY_PUBLIC_ALLOWED_TOOLS='mcp__channel__reply' bash "$HOOK" 2>/dev/null); PUB_RC=$?
|
|
502
|
+
if [ "$PUB_RC" -eq 0 ] && [ -z "$PUB_OUT" ]; then
|
|
503
|
+
ok "public turn exits 0 with no stdout even with ACCOUNT_DIR set"
|
|
504
|
+
else
|
|
505
|
+
bad "public turn injected (exit=$PUB_RC stdout=$PUB_OUT)"
|
|
506
|
+
fi
|
|
507
|
+
rm -rf "$PUB_A"
|
|
508
|
+
|
|
496
509
|
echo "----"
|
|
497
510
|
echo "PASS=$PASS FAIL=$FAIL"
|
|
498
511
|
# The count is asserted, not just the failures: a case that stops running (a
|
|
499
512
|
# broken helper, a guard, an early exit) otherwise leaves a green suite that
|
|
500
513
|
# tested less than it claims, and .husky/pre-commit reads only the exit status.
|
|
501
|
-
EXPECTED=
|
|
502
|
-
if [ "$(id -u)" -eq 0 ]; then EXPECTED=
|
|
514
|
+
EXPECTED=29
|
|
515
|
+
if [ "$(id -u)" -eq 0 ]; then EXPECTED=27; fi
|
|
503
516
|
if [[ $((PASS+FAIL)) -ne "$EXPECTED" ]]; then
|
|
504
517
|
echo "FAIL: expected $EXPECTED cases, ran $((PASS+FAIL))" >&2
|
|
505
518
|
exit 1
|
|
@@ -10,6 +10,12 @@ check 'printf "%s" "$OUT" | grep -q "hookSpecificOutput"' "emits envelope"
|
|
|
10
10
|
check 'printf "%s" "$OUT" | grep -q "profile-read"' "names profile-read"
|
|
11
11
|
check 'printf "%s" "$OUT" | grep -q "output/"' "names output scratch rule"
|
|
12
12
|
check '! printf "%s" "$OUT" | grep -q "—"' "no em-dash"
|
|
13
|
+
# Task 2424 — a public visitor's turn carries only its agent's authored
|
|
14
|
+
# IDENTITY/SOUL/KNOWLEDGE. MAXY_PUBLIC_ALLOWED_TOOLS is stamped for role=public
|
|
15
|
+
# spawns only, by both spawn paths.
|
|
16
|
+
OUT_PUB=$(printf '{"session_id":"x","prompt":"hi"}' | MAXY_PUBLIC_ALLOWED_TOOLS='mcp__channel__reply' bash "$HOOK" 2>/dev/null); RC_PUB=$?
|
|
17
|
+
check '[ "$RC_PUB" = "0" ]' "public turn exits 0"
|
|
18
|
+
check '[ -z "$OUT_PUB" ]' "public turn emits nothing on stdout"
|
|
13
19
|
echo "----- $PASS passed, $FAIL failed -----"
|
|
14
20
|
for f in "${FAILED[@]:-}"; do [ -n "$f" ] && echo " $f"; done
|
|
15
21
|
[ "$FAIL" -eq 0 ]
|
|
@@ -195,6 +195,16 @@ else
|
|
|
195
195
|
echo "FAIL: false-suppression on a normal prompt (exit=$norm_exit stdout=$norm_out)" >&2; FAIL=$((FAIL+1))
|
|
196
196
|
fi
|
|
197
197
|
|
|
198
|
+
# Task 2424 — a public visitor's turn carries only its agent's authored
|
|
199
|
+
# IDENTITY/SOUL/KNOWLEDGE. MAXY_PUBLIC_ALLOWED_TOOLS is stamped for role=public
|
|
200
|
+
# spawns only, by both spawn paths.
|
|
201
|
+
pub_out=$(printf '%s' '{"session_id":"x","prompt":"hi"}' | MAXY_PUBLIC_ALLOWED_TOOLS='mcp__channel__reply' bash "$HOOK" 2>/dev/null); pub_rc=$?
|
|
202
|
+
if [[ "$pub_rc" -eq 0 && -z "$pub_out" ]]; then
|
|
203
|
+
echo "PASS: public turn exits 0 with no stdout"; PASS=$((PASS+1))
|
|
204
|
+
else
|
|
205
|
+
echo "FAIL: public turn injected (exit=$pub_rc stdout=$pub_out)" >&2; FAIL=$((FAIL+1))
|
|
206
|
+
fi
|
|
207
|
+
|
|
198
208
|
echo "----"
|
|
199
209
|
echo "PASS=$PASS FAIL=$FAIL"
|
|
200
210
|
[[ "$FAIL" -eq 0 ]]
|
|
@@ -34,6 +34,16 @@ set -uo pipefail
|
|
|
34
34
|
# session_id is used here (for the breadcrumb); the block is prompt-independent.
|
|
35
35
|
HOOK_INPUT=$(cat 2>/dev/null || true)
|
|
36
36
|
|
|
37
|
+
# Task 2424 — a public visitor's turn carries only its agent's authored
|
|
38
|
+
# IDENTITY/SOUL/KNOWLEDGE. MAXY_PUBLIC_ALLOWED_TOOLS is stamped for role=public
|
|
39
|
+
# spawns only, by both spawn paths, and is the signal public-tool-surface.sh
|
|
40
|
+
# already keys on. Placed after the stdin drain so the parent never sees EPIPE.
|
|
41
|
+
if [ -n "${MAXY_PUBLIC_ALLOWED_TOOLS:-}" ]; then
|
|
42
|
+
echo "[public-prompt-gate] op=gate hook=datetime-inject.sh public=1" >&2
|
|
43
|
+
exit 0
|
|
44
|
+
fi
|
|
45
|
+
echo "[public-prompt-gate] op=gate hook=datetime-inject.sh public=0" >&2
|
|
46
|
+
|
|
37
47
|
# Fail-open if either encoder is unavailable: python3 wraps the JSON envelope,
|
|
38
48
|
# node formats the instant in the target zone (Intl has the full IANA database).
|
|
39
49
|
command -v python3 >/dev/null 2>&1 || exit 0
|
|
@@ -32,6 +32,20 @@ set -uo pipefail
|
|
|
32
32
|
# hit with EPIPE. Same reason the two sibling hooks drain first.
|
|
33
33
|
HOOK_INPUT=$(cat 2>/dev/null || true)
|
|
34
34
|
|
|
35
|
+
# Task 2424 — a public visitor's turn carries only its agent's authored
|
|
36
|
+
# IDENTITY/SOUL/KNOWLEDGE. The ACCOUNT_DIR test below is NOT a role gate on the
|
|
37
|
+
# rc-spawn path: buildRcChildEnv stamps that variable unconditionally
|
|
38
|
+
# (rc-daemon.ts), so Task 2271's reasoning held only for pty-spawner, and after
|
|
39
|
+
# Task 2423 routed public Telegram through /rc-spawn the operator's mailbox list
|
|
40
|
+
# reached visitors. MAXY_PUBLIC_ALLOWED_TOOLS is stamped for role=public spawns
|
|
41
|
+
# only, by both paths. Placed after the stdin drain so the parent never sees
|
|
42
|
+
# EPIPE.
|
|
43
|
+
if [ -n "${MAXY_PUBLIC_ALLOWED_TOOLS:-}" ]; then
|
|
44
|
+
echo "[public-prompt-gate] op=gate hook=mailbox-inject.sh public=1" >&2
|
|
45
|
+
exit 0
|
|
46
|
+
fi
|
|
47
|
+
echo "[public-prompt-gate] op=gate hook=mailbox-inject.sh public=0" >&2
|
|
48
|
+
|
|
35
49
|
command -v python3 >/dev/null 2>&1 || exit 0
|
|
36
50
|
command -v node >/dev/null 2>&1 || exit 0
|
|
37
51
|
|
|
@@ -5,6 +5,15 @@
|
|
|
5
5
|
# promote finished deliverables out of output/. Prompt-independent; fail-open.
|
|
6
6
|
set -uo pipefail
|
|
7
7
|
HOOK_INPUT=$(cat 2>/dev/null || true)
|
|
8
|
+
# Task 2424 — a public visitor's turn carries only its agent's authored
|
|
9
|
+
# IDENTITY/SOUL/KNOWLEDGE. MAXY_PUBLIC_ALLOWED_TOOLS is stamped for role=public
|
|
10
|
+
# spawns only, by both spawn paths, and is the signal public-tool-surface.sh
|
|
11
|
+
# already keys on. Placed after the stdin drain so the parent never sees EPIPE.
|
|
12
|
+
if [ -n "${MAXY_PUBLIC_ALLOWED_TOOLS:-}" ]; then
|
|
13
|
+
echo "[public-prompt-gate] op=gate hook=preference-consult-directive.sh public=1" >&2
|
|
14
|
+
exit 0
|
|
15
|
+
fi
|
|
16
|
+
echo "[public-prompt-gate] op=gate hook=preference-consult-directive.sh public=0" >&2
|
|
8
17
|
command -v python3 >/dev/null 2>&1 || exit 0
|
|
9
18
|
python3 - <<'PY' 2>/dev/null || exit 0
|
|
10
19
|
import json
|
|
@@ -38,6 +38,16 @@ set -uo pipefail
|
|
|
38
38
|
# UserPromptSubmit payload carries session_id, used for the durable breadcrumb.
|
|
39
39
|
HOOK_INPUT=$(cat 2>/dev/null || true)
|
|
40
40
|
|
|
41
|
+
# Task 2424 — a public visitor's turn carries only its agent's authored
|
|
42
|
+
# IDENTITY/SOUL/KNOWLEDGE. MAXY_PUBLIC_ALLOWED_TOOLS is stamped for role=public
|
|
43
|
+
# spawns only, by both spawn paths, and is the signal public-tool-surface.sh
|
|
44
|
+
# already keys on. Placed after the stdin drain so the parent never sees EPIPE.
|
|
45
|
+
if [ -n "${MAXY_PUBLIC_ALLOWED_TOOLS:-}" ]; then
|
|
46
|
+
echo "[public-prompt-gate] op=gate hook=prompt-optimiser-directive.sh public=1" >&2
|
|
47
|
+
exit 0
|
|
48
|
+
fi
|
|
49
|
+
echo "[public-prompt-gate] op=gate hook=prompt-optimiser-directive.sh public=0" >&2
|
|
50
|
+
|
|
41
51
|
# Fail-open if the JSON encoder is unavailable.
|
|
42
52
|
command -v python3 >/dev/null 2>&1 || exit 0
|
|
43
53
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: platform-architecture
|
|
3
3
|
description: Use when grounding any documented-surface claim about what Maxy ships — plugins, skills, specialists, install/deploy flows, internals. This is the install catalogue, not evidence of what is enabled on the current account. For install state on this account, call `capabilities-here`; for documented surface, cite the `Source:` URL inline.
|
|
4
|
-
content-hash: sha256:
|
|
4
|
+
content-hash: sha256:2a6a3a040c8d55a472cfc2eda9180b15e5a0ebd8e5ce49a997c4348570ffd37b
|
|
5
5
|
brand: maxy-code
|
|
6
6
|
product-name: Maxy
|
|
7
7
|
---
|
|
@@ -5557,6 +5557,8 @@ There is exactly one place each datum is read from, and every subsystem reads it
|
|
|
5557
5557
|
- **Default agent + account id** come from the canonical account config `data/accounts/<id>/account.json`, resolved via `resolveAccount` / `resolveDefaultAgentSlug` / `getDefaultAccountId` (`app/lib/claude-agent/account.ts`). The UI server's bare-root handler and branding loader read it through these helpers — never from `~/.<brand>/account.json`, which nothing writes. A `null` default means a genuinely unset `defaultAgent`; the root logs `[public-root] op=serve-default slug=<s>` on success and `op=no-default` only when the canonical default is truly empty.
|
|
5558
5558
|
- **Identity files** (IDENTITY.md, SOUL.md, KNOWLEDGE.md) come from `agents/<slug>/` — keyed on the agent's slug, never the literal role. A public agent renamed to a slug `≠ "public"` (e.g. `maxy-info`) spawns from `agents/maxy-info/`. Admin reads `agents/admin/`. The spawn composer and the standing reachability audit resolve this directory through one shared function, `resolveAgentIdentityDir(accountDir, role, slug)` (`claude-session-manager/src/agent-identity-locator.ts`), so the path the audit vouches for is byte-identical to the path the spawn reads — the audit's `reachable=true` implies the spawn will find the files, and a slug whose files are misplaced is reported `reachable=false reason=files-missing file=<path>` without a visitor request.
|
|
5559
5559
|
|
|
5560
|
+
**What a public block contains.** A `role='public'` compose emits `<identity>`, `<soul>`, `<platform-boundary>`, `<data-stewardship>` and `<knowledge>`, and nothing else. The `<host>` section every other role gets is omitted: hostname, LAN IPv4, admin URL and tunnel URL are facts about the operator's machine, and a visitor's agent holds only what its author wrote. The two standing directives stay because they constrain behaviour rather than stating extractable facts. `op=public-persona-composed … hostBytes=0` is the per-spawn proof; non-zero on a public spawn is the regression signal, readable without reproducing a visitor turn.
|
|
5561
|
+
|
|
5560
5562
|
Which account is `<id>`? The slug is the address, and the address names the account. `resolvePublicAddressFromDisk` (`platform/ui/app/lib/claude-agent/public-address.ts`) walks every valid account for an active agent at the requested slug: exactly one claimant serves; the house serves when it is among several claimants and the others are refused by name; two or more non-house claimants serve nobody. The bare root `/` carries no slug and stays the house's door with the house's `defaultAgent`. The four public surfaces — the `/{slug}` page, `POST /api/chat`, `POST /api/session` and the visitor transcript reader — share that one resolver, and the resolved account rides `agentAccountId` to `/public-spawn`, which re-scopes the spawn onto it. A visitor's uploads follow the same account, and the reader locates them from the session's own sidecar `accountId` rather than re-deriving them from the slug, because a slug's owner can change after the session ran. Slugs are not unique across accounts, so a collision is reported on every refusal (`[webchat] op=address-collision`) and counted on every run of the `webchat-address-audit` loop.
|
|
5561
5563
|
|
|
5562
5564
|
### Observability
|
|
@@ -9,6 +9,11 @@ Invoked by the admin agent directly.
|
|
|
9
9
|
|
|
10
10
|
This is the platform's release timeline, newest first. Each entry shows the date it shipped and the version it shipped in, so you can tell the operator how current their install is. To compare, read the installed version from `capabilities-here` and match it against the versions below. Keep answers high level and in plain English; this is a summary, not a full commit log.
|
|
11
11
|
|
|
12
|
+
## 2026-08-05 (0.1.562)
|
|
13
|
+
|
|
14
|
+
- A public agent no longer sees anything about you or your machine. A stranger messaging a public bot was having your connected mailbox list, this machine's name and network addresses, its admin and tunnel web addresses, today's date and your working preferences added to every turn, alongside the persona you wrote. A public turn now carries only what the agent's author wrote plus its single reply tool.
|
|
15
|
+
- Two standing checks back that up: one records, for every account, exactly what gets added to a turn, and the other refuses to start if that picture drifts from what is expected. Each spawn also reports how much was added, so a public one showing anything other than nothing is visible immediately.
|
|
16
|
+
|
|
12
17
|
## 2026-08-04 (0.1.561)
|
|
13
18
|
|
|
14
19
|
- A public Telegram bot now answers. Messages were arriving and the conversation staying alive, but nothing the visitor sent ever became a turn, so a public bot received everything and replied to nothing. Public bots now open through the same locked-down path the platform already uses elsewhere: the ordinary setup with only the reply tool available.
|
|
@@ -238,6 +238,8 @@ There is exactly one place each datum is read from, and every subsystem reads it
|
|
|
238
238
|
- **Default agent + account id** come from the canonical account config `data/accounts/<id>/account.json`, resolved via `resolveAccount` / `resolveDefaultAgentSlug` / `getDefaultAccountId` (`app/lib/claude-agent/account.ts`). The UI server's bare-root handler and branding loader read it through these helpers — never from `~/.<brand>/account.json`, which nothing writes. A `null` default means a genuinely unset `defaultAgent`; the root logs `[public-root] op=serve-default slug=<s>` on success and `op=no-default` only when the canonical default is truly empty.
|
|
239
239
|
- **Identity files** (IDENTITY.md, SOUL.md, KNOWLEDGE.md) come from `agents/<slug>/` — keyed on the agent's slug, never the literal role. A public agent renamed to a slug `≠ "public"` (e.g. `maxy-info`) spawns from `agents/maxy-info/`. Admin reads `agents/admin/`. The spawn composer and the standing reachability audit resolve this directory through one shared function, `resolveAgentIdentityDir(accountDir, role, slug)` (`claude-session-manager/src/agent-identity-locator.ts`), so the path the audit vouches for is byte-identical to the path the spawn reads — the audit's `reachable=true` implies the spawn will find the files, and a slug whose files are misplaced is reported `reachable=false reason=files-missing file=<path>` without a visitor request.
|
|
240
240
|
|
|
241
|
+
**What a public block contains.** A `role='public'` compose emits `<identity>`, `<soul>`, `<platform-boundary>`, `<data-stewardship>` and `<knowledge>`, and nothing else. The `<host>` section every other role gets is omitted: hostname, LAN IPv4, admin URL and tunnel URL are facts about the operator's machine, and a visitor's agent holds only what its author wrote. The two standing directives stay because they constrain behaviour rather than stating extractable facts. `op=public-persona-composed … hostBytes=0` is the per-spawn proof; non-zero on a public spawn is the regression signal, readable without reproducing a visitor turn.
|
|
242
|
+
|
|
241
243
|
Which account is `<id>`? The slug is the address, and the address names the account. `resolvePublicAddressFromDisk` (`platform/ui/app/lib/claude-agent/public-address.ts`) walks every valid account for an active agent at the requested slug: exactly one claimant serves; the house serves when it is among several claimants and the others are refused by name; two or more non-house claimants serve nobody. The bare root `/` carries no slug and stays the house's door with the house's `defaultAgent`. The four public surfaces — the `/{slug}` page, `POST /api/chat`, `POST /api/session` and the visitor transcript reader — share that one resolver, and the resolved account rides `agentAccountId` to `/public-spawn`, which re-scopes the spawn onto it. A visitor's uploads follow the same account, and the reader locates them from the session's own sidecar `accountId` rather than re-deriving them from the slug, because a slug's owner can change after the session ran. Slugs are not unique across accounts, so a collision is reported on every refusal (`[webchat] op=address-collision`) and counted on every run of the `webchat-address-audit` loop.
|
|
242
244
|
|
|
243
245
|
### Observability
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Task 2424 — every UserPromptSubmit hook must either carry the public gate or
|
|
3
|
+
# be on the inert list. A fifth injecting hook added without a gate emits no
|
|
4
|
+
# event and does not reproduce on demand: the only symptom is a stranger being
|
|
5
|
+
# told something about the operator.
|
|
6
|
+
#
|
|
7
|
+
# Both writers are scanned. provision-account-dir.sh writes the array at account
|
|
8
|
+
# creation; the account-settings-*.sh libs patch it onto accounts that already
|
|
9
|
+
# exist, which is the only path that reaches client sub-accounts. Of the four
|
|
10
|
+
# libs only mailbox-inject and public-surface touch this event — askgate patches
|
|
11
|
+
# PreToolUse and pdf-conformance patches PostToolUse, so the filter skipping
|
|
12
|
+
# them is correct rather than a broken parser.
|
|
13
|
+
#
|
|
14
|
+
# The guard is matched as a fixed string, never as a mention of the variable: a
|
|
15
|
+
# hook that merely READS MAXY_PUBLIC_ALLOWED_TOOLS is not gated by it, and
|
|
16
|
+
# counting it as gated is exactly the false green this test exists to avoid.
|
|
17
|
+
set -uo pipefail
|
|
18
|
+
DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
19
|
+
PROV="$DIR/../lib/provision-account-dir.sh"
|
|
20
|
+
HOOKS_DIR="$DIR/../../plugins/admin/hooks"
|
|
21
|
+
GUARD='[ -n "${MAXY_PUBLIC_ALLOWED_TOOLS:-}" ]'
|
|
22
|
+
INERT="archive-ingest-surface-gate.sh public-tool-surface.sh"
|
|
23
|
+
PASS=0; FAIL=0
|
|
24
|
+
|
|
25
|
+
ok() { echo "PASS: $1"; PASS=$((PASS+1)); }
|
|
26
|
+
bad() { echo "FAIL: $1" >&2; FAIL=$((FAIL+1)); }
|
|
27
|
+
|
|
28
|
+
# Every hook script named inside the provision heredoc's UserPromptSubmit array.
|
|
29
|
+
# The script name is taken from the END of each `"command"` string, not from a
|
|
30
|
+
# `hooks/` prefix: provision writes `bash $HOOKS_PATH/<name>.sh`, so the literal
|
|
31
|
+
# directory never appears there and a prefix anchor silently matches nothing.
|
|
32
|
+
PROV_HOOKS=$(python3 - "$PROV" <<'PY'
|
|
33
|
+
import re, sys
|
|
34
|
+
src = open(sys.argv[1], encoding="utf-8").read()
|
|
35
|
+
m = re.search(r'"UserPromptSubmit"\s*:\s*\[(.*?)\n \]', src, re.S)
|
|
36
|
+
if not m:
|
|
37
|
+
print("")
|
|
38
|
+
sys.exit(0)
|
|
39
|
+
cmds = re.findall(r'"command"\s*:\s*"([^"]*)"', m.group(1))
|
|
40
|
+
names = [n.group(1) for n in (re.search(r'([A-Za-z0-9_.-]+\.sh)\s*$', c.strip()) for c in cmds) if n]
|
|
41
|
+
print("\n".join(sorted(set(names))))
|
|
42
|
+
PY
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
# Every hook script named by a backfill lib that patches the same array. Read
|
|
46
|
+
# only from the `_<NAME>_CMD=` constant each lib registers, not from every `.sh`
|
|
47
|
+
# in the file: these libs name their siblings in prose, and a bare `.sh` sweep
|
|
48
|
+
# turns those comments into phantom registrations.
|
|
49
|
+
BACKFILL_HOOKS=""
|
|
50
|
+
for lib in "$DIR"/../lib/account-settings-*.sh; do
|
|
51
|
+
grep -q 'hooks\.UserPromptSubmit' "$lib" || continue
|
|
52
|
+
cmd_names=$(grep -oE "^_[A-Z0-9_]+_CMD='[^']*'" "$lib" | grep -oE "[A-Za-z0-9_.-]+\.sh'$" | tr -d "'")
|
|
53
|
+
if [ -z "$cmd_names" ]; then
|
|
54
|
+
bad "$(basename "$lib") patches UserPromptSubmit but names no _<NAME>_CMD script — the parser cannot see what it registers"
|
|
55
|
+
fi
|
|
56
|
+
BACKFILL_HOOKS="$BACKFILL_HOOKS
|
|
57
|
+
$cmd_names"
|
|
58
|
+
done
|
|
59
|
+
|
|
60
|
+
ALL=$(printf '%s\n%s\n' "$PROV_HOOKS" "$BACKFILL_HOOKS" | grep -v '^$' | sort -u)
|
|
61
|
+
|
|
62
|
+
if [ -z "$ALL" ]; then
|
|
63
|
+
bad "no UserPromptSubmit hooks found in either writer — the parser is broken, not the tree"
|
|
64
|
+
else
|
|
65
|
+
ok "found $(printf '%s\n' "$ALL" | wc -l | tr -d ' ') registered UserPromptSubmit hooks across both writers"
|
|
66
|
+
fi
|
|
67
|
+
|
|
68
|
+
for h in $ALL; do
|
|
69
|
+
case " $INERT " in
|
|
70
|
+
*" $h "*) ok "$h is on the inert list"; continue ;;
|
|
71
|
+
esac
|
|
72
|
+
if [ ! -f "$HOOKS_DIR/$h" ]; then
|
|
73
|
+
bad "$h is registered but has no script at $HOOKS_DIR/$h"
|
|
74
|
+
elif grep -qF "$GUARD" "$HOOKS_DIR/$h"; then
|
|
75
|
+
ok "$h carries the public gate"
|
|
76
|
+
else
|
|
77
|
+
bad "$h is registered on UserPromptSubmit with no public gate and is not inert"
|
|
78
|
+
fi
|
|
79
|
+
done
|
|
80
|
+
|
|
81
|
+
# The guard and the inert list each exist twice: here, and as exported constants
|
|
82
|
+
# in public-prompt-surface.ts, which the standing census matches against. Two
|
|
83
|
+
# hand-maintained copies of one literal is the drift class that bites silently,
|
|
84
|
+
# so assert them equal. Divergence is not silent here — the census would FATAL
|
|
85
|
+
# every hook on every account and fail every install smoke run — but it would be
|
|
86
|
+
# a false alarm, and the cause would not be obvious from that output.
|
|
87
|
+
CENSUS_TS="$DIR/../../services/claude-session-manager/src/public-prompt-surface.ts"
|
|
88
|
+
if [ ! -f "$CENSUS_TS" ]; then
|
|
89
|
+
bad "the census module is not at $CENSUS_TS — this test can no longer check the shared literals"
|
|
90
|
+
else
|
|
91
|
+
if grep -qF "export const PUBLIC_GATE_GUARD = '$GUARD'" "$CENSUS_TS"; then
|
|
92
|
+
ok "the census module's PUBLIC_GATE_GUARD matches the guard this test greps for"
|
|
93
|
+
else
|
|
94
|
+
bad "PUBLIC_GATE_GUARD in $CENSUS_TS differs from this test's GUARD — the two would disagree about what 'gated' means"
|
|
95
|
+
fi
|
|
96
|
+
for h in $INERT; do
|
|
97
|
+
if grep -qF "'$h'," "$CENSUS_TS" || grep -qF "'$h'" "$CENSUS_TS"; then
|
|
98
|
+
ok "the census module lists $h as inert too"
|
|
99
|
+
else
|
|
100
|
+
bad "$h is inert here but not in $CENSUS_TS — the census would FATAL on it"
|
|
101
|
+
fi
|
|
102
|
+
done
|
|
103
|
+
fi
|
|
104
|
+
|
|
105
|
+
# The path the standing census reads must stay the path provision writes.
|
|
106
|
+
if grep -qF 'ACCOUNT_SETTINGS="$ACCOUNT_DIR/.claude/settings.json"' "$PROV"; then
|
|
107
|
+
ok "the settings path the census reads is still the path provision writes"
|
|
108
|
+
else
|
|
109
|
+
bad "provision no longer writes .claude/settings.json — the census path is stale"
|
|
110
|
+
fi
|
|
111
|
+
|
|
112
|
+
echo "----"
|
|
113
|
+
echo "PASS=$PASS FAIL=$FAIL"
|
|
114
|
+
[ "$FAIL" -eq 0 ]
|
|
@@ -284,6 +284,20 @@ run_one() {
|
|
|
284
284
|
fi
|
|
285
285
|
done
|
|
286
286
|
|
|
287
|
+
# Task 2424 — a public-prompt-surface drift line means some account has a
|
|
288
|
+
# per-turn hook that will inject operator context into a visitor's turn. The
|
|
289
|
+
# census fires once at boot, inside this window. Scoped to this one tag rather
|
|
290
|
+
# than widening the generic `fatal ` regex above, which would newly fail this
|
|
291
|
+
# gate on unrelated subsystems' pre-existing drift lines.
|
|
292
|
+
local drift_line
|
|
293
|
+
drift_line="$(grep -m1 -F '[public-prompt-surface] op=drift FATAL' "$log_file" || true)"
|
|
294
|
+
if [ -n "$drift_line" ]; then
|
|
295
|
+
echo "[smoke] service=$name reason=public-prompt-surface-drift matched=$drift_line" >&2
|
|
296
|
+
echo "--- log ($log_file) ---" >&2
|
|
297
|
+
cat "$log_file" >&2
|
|
298
|
+
return 1
|
|
299
|
+
fi
|
|
300
|
+
|
|
287
301
|
echo "[smoke-boot] service=$name specialist-tool-drift=ok system-prompt-sentinels=ok identity-drift=ok boot=ok elapsed-ms=$elapsed_ms"
|
|
288
302
|
return 0
|
|
289
303
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http-server.d.ts","sourceRoot":"","sources":["../src/http-server.ts"],"names":[],"mappings":"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
|
|
1
|
+
{"version":3,"file":"http-server.d.ts","sourceRoot":"","sources":["../src/http-server.ts"],"names":[],"mappings":"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"}
|
|
@@ -3502,7 +3502,13 @@ export function buildHttpApp(deps) {
|
|
|
3502
3502
|
return c.json({ error: 'spawn-failed', reason: composed.reason }, 503);
|
|
3503
3503
|
}
|
|
3504
3504
|
appendSegments.push(composed.block);
|
|
3505
|
-
|
|
3505
|
+
// Task 2424 — `hostBytes=0` is the healthy value here and the per-spawn
|
|
3506
|
+
// proof that a visitor's block carries no host addresses; non-zero is the
|
|
3507
|
+
// composer regressing, readable without reproducing a visitor turn.
|
|
3508
|
+
// `blockBytes` bounds the whole composed surface, so an unexpected growth
|
|
3509
|
+
// (a section reaching this path that should not) is visible as a number
|
|
3510
|
+
// rather than needing the block itself.
|
|
3511
|
+
deps.logger(`[rc-spawn] op=public-persona-composed slug=${rcAgentSlug} identityBytes=${composed.identityBytes} soulBytes=${composed.soulBytes} knowledgeBytes=${composed.knowledgeBytes} hostBytes=${composed.hostBytes} blockBytes=${Buffer.byteLength(composed.block, 'utf8')}`);
|
|
3506
3512
|
}
|
|
3507
3513
|
if (appendSegments.length > 0) {
|
|
3508
3514
|
argv.push('--append-system-prompt', appendSegments.join('\n\n'));
|
|
@@ -3678,6 +3684,20 @@ export function buildHttpApp(deps) {
|
|
|
3678
3684
|
// match to false — grep-able (`op=cwd-check` + `match=false`) with no
|
|
3679
3685
|
// live repro needed.
|
|
3680
3686
|
deps.logger(`[rc-spawn] op=cwd-check unitToken=${unitToken} resolved=${env.ACCOUNT_DIR} actual=${effectiveAccountDir} match=${env.ACCOUNT_DIR === effectiveAccountDir}`);
|
|
3687
|
+
// Task 2339 — the realised-surface hook's only input. `tool-allowlist` below
|
|
3688
|
+
// records the surface this spawn was GIVEN; nothing recorded the surface it
|
|
3689
|
+
// REALISES, so five tools ran under a one-tool allowlist and produced no
|
|
3690
|
+
// line. Stamped here rather than inside buildRcChildEnv so it lands after
|
|
3691
|
+
// the last argv push and the value is this spawn's final `--allowed-tools`
|
|
3692
|
+
// set. Public spawns only; its presence is the signal the hook keys on.
|
|
3693
|
+
//
|
|
3694
|
+
// Task 2424 — moved ABOVE the `tool-allowlist` line, which now reports this
|
|
3695
|
+
// value. It used to be stamped after, so the line could not see it. The four
|
|
3696
|
+
// per-turn hook gates also key on this variable, so its absence on a public
|
|
3697
|
+
// spawn is what `publicToolsEnv=0` names.
|
|
3698
|
+
if (publicLockdown) {
|
|
3699
|
+
env[PUBLIC_ALLOWED_TOOLS_ENV] = allowedToolsFromArgv(argv).join(',');
|
|
3700
|
+
}
|
|
3681
3701
|
// Task 2260 — the realised tool surface, on EVERY rc-spawn. /rc-spawn emitted
|
|
3682
3702
|
// no such line, which is why the incident session was invisible to every
|
|
3683
3703
|
// tool-surface audit. An admin spawn reports count=0 strictMcp=n, which is
|
|
@@ -3688,16 +3708,12 @@ export function buildHttpApp(deps) {
|
|
|
3688
3708
|
count: publicLockdown ? publicLockdown.allowedTools.length : 0,
|
|
3689
3709
|
strictMcp: publicLockdown !== null,
|
|
3690
3710
|
mode: effectiveRcPermissionMode,
|
|
3711
|
+
// Task 2424 — read back from the env actually stamped, not from the
|
|
3712
|
+
// allowlist computed above, so the line reports what reached the child.
|
|
3713
|
+
publicToolsEnv: (env[PUBLIC_ALLOWED_TOOLS_ENV] ?? '')
|
|
3714
|
+
.split(',')
|
|
3715
|
+
.filter((t) => t.length > 0).length,
|
|
3691
3716
|
}));
|
|
3692
|
-
// Task 2339 — the realised-surface hook's only input. `tool-allowlist` above
|
|
3693
|
-
// records the surface this spawn was GIVEN; nothing recorded the surface it
|
|
3694
|
-
// REALISES, so five tools ran under a one-tool allowlist and produced no
|
|
3695
|
-
// line. Stamped here rather than inside buildRcChildEnv so it lands after
|
|
3696
|
-
// the last argv push and the value is this spawn's final `--allowed-tools`
|
|
3697
|
-
// set. Public spawns only; its presence is the signal the hook keys on.
|
|
3698
|
-
if (publicLockdown) {
|
|
3699
|
-
env[PUBLIC_ALLOWED_TOOLS_ENV] = allowedToolsFromArgv(argv).join(',');
|
|
3700
|
-
}
|
|
3701
3717
|
deps.logger(`[rc-spawn] op=argv unitToken=${unitToken} cwd=${effectiveAccountDir} permissionMode=${effectiveRcPermissionMode} model=${effectiveRcModel ?? 'unset'} effort=${effectiveRcEffort ?? 'unset'} positionalBytes=${positionalBytes} voiceBytes=${voiceBytes} argv=${JSON.stringify([deps.claudeBin, ...argv])}`);
|
|
3702
3718
|
emitRcLife(deps.logger, 'argv', 'rc-spawn', 'node-pty', rcLifeMode, unitToken, {
|
|
3703
3719
|
cwd: effectiveAccountDir,
|