@rubytech/create-maxy 1.0.884 → 1.0.885
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/PLUGIN.md +1 -1
- package/payload/platform/plugins/admin/mcp/dist/index.js +21 -50
- package/payload/platform/plugins/admin/mcp/dist/index.js.map +1 -1
- package/payload/platform/plugins/docs/references/plugins-guide.md +1 -1
- package/payload/platform/plugins/docs/references/troubleshooting.md +1 -1
- package/payload/platform/scripts/__tests__/logs-read-prefix.sh +85 -240
- package/payload/platform/scripts/log-adherence-check.sh +100 -0
- package/payload/platform/scripts/logs-read.sh +71 -141
- package/payload/platform/scripts/logs-read.test.sh +47 -104
- package/payload/premium-plugins/real-agency/BUNDLE.md +1 -1
- package/payload/server/chunk-NPKQWE3S.js +1431 -0
- package/payload/server/chunk-ZVO5ASQA.js +11660 -0
- package/payload/server/client-pool-QUMX7OUT.js +34 -0
- package/payload/server/maxy-edge.js +2 -2
- package/payload/server/server.js +123 -121
|
@@ -83,7 +83,7 @@ Premium plugins are one-off purchases that grant permanent ownership. They are n
|
|
|
83
83
|
|
|
84
84
|
Some premium plugins are **bundles** — a single purchase that delivers multiple sub-plugins, each independently activatable. For example, Real Agency delivers 11 sub-plugins covering different aspects of estate agency work. You can enable all of them or just the ones you need (e.g., "Enable estate-sales" for just the sales skills). Enabling or disabling individual sub-plugins does not affect the others.
|
|
85
85
|
|
|
86
|
-
|
|
86
|
+
Every admin session start reconciles your `enabledPlugins` against what is actually on disk: any purchased sub-plugin present in `platform/plugins/` is enabled, so you don't need to enable each one by hand. This holds whether the bundle was delivered just now (fresh purchase via `premium-deliver`) or has been on disk from a prior install. Sub-plugins you don't want active can be turned off individually with "disable <name>". Standalone premium plugins behave the same way: presence on disk implies enablement. If you ask {{productName}} about a tool from a plugin you haven't purchased, {{productName}} responds with a structured `<tool-surface-error>` envelope naming the missing plugin and the remedy, rather than improvising with a generic alternative.
|
|
87
87
|
|
|
88
88
|
**Public agent embedding:** Premium plugins marked as public-eligible have their full content (skills and reference knowledge) embedded in public agent prompts. This means a public agent for a Real Agency member can handle buyer enquiries, book viewings, deliver coaching content, and onboard new applicants — all powered by the premium plugin's domain knowledge. Plugins marked admin-only (listings, vendors, leads, business) are only available to the account owner's admin agent.
|
|
89
89
|
|
|
@@ -73,7 +73,7 @@ tail -200 ~/.maxy/logs/maxy-ui.log | rg '\[remote-auth\].*resolvedKind='
|
|
|
73
73
|
**Agent searches the filesystem after uploading a zip.** If you uploaded a zip and the agent burns several turns running `find` / `Glob` instead of unzipping, that is the symptom of the recovery-retry attachment-context regression (now closed by the recovery context preservation contract in `.docs/agents.md`). Greppable confirmation is the `[context-overflow-recovery] retry … attachmentsCarried=<n>` line in the conversation stream log. If you see `[context-overflow-recovery] WARN attachment-context-lost`, the regression has returned — surface to support.
|
|
74
74
|
|
|
75
75
|
|
|
76
|
-
**A turn rendered in chat is missing on next page-refresh.** Pre-the 2026-05-07 mandate this was a class of silent failure — Neo4j persists were wrapped in a no-op error catch and a write that threw left the artefact "rendered then disappeared on resume". The 2026-05-07 mandate makes JSONL canonical: the resume route reads the SDK transcript file at `~/.claude/projects/<project-key>/<sessionId>.jsonl` first, supplements from Neo4j, and triggers async heal-on-resume writes for any turn the JSONL has but Neo4j does not. So a refreshed conversation always renders what the SDK saw, regardless of write outcome. If a heal write itself fails, the chat shows a top-of-conversation banner naming the count; if every heal succeeds the resume is silent and the missing rows are quietly restored to Neo4j. Greppable post-deploy invariants in the per-
|
|
76
|
+
**A turn rendered in chat is missing on next page-refresh.** Pre-the 2026-05-07 mandate this was a class of silent failure — Neo4j persists were wrapped in a no-op error catch and a write that threw left the artefact "rendered then disappeared on resume". The 2026-05-07 mandate makes JSONL canonical: the resume route reads the SDK transcript file at `~/.claude/projects/<project-key>/<sessionId>.jsonl` first, supplements from Neo4j, and triggers async heal-on-resume writes for any turn the JSONL has but Neo4j does not. So a refreshed conversation always renders what the SDK saw, regardless of write outcome. If a heal write itself fails, the chat shows a top-of-conversation banner naming the count; if every heal succeeds the resume is silent and the missing rows are quietly restored to Neo4j. Greppable post-deploy invariants in the per-session stream log (`logs/claude-agent-stream-<sessionKey>.log`): `[admin-resume] reason=<…> source=<jsonl|jsonl-missing|neo4j-only>` (one per resume), `[admin-persist] convId=<8> writer=<…> outcome=<ok|fail|skip>` (per persist site), `[admin-persist-heal] convId=<8> turnIndex=<n> outcome=<ok|fail>` (per heal write). To force-audit a specific conversation against its Neo4j projection without re-executing it, run `tsx platform/scripts/admin-persist-audit.ts --conversation-id=<uuid> --account-id=<uuid> --session-id=<uuid>` — non-zero exit + per-divergence `[admin-persist-audit] expected=<message|component> missing reason=neo4j-row-absent` lines name what would have been silently lost pre-mandate.
|
|
77
77
|
**Wrong Claude account answering on a multi-brand device.** On a host running both Maxy and Real Agent, each brand's admin agent reads its own `~/${brand.configDir}/.claude/.credentials.json`; there is no longer a shared `~/.claude/` thrashing them against one another. If a brand reports auth failures or appears to be operating against the wrong subscription, check three things:
|
|
78
78
|
1. `grep "\[claude-auth\] init" ~/.${brand}/logs/server.log | tail -1` — the resolved path must end with `~/.${brand}/.claude/.credentials.json`. If a `[claude-auth] WARN cross-brand-path-detected` line is present, the runtime is still pointing at `~/.claude/`; the brand main service did not pick up the `Environment=CLAUDE_CONFIG_DIR=` setting (re-run the brand installer to refresh the unit file).
|
|
79
79
|
2. `diff <(jq .claudeAiOauth.accessToken ~/.maxy/.claude/.credentials.json) <(jq .claudeAiOauth.accessToken ~/.realagent/.claude/.credentials.json)` — must be non-empty after each brand's operator has run `claude /login` against distinct Anthropic accounts; if it's empty, both brands are still logged in to the same account (operator action, not a code bug).
|
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env bash
|
|
2
|
-
# Task
|
|
2
|
+
# Task 1006 — prefix-match harness for logs-read.sh.
|
|
3
3
|
#
|
|
4
|
-
#
|
|
5
|
-
#
|
|
6
|
-
#
|
|
7
|
-
# 3. More-specific prefix disambiguates
|
|
8
|
-
# 4. Zero matches → reason=file-not-found-in-either-shape with glob patterns
|
|
9
|
-
# 5. Full 36-char UUID still resolves (regression boundary)
|
|
10
|
-
# 6. Preflush prefix match
|
|
11
|
-
# 7. Preflush ambiguity (Pass 1 zero, Pass 2 multiple)
|
|
12
|
-
# 8. Every per-conversation type (agent-stream, session, error, public)
|
|
13
|
-
# resolves under prefix-match
|
|
4
|
+
# Task 998 introduced 8-char-prefix resolution; Task 1006 collapses the
|
|
5
|
+
# writer's two-shape contract to one (`claude-agent-stream-<sessionKey>.log`).
|
|
6
|
+
# The prefix match still applies, now against a single basename shape.
|
|
14
7
|
#
|
|
15
|
-
#
|
|
16
|
-
#
|
|
8
|
+
# Cases:
|
|
9
|
+
# 1. 8-char prefix resolves a full sessionKey-named file.
|
|
10
|
+
# 2. Multiple matches → ambiguous-prefix, exit 1, never picks silently.
|
|
11
|
+
# 3. More-specific prefix disambiguates.
|
|
12
|
+
# 4. Zero matches → exit 1 with file-not-found trailer.
|
|
13
|
+
# 5. Full 36-char sessionKey still resolves (regression boundary).
|
|
14
|
+
# 6. Every per-session type (agent-stream, session, error, public)
|
|
15
|
+
# resolves under prefix-match.
|
|
17
16
|
set -euo pipefail
|
|
18
17
|
|
|
19
18
|
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
@@ -26,9 +25,6 @@ fi
|
|
|
26
25
|
PASS=0
|
|
27
26
|
FAIL=0
|
|
28
27
|
|
|
29
|
-
# logs-read.sh resolves PLATFORM_ROOT from its own location; mirror the
|
|
30
|
-
# logs-read.test.sh convention by symlinking the script into an ephemeral
|
|
31
|
-
# install tree per case.
|
|
32
28
|
setup_install_tree() {
|
|
33
29
|
local root="$1"
|
|
34
30
|
mkdir -p "$root/platform/scripts"
|
|
@@ -58,281 +54,130 @@ run_case() {
|
|
|
58
54
|
fi
|
|
59
55
|
}
|
|
60
56
|
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
local root="/tmp/maxy-logs-read-prefix-1-$$"
|
|
57
|
+
case_8char_prefix_resolves_full() {
|
|
58
|
+
local root="/tmp/maxy-prefix-test-1-$$"
|
|
64
59
|
local script
|
|
65
60
|
script=$(setup_install_tree "$root")
|
|
66
61
|
local logdir="$root/data/accounts/acct-test/logs"
|
|
67
|
-
local
|
|
68
|
-
|
|
69
|
-
echo "prefix-match-sentinel" > "$logdir/$filename"
|
|
70
|
-
|
|
71
|
-
local stdout stderr rc=0
|
|
72
|
-
stdout=$("$script" "3483269d" system 2>/tmp/logs-read-prefix-stderr-1-$$) || rc=$?
|
|
73
|
-
stderr=$(cat /tmp/logs-read-prefix-stderr-1-$$)
|
|
74
|
-
rm -f /tmp/logs-read-prefix-stderr-1-$$
|
|
62
|
+
local sk="abcd1234-5678-90ab-cdef-1234567890ab"
|
|
63
|
+
echo "sentinel-1" > "$logdir/claude-agent-stream-${sk}.log"
|
|
75
64
|
|
|
65
|
+
local rc=0
|
|
66
|
+
local stdout
|
|
67
|
+
stdout=$("$script" "abcd1234" agent-stream 2>/dev/null) || rc=$?
|
|
76
68
|
cleanup_install_tree "$root"
|
|
77
69
|
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
echo " stdout missing sentinel: $stdout"; return 1
|
|
81
|
-
fi
|
|
82
|
-
if [[ "$stderr" != *"matched_shape=full"* ]]; then
|
|
83
|
-
echo " stderr missing matched_shape=full: $stderr"; return 1
|
|
84
|
-
fi
|
|
85
|
-
if [[ "$stdout" != *"$filename"* ]]; then
|
|
86
|
-
echo " header missing matched filename: $stdout"; return 1
|
|
87
|
-
fi
|
|
70
|
+
[[ $rc -eq 0 ]] || { echo " expected exit 0, got $rc"; return 1; }
|
|
71
|
+
[[ "$stdout" == *"sentinel-1"* ]] || { echo " missing sentinel"; return 1; }
|
|
88
72
|
return 0
|
|
89
73
|
}
|
|
90
74
|
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
local root="/tmp/maxy-logs-read-prefix-2-$$"
|
|
75
|
+
case_ambiguous_prefix_refuses() {
|
|
76
|
+
local root="/tmp/maxy-prefix-test-2-$$"
|
|
94
77
|
local script
|
|
95
78
|
script=$(setup_install_tree "$root")
|
|
96
79
|
local logdir="$root/data/accounts/acct-test/logs"
|
|
97
|
-
echo "
|
|
98
|
-
echo "
|
|
99
|
-
|
|
100
|
-
local
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
80
|
+
echo "a" > "$logdir/claude-agent-stream-abcd1111.log"
|
|
81
|
+
echo "b" > "$logdir/claude-agent-stream-abcd2222.log"
|
|
82
|
+
|
|
83
|
+
local rc=0
|
|
84
|
+
"$script" "abcd" agent-stream >/dev/null 2>/tmp/prefix-stderr-2-$$ || rc=$?
|
|
85
|
+
local stderr
|
|
86
|
+
stderr=$(cat /tmp/prefix-stderr-2-$$)
|
|
87
|
+
rm -f /tmp/prefix-stderr-2-$$
|
|
105
88
|
cleanup_install_tree "$root"
|
|
106
89
|
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
echo " stderr missing reason=ambiguous-prefix: $stderr"; return 1
|
|
110
|
-
fi
|
|
111
|
-
if [[ "$stderr" != *"matches=2"* ]]; then
|
|
112
|
-
echo " stderr missing matches=2: $stderr"; return 1
|
|
113
|
-
fi
|
|
114
|
-
if [[ "$stderr" != *"claude-agent-stream-aaaaaaaa-1111-A.log"* ]]; then
|
|
115
|
-
echo " stderr missing candidate A: $stderr"; return 1
|
|
116
|
-
fi
|
|
117
|
-
if [[ "$stderr" != *"claude-agent-stream-aaaaaaaa-2222-B.log"* ]]; then
|
|
118
|
-
echo " stderr missing candidate B: $stderr"; return 1
|
|
119
|
-
fi
|
|
120
|
-
# Body must NOT contain either sentinel — refusal means no body emitted.
|
|
121
|
-
if [[ -n "$stdout" ]]; then
|
|
122
|
-
echo " stdout non-empty on ambiguous refusal: $stdout"; return 1
|
|
123
|
-
fi
|
|
90
|
+
[[ $rc -eq 1 ]] || { echo " expected exit 1, got $rc"; return 1; }
|
|
91
|
+
[[ "$stderr" == *"ambiguous-prefix"* ]] || { echo " stderr missing ambiguous-prefix: $stderr"; return 1; }
|
|
124
92
|
return 0
|
|
125
93
|
}
|
|
126
94
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
local root="/tmp/maxy-logs-read-prefix-3-$$"
|
|
95
|
+
case_more_specific_prefix_resolves() {
|
|
96
|
+
local root="/tmp/maxy-prefix-test-3-$$"
|
|
130
97
|
local script
|
|
131
98
|
script=$(setup_install_tree "$root")
|
|
132
99
|
local logdir="$root/data/accounts/acct-test/logs"
|
|
133
|
-
echo "
|
|
134
|
-
echo "
|
|
135
|
-
|
|
136
|
-
local stdout stderr rc=0
|
|
137
|
-
stdout=$("$script" "aaaaaaaa-1111" system 2>/tmp/logs-read-prefix-stderr-3-$$) || rc=$?
|
|
138
|
-
stderr=$(cat /tmp/logs-read-prefix-stderr-3-$$)
|
|
139
|
-
rm -f /tmp/logs-read-prefix-stderr-3-$$
|
|
100
|
+
echo "a" > "$logdir/claude-agent-stream-abcd1111.log"
|
|
101
|
+
echo "b" > "$logdir/claude-agent-stream-abcd2222.log"
|
|
140
102
|
|
|
103
|
+
local rc=0
|
|
104
|
+
local stdout
|
|
105
|
+
stdout=$("$script" "abcd1111" agent-stream 2>/dev/null) || rc=$?
|
|
141
106
|
cleanup_install_tree "$root"
|
|
142
107
|
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
if [[ "$stdout" == *"TWO"* ]]; then echo " stdout contained TWO: $stdout"; return 1; fi
|
|
146
|
-
if [[ "$stderr" != *"matched_shape=full"* ]]; then
|
|
147
|
-
echo " stderr missing matched_shape=full: $stderr"; return 1
|
|
148
|
-
fi
|
|
108
|
+
[[ $rc -eq 0 ]] || { echo " expected exit 0, got $rc"; return 1; }
|
|
109
|
+
[[ "$stdout" == *"a"* ]] || { echo " missing sentinel"; return 1; }
|
|
149
110
|
return 0
|
|
150
111
|
}
|
|
151
112
|
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
local root="/tmp/maxy-logs-read-prefix-4-$$"
|
|
113
|
+
case_zero_match_miss() {
|
|
114
|
+
local root="/tmp/maxy-prefix-test-4-$$"
|
|
155
115
|
local script
|
|
156
116
|
script=$(setup_install_tree "$root")
|
|
157
117
|
|
|
158
|
-
local
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
118
|
+
local rc=0
|
|
119
|
+
"$script" "nomatch1" agent-stream >/dev/null 2>/tmp/prefix-stderr-4-$$ || rc=$?
|
|
120
|
+
local stderr
|
|
121
|
+
stderr=$(cat /tmp/prefix-stderr-4-$$)
|
|
122
|
+
rm -f /tmp/prefix-stderr-4-$$
|
|
163
123
|
cleanup_install_tree "$root"
|
|
164
124
|
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
echo " stderr missing reason=file-not-found-in-either-shape: $stderr"; return 1
|
|
168
|
-
fi
|
|
169
|
-
if [[ "$stderr" != *"tried=[claude-agent-stream-xxxxxxxx*.log, claude-agent-stream-preflush-xxxxxxxx*.log]"* ]]; then
|
|
170
|
-
echo " stderr missing tried=[<glob>, <glob>]: $stderr"; return 1
|
|
171
|
-
fi
|
|
125
|
+
[[ $rc -eq 1 ]] || { echo " expected exit 1, got $rc"; return 1; }
|
|
126
|
+
[[ "$stderr" == *"file-not-found"* ]] || { echo " stderr missing file-not-found"; return 1; }
|
|
172
127
|
return 0
|
|
173
128
|
}
|
|
174
129
|
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
local root="/tmp/maxy-logs-read-prefix-5-$$"
|
|
130
|
+
case_full_sessionkey_resolves() {
|
|
131
|
+
local root="/tmp/maxy-prefix-test-5-$$"
|
|
178
132
|
local script
|
|
179
133
|
script=$(setup_install_tree "$root")
|
|
180
134
|
local logdir="$root/data/accounts/acct-test/logs"
|
|
181
|
-
local
|
|
182
|
-
|
|
183
|
-
echo "regression-sentinel" > "$logdir/$filename"
|
|
184
|
-
|
|
185
|
-
local stdout stderr rc=0
|
|
186
|
-
stdout=$("$script" "$full_uuid" system 2>/tmp/logs-read-prefix-stderr-5-$$) || rc=$?
|
|
187
|
-
stderr=$(cat /tmp/logs-read-prefix-stderr-5-$$)
|
|
188
|
-
rm -f /tmp/logs-read-prefix-stderr-5-$$
|
|
135
|
+
local sk="11111111-2222-3333-4444-555555555555"
|
|
136
|
+
echo "sentinel-5" > "$logdir/claude-agent-stream-${sk}.log"
|
|
189
137
|
|
|
138
|
+
local rc=0
|
|
139
|
+
local stdout
|
|
140
|
+
stdout=$("$script" "$sk" agent-stream 2>/dev/null) || rc=$?
|
|
190
141
|
cleanup_install_tree "$root"
|
|
191
142
|
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
echo " stdout missing sentinel: $stdout"; return 1
|
|
195
|
-
fi
|
|
196
|
-
if [[ "$stderr" != *"matched_shape=full"* ]]; then
|
|
197
|
-
echo " stderr missing matched_shape=full: $stderr"; return 1
|
|
198
|
-
fi
|
|
143
|
+
[[ $rc -eq 0 ]] || { echo " expected exit 0, got $rc"; return 1; }
|
|
144
|
+
[[ "$stdout" == *"sentinel-5"* ]] || { echo " missing sentinel"; return 1; }
|
|
199
145
|
return 0
|
|
200
146
|
}
|
|
201
147
|
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
local root="/tmp/maxy-logs-read-prefix-6-$$"
|
|
148
|
+
case_every_per_session_type() {
|
|
149
|
+
local root="/tmp/maxy-prefix-test-6-$$"
|
|
205
150
|
local script
|
|
206
151
|
script=$(setup_install_tree "$root")
|
|
207
152
|
local logdir="$root/data/accounts/acct-test/logs"
|
|
208
|
-
|
|
209
|
-
echo "
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
fi
|
|
225
|
-
return 0
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
# --- Case 7: preflush ambiguity (Pass 1 zero, Pass 2 multiple) ---
|
|
229
|
-
case_preflush_ambiguity() {
|
|
230
|
-
local root="/tmp/maxy-logs-read-prefix-7-$$"
|
|
231
|
-
local script
|
|
232
|
-
script=$(setup_install_tree "$root")
|
|
233
|
-
local logdir="$root/data/accounts/acct-test/logs"
|
|
234
|
-
echo "P1" > "$logdir/claude-agent-stream-preflush-cccccccc-111.log"
|
|
235
|
-
echo "P2" > "$logdir/claude-agent-stream-preflush-cccccccc-222.log"
|
|
236
|
-
|
|
237
|
-
local stdout stderr rc=0
|
|
238
|
-
stdout=$("$script" "cccccccc" system 2>/tmp/logs-read-prefix-stderr-7-$$) || rc=$?
|
|
239
|
-
stderr=$(cat /tmp/logs-read-prefix-stderr-7-$$)
|
|
240
|
-
rm -f /tmp/logs-read-prefix-stderr-7-$$
|
|
241
|
-
|
|
242
|
-
cleanup_install_tree "$root"
|
|
243
|
-
|
|
244
|
-
if [[ $rc -ne 1 ]]; then echo " expected exit 1, got $rc"; return 1; fi
|
|
245
|
-
if [[ "$stderr" != *"reason=ambiguous-prefix"* ]]; then
|
|
246
|
-
echo " stderr missing reason=ambiguous-prefix: $stderr"; return 1
|
|
247
|
-
fi
|
|
248
|
-
if [[ "$stderr" != *"matches=2"* ]]; then
|
|
249
|
-
echo " stderr missing matches=2: $stderr"; return 1
|
|
250
|
-
fi
|
|
251
|
-
if [[ "$stderr" != *"claude-agent-stream-preflush-cccccccc-111.log"* ]]; then
|
|
252
|
-
echo " stderr missing preflush candidate 1: $stderr"; return 1
|
|
253
|
-
fi
|
|
254
|
-
if [[ "$stderr" != *"claude-agent-stream-preflush-cccccccc-222.log"* ]]; then
|
|
255
|
-
echo " stderr missing preflush candidate 2: $stderr"; return 1
|
|
256
|
-
fi
|
|
257
|
-
return 0
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
# --- Case 8: every per-conversation type resolves under prefix-match ---
|
|
261
|
-
# Parameterised over the four prefix_for_type values:
|
|
262
|
-
# agent-stream → claude-agent-stream-
|
|
263
|
-
# session → sse-events-
|
|
264
|
-
# error → claude-agent-stderr-
|
|
265
|
-
# public → public-agent-stream-
|
|
266
|
-
case_all_types_prefix_match() {
|
|
267
|
-
local root="/tmp/maxy-logs-read-prefix-8-$$"
|
|
268
|
-
local script
|
|
269
|
-
script=$(setup_install_tree "$root")
|
|
270
|
-
local logdir="$root/data/accounts/acct-test/logs"
|
|
271
|
-
local full_uuid="7d49ef21-1234-4567-89ab-cdef01234567"
|
|
272
|
-
|
|
273
|
-
echo "sentinel-agent-stream" > "$logdir/claude-agent-stream-${full_uuid}.log"
|
|
274
|
-
echo "sentinel-session" > "$logdir/sse-events-${full_uuid}.log"
|
|
275
|
-
echo "sentinel-error" > "$logdir/claude-agent-stderr-${full_uuid}.log"
|
|
276
|
-
echo "sentinel-public" > "$logdir/public-agent-stream-${full_uuid}.log"
|
|
277
|
-
|
|
278
|
-
local pairs=(
|
|
279
|
-
"agent-stream|sentinel-agent-stream"
|
|
280
|
-
"session|sentinel-session"
|
|
281
|
-
"error|sentinel-error"
|
|
282
|
-
"public|sentinel-public"
|
|
283
|
-
)
|
|
284
|
-
local fail=0
|
|
285
|
-
local pair t expected_sentinel
|
|
286
|
-
for pair in "${pairs[@]}"; do
|
|
287
|
-
t="${pair%%|*}"
|
|
288
|
-
expected_sentinel="${pair##*|}"
|
|
289
|
-
local stdout stderr rc=0
|
|
290
|
-
stdout=$("$script" "7d49ef21" "$t" 2>/tmp/logs-read-prefix-stderr-8-$$) || rc=$?
|
|
291
|
-
stderr=$(cat /tmp/logs-read-prefix-stderr-8-$$)
|
|
292
|
-
rm -f /tmp/logs-read-prefix-stderr-8-$$
|
|
293
|
-
if [[ $rc -ne 0 ]]; then echo " [$t] expected exit 0, got $rc — stderr: $stderr"; fail=1; continue; fi
|
|
294
|
-
if [[ "$stdout" != *"$expected_sentinel"* ]]; then
|
|
295
|
-
echo " [$t] missing sentinel '$expected_sentinel': $stdout"; fail=1
|
|
296
|
-
fi
|
|
297
|
-
if [[ "$stderr" != *"matched_shape=full"* ]]; then
|
|
298
|
-
echo " [$t] stderr missing matched_shape=full: $stderr"; fail=1
|
|
299
|
-
fi
|
|
153
|
+
local sk="abcdef00-0000-0000-0000-000000000000"
|
|
154
|
+
echo "stream" > "$logdir/claude-agent-stream-${sk}.log"
|
|
155
|
+
echo "err" > "$logdir/claude-agent-stderr-${sk}.log"
|
|
156
|
+
echo "sse" > "$logdir/sse-events-${sk}.log"
|
|
157
|
+
echo "pub" > "$logdir/public-agent-stream-${sk}.log"
|
|
158
|
+
|
|
159
|
+
local ok=1
|
|
160
|
+
for type in agent-stream error session public; do
|
|
161
|
+
local out
|
|
162
|
+
out=$("$script" "abcdef00" "$type" 2>/dev/null) || ok=0
|
|
163
|
+
case "$type" in
|
|
164
|
+
agent-stream) [[ "$out" == *"stream"* ]] || ok=0 ;;
|
|
165
|
+
error) [[ "$out" == *"err"* ]] || ok=0 ;;
|
|
166
|
+
session) [[ "$out" == *"sse"* ]] || ok=0 ;;
|
|
167
|
+
public) [[ "$out" == *"pub"* ]] || ok=0 ;;
|
|
168
|
+
esac
|
|
300
169
|
done
|
|
301
|
-
|
|
302
|
-
cleanup_install_tree "$root"
|
|
303
|
-
[[ $fail -eq 0 ]]
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
# --- Case 9: conv_id with shell metacharacters is rejected ---
|
|
307
|
-
case_metacharacter_rejected() {
|
|
308
|
-
local root="/tmp/maxy-logs-read-prefix-9-$$"
|
|
309
|
-
local script
|
|
310
|
-
script=$(setup_install_tree "$root")
|
|
311
|
-
|
|
312
|
-
local stdout stderr rc=0
|
|
313
|
-
# Use a backslash-escaped wildcard to feed it as a literal argument.
|
|
314
|
-
stdout=$("$script" "ab*cd" system 2>/tmp/logs-read-prefix-stderr-9-$$) || rc=$?
|
|
315
|
-
stderr=$(cat /tmp/logs-read-prefix-stderr-9-$$)
|
|
316
|
-
rm -f /tmp/logs-read-prefix-stderr-9-$$
|
|
317
|
-
|
|
318
170
|
cleanup_install_tree "$root"
|
|
319
|
-
|
|
320
|
-
if [[ $rc -ne 2 ]]; then echo " expected exit 2 (usage), got $rc"; return 1; fi
|
|
321
|
-
if [[ "$stderr" != *"invalid characters"* ]]; then
|
|
322
|
-
echo " stderr missing invalid-characters guard: $stderr"; return 1
|
|
323
|
-
fi
|
|
171
|
+
[[ $ok -eq 1 ]] || { echo " one or more per-session types failed to resolve"; return 1; }
|
|
324
172
|
return 0
|
|
325
173
|
}
|
|
326
174
|
|
|
327
|
-
run_case "8-char prefix resolves full
|
|
328
|
-
run_case "ambiguous prefix
|
|
329
|
-
run_case "more-specific prefix disambiguates"
|
|
330
|
-
run_case "zero matches → file-not-found
|
|
331
|
-
run_case "full 36-char
|
|
332
|
-
run_case "
|
|
333
|
-
run_case "preflush ambiguity refused" case_preflush_ambiguity
|
|
334
|
-
run_case "every per-conversation type prefix-matches" case_all_types_prefix_match
|
|
335
|
-
run_case "shell metacharacters rejected" case_metacharacter_rejected
|
|
175
|
+
run_case "8-char prefix resolves full sessionKey file" case_8char_prefix_resolves_full
|
|
176
|
+
run_case "ambiguous prefix refuses to pick" case_ambiguous_prefix_refuses
|
|
177
|
+
run_case "more-specific prefix disambiguates" case_more_specific_prefix_resolves
|
|
178
|
+
run_case "zero matches → file-not-found" case_zero_match_miss
|
|
179
|
+
run_case "full 36-char sessionKey resolves" case_full_sessionkey_resolves
|
|
180
|
+
run_case "every per-session type resolves" case_every_per_session_type
|
|
336
181
|
|
|
337
182
|
echo ""
|
|
338
183
|
echo "================================================"
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# log-adherence-check.sh — Task 1006 existence contract diagnostic.
|
|
3
|
+
#
|
|
4
|
+
# Reads every sessionKey under the active install's per-account logs and
|
|
5
|
+
# asserts that a resolvable claude-agent-stream-<sessionKey>.log exists for
|
|
6
|
+
# each. Runs hourly on-device in addition to the in-process timer wired
|
|
7
|
+
# from platform/ui/app/lib/claude-agent/logging.ts; both are intentionally
|
|
8
|
+
# redundant — one runs inside the server process, this one runs out of band
|
|
9
|
+
# so a stalled server still leaves a fresh adherence record.
|
|
10
|
+
#
|
|
11
|
+
# Emits one `[log-tee] adherence-check window=24h sessions=N misses=M ts=...`
|
|
12
|
+
# line to the platform server log on every run. Per-miss lines surface as
|
|
13
|
+
# `[log-tee] missing-on-resolve sessionKey=<8> surface=adherence-script
|
|
14
|
+
# reason=file-not-on-disk`. `misses=0` is the steady state.
|
|
15
|
+
#
|
|
16
|
+
# Exit codes:
|
|
17
|
+
# 0 No misses (steady state).
|
|
18
|
+
# 1 At least one miss recorded.
|
|
19
|
+
# 2 Usage / environment error.
|
|
20
|
+
set -euo pipefail
|
|
21
|
+
|
|
22
|
+
# --- Resolve install dir, the same way logs-read.sh does. ---
|
|
23
|
+
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
|
|
24
|
+
PLATFORM_ROOT="$(cd "$SCRIPT_DIR/.." && pwd)"
|
|
25
|
+
INSTALL_DIR="$(dirname "$PLATFORM_ROOT")"
|
|
26
|
+
INSTALL_DIR_NAME=$(basename "$INSTALL_DIR")
|
|
27
|
+
CONFIG_DIR="$HOME/.$INSTALL_DIR_NAME"
|
|
28
|
+
SERVER_LOG="$CONFIG_DIR/logs/server.log"
|
|
29
|
+
|
|
30
|
+
ACCOUNTS_DIR="$INSTALL_DIR/data/accounts"
|
|
31
|
+
if [[ ! -d "$ACCOUNTS_DIR" ]]; then
|
|
32
|
+
echo "[log-adherence-check] accounts directory missing: $ACCOUNTS_DIR" >&2
|
|
33
|
+
exit 2
|
|
34
|
+
fi
|
|
35
|
+
|
|
36
|
+
# Window: only count session keys whose stream-log file was modified in the
|
|
37
|
+
# last 24h. This avoids fingerprinting long-archived sessions that have aged
|
|
38
|
+
# out via the 7-day retention purge.
|
|
39
|
+
WINDOW_HOURS=24
|
|
40
|
+
|
|
41
|
+
# Collect every basename-derived sessionKey from claude-agent-stream-*.log
|
|
42
|
+
# files across all account log directories, filtered to the active window.
|
|
43
|
+
declare -a KEYS=()
|
|
44
|
+
shopt -s nullglob
|
|
45
|
+
for log_dir in "$ACCOUNTS_DIR"/*/logs; do
|
|
46
|
+
for f in "$log_dir"/claude-agent-stream-*.log; do
|
|
47
|
+
if [[ -f "$f" ]]; then
|
|
48
|
+
# find -mmin needs minutes; 24h = 1440 min.
|
|
49
|
+
if find "$f" -mmin "-$((WINDOW_HOURS * 60))" -print -quit | grep -q .; then
|
|
50
|
+
base=$(basename "$f")
|
|
51
|
+
key="${base#claude-agent-stream-}"
|
|
52
|
+
key="${key%.log}"
|
|
53
|
+
KEYS+=("$key")
|
|
54
|
+
fi
|
|
55
|
+
fi
|
|
56
|
+
done
|
|
57
|
+
done
|
|
58
|
+
shopt -u nullglob
|
|
59
|
+
|
|
60
|
+
# De-duplicate sessionKeys (one session may emit multiple .log files via
|
|
61
|
+
# sibling prefixes; the existence contract is per-sessionKey).
|
|
62
|
+
declare -A SEEN=()
|
|
63
|
+
declare -a UNIQUE=()
|
|
64
|
+
for k in "${KEYS[@]:-}"; do
|
|
65
|
+
if [[ -z "${SEEN[$k]:-}" ]]; then
|
|
66
|
+
SEEN[$k]=1
|
|
67
|
+
UNIQUE+=("$k")
|
|
68
|
+
fi
|
|
69
|
+
done
|
|
70
|
+
|
|
71
|
+
SESSIONS=${#UNIQUE[@]}
|
|
72
|
+
MISSES=0
|
|
73
|
+
|
|
74
|
+
# For each sessionKey, verify the agent-stream file is present. Misses
|
|
75
|
+
# surface as a missing-on-resolve line. The file we just listed must
|
|
76
|
+
# exist by definition; this loop catches the case where a sessionKey was
|
|
77
|
+
# referenced in tee/register emissions on server.log but the file itself
|
|
78
|
+
# was unlinked between the scan and the check (very rare, but visible).
|
|
79
|
+
TS=$(date -u +%Y-%m-%dT%H:%M:%SZ)
|
|
80
|
+
for k in "${UNIQUE[@]:-}"; do
|
|
81
|
+
found=0
|
|
82
|
+
for log_dir in "$ACCOUNTS_DIR"/*/logs; do
|
|
83
|
+
if [[ -f "$log_dir/claude-agent-stream-${k}.log" ]]; then
|
|
84
|
+
found=1
|
|
85
|
+
break
|
|
86
|
+
fi
|
|
87
|
+
done
|
|
88
|
+
if [[ $found -eq 0 ]]; then
|
|
89
|
+
MISSES=$((MISSES + 1))
|
|
90
|
+
echo "${TS} [log-tee] missing-on-resolve sessionKey=${k:0:8} surface=adherence-script reason=file-not-on-disk" >> "$SERVER_LOG" 2>/dev/null || true
|
|
91
|
+
fi
|
|
92
|
+
done
|
|
93
|
+
|
|
94
|
+
echo "${TS} [log-tee] adherence-check window=${WINDOW_HOURS}h sessions=${SESSIONS} misses=${MISSES} surface=script ts=${TS}" >> "$SERVER_LOG" 2>/dev/null || true
|
|
95
|
+
echo "[log-adherence-check] sessions=${SESSIONS} misses=${MISSES} window=${WINDOW_HOURS}h"
|
|
96
|
+
|
|
97
|
+
if [[ $MISSES -gt 0 ]]; then
|
|
98
|
+
exit 1
|
|
99
|
+
fi
|
|
100
|
+
exit 0
|