velocious 1.0.604 → 1.0.605
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/README.md +19 -10
- package/build/cli/commands/test/timing-manifest/merge.js +14 -0
- package/build/environment-handlers/base.js +9 -0
- package/build/environment-handlers/node/cli/commands/test/timing-manifest/merge.js +108 -0
- package/build/environment-handlers/node/cli/commands/test.js +48 -7
- package/build/environment-handlers/node.js +10 -0
- package/build/src/cli/commands/test/timing-manifest/merge.d.ts +10 -0
- package/build/src/cli/commands/test/timing-manifest/merge.d.ts.map +1 -0
- package/build/src/cli/commands/test/timing-manifest/merge.js +13 -0
- package/build/src/environment-handlers/base.d.ts +6 -0
- package/build/src/environment-handlers/base.d.ts.map +1 -1
- package/build/src/environment-handlers/base.js +9 -1
- package/build/src/environment-handlers/node/cli/commands/test/timing-manifest/merge.d.ts +32 -0
- package/build/src/environment-handlers/node/cli/commands/test/timing-manifest/merge.d.ts.map +1 -0
- package/build/src/environment-handlers/node/cli/commands/test/timing-manifest/merge.js +97 -0
- package/build/src/environment-handlers/node/cli/commands/test.d.ts +3 -3
- package/build/src/environment-handlers/node/cli/commands/test.d.ts.map +1 -1
- package/build/src/environment-handlers/node/cli/commands/test.js +36 -9
- package/build/src/environment-handlers/node.d.ts +6 -0
- package/build/src/environment-handlers/node.d.ts.map +1 -1
- package/build/src/environment-handlers/node.js +10 -1
- package/build/src/testing/test-profile-output.d.ts +11 -0
- package/build/src/testing/test-profile-output.d.ts.map +1 -1
- package/build/src/testing/test-profile-output.js +21 -7
- package/build/src/testing/test-profiler.d.ts.map +1 -1
- package/build/src/testing/test-profiler.js +3 -2
- package/build/src/testing/test-suite-splitter.d.ts +33 -0
- package/build/src/testing/test-suite-splitter.d.ts.map +1 -1
- package/build/src/testing/test-suite-splitter.js +84 -8
- package/build/src/testing/timing-manifest.d.ts +94 -0
- package/build/src/testing/timing-manifest.d.ts.map +1 -0
- package/build/src/testing/timing-manifest.js +284 -0
- package/build/testing/test-profile-output.js +23 -8
- package/build/testing/test-profiler.js +2 -1
- package/build/testing/test-suite-splitter.js +91 -7
- package/build/testing/timing-manifest.js +346 -0
- package/build/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/scripts/bootstrap-provider-runtime.sh +249 -0
- package/scripts/verify-docker-dev-environment.js +219 -29
- package/src/cli/commands/test/timing-manifest/merge.js +14 -0
- package/src/environment-handlers/base.js +9 -0
- package/src/environment-handlers/node/cli/commands/test/timing-manifest/merge.js +108 -0
- package/src/environment-handlers/node/cli/commands/test.js +48 -7
- package/src/environment-handlers/node.js +10 -0
- package/src/testing/test-profile-output.js +23 -8
- package/src/testing/test-profiler.js +2 -1
- package/src/testing/test-suite-splitter.js +91 -7
- package/src/testing/timing-manifest.js +346 -0
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
#!/usr/bin/env bash
|
|
2
|
+
# Bootstrap durable provider homes and immutable agent-context discovery links.
|
|
3
|
+
set -euo pipefail
|
|
4
|
+
|
|
5
|
+
fail() {
|
|
6
|
+
printf 'Provider runtime bootstrap failed: %s\n' "$1" >&2
|
|
7
|
+
exit 1
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
if (($# < 4)); then
|
|
11
|
+
fail "usage: $0 <home> <provider-runtime> <agent-context> <command> [args...]"
|
|
12
|
+
fi
|
|
13
|
+
|
|
14
|
+
dev_home=$1
|
|
15
|
+
provider_runtime=$2
|
|
16
|
+
agent_context=$3
|
|
17
|
+
shift 3
|
|
18
|
+
|
|
19
|
+
for required_path in "$dev_home" "$provider_runtime" "$agent_context"; do
|
|
20
|
+
if [[ $required_path != /* ]]; then
|
|
21
|
+
fail "Required path must be absolute: $required_path"
|
|
22
|
+
fi
|
|
23
|
+
|
|
24
|
+
if [[ ! -d $required_path ]]; then
|
|
25
|
+
fail "Required directory does not exist: $required_path"
|
|
26
|
+
fi
|
|
27
|
+
done
|
|
28
|
+
|
|
29
|
+
if [[ ! -w $dev_home ]]; then
|
|
30
|
+
fail "Development home is not writable: $dev_home"
|
|
31
|
+
fi
|
|
32
|
+
|
|
33
|
+
if [[ ! -w $provider_runtime ]]; then
|
|
34
|
+
fail "Provider runtime is not writable: $provider_runtime"
|
|
35
|
+
fi
|
|
36
|
+
|
|
37
|
+
if [[ ! -f $agent_context/AGENTS.md || ! -d $agent_context/skills ]]; then
|
|
38
|
+
fail "Agent context must contain AGENTS.md and skills/: $agent_context"
|
|
39
|
+
fi
|
|
40
|
+
|
|
41
|
+
umask 077
|
|
42
|
+
backup_directory=
|
|
43
|
+
|
|
44
|
+
ensure_writable_parent() {
|
|
45
|
+
local parent_path=$1
|
|
46
|
+
|
|
47
|
+
if [[ -L $parent_path ]]; then
|
|
48
|
+
fail "Required parent directory must be lane-local, not a symlink: $parent_path"
|
|
49
|
+
fi
|
|
50
|
+
|
|
51
|
+
if [[ -e $parent_path && ! -d $parent_path ]]; then
|
|
52
|
+
fail "Required parent path is not a directory: $parent_path"
|
|
53
|
+
fi
|
|
54
|
+
|
|
55
|
+
if [[ ! -d $parent_path ]] && ! mkdir -p -- "$parent_path"; then
|
|
56
|
+
fail "Could not create required parent directory; verify ownership: $parent_path"
|
|
57
|
+
fi
|
|
58
|
+
|
|
59
|
+
if [[ ! -w $parent_path ]]; then
|
|
60
|
+
fail "Required parent directory is not writable; verify ownership: $parent_path"
|
|
61
|
+
fi
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
backup_relative_path() {
|
|
65
|
+
local target_path=$1
|
|
66
|
+
|
|
67
|
+
case $target_path in
|
|
68
|
+
"$dev_home"/*)
|
|
69
|
+
printf '%s\n' "${target_path#"$dev_home"/}"
|
|
70
|
+
;;
|
|
71
|
+
"$provider_runtime"/*)
|
|
72
|
+
printf '%s\n' "provider-runtime/${target_path#"$provider_runtime"/}"
|
|
73
|
+
;;
|
|
74
|
+
*)
|
|
75
|
+
fail "Refusing to migrate a path outside the development home or provider runtime: $target_path"
|
|
76
|
+
;;
|
|
77
|
+
esac
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
preserve_conflict() {
|
|
81
|
+
local target_path=$1
|
|
82
|
+
local relative_path
|
|
83
|
+
local backup_path
|
|
84
|
+
|
|
85
|
+
if [[ -z $backup_directory ]]; then
|
|
86
|
+
ensure_writable_parent "$dev_home/.provider-runtime-migration-backups"
|
|
87
|
+
|
|
88
|
+
if ! backup_directory=$(mktemp -d "$dev_home/.provider-runtime-migration-backups/$(date -u +%Y%m%dT%H%M%SZ)-XXXXXX"); then
|
|
89
|
+
fail "Could not create a timestamped migration backup under $dev_home/.provider-runtime-migration-backups"
|
|
90
|
+
fi
|
|
91
|
+
fi
|
|
92
|
+
|
|
93
|
+
relative_path=$(backup_relative_path "$target_path")
|
|
94
|
+
backup_path="$backup_directory/$relative_path"
|
|
95
|
+
|
|
96
|
+
if [[ -e $backup_path || -L $backup_path ]]; then
|
|
97
|
+
fail "Migration backup target already exists: $backup_path"
|
|
98
|
+
fi
|
|
99
|
+
|
|
100
|
+
if ! mkdir -p -- "$(dirname -- "$backup_path")"; then
|
|
101
|
+
fail "Could not create migration backup parent: $(dirname -- "$backup_path")"
|
|
102
|
+
fi
|
|
103
|
+
|
|
104
|
+
if ! mv -- "$target_path" "$backup_path"; then
|
|
105
|
+
fail "Could not preserve conflicting path at $backup_path: $target_path"
|
|
106
|
+
fi
|
|
107
|
+
|
|
108
|
+
printf 'Provider runtime bootstrap preserved %s at %s\n' "$target_path" "$backup_path" >&2
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
ensure_real_directory() {
|
|
112
|
+
local directory_path=$1
|
|
113
|
+
|
|
114
|
+
ensure_writable_parent "$(dirname -- "$directory_path")"
|
|
115
|
+
|
|
116
|
+
if [[ -L $directory_path || (-e $directory_path && ! -d $directory_path) ]]; then
|
|
117
|
+
preserve_conflict "$directory_path"
|
|
118
|
+
fi
|
|
119
|
+
|
|
120
|
+
if [[ ! -d $directory_path ]] && ! mkdir -- "$directory_path"; then
|
|
121
|
+
fail "Could not create required directory: $directory_path"
|
|
122
|
+
fi
|
|
123
|
+
|
|
124
|
+
if [[ ! -w $directory_path ]]; then
|
|
125
|
+
fail "Required directory is not writable; verify ownership: $directory_path"
|
|
126
|
+
fi
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
ensure_exact_link() {
|
|
130
|
+
local target_path=$1
|
|
131
|
+
local source_path=$2
|
|
132
|
+
|
|
133
|
+
ensure_writable_parent "$(dirname -- "$target_path")"
|
|
134
|
+
|
|
135
|
+
if [[ -L $target_path ]] && [[ $(readlink -- "$target_path") == "$source_path" ]]; then
|
|
136
|
+
return
|
|
137
|
+
fi
|
|
138
|
+
|
|
139
|
+
if [[ -e $target_path || -L $target_path ]]; then
|
|
140
|
+
preserve_conflict "$target_path"
|
|
141
|
+
fi
|
|
142
|
+
|
|
143
|
+
if ! ln -s -- "$source_path" "$target_path"; then
|
|
144
|
+
fail "Could not create required symlink: $target_path -> $source_path"
|
|
145
|
+
fi
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
require_real_runtime_directory() {
|
|
149
|
+
local directory_path=$1
|
|
150
|
+
|
|
151
|
+
if [[ -L $directory_path || ! -d $directory_path ]]; then
|
|
152
|
+
fail "Provider runtime directory must be a real directory: $directory_path"
|
|
153
|
+
fi
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
require_exact_runtime_alias() {
|
|
157
|
+
local alias_path=$1
|
|
158
|
+
local expected_link=$2
|
|
159
|
+
local resolved_directory=$3
|
|
160
|
+
local resolved_alias_path
|
|
161
|
+
local resolved_directory_path
|
|
162
|
+
|
|
163
|
+
if [[ ! -L $alias_path ]] || [[ $(readlink -- "$alias_path") != "$expected_link" ]]; then
|
|
164
|
+
fail "Provider runtime alias must be exact: $alias_path -> $expected_link"
|
|
165
|
+
fi
|
|
166
|
+
|
|
167
|
+
if [[ -L $resolved_directory || ! -d $resolved_directory ]]; then
|
|
168
|
+
fail "Provider runtime alias target must be an existing real directory: $resolved_directory"
|
|
169
|
+
fi
|
|
170
|
+
|
|
171
|
+
if ! resolved_alias_path=$(realpath -e -- "$alias_path"); then
|
|
172
|
+
fail "Provider runtime alias does not resolve: $alias_path"
|
|
173
|
+
fi
|
|
174
|
+
|
|
175
|
+
if ! resolved_directory_path=$(realpath -e -- "$resolved_directory"); then
|
|
176
|
+
fail "Provider runtime alias target does not resolve: $resolved_directory"
|
|
177
|
+
fi
|
|
178
|
+
|
|
179
|
+
if [[ $resolved_alias_path != "$resolved_directory_path" ]]; then
|
|
180
|
+
fail "Provider runtime alias must resolve to $resolved_directory: $alias_path"
|
|
181
|
+
fi
|
|
182
|
+
|
|
183
|
+
if [[ ! -w $resolved_directory ]]; then
|
|
184
|
+
fail "Provider runtime alias target is not writable; verify ownership: $resolved_directory"
|
|
185
|
+
fi
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
validate_runtime_aliases() {
|
|
189
|
+
require_real_runtime_directory "$provider_runtime/.local"
|
|
190
|
+
require_real_runtime_directory "$provider_runtime/.local/share"
|
|
191
|
+
require_exact_runtime_alias "$provider_runtime/.codex" "codex" "$provider_runtime/codex"
|
|
192
|
+
require_exact_runtime_alias "$provider_runtime/.kimi-code" "kimi-code" "$provider_runtime/kimi-code"
|
|
193
|
+
require_exact_runtime_alias "$provider_runtime/.opencode" "opencode" "$provider_runtime/opencode"
|
|
194
|
+
require_exact_runtime_alias "$provider_runtime/.local/share/opencode" "../../opencode" "$provider_runtime/opencode"
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
validate_runtime_aliases
|
|
198
|
+
|
|
199
|
+
if ! exec {provider_lock_fd}<"$provider_runtime"; then
|
|
200
|
+
fail "Could not open the provider runtime for shared-link locking: $provider_runtime"
|
|
201
|
+
fi
|
|
202
|
+
|
|
203
|
+
if ! flock --exclusive "$provider_lock_fd"; then
|
|
204
|
+
fail "Could not lock shared provider links: $provider_runtime"
|
|
205
|
+
fi
|
|
206
|
+
|
|
207
|
+
validate_runtime_aliases
|
|
208
|
+
|
|
209
|
+
for provider_home in \
|
|
210
|
+
"$provider_runtime/codex" \
|
|
211
|
+
"$provider_runtime/kimi-code"; do
|
|
212
|
+
ensure_exact_link "$provider_home/AGENTS.md" "$agent_context/AGENTS.md"
|
|
213
|
+
ensure_exact_link "$provider_home/CLAUDE.md" "$agent_context/AGENTS.md"
|
|
214
|
+
ensure_exact_link "$provider_home/skills" "$agent_context/skills"
|
|
215
|
+
done
|
|
216
|
+
|
|
217
|
+
if ! flock --unlock "$provider_lock_fd"; then
|
|
218
|
+
fail "Could not unlock shared provider links: $provider_runtime"
|
|
219
|
+
fi
|
|
220
|
+
|
|
221
|
+
exec {provider_lock_fd}<&-
|
|
222
|
+
|
|
223
|
+
for lane_parent in \
|
|
224
|
+
"$dev_home/.config" \
|
|
225
|
+
"$dev_home/.local" \
|
|
226
|
+
"$dev_home/.local/share" \
|
|
227
|
+
"$dev_home/.local/state" \
|
|
228
|
+
"$dev_home/.cache"; do
|
|
229
|
+
ensure_writable_parent "$lane_parent"
|
|
230
|
+
done
|
|
231
|
+
|
|
232
|
+
for lane_directory in \
|
|
233
|
+
"$dev_home/.config/opencode" \
|
|
234
|
+
"$dev_home/.local/share/opencode" \
|
|
235
|
+
"$dev_home/.local/state/opencode" \
|
|
236
|
+
"$dev_home/.cache/opencode"; do
|
|
237
|
+
ensure_real_directory "$lane_directory"
|
|
238
|
+
done
|
|
239
|
+
|
|
240
|
+
ensure_exact_link "$dev_home/.codex" "$provider_runtime/.codex"
|
|
241
|
+
ensure_exact_link "$dev_home/.kimi-code" "$provider_runtime/.kimi-code"
|
|
242
|
+
ensure_exact_link "$dev_home/.opencode" "$provider_runtime/.opencode"
|
|
243
|
+
ensure_exact_link "$dev_home/.local/share/opencode/auth.json" "$provider_runtime/.local/share/opencode/auth.json"
|
|
244
|
+
|
|
245
|
+
ensure_exact_link "$dev_home/.config/opencode/AGENTS.md" "$agent_context/AGENTS.md"
|
|
246
|
+
ensure_exact_link "$dev_home/.config/opencode/CLAUDE.md" "$agent_context/AGENTS.md"
|
|
247
|
+
ensure_exact_link "$dev_home/.config/opencode/skills" "$agent_context/skills"
|
|
248
|
+
|
|
249
|
+
exec "$@"
|
|
@@ -3,10 +3,11 @@
|
|
|
3
3
|
* Static contract verifier for the canonical Docker development environment.
|
|
4
4
|
*
|
|
5
5
|
* Dependency-free: checks the tracked Dockerfile, compose.yml, .env.example,
|
|
6
|
-
* .dockerignore, .gitignore, and scripts
|
|
7
|
-
* development-environment contract, then runs negative probes
|
|
8
|
-
* the real files to prove the checks actually catch regressions
|
|
9
|
-
* a standalone project `npm install` or a `COPY` of project
|
|
6
|
+
* .dockerignore, .gitignore, and development bootstrap scripts against the
|
|
7
|
+
* canonical development-environment contract, then runs negative probes
|
|
8
|
+
* derived from the real files to prove the checks actually catch regressions
|
|
9
|
+
* (for example a standalone project `npm install` or a `COPY` of project
|
|
10
|
+
* source).
|
|
10
11
|
*/
|
|
11
12
|
|
|
12
13
|
import fs from "node:fs"
|
|
@@ -280,7 +281,8 @@ function composeServiceNames(content) {
|
|
|
280
281
|
}
|
|
281
282
|
|
|
282
283
|
/**
|
|
283
|
-
* Extracts
|
|
284
|
+
* Extracts normalized volume entries from the first service-level volumes
|
|
285
|
+
* block.
|
|
284
286
|
*
|
|
285
287
|
* @param {string} content - The compose.yml content.
|
|
286
288
|
* @returns {string[]} - The volume entries.
|
|
@@ -289,6 +291,8 @@ function composeServiceVolumes(content) {
|
|
|
289
291
|
const lines = content.split("\n")
|
|
290
292
|
const volumes = []
|
|
291
293
|
let volumesIndent = null
|
|
294
|
+
/** @type {string[] | null} */
|
|
295
|
+
let currentVolume = null
|
|
292
296
|
|
|
293
297
|
for (const line of lines) {
|
|
294
298
|
if (volumesIndent === null) {
|
|
@@ -301,16 +305,27 @@ function composeServiceVolumes(content) {
|
|
|
301
305
|
|
|
302
306
|
const entryMatch = line.match(/^(\s+)-\s+(.+?)\s*$/u)
|
|
303
307
|
|
|
304
|
-
if (entryMatch && entryMatch[1].length
|
|
305
|
-
volumes.push(
|
|
308
|
+
if (entryMatch && entryMatch[1].length == volumesIndent + 2) {
|
|
309
|
+
if (currentVolume) volumes.push(currentVolume.join("\n"))
|
|
310
|
+
|
|
311
|
+
currentVolume = [`- ${entryMatch[2]}`]
|
|
306
312
|
continue
|
|
307
313
|
}
|
|
308
314
|
|
|
309
315
|
if (line.trim() == "" || line.trimStart().startsWith("#")) continue
|
|
310
316
|
|
|
317
|
+
const indentation = line.match(/^\s*/u)?.[0].length || 0
|
|
318
|
+
|
|
319
|
+
if (currentVolume && indentation > volumesIndent + 2) {
|
|
320
|
+
currentVolume.push(line.trim())
|
|
321
|
+
continue
|
|
322
|
+
}
|
|
323
|
+
|
|
311
324
|
break
|
|
312
325
|
}
|
|
313
326
|
|
|
327
|
+
if (currentVolume) volumes.push(currentVolume.join("\n"))
|
|
328
|
+
|
|
314
329
|
return volumes
|
|
315
330
|
}
|
|
316
331
|
|
|
@@ -337,10 +352,6 @@ function verifyCompose(content) {
|
|
|
337
352
|
problems.push(`compose.yml must not contain credential values (matched: ${content.match(COMPOSE_CREDENTIAL_VALUE_PATTERN)?.[0].trim()})`)
|
|
338
353
|
}
|
|
339
354
|
|
|
340
|
-
if (/threadwire/iu.test(content)) {
|
|
341
|
-
problems.push("compose.yml must not couple the development service to Threadwire")
|
|
342
|
-
}
|
|
343
|
-
|
|
344
355
|
if (!/^\s+init:\s*true\s*$/mu.test(content)) {
|
|
345
356
|
problems.push("The dev service must set init: true")
|
|
346
357
|
}
|
|
@@ -353,24 +364,44 @@ function verifyCompose(content) {
|
|
|
353
364
|
problems.push("The dev service must set working_dir /home/dev/velocious")
|
|
354
365
|
}
|
|
355
366
|
|
|
356
|
-
for (const envLine of [
|
|
367
|
+
for (const envLine of [
|
|
368
|
+
"GH_CONFIG_DIR: /home/dev/.config/gh",
|
|
369
|
+
"HOME: /home/dev",
|
|
370
|
+
"KIMI_CODE_HOME: /home/dev/.kimi-code",
|
|
371
|
+
"NODE_ENV: development",
|
|
372
|
+
"THREADWIRE_CODEX_BIN: /usr/local/bin/codex",
|
|
373
|
+
"THREADWIRE_KIMI_BIN: /usr/local/bin/kimi",
|
|
374
|
+
"THREADWIRE_OPENCODE_BIN: /usr/local/bin/opencode"
|
|
375
|
+
]) {
|
|
357
376
|
if (!content.includes(envLine)) {
|
|
358
377
|
problems.push(`The dev service is missing environment entry: ${envLine}`)
|
|
359
378
|
}
|
|
360
379
|
}
|
|
361
380
|
|
|
362
381
|
const expectedVolumes = [
|
|
363
|
-
"${DEV_HOME_PATH:-/home/dev}
|
|
364
|
-
"${
|
|
382
|
+
"- type: bind\nsource: ${DEV_HOME_PATH:-/home/dev}\ntarget: /home/dev\nbind:\ncreate_host_path: false",
|
|
383
|
+
"- type: bind\nsource: ${AI_PROVIDER_RUNTIME_SOURCE_PATH:?Set AI_PROVIDER_RUNTIME_SOURCE_PATH in .env}\ntarget: /opt/hermes-dind-shared/auth/provider-runtime\nbind:\ncreate_host_path: false",
|
|
384
|
+
"- type: bind\nsource: ${AGENT_CONTEXT_SOURCE_PATH:?Set AGENT_CONTEXT_SOURCE_PATH in .env}\ntarget: /opt/hermes-agent-context\nread_only: true\nbind:\ncreate_host_path: false",
|
|
385
|
+
"- type: bind\nsource: ${GH_CONFIG_SOURCE_PATH:?Set GH_CONFIG_SOURCE_PATH in .env}\ntarget: /home/dev/.config/gh\nread_only: true\nbind:\ncreate_host_path: false"
|
|
365
386
|
]
|
|
366
387
|
const actualVolumes = composeServiceVolumes(content)
|
|
367
388
|
|
|
368
389
|
if (actualVolumes.length != expectedVolumes.length || !expectedVolumes.every((volume) => actualVolumes.includes(volume))) {
|
|
369
|
-
problems.push(`The dev service must mount exactly the development
|
|
390
|
+
problems.push(`The dev service must mount exactly the development home, provider runtime, immutable agent context, and read-only GH config binds (found: ${actualVolumes.join(" | ") || "none"})`)
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
const expectedEntrypoint = " entrypoint: [\"/home/dev/velocious/scripts/bootstrap-provider-runtime.sh\", \"/home/dev\", \"/opt/hermes-dind-shared/auth/provider-runtime\", \"/opt/hermes-agent-context\"]"
|
|
394
|
+
|
|
395
|
+
if (!content.includes(expectedEntrypoint)) {
|
|
396
|
+
problems.push("The dev service must use the provider runtime bootstrap as its entrypoint so Compose command overrides remain bootstrapped")
|
|
370
397
|
}
|
|
371
398
|
|
|
372
399
|
if (!/^\s+command:\s*(\[\s*"sleep",\s*"infinity"\s*\]|sleep\s+infinity)\s*$/mu.test(content)) {
|
|
373
|
-
problems.push("The dev service must run sleep infinity")
|
|
400
|
+
problems.push("The dev service must run sleep infinity by default")
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
if (/\.npmrc|NPM_CONFIG_USERCONFIG/iu.test(content)) {
|
|
404
|
+
problems.push("The normal dev service must not mount or configure npm credentials")
|
|
374
405
|
}
|
|
375
406
|
|
|
376
407
|
if (/container_name:/u.test(content)) {
|
|
@@ -401,6 +432,14 @@ function verifyEnvExample(content) {
|
|
|
401
432
|
problems.push(".env.example must document GH_CONFIG_SOURCE_PATH")
|
|
402
433
|
}
|
|
403
434
|
|
|
435
|
+
if (!content.includes("AI_PROVIDER_RUNTIME_SOURCE_PATH")) {
|
|
436
|
+
problems.push(".env.example must document AI_PROVIDER_RUNTIME_SOURCE_PATH")
|
|
437
|
+
}
|
|
438
|
+
|
|
439
|
+
if (!content.includes("AGENT_CONTEXT_SOURCE_PATH")) {
|
|
440
|
+
problems.push(".env.example must document AGENT_CONTEXT_SOURCE_PATH")
|
|
441
|
+
}
|
|
442
|
+
|
|
404
443
|
if (!content.includes("DEV_HOME_PATH")) {
|
|
405
444
|
problems.push(".env.example must document DEV_HOME_PATH")
|
|
406
445
|
}
|
|
@@ -459,8 +498,79 @@ function verifyDockerRunSh(content) {
|
|
|
459
498
|
problems.push("scripts/docker-run.sh must execute commands through docker compose run")
|
|
460
499
|
}
|
|
461
500
|
|
|
462
|
-
|
|
463
|
-
|
|
501
|
+
return problems
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
/**
|
|
505
|
+
* Verifies the documented source-path preflight.
|
|
506
|
+
*
|
|
507
|
+
* @param {string} content - The Docker development guide content.
|
|
508
|
+
* @returns {string[]} - The problems found.
|
|
509
|
+
*/
|
|
510
|
+
function verifyDockerDevelopmentDocs(content) {
|
|
511
|
+
const problems = []
|
|
512
|
+
|
|
513
|
+
for (const containmentCheck of [
|
|
514
|
+
'case "$agent_context_source/" in "$provider_runtime_source/"*) exit 1;; esac',
|
|
515
|
+
'case "$provider_runtime_source/" in "$agent_context_source/"*) exit 1;; esac'
|
|
516
|
+
]) {
|
|
517
|
+
if (!content.includes(containmentCheck)) {
|
|
518
|
+
problems.push(`Docker preflight must reject bidirectional provider-runtime/agent-context containment: ${containmentCheck}`)
|
|
519
|
+
}
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
return problems
|
|
523
|
+
}
|
|
524
|
+
|
|
525
|
+
/**
|
|
526
|
+
* Verifies that the provider bootstrap preserves the canonical runtime aliases.
|
|
527
|
+
*
|
|
528
|
+
* @param {string} content - The bootstrap script content.
|
|
529
|
+
* @returns {string[]} - The problems found.
|
|
530
|
+
*/
|
|
531
|
+
function verifyProviderBootstrap(content) {
|
|
532
|
+
const problems = []
|
|
533
|
+
const firstAliasValidation = content.indexOf('require_exact_runtime_alias "$provider_runtime/.codex"')
|
|
534
|
+
|
|
535
|
+
for (const requiredParent of [
|
|
536
|
+
'require_real_runtime_directory "$provider_runtime/.local"',
|
|
537
|
+
'require_real_runtime_directory "$provider_runtime/.local/share"'
|
|
538
|
+
]) {
|
|
539
|
+
const parentValidation = content.indexOf(requiredParent)
|
|
540
|
+
|
|
541
|
+
if (parentValidation == -1 || firstAliasValidation == -1 || parentValidation > firstAliasValidation) {
|
|
542
|
+
problems.push(`scripts/bootstrap-provider-runtime.sh must validate the real runtime parent before aliases: ${requiredParent}`)
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
|
|
546
|
+
for (const requiredAlias of [
|
|
547
|
+
'require_exact_runtime_alias "$provider_runtime/.codex" "codex" "$provider_runtime/codex"',
|
|
548
|
+
'require_exact_runtime_alias "$provider_runtime/.kimi-code" "kimi-code" "$provider_runtime/kimi-code"',
|
|
549
|
+
'require_exact_runtime_alias "$provider_runtime/.opencode" "opencode" "$provider_runtime/opencode"',
|
|
550
|
+
'require_exact_runtime_alias "$provider_runtime/.local/share/opencode" "../../opencode" "$provider_runtime/opencode"'
|
|
551
|
+
]) {
|
|
552
|
+
if (!content.includes(requiredAlias)) {
|
|
553
|
+
problems.push(`scripts/bootstrap-provider-runtime.sh must validate canonical provider runtime alias: ${requiredAlias}`)
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
|
|
557
|
+
for (const resolutionCheck of [
|
|
558
|
+
'resolved_alias_path=$(realpath -e -- "$alias_path")',
|
|
559
|
+
'resolved_directory_path=$(realpath -e -- "$resolved_directory")',
|
|
560
|
+
'if [[ $resolved_alias_path != "$resolved_directory_path" ]]; then'
|
|
561
|
+
]) {
|
|
562
|
+
if (!content.includes(resolutionCheck)) {
|
|
563
|
+
problems.push(`scripts/bootstrap-provider-runtime.sh must verify canonical provider runtime aliases resolve to their target: ${resolutionCheck}`)
|
|
564
|
+
}
|
|
565
|
+
}
|
|
566
|
+
|
|
567
|
+
for (const lockOperation of [
|
|
568
|
+
'flock --exclusive "$provider_lock_fd"',
|
|
569
|
+
'flock --unlock "$provider_lock_fd"'
|
|
570
|
+
]) {
|
|
571
|
+
if (!content.includes(lockOperation)) {
|
|
572
|
+
problems.push(`scripts/bootstrap-provider-runtime.sh must serialize shared provider-link mutations: ${lockOperation}`)
|
|
573
|
+
}
|
|
464
574
|
}
|
|
465
575
|
|
|
466
576
|
return problems
|
|
@@ -470,7 +580,7 @@ function verifyDockerRunSh(content) {
|
|
|
470
580
|
* Builds negative probes derived from the real files. Each probe mutates the
|
|
471
581
|
* real content with a regression and must be caught by the matching verifier.
|
|
472
582
|
*
|
|
473
|
-
* @param {{dockerfile: string, compose: string}} contents - The real file contents.
|
|
583
|
+
* @param {{dockerDocs: string, dockerfile: string, compose: string, providerBootstrap: string}} contents - The real file contents.
|
|
474
584
|
* @returns {{name: string, problems: string[], expected: string}[]} - The probes.
|
|
475
585
|
*/
|
|
476
586
|
function negativeProbes(contents) {
|
|
@@ -502,7 +612,7 @@ function negativeProbes(contents) {
|
|
|
502
612
|
},
|
|
503
613
|
{
|
|
504
614
|
name: "compose credential mount regression",
|
|
505
|
-
problems: verifyCompose(contents.compose.replace("
|
|
615
|
+
problems: verifyCompose(contents.compose.replace(" entrypoint:", " - type: bind\n source: $" + "{HOME}/.kimi\n target: /home/dev/.kimi\n entrypoint:")),
|
|
506
616
|
expected: "mount exactly"
|
|
507
617
|
},
|
|
508
618
|
{
|
|
@@ -511,14 +621,74 @@ function negativeProbes(contents) {
|
|
|
511
621
|
expected: "credential values"
|
|
512
622
|
},
|
|
513
623
|
{
|
|
514
|
-
name: "compose
|
|
515
|
-
problems: verifyCompose(contents.compose.replace("
|
|
516
|
-
expected: "
|
|
624
|
+
name: "compose stale provider runtime target regression",
|
|
625
|
+
problems: verifyCompose(contents.compose.replace("target: /opt/hermes-dind-shared/auth/provider-runtime", "target: /run/ai-provider-runtime")),
|
|
626
|
+
expected: "mount exactly"
|
|
627
|
+
},
|
|
628
|
+
{
|
|
629
|
+
name: "compose mutable agent context source regression",
|
|
630
|
+
problems: verifyCompose(contents.compose.replace("${AGENT_CONTEXT_SOURCE_PATH:?Set AGENT_CONTEXT_SOURCE_PATH in .env}", "/opt/hermes-dind-shared/agent-context/current")),
|
|
631
|
+
expected: "mount exactly"
|
|
632
|
+
},
|
|
633
|
+
{
|
|
634
|
+
name: "compose stale agent context target regression",
|
|
635
|
+
problems: verifyCompose(contents.compose.replaceAll("/opt/hermes-agent-context", "/opt/agent-context")),
|
|
636
|
+
expected: "mount exactly"
|
|
637
|
+
},
|
|
638
|
+
{
|
|
639
|
+
name: "compose npm credential mount regression",
|
|
640
|
+
problems: verifyCompose(contents.compose.replace(" entrypoint:", " - type: bind\n source: $" + "{HOME}/.npmrc\n target: /home/dev/.npmrc\n read_only: true\n entrypoint:")),
|
|
641
|
+
expected: "npm credentials"
|
|
642
|
+
},
|
|
643
|
+
{
|
|
644
|
+
name: "compose bootstrap entrypoint regression",
|
|
645
|
+
problems: verifyCompose(contents.compose.replace(" entrypoint:", " x-entrypoint:")),
|
|
646
|
+
expected: "command overrides"
|
|
517
647
|
},
|
|
518
648
|
{
|
|
519
649
|
name: "compose second service regression",
|
|
520
650
|
problems: verifyCompose(`${contents.compose}\n worker:\n image: ubuntu\n`),
|
|
521
651
|
expected: "dev"
|
|
652
|
+
},
|
|
653
|
+
{
|
|
654
|
+
name: "provider runtime alias regression",
|
|
655
|
+
problems: verifyProviderBootstrap(contents.providerBootstrap.replace(
|
|
656
|
+
'require_exact_runtime_alias "$provider_runtime/.local/share/opencode" "../../opencode" "$provider_runtime/opencode"',
|
|
657
|
+
'require_exact_runtime_alias "$provider_runtime/.local/share/opencode" "opencode" "$provider_runtime/opencode"'
|
|
658
|
+
)),
|
|
659
|
+
expected: "canonical provider runtime alias"
|
|
660
|
+
},
|
|
661
|
+
{
|
|
662
|
+
name: "provider runtime alias resolution regression",
|
|
663
|
+
problems: verifyProviderBootstrap(contents.providerBootstrap.replace(
|
|
664
|
+
'resolved_alias_path=$(realpath -e -- "$alias_path")',
|
|
665
|
+
'resolved_alias_path=$(realpath -e -- "$resolved_directory")'
|
|
666
|
+
)),
|
|
667
|
+
expected: "resolve to their target"
|
|
668
|
+
},
|
|
669
|
+
{
|
|
670
|
+
name: "provider runtime real parent regression",
|
|
671
|
+
problems: verifyProviderBootstrap(contents.providerBootstrap.replace(
|
|
672
|
+
'require_real_runtime_directory "$provider_runtime/.local/share"',
|
|
673
|
+
'require_real_runtime_directory "$provider_runtime/.local-share"'
|
|
674
|
+
)),
|
|
675
|
+
expected: "real runtime parent"
|
|
676
|
+
},
|
|
677
|
+
{
|
|
678
|
+
name: "provider runtime serialization regression",
|
|
679
|
+
problems: verifyProviderBootstrap(contents.providerBootstrap.replace(
|
|
680
|
+
'flock --exclusive "$provider_lock_fd"',
|
|
681
|
+
'flock --shared "$provider_lock_fd"'
|
|
682
|
+
)),
|
|
683
|
+
expected: "serialize shared provider-link mutations"
|
|
684
|
+
},
|
|
685
|
+
{
|
|
686
|
+
name: "provider runtime and agent context containment regression",
|
|
687
|
+
problems: verifyDockerDevelopmentDocs(contents.dockerDocs.replace(
|
|
688
|
+
'case "$agent_context_source/" in "$provider_runtime_source/"*) exit 1;; esac',
|
|
689
|
+
""
|
|
690
|
+
)),
|
|
691
|
+
expected: "bidirectional provider-runtime/agent-context containment"
|
|
522
692
|
}
|
|
523
693
|
]
|
|
524
694
|
}
|
|
@@ -558,17 +728,37 @@ function main() {
|
|
|
558
728
|
}
|
|
559
729
|
|
|
560
730
|
const dockerRunPath = path.join(repoRoot, "scripts", "docker-run.sh")
|
|
731
|
+
const dockerDocsPath = path.join(repoRoot, "docs", "docker-development-environment.md")
|
|
732
|
+
const providerBootstrapPath = path.join(repoRoot, "scripts", "bootstrap-provider-runtime.sh")
|
|
733
|
+
const dockerDocs = fs.existsSync(dockerDocsPath) ? fs.readFileSync(dockerDocsPath, "utf8") : null
|
|
734
|
+
const providerBootstrap = fs.existsSync(providerBootstrapPath) ? fs.readFileSync(providerBootstrapPath, "utf8") : null
|
|
735
|
+
|
|
736
|
+
if (dockerDocs === null) {
|
|
737
|
+
problems.push("Missing required file: docs/docker-development-environment.md")
|
|
738
|
+
} else {
|
|
739
|
+
problems.push(...verifyDockerDevelopmentDocs(dockerDocs))
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
if (providerBootstrap === null) {
|
|
743
|
+
problems.push("Missing required file: scripts/bootstrap-provider-runtime.sh")
|
|
744
|
+
} else {
|
|
745
|
+
problems.push(...verifyProviderBootstrap(providerBootstrap))
|
|
746
|
+
}
|
|
561
747
|
|
|
562
|
-
if (
|
|
563
|
-
const
|
|
748
|
+
if (process.platform != "win32") {
|
|
749
|
+
for (const executablePath of [dockerRunPath, providerBootstrapPath]) {
|
|
750
|
+
if (!fs.existsSync(executablePath)) continue
|
|
564
751
|
|
|
565
|
-
|
|
566
|
-
|
|
752
|
+
const mode = fs.statSync(executablePath).mode
|
|
753
|
+
|
|
754
|
+
if ((mode & 0o111) == 0) {
|
|
755
|
+
problems.push(`${path.relative(repoRoot, executablePath)} must be executable`)
|
|
756
|
+
}
|
|
567
757
|
}
|
|
568
758
|
}
|
|
569
759
|
|
|
570
|
-
if (dockerfile !== null && compose !== null) {
|
|
571
|
-
for (const probe of negativeProbes({dockerfile, compose})) {
|
|
760
|
+
if (dockerDocs !== null && dockerfile !== null && compose !== null && providerBootstrap !== null) {
|
|
761
|
+
for (const probe of negativeProbes({dockerDocs, dockerfile, compose, providerBootstrap})) {
|
|
572
762
|
if (!probe.problems.some((problem) => problem.toLowerCase().includes(probe.expected.toLowerCase()))) {
|
|
573
763
|
problems.push(`Negative probe was not caught by the verifier: ${probe.name}`)
|
|
574
764
|
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
// @ts-check
|
|
2
|
+
|
|
3
|
+
import BaseCommand from "../../../base-command.js"
|
|
4
|
+
|
|
5
|
+
/** CLI command for merging complete rich test-profile shards into timing history. */
|
|
6
|
+
export default class TestTimingManifestMerge extends BaseCommand {
|
|
7
|
+
/**
|
|
8
|
+
* Runs execute.
|
|
9
|
+
* @returns {Promise<ReturnType<typeof JSON.parse>>} - Resolves with the merged manifest.
|
|
10
|
+
*/
|
|
11
|
+
async execute() {
|
|
12
|
+
return await this.getConfiguration().getEnvironmentHandler().cliCommandsTestTimingManifestMerge(this)
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -406,6 +406,15 @@ export default class VelociousEnvironmentHandlerBase {
|
|
|
406
406
|
throw new Error("cliCommandsTest not implemented")
|
|
407
407
|
}
|
|
408
408
|
|
|
409
|
+
/**
|
|
410
|
+
* Runs cli commands test timing manifest merge.
|
|
411
|
+
* @param {import("../cli/base-command.js").default} _command - Command.
|
|
412
|
+
* @returns {Promise<ReturnType<typeof JSON.parse>>} - Resolves with the command result.
|
|
413
|
+
*/
|
|
414
|
+
async cliCommandsTestTimingManifestMerge(_command) {
|
|
415
|
+
throw new Error("cliCommandsTestTimingManifestMerge not implemented")
|
|
416
|
+
}
|
|
417
|
+
|
|
409
418
|
/**
|
|
410
419
|
* Runs cli commands background jobs main.
|
|
411
420
|
* @param {import("../cli/base-command.js").default} _command - Command.
|