audrey 1.0.0 → 1.0.2
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/CHANGELOG.md +54 -0
- package/README.md +30 -6
- package/benchmarks/adapter-self-test.mjs +6 -2
- package/benchmarks/adapters/example-allow.mjs +5 -2
- package/benchmarks/adapters/mem0-platform.mjs +19 -12
- package/benchmarks/adapters/zep-cloud.mjs +51 -27
- package/benchmarks/baselines.js +11 -6
- package/benchmarks/build-leaderboard.mjs +36 -23
- package/benchmarks/cases.js +24 -12
- package/benchmarks/create-conformance-card.mjs +12 -3
- package/benchmarks/create-submission-bundle.mjs +22 -8
- package/benchmarks/dry-run-external-adapters.mjs +24 -12
- package/benchmarks/guardbench.js +354 -124
- package/benchmarks/output/adapter-self-test/guardbench-adapter-self-test.json +7 -7
- package/benchmarks/output/external/guardbench-external-dry-run.json +1 -1
- package/benchmarks/output/external/guardbench-external-evidence.json +1 -1
- package/benchmarks/output/guardbench-conformance-card.json +12 -12
- package/benchmarks/output/guardbench-raw.json +243 -144
- package/benchmarks/output/guardbench-summary.json +354 -230
- package/benchmarks/output/leaderboard/guardbench-leaderboard.json +5 -5
- package/benchmarks/output/leaderboard/guardbench-leaderboard.md +2 -2
- package/benchmarks/output/submission-bundle/guardbench-conformance-card.json +12 -12
- package/benchmarks/output/submission-bundle/guardbench-raw.json +243 -144
- package/benchmarks/output/submission-bundle/guardbench-summary.json +354 -230
- package/benchmarks/output/submission-bundle/schemas/guardbench-raw.schema.json +21 -1
- package/benchmarks/output/submission-bundle/schemas/guardbench-summary.schema.json +23 -2
- package/benchmarks/output/submission-bundle/submission-manifest.json +15 -15
- package/benchmarks/output/submission-bundle/validation-report.json +1 -1
- package/benchmarks/output/summary.json +58 -58
- package/benchmarks/perf-snapshot.js +12 -9
- package/benchmarks/perf.bench.js +14 -6
- package/benchmarks/public-paths.mjs +11 -5
- package/benchmarks/reference-results.js +10 -5
- package/benchmarks/report.js +48 -27
- package/benchmarks/run-external-guardbench.mjs +47 -25
- package/benchmarks/run.js +112 -59
- package/benchmarks/schemas/guardbench-raw.schema.json +21 -1
- package/benchmarks/schemas/guardbench-summary.schema.json +23 -2
- package/benchmarks/validate-adapter-module.mjs +13 -10
- package/benchmarks/validate-adapter-registry.mjs +16 -5
- package/benchmarks/validate-guardbench-artifacts.mjs +76 -19
- package/benchmarks/verify-external-evidence.mjs +86 -31
- package/benchmarks/verify-publication-artifacts.mjs +34 -11
- package/benchmarks/verify-submission-bundle.mjs +9 -4
- package/dist/mcp-server/config.d.ts +1 -1
- package/dist/mcp-server/config.d.ts.map +1 -1
- package/dist/mcp-server/config.js +5 -3
- package/dist/mcp-server/config.js.map +1 -1
- package/dist/mcp-server/index.d.ts +4 -3
- package/dist/mcp-server/index.d.ts.map +1 -1
- package/dist/mcp-server/index.js +479 -172
- package/dist/mcp-server/index.js.map +1 -1
- package/dist/src/action-key.d.ts.map +1 -1
- package/dist/src/action-key.js +6 -2
- package/dist/src/action-key.js.map +1 -1
- package/dist/src/adaptive.d.ts.map +1 -1
- package/dist/src/adaptive.js +4 -2
- package/dist/src/adaptive.js.map +1 -1
- package/dist/src/affect.d.ts.map +1 -1
- package/dist/src/affect.js +8 -5
- package/dist/src/affect.js.map +1 -1
- package/dist/src/audrey.d.ts +11 -1
- package/dist/src/audrey.d.ts.map +1 -1
- package/dist/src/audrey.js +110 -53
- package/dist/src/audrey.js.map +1 -1
- package/dist/src/capsule.d.ts.map +1 -1
- package/dist/src/capsule.js +37 -15
- package/dist/src/capsule.js.map +1 -1
- package/dist/src/causal.d.ts +1 -1
- package/dist/src/causal.d.ts.map +1 -1
- package/dist/src/causal.js +4 -2
- package/dist/src/causal.js.map +1 -1
- package/dist/src/confidence.d.ts.map +1 -1
- package/dist/src/confidence.js +5 -5
- package/dist/src/confidence.js.map +1 -1
- package/dist/src/consolidate.d.ts.map +1 -1
- package/dist/src/consolidate.js +17 -9
- package/dist/src/consolidate.js.map +1 -1
- package/dist/src/context.js +1 -1
- package/dist/src/context.js.map +1 -1
- package/dist/src/controller.d.ts +17 -1
- package/dist/src/controller.d.ts.map +1 -1
- package/dist/src/controller.js +73 -23
- package/dist/src/controller.js.map +1 -1
- package/dist/src/db.d.ts.map +1 -1
- package/dist/src/db.js +78 -27
- package/dist/src/db.js.map +1 -1
- package/dist/src/decay.d.ts +1 -1
- package/dist/src/decay.d.ts.map +1 -1
- package/dist/src/decay.js +1 -1
- package/dist/src/decay.js.map +1 -1
- package/dist/src/embedding.d.ts +12 -4
- package/dist/src/embedding.d.ts.map +1 -1
- package/dist/src/embedding.js +18 -16
- package/dist/src/embedding.js.map +1 -1
- package/dist/src/encode.d.ts.map +1 -1
- package/dist/src/encode.js +5 -4
- package/dist/src/encode.js.map +1 -1
- package/dist/src/events.d.ts +3 -2
- package/dist/src/events.d.ts.map +1 -1
- package/dist/src/events.js +7 -3
- package/dist/src/events.js.map +1 -1
- package/dist/src/export.d.ts.map +1 -1
- package/dist/src/export.js +21 -7
- package/dist/src/export.js.map +1 -1
- package/dist/src/feedback.d.ts.map +1 -1
- package/dist/src/feedback.js +1 -1
- package/dist/src/feedback.js.map +1 -1
- package/dist/src/forget.d.ts.map +1 -1
- package/dist/src/forget.js +12 -6
- package/dist/src/forget.js.map +1 -1
- package/dist/src/fts.d.ts.map +1 -1
- package/dist/src/fts.js +20 -8
- package/dist/src/fts.js.map +1 -1
- package/dist/src/hybrid-recall.d.ts.map +1 -1
- package/dist/src/hybrid-recall.js +12 -6
- package/dist/src/hybrid-recall.js.map +1 -1
- package/dist/src/impact.d.ts.map +1 -1
- package/dist/src/impact.js +26 -10
- package/dist/src/impact.js.map +1 -1
- package/dist/src/import.d.ts.map +1 -1
- package/dist/src/import.js +11 -6
- package/dist/src/import.js.map +1 -1
- package/dist/src/index.d.ts +5 -4
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +4 -4
- package/dist/src/index.js.map +1 -1
- package/dist/src/interference.d.ts.map +1 -1
- package/dist/src/interference.js +10 -5
- package/dist/src/interference.js.map +1 -1
- package/dist/src/introspect.d.ts.map +1 -1
- package/dist/src/introspect.js +12 -6
- package/dist/src/introspect.js.map +1 -1
- package/dist/src/llm.d.ts +2 -2
- package/dist/src/llm.d.ts.map +1 -1
- package/dist/src/llm.js +6 -6
- package/dist/src/llm.js.map +1 -1
- package/dist/src/migrate.d.ts.map +1 -1
- package/dist/src/migrate.js +10 -4
- package/dist/src/migrate.js.map +1 -1
- package/dist/src/preflight.d.ts.map +1 -1
- package/dist/src/preflight.js +6 -8
- package/dist/src/preflight.js.map +1 -1
- package/dist/src/profile.d.ts.map +1 -1
- package/dist/src/profile.js.map +1 -1
- package/dist/src/promote.d.ts.map +1 -1
- package/dist/src/promote.js +16 -7
- package/dist/src/promote.js.map +1 -1
- package/dist/src/prompts.d.ts.map +1 -1
- package/dist/src/prompts.js +1 -2
- package/dist/src/prompts.js.map +1 -1
- package/dist/src/recall.d.ts.map +1 -1
- package/dist/src/recall.js +85 -18
- package/dist/src/recall.js.map +1 -1
- package/dist/src/redact.d.ts.map +1 -1
- package/dist/src/redact.js +9 -4
- package/dist/src/redact.js.map +1 -1
- package/dist/src/reflexes.d.ts.map +1 -1
- package/dist/src/reflexes.js +1 -7
- package/dist/src/reflexes.js.map +1 -1
- package/dist/src/rollback.d.ts.map +1 -1
- package/dist/src/rollback.js +4 -2
- package/dist/src/rollback.js.map +1 -1
- package/dist/src/routes.d.ts.map +1 -1
- package/dist/src/routes.js +37 -14
- package/dist/src/routes.js.map +1 -1
- package/dist/src/rules-compiler.d.ts.map +1 -1
- package/dist/src/rules-compiler.js +24 -2
- package/dist/src/rules-compiler.js.map +1 -1
- package/dist/src/server.js +2 -2
- package/dist/src/server.js.map +1 -1
- package/dist/src/tool-trace.d.ts +2 -2
- package/dist/src/tool-trace.d.ts.map +1 -1
- package/dist/src/tool-trace.js +12 -4
- package/dist/src/tool-trace.js.map +1 -1
- package/dist/src/types.d.ts.map +1 -1
- package/dist/src/ulid.js +1 -1
- package/dist/src/ulid.js.map +1 -1
- package/dist/src/utils.d.ts.map +1 -1
- package/dist/src/utils.js.map +1 -1
- package/dist/src/validate.d.ts.map +1 -1
- package/dist/src/validate.js +20 -10
- package/dist/src/validate.js.map +1 -1
- package/docs/paper/07-evaluation.md +5 -5
- package/docs/paper/audrey-paper-v1.md +6 -6
- package/docs/paper/evidence-ledger.md +1 -1
- package/docs/paper/output/arxiv/arxiv-manifest.json +4 -4
- package/docs/paper/output/arxiv/main.tex +6 -6
- package/docs/paper/output/arxiv-compile-report.json +3 -3
- package/docs/paper/output/submission-bundle/README.md +30 -6
- package/docs/paper/output/submission-bundle/benchmarks/output/adapter-self-test/guardbench-adapter-self-test.json +7 -7
- package/docs/paper/output/submission-bundle/benchmarks/output/external/guardbench-external-dry-run.json +1 -1
- package/docs/paper/output/submission-bundle/benchmarks/output/external/guardbench-external-evidence.json +1 -1
- package/docs/paper/output/submission-bundle/benchmarks/output/guardbench-conformance-card.json +12 -12
- package/docs/paper/output/submission-bundle/benchmarks/output/guardbench-raw.json +243 -144
- package/docs/paper/output/submission-bundle/benchmarks/output/guardbench-summary.json +354 -230
- package/docs/paper/output/submission-bundle/benchmarks/output/leaderboard/guardbench-leaderboard.json +5 -5
- package/docs/paper/output/submission-bundle/benchmarks/output/leaderboard/guardbench-leaderboard.md +2 -2
- package/docs/paper/output/submission-bundle/benchmarks/output/submission-bundle/submission-manifest.json +15 -15
- package/docs/paper/output/submission-bundle/benchmarks/output/submission-bundle/validation-report.json +1 -1
- package/docs/paper/output/submission-bundle/benchmarks/output/summary.json +52 -52
- package/docs/paper/output/submission-bundle/benchmarks/schemas/guardbench-raw.schema.json +21 -1
- package/docs/paper/output/submission-bundle/benchmarks/schemas/guardbench-summary.schema.json +23 -2
- package/docs/paper/output/submission-bundle/docs/paper/07-evaluation.md +5 -5
- package/docs/paper/output/submission-bundle/docs/paper/audrey-paper-v1.md +6 -6
- package/docs/paper/output/submission-bundle/docs/paper/evidence-ledger.md +1 -1
- package/docs/paper/output/submission-bundle/docs/paper/output/arxiv/arxiv-manifest.json +4 -4
- package/docs/paper/output/submission-bundle/docs/paper/output/arxiv/main.tex +6 -6
- package/docs/paper/output/submission-bundle/docs/paper/output/arxiv-compile-report.json +3 -3
- package/docs/paper/output/submission-bundle/package.json +18 -5
- package/docs/paper/output/submission-bundle/paper-submission-manifest.json +40 -40
- package/examples/fintech-ops-demo.js +12 -5
- package/examples/healthcare-ops-demo.js +8 -4
- package/examples/ollama-memory-agent.js +41 -13
- package/examples/stripe-demo.js +12 -5
- package/package.json +18 -5
- package/scripts/audit-release-completion.mjs +179 -101
- package/scripts/create-arxiv-source.mjs +20 -14
- package/scripts/create-paper-submission-bundle.mjs +6 -2
- package/scripts/finalize-release.mjs +111 -36
- package/scripts/prepare-release-cut.mjs +14 -6
- package/scripts/publish-release-bundle.mjs +62 -23
- package/scripts/publish-release-github-api.mjs +89 -24
- package/scripts/smoke-cli.js +26 -6
- package/scripts/sync-paper-artifacts.mjs +5 -1
- package/scripts/verify-arxiv-compile.mjs +52 -16
- package/scripts/verify-arxiv-source.mjs +45 -15
- package/scripts/verify-browser-launch-plan.mjs +28 -11
- package/scripts/verify-browser-launch-results.mjs +32 -14
- package/scripts/verify-paper-artifacts.mjs +539 -79
- package/scripts/verify-paper-claims.mjs +48 -20
- package/scripts/verify-paper-submission-bundle.mjs +22 -11
- package/scripts/verify-publication-pack.mjs +23 -9
- package/scripts/verify-release-readiness.mjs +250 -71
|
@@ -27,11 +27,14 @@ function parseArgs(argv = process.argv.slice(2)) {
|
|
|
27
27
|
|
|
28
28
|
for (let i = 0; i < argv.length; i++) {
|
|
29
29
|
const token = argv[i];
|
|
30
|
-
if ((token === '--repository' || token === '--repo') && argv[i + 1])
|
|
30
|
+
if ((token === '--repository' || token === '--repo') && argv[i + 1])
|
|
31
|
+
args.repository = argv[++i];
|
|
31
32
|
else if (token === '--branch' && argv[i + 1]) args.branch = argv[++i];
|
|
32
|
-
else if ((token === '--version' || token === '--target-version') && argv[i + 1])
|
|
33
|
+
else if ((token === '--version' || token === '--target-version') && argv[i + 1])
|
|
34
|
+
args.version = argv[++i];
|
|
33
35
|
else if (token === '--token-env' && argv[i + 1]) args.tokenEnv = argv[++i];
|
|
34
|
-
else if (token === '--concurrency' && argv[i + 1])
|
|
36
|
+
else if (token === '--concurrency' && argv[i + 1])
|
|
37
|
+
args.concurrency = Number.parseInt(argv[++i], 10);
|
|
35
38
|
else if (token === '--apply') args.apply = true;
|
|
36
39
|
else if (token === '--json') args.json = true;
|
|
37
40
|
else if (token === '--force') args.force = true;
|
|
@@ -84,7 +87,10 @@ function run(command, args, options = {}) {
|
|
|
84
87
|
}
|
|
85
88
|
|
|
86
89
|
function assertOk(result) {
|
|
87
|
-
if (!result.ok)
|
|
90
|
+
if (!result.ok)
|
|
91
|
+
throw new Error(
|
|
92
|
+
`${result.command} failed: ${result.stderr || result.stdout || result.error || result.status}`,
|
|
93
|
+
);
|
|
88
94
|
return result.stdout;
|
|
89
95
|
}
|
|
90
96
|
|
|
@@ -97,7 +103,9 @@ function normalized(path) {
|
|
|
97
103
|
}
|
|
98
104
|
|
|
99
105
|
function sha256(path) {
|
|
100
|
-
return createHash('sha256')
|
|
106
|
+
return createHash('sha256')
|
|
107
|
+
.update(readFileSync(resolve(ROOT, path)))
|
|
108
|
+
.digest('hex');
|
|
101
109
|
}
|
|
102
110
|
|
|
103
111
|
function readJsonIfExists(path) {
|
|
@@ -106,9 +114,24 @@ function readJsonIfExists(path) {
|
|
|
106
114
|
}
|
|
107
115
|
|
|
108
116
|
function collectChangedPaths() {
|
|
109
|
-
const changed = splitZ(
|
|
110
|
-
|
|
111
|
-
|
|
117
|
+
const changed = splitZ(
|
|
118
|
+
assertOk(run('git', ['-c', 'core.quotepath=false', 'diff', '--name-only', '-z', 'HEAD', '--'])),
|
|
119
|
+
);
|
|
120
|
+
const untracked = splitZ(
|
|
121
|
+
assertOk(
|
|
122
|
+
run('git', [
|
|
123
|
+
'-c',
|
|
124
|
+
'core.quotepath=false',
|
|
125
|
+
'ls-files',
|
|
126
|
+
'--others',
|
|
127
|
+
'--exclude-standard',
|
|
128
|
+
'-z',
|
|
129
|
+
]),
|
|
130
|
+
),
|
|
131
|
+
);
|
|
132
|
+
return [...new Set([...changed, ...untracked].map(normalized))].sort((a, b) =>
|
|
133
|
+
a.localeCompare(b),
|
|
134
|
+
);
|
|
112
135
|
}
|
|
113
136
|
|
|
114
137
|
function fileMode(path) {
|
|
@@ -136,9 +159,34 @@ function changedEntries() {
|
|
|
136
159
|
}
|
|
137
160
|
|
|
138
161
|
function remoteRefs(repository, branch, version) {
|
|
139
|
-
let result = run(
|
|
140
|
-
|
|
141
|
-
|
|
162
|
+
let result = run(
|
|
163
|
+
'git',
|
|
164
|
+
[
|
|
165
|
+
'ls-remote',
|
|
166
|
+
`https://github.com/${repository}.git`,
|
|
167
|
+
`refs/heads/${branch}`,
|
|
168
|
+
`refs/tags/v${version}`,
|
|
169
|
+
],
|
|
170
|
+
{ timeout: 60_000 },
|
|
171
|
+
);
|
|
172
|
+
if (
|
|
173
|
+
!result.ok &&
|
|
174
|
+
/schannel|AcquireCredentialsHandle|SEC_E_NO_CREDENTIALS/i.test(
|
|
175
|
+
`${result.stderr}\n${result.stdout}`,
|
|
176
|
+
)
|
|
177
|
+
) {
|
|
178
|
+
result = run(
|
|
179
|
+
'git',
|
|
180
|
+
[
|
|
181
|
+
'-c',
|
|
182
|
+
'http.sslBackend=openssl',
|
|
183
|
+
'ls-remote',
|
|
184
|
+
`https://github.com/${repository}.git`,
|
|
185
|
+
`refs/heads/${branch}`,
|
|
186
|
+
`refs/tags/v${version}`,
|
|
187
|
+
],
|
|
188
|
+
{ timeout: 60_000 },
|
|
189
|
+
);
|
|
142
190
|
result.fallback = 'openssl';
|
|
143
191
|
}
|
|
144
192
|
|
|
@@ -155,7 +203,10 @@ function remoteRefs(repository, branch, version) {
|
|
|
155
203
|
}
|
|
156
204
|
|
|
157
205
|
function releaseDates() {
|
|
158
|
-
const headTime = Number.parseInt(
|
|
206
|
+
const headTime = Number.parseInt(
|
|
207
|
+
assertOk(run('git', ['show', '-s', '--format=%ct', 'HEAD'])),
|
|
208
|
+
10,
|
|
209
|
+
);
|
|
159
210
|
const commitEpoch = headTime + 1;
|
|
160
211
|
return {
|
|
161
212
|
commitEpoch,
|
|
@@ -191,7 +242,9 @@ async function githubJson(token, repository, path, options = {}) {
|
|
|
191
242
|
const payload = text ? JSON.parse(text) : null;
|
|
192
243
|
if (!response.ok) {
|
|
193
244
|
const message = payload?.message ?? text.slice(0, 500) ?? response.statusText;
|
|
194
|
-
throw new Error(
|
|
245
|
+
throw new Error(
|
|
246
|
+
`GitHub API ${options.method ?? 'GET'} ${path} failed (${response.status}): ${message}`,
|
|
247
|
+
);
|
|
195
248
|
}
|
|
196
249
|
return payload;
|
|
197
250
|
}
|
|
@@ -238,12 +291,18 @@ function localState(args) {
|
|
|
238
291
|
const bytes = entries.reduce((total, entry) => total + entry.bytes, 0);
|
|
239
292
|
const blockers = [];
|
|
240
293
|
|
|
241
|
-
if (!refs.result.ok)
|
|
294
|
+
if (!refs.result.ok)
|
|
295
|
+
blockers.push(
|
|
296
|
+
`Remote ref check failed: ${refs.result.stderr || refs.result.stdout || refs.result.error}`,
|
|
297
|
+
);
|
|
242
298
|
if (refs.branch && refs.branch !== localHead && !args.force) {
|
|
243
299
|
blockers.push(`Remote ${args.branch} is ${refs.branch}, but local HEAD is ${localHead}`);
|
|
244
300
|
}
|
|
245
301
|
if (refs.tag) blockers.push(`Remote tag v${args.version} already exists at ${refs.tag}`);
|
|
246
|
-
if (!objectReport?.commit || !objectReport?.tree)
|
|
302
|
+
if (!objectReport?.commit || !objectReport?.tree)
|
|
303
|
+
blockers.push(
|
|
304
|
+
'Missing .tmp/release-git-object-report.json; run npm run release:artifacts first',
|
|
305
|
+
);
|
|
247
306
|
|
|
248
307
|
return {
|
|
249
308
|
localHead,
|
|
@@ -283,7 +342,9 @@ async function publishWithGitHubApi(args, state, token) {
|
|
|
283
342
|
});
|
|
284
343
|
|
|
285
344
|
if (state.expectedReleaseTree && tree.sha !== state.expectedReleaseTree) {
|
|
286
|
-
throw new Error(
|
|
345
|
+
throw new Error(
|
|
346
|
+
`GitHub release tree ${tree.sha} does not match local source-bundle tree ${state.expectedReleaseTree}`,
|
|
347
|
+
);
|
|
287
348
|
}
|
|
288
349
|
|
|
289
350
|
const dates = releaseDates();
|
|
@@ -300,7 +361,9 @@ async function publishWithGitHubApi(args, state, token) {
|
|
|
300
361
|
});
|
|
301
362
|
|
|
302
363
|
if (state.expectedReleaseCommit && commit.sha !== state.expectedReleaseCommit) {
|
|
303
|
-
throw new Error(
|
|
364
|
+
throw new Error(
|
|
365
|
+
`GitHub release commit ${commit.sha} does not match local source-bundle commit ${state.expectedReleaseCommit}`,
|
|
366
|
+
);
|
|
304
367
|
}
|
|
305
368
|
|
|
306
369
|
const branchUpdate = await githubJson(token, args.repository, `/git/refs/heads/${args.branch}`, {
|
|
@@ -398,12 +461,12 @@ async function main() {
|
|
|
398
461
|
})),
|
|
399
462
|
changedEntries: args.includeEntries
|
|
400
463
|
? state.entries.map(entry => ({
|
|
401
|
-
|
|
402
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
464
|
+
path: entry.path,
|
|
465
|
+
deleted: entry.deleted,
|
|
466
|
+
mode: entry.mode,
|
|
467
|
+
bytes: entry.bytes,
|
|
468
|
+
sha256: entry.sha256,
|
|
469
|
+
}))
|
|
407
470
|
: undefined,
|
|
408
471
|
finalizeArtifacts: state.finalizeArtifacts,
|
|
409
472
|
blockers: [...state.blockers],
|
|
@@ -413,7 +476,9 @@ async function main() {
|
|
|
413
476
|
if (args.apply) {
|
|
414
477
|
const token = process.env[args.tokenEnv];
|
|
415
478
|
if (!token) {
|
|
416
|
-
report.blockers.push(
|
|
479
|
+
report.blockers.push(
|
|
480
|
+
`Set ${args.tokenEnv} to a GitHub token with contents:write before applying`,
|
|
481
|
+
);
|
|
417
482
|
} else if (report.blockers.length === 0) {
|
|
418
483
|
report.publish = await publishWithGitHubApi(args, state, token);
|
|
419
484
|
}
|
package/scripts/smoke-cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
|
|
3
3
|
import { spawnSync } from 'node:child_process';
|
|
4
|
-
import { existsSync, mkdtempSync, rmSync, readFileSync } from 'node:fs';
|
|
4
|
+
import { existsSync, mkdirSync, mkdtempSync, rmSync, readFileSync } from 'node:fs';
|
|
5
5
|
import { tmpdir } from 'node:os';
|
|
6
6
|
import { dirname, join, resolve } from 'node:path';
|
|
7
7
|
import { fileURLToPath } from 'node:url';
|
|
@@ -23,7 +23,25 @@ if (!existsSync(cli)) {
|
|
|
23
23
|
fail(`missing built CLI at ${cli}; run npm run build first`);
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
-
|
|
26
|
+
function createTempRoot() {
|
|
27
|
+
const candidates = [process.env.AUDREY_SMOKE_TMPDIR, tmpdir(), join(root, '.tmp')].filter(
|
|
28
|
+
Boolean,
|
|
29
|
+
);
|
|
30
|
+
const failures = [];
|
|
31
|
+
|
|
32
|
+
for (const candidate of candidates) {
|
|
33
|
+
try {
|
|
34
|
+
mkdirSync(candidate, { recursive: true });
|
|
35
|
+
return mkdtempSync(join(candidate, 'audrey-smoke-'));
|
|
36
|
+
} catch (error) {
|
|
37
|
+
failures.push(`${candidate}: ${error.code ?? error.message}`);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
fail(`unable to create smoke temp directory (${failures.join('; ')})`);
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const tempRoot = createTempRoot();
|
|
27
45
|
const env = {
|
|
28
46
|
...process.env,
|
|
29
47
|
AUDREY_DATA_DIR: join(tempRoot, 'store'),
|
|
@@ -55,10 +73,12 @@ try {
|
|
|
55
73
|
|
|
56
74
|
const doctor = JSON.parse(run('doctor --json', ['doctor', '--json']));
|
|
57
75
|
if (doctor.version !== pkg.version || doctor.ok !== true) {
|
|
58
|
-
fail(
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
76
|
+
fail(
|
|
77
|
+
`doctor --json returned unexpected release status: ${JSON.stringify({
|
|
78
|
+
version: doctor.version,
|
|
79
|
+
ok: doctor.ok,
|
|
80
|
+
})}`,
|
|
81
|
+
);
|
|
62
82
|
}
|
|
63
83
|
|
|
64
84
|
const demo = run('demo', ['demo']);
|
|
@@ -106,4 +106,8 @@ for (const [path, updater] of updates) {
|
|
|
106
106
|
}
|
|
107
107
|
}
|
|
108
108
|
|
|
109
|
-
console.log(
|
|
109
|
+
console.log(
|
|
110
|
+
changed.length
|
|
111
|
+
? `Synced paper artifacts: ${changed.join(', ')}`
|
|
112
|
+
: 'Paper artifacts already in sync.',
|
|
113
|
+
);
|
|
@@ -37,9 +37,12 @@ function pathForReport(path) {
|
|
|
37
37
|
}
|
|
38
38
|
|
|
39
39
|
function commandExists(command) {
|
|
40
|
-
const result =
|
|
41
|
-
|
|
42
|
-
|
|
40
|
+
const result =
|
|
41
|
+
process.platform === 'win32'
|
|
42
|
+
? spawnSync(process.env.ComSpec ?? 'cmd.exe', ['/d', '/c', 'where', command], {
|
|
43
|
+
encoding: 'utf-8',
|
|
44
|
+
})
|
|
45
|
+
: spawnSync('sh', ['-lc', `command -v ${command}`], { encoding: 'utf-8' });
|
|
43
46
|
return result.status === 0;
|
|
44
47
|
}
|
|
45
48
|
|
|
@@ -47,16 +50,17 @@ function compilerPlan(exists = commandExists) {
|
|
|
47
50
|
if (exists('tectonic')) {
|
|
48
51
|
return {
|
|
49
52
|
name: 'tectonic',
|
|
50
|
-
stages: [
|
|
51
|
-
{ command: 'tectonic', args: ['--keep-logs', '--keep-intermediates', MAIN_TEX] },
|
|
52
|
-
],
|
|
53
|
+
stages: [{ command: 'tectonic', args: ['--keep-logs', '--keep-intermediates', MAIN_TEX] }],
|
|
53
54
|
};
|
|
54
55
|
}
|
|
55
56
|
if (exists('latexmk')) {
|
|
56
57
|
return {
|
|
57
58
|
name: 'latexmk',
|
|
58
59
|
stages: [
|
|
59
|
-
{
|
|
60
|
+
{
|
|
61
|
+
command: 'latexmk',
|
|
62
|
+
args: ['-pdf', '-interaction=nonstopmode', '-halt-on-error', MAIN_TEX],
|
|
63
|
+
},
|
|
60
64
|
],
|
|
61
65
|
};
|
|
62
66
|
}
|
|
@@ -78,7 +82,18 @@ function compilerPlan(exists = commandExists) {
|
|
|
78
82
|
stages: [
|
|
79
83
|
{
|
|
80
84
|
command: 'uvx',
|
|
81
|
-
args: [
|
|
85
|
+
args: [
|
|
86
|
+
'tecto',
|
|
87
|
+
'-X',
|
|
88
|
+
'compile',
|
|
89
|
+
'--bundle',
|
|
90
|
+
'__TECTONIC_BUNDLE_URL__',
|
|
91
|
+
'--keep-logs',
|
|
92
|
+
'--keep-intermediates',
|
|
93
|
+
'--reruns',
|
|
94
|
+
'2',
|
|
95
|
+
MAIN_TEX,
|
|
96
|
+
],
|
|
82
97
|
},
|
|
83
98
|
],
|
|
84
99
|
};
|
|
@@ -164,7 +179,14 @@ async function startTectonicBundleProxy(bundleUrl = TECTONIC_BUNDLE_URL) {
|
|
|
164
179
|
if (request.headers.range) headers.range = request.headers.range;
|
|
165
180
|
const upstream = await fetch(remoteUrl, { headers });
|
|
166
181
|
response.statusCode = upstream.status;
|
|
167
|
-
for (const header of [
|
|
182
|
+
for (const header of [
|
|
183
|
+
'accept-ranges',
|
|
184
|
+
'content-length',
|
|
185
|
+
'content-range',
|
|
186
|
+
'content-type',
|
|
187
|
+
'etag',
|
|
188
|
+
'last-modified',
|
|
189
|
+
]) {
|
|
168
190
|
const value = upstream.headers.get(header);
|
|
169
191
|
if (value) response.setHeader(header, value);
|
|
170
192
|
}
|
|
@@ -196,7 +218,7 @@ async function startTectonicBundleProxy(bundleUrl = TECTONIC_BUNDLE_URL) {
|
|
|
196
218
|
function stageWithBundle(stage, bundleUrl) {
|
|
197
219
|
return {
|
|
198
220
|
command: stage.command,
|
|
199
|
-
args: stage.args.map(arg => arg === '__TECTONIC_BUNDLE_URL__' ? bundleUrl : arg),
|
|
221
|
+
args: stage.args.map(arg => (arg === '__TECTONIC_BUNDLE_URL__' ? bundleUrl : arg)),
|
|
200
222
|
};
|
|
201
223
|
}
|
|
202
224
|
|
|
@@ -210,7 +232,12 @@ function runStage(stage, cwd) {
|
|
|
210
232
|
child.kill();
|
|
211
233
|
if (!settled) {
|
|
212
234
|
settled = true;
|
|
213
|
-
resolveRun({
|
|
235
|
+
resolveRun({
|
|
236
|
+
status: 1,
|
|
237
|
+
signal: 'TIMEOUT',
|
|
238
|
+
stdout,
|
|
239
|
+
stderr: `${stderr}\nTimed out after 120000ms`.trim(),
|
|
240
|
+
});
|
|
214
241
|
}
|
|
215
242
|
}, 120000);
|
|
216
243
|
|
|
@@ -297,7 +324,9 @@ export async function verifyArxivCompile(options = {}) {
|
|
|
297
324
|
const logLines = [];
|
|
298
325
|
try {
|
|
299
326
|
proxy = plan.bundleProxy ? await startTectonicBundleProxy() : null;
|
|
300
|
-
const stages = proxy
|
|
327
|
+
const stages = proxy
|
|
328
|
+
? plan.stages.map(stage => stageWithBundle(stage, proxy.url))
|
|
329
|
+
: plan.stages;
|
|
301
330
|
for (const stage of stages) {
|
|
302
331
|
logLines.push(`$ ${stage.command} ${stage.args.join(' ')}`);
|
|
303
332
|
const result = await runStage(stage, outDir);
|
|
@@ -344,7 +373,7 @@ export function verifyArxivCompileReport(options = {}) {
|
|
|
344
373
|
const allowPending = options.allowPending !== false;
|
|
345
374
|
const failures = [];
|
|
346
375
|
const blockers = [];
|
|
347
|
-
let report
|
|
376
|
+
let report;
|
|
348
377
|
|
|
349
378
|
try {
|
|
350
379
|
report = JSON.parse(readFileSync(reportPath, 'utf-8'));
|
|
@@ -359,7 +388,9 @@ export function verifyArxivCompileReport(options = {}) {
|
|
|
359
388
|
}
|
|
360
389
|
|
|
361
390
|
try {
|
|
362
|
-
failures.push(
|
|
391
|
+
failures.push(
|
|
392
|
+
...validateSchema(report, readJson(pathForReport(schemaPath)), 'audrey-arxiv-compile-report'),
|
|
393
|
+
);
|
|
363
394
|
} catch (error) {
|
|
364
395
|
failures.push(`schema: ${error.message}`);
|
|
365
396
|
}
|
|
@@ -386,8 +417,13 @@ export function verifyArxivCompileReport(options = {}) {
|
|
|
386
417
|
} else if (report.status === 'failed') {
|
|
387
418
|
failures.push(...(report.failures?.length ? report.failures : ['arXiv compile failed']));
|
|
388
419
|
} else if (report.status === 'passed') {
|
|
389
|
-
if (!report.outputPdf || !existsSync(fromRoot(report.outputPdf)))
|
|
390
|
-
|
|
420
|
+
if (!report.outputPdf || !existsSync(fromRoot(report.outputPdf)))
|
|
421
|
+
failures.push('arxiv-compile-report.json: outputPdf is missing');
|
|
422
|
+
if (
|
|
423
|
+
report.outputPdf &&
|
|
424
|
+
report.outputPdfSha256 &&
|
|
425
|
+
sha256File(fromRoot(report.outputPdf)) !== report.outputPdfSha256
|
|
426
|
+
) {
|
|
391
427
|
failures.push('arxiv-compile-report.json: outputPdfSha256 is stale');
|
|
392
428
|
}
|
|
393
429
|
}
|
|
@@ -41,7 +41,8 @@ function checkSourceHash(label, sourcePath, expectedHash, failures) {
|
|
|
41
41
|
failures.push(`arxiv-manifest.json: missing source file for ${label}: ${sourcePath}`);
|
|
42
42
|
return;
|
|
43
43
|
}
|
|
44
|
-
if (expectedHash !== sha256File(absolute))
|
|
44
|
+
if (expectedHash !== sha256File(absolute))
|
|
45
|
+
failures.push(`arxiv-manifest.json: ${label} hash is stale`);
|
|
45
46
|
}
|
|
46
47
|
|
|
47
48
|
function parseArgs(argv = process.argv.slice(2)) {
|
|
@@ -76,7 +77,7 @@ export function verifyArxivSourcePackage(options = {}) {
|
|
|
76
77
|
const schemaPath = fromRoot(options.schema ?? DEFAULT_SCHEMA);
|
|
77
78
|
const manifestPath = join(dir, 'arxiv-manifest.json');
|
|
78
79
|
const failures = [];
|
|
79
|
-
let manifest
|
|
80
|
+
let manifest;
|
|
80
81
|
|
|
81
82
|
try {
|
|
82
83
|
manifest = readJson(manifestPath);
|
|
@@ -99,12 +100,29 @@ export function verifyArxivSourcePackage(options = {}) {
|
|
|
99
100
|
|
|
100
101
|
const listed = new Map((manifest.files ?? []).map(file => [file.path, file]));
|
|
101
102
|
for (const file of REQUIRED_FILES) {
|
|
102
|
-
if (!listed.has(file))
|
|
103
|
+
if (!listed.has(file))
|
|
104
|
+
failures.push(`arxiv-manifest.json: missing required file record ${file}`);
|
|
103
105
|
}
|
|
104
|
-
if (listed.has('arxiv-manifest.json'))
|
|
105
|
-
|
|
106
|
-
checkSourceHash(
|
|
107
|
-
|
|
106
|
+
if (listed.has('arxiv-manifest.json'))
|
|
107
|
+
failures.push('arxiv-manifest.json: must not include a self-hash file record');
|
|
108
|
+
checkSourceHash(
|
|
109
|
+
'sourceMarkdown',
|
|
110
|
+
manifest.sourceMarkdown,
|
|
111
|
+
manifest.sourceHashes?.sourceMarkdown,
|
|
112
|
+
failures,
|
|
113
|
+
);
|
|
114
|
+
checkSourceHash(
|
|
115
|
+
'publicationPack',
|
|
116
|
+
manifest.publicationPack,
|
|
117
|
+
manifest.sourceHashes?.publicationPack,
|
|
118
|
+
failures,
|
|
119
|
+
);
|
|
120
|
+
checkSourceHash(
|
|
121
|
+
'referencesBib',
|
|
122
|
+
'docs/paper/references.bib',
|
|
123
|
+
manifest.sourceHashes?.referencesBib,
|
|
124
|
+
failures,
|
|
125
|
+
);
|
|
108
126
|
|
|
109
127
|
for (const [file, record] of listed) {
|
|
110
128
|
const path = join(dir, file);
|
|
@@ -126,11 +144,14 @@ export function verifyArxivSourcePackage(options = {}) {
|
|
|
126
144
|
}
|
|
127
145
|
}
|
|
128
146
|
|
|
129
|
-
const actualFiles = walkFiles(dir)
|
|
147
|
+
const actualFiles = walkFiles(dir)
|
|
148
|
+
.filter(file => file !== 'arxiv-manifest.json')
|
|
149
|
+
.sort();
|
|
130
150
|
const listedFiles = [...listed.keys()].sort();
|
|
131
151
|
const listedSet = new Set(listedFiles);
|
|
132
152
|
for (const file of actualFiles) {
|
|
133
|
-
if (!listedSet.has(file))
|
|
153
|
+
if (!listedSet.has(file))
|
|
154
|
+
failures.push(`${file}: present in package but missing from manifest`);
|
|
134
155
|
}
|
|
135
156
|
|
|
136
157
|
const mainPath = join(dir, 'main.tex');
|
|
@@ -138,24 +159,33 @@ export function verifyArxivSourcePackage(options = {}) {
|
|
|
138
159
|
const main = existsSync(mainPath) ? readFileSync(mainPath, 'utf-8') : '';
|
|
139
160
|
const bib = existsSync(bibPath) ? readFileSync(bibPath, 'utf-8') : '';
|
|
140
161
|
const citationCount = [...main.matchAll(/\\cite\{([^}]+)\}/g)].length;
|
|
141
|
-
const citedIds = new Set(
|
|
162
|
+
const citedIds = new Set(
|
|
163
|
+
[...main.matchAll(/\\cite\{([^}]+)\}/g)].flatMap(match =>
|
|
164
|
+
match[1].split(',').map(id => id.trim()),
|
|
165
|
+
),
|
|
166
|
+
);
|
|
142
167
|
const bibIds = new Set([...bib.matchAll(/@\w+\s*\{\s*([^,\s]+)/g)].map(match => match[1].trim()));
|
|
143
168
|
const bibEntries = countBibEntries(bib);
|
|
144
169
|
|
|
145
170
|
if (!main.includes('\\documentclass')) failures.push('main.tex: missing documentclass');
|
|
146
171
|
if (!main.includes('\\begin{abstract}')) failures.push('main.tex: missing abstract');
|
|
147
|
-
if (!main.includes('\\bibliography{references}'))
|
|
172
|
+
if (!main.includes('\\bibliography{references}'))
|
|
173
|
+
failures.push('main.tex: missing bibliography command');
|
|
148
174
|
if (main.includes('[@')) failures.push('main.tex: contains unconverted Markdown citation syntax');
|
|
149
|
-
if (/^#{1,6}\s/m.test(main))
|
|
175
|
+
if (/^#{1,6}\s/m.test(main))
|
|
176
|
+
failures.push('main.tex: contains unconverted Markdown heading syntax');
|
|
150
177
|
if (main.includes(SEEDED_SECRET)) failures.push('main.tex: contains seeded raw secret');
|
|
151
|
-
if (/([A-Z]:\\|file:\/\/|C:\\Users\\|B:\\Projects\\)/i.test(main))
|
|
178
|
+
if (/([A-Z]:\\|file:\/\/|C:\\Users\\|B:\\Projects\\)/i.test(main))
|
|
179
|
+
failures.push('main.tex: contains a local absolute path');
|
|
152
180
|
if (citationCount < 1) failures.push('main.tex: expected at least one citation');
|
|
153
181
|
if (bibEntries !== 21) failures.push(`references.bib: expected 21 entries, found ${bibEntries}`);
|
|
154
182
|
for (const id of citedIds) {
|
|
155
183
|
if (!bibIds.has(id)) failures.push(`main.tex: cites missing bibliography id ${id}`);
|
|
156
184
|
}
|
|
157
|
-
if (manifest.tex?.citationCount !== citationCount)
|
|
158
|
-
|
|
185
|
+
if (manifest.tex?.citationCount !== citationCount)
|
|
186
|
+
failures.push('arxiv-manifest.json: citation count is stale');
|
|
187
|
+
if (manifest.tex?.bibEntryCount !== bibEntries)
|
|
188
|
+
failures.push('arxiv-manifest.json: bibliography count is stale');
|
|
159
189
|
|
|
160
190
|
return {
|
|
161
191
|
ok: failures.length === 0,
|
|
@@ -89,7 +89,9 @@ function isAllowedHost(platform, value) {
|
|
|
89
89
|
}
|
|
90
90
|
|
|
91
91
|
function hasPendingBoundary(text) {
|
|
92
|
-
return /\b(pending|not claim|not claimed|does not report|remain pending|live evidence|strict evidence)\b/i.test(
|
|
92
|
+
return /\b(pending|not claim|not claimed|does not report|remain pending|live evidence|strict evidence)\b/i.test(
|
|
93
|
+
text,
|
|
94
|
+
);
|
|
93
95
|
}
|
|
94
96
|
|
|
95
97
|
function validateTarget(target, entryMap, sourceIds) {
|
|
@@ -107,7 +109,9 @@ function validateTarget(target, entryMap, sourceIds) {
|
|
|
107
109
|
continue;
|
|
108
110
|
}
|
|
109
111
|
if (entry.platform !== target.platform) {
|
|
110
|
-
failures.push(
|
|
112
|
+
failures.push(
|
|
113
|
+
`${target.id}: entry ${entryId} belongs to ${entry.platform}, not ${target.platform}`,
|
|
114
|
+
);
|
|
111
115
|
}
|
|
112
116
|
if (!allowedEntries.has(entryId)) {
|
|
113
117
|
failures.push(`${target.id}: entry ${entryId} is not approved for ${target.platform}`);
|
|
@@ -116,7 +120,9 @@ function validateTarget(target, entryMap, sourceIds) {
|
|
|
116
120
|
failures.push(`${target.id}: entry ${entryId} exceeds maxChars`);
|
|
117
121
|
}
|
|
118
122
|
if (/\b(Mem0|Zep)\b/.test(entry.text) && !hasPendingBoundary(entry.text)) {
|
|
119
|
-
failures.push(
|
|
123
|
+
failures.push(
|
|
124
|
+
`${target.id}: entry ${entryId} mentions Mem0/Zep without pending boundary language`,
|
|
125
|
+
);
|
|
120
126
|
}
|
|
121
127
|
targetEntries.push(entry);
|
|
122
128
|
}
|
|
@@ -124,7 +130,8 @@ function validateTarget(target, entryMap, sourceIds) {
|
|
|
124
130
|
if (!sourceIds.has(sourceId)) failures.push(`${target.id}: unknown sourceRef ${sourceId}`);
|
|
125
131
|
}
|
|
126
132
|
for (const artifact of target.artifactRefs) {
|
|
127
|
-
if (!existsSync(fromRoot(artifact)))
|
|
133
|
+
if (!existsSync(fromRoot(artifact)))
|
|
134
|
+
failures.push(`${target.id}: missing artifactRef ${artifact}`);
|
|
128
135
|
}
|
|
129
136
|
if (target.platform === 'reddit' && target.manualRuleCheckRequired !== true) {
|
|
130
137
|
failures.push(`${target.id}: Reddit target must require a manual subreddit rule check`);
|
|
@@ -135,16 +142,21 @@ function validateTarget(target, entryMap, sourceIds) {
|
|
|
135
142
|
if (target.platform === 'arxiv' && target.manualRuleCheckRequired !== true) {
|
|
136
143
|
failures.push(`${target.id}: arXiv target must require a manual category/metadata check`);
|
|
137
144
|
}
|
|
138
|
-
if (!target.humanRequired)
|
|
139
|
-
|
|
140
|
-
if (target.
|
|
145
|
+
if (!target.humanRequired)
|
|
146
|
+
failures.push(`${target.id}: browser launch targets must require a human operator`);
|
|
147
|
+
if (!target.authRequired)
|
|
148
|
+
failures.push(`${target.id}: browser launch targets must require authenticated account review`);
|
|
149
|
+
if (target.operatorChecks.length < 2)
|
|
150
|
+
failures.push(`${target.id}: operator checklist is too thin`);
|
|
141
151
|
if (target.postSubmitChecks.length < 1) failures.push(`${target.id}: missing post-submit checks`);
|
|
142
152
|
if (
|
|
143
153
|
target.platform === 'x' &&
|
|
144
154
|
target.status === 'blocked-until-artifact-url' &&
|
|
145
155
|
!targetEntries.some(entry => entry.requiresArtifactUrl === true)
|
|
146
156
|
) {
|
|
147
|
-
failures.push(
|
|
157
|
+
failures.push(
|
|
158
|
+
`${target.id}: X artifact-url launch target must include a publication entry with reserved URL budget`,
|
|
159
|
+
);
|
|
148
160
|
}
|
|
149
161
|
|
|
150
162
|
return failures;
|
|
@@ -160,7 +172,9 @@ export async function verifyBrowserLaunchPlan(options = {}) {
|
|
|
160
172
|
const ids = new Set();
|
|
161
173
|
const targetReports = [];
|
|
162
174
|
const failures = [
|
|
163
|
-
...validateSchema(plan, schema, 'audrey-browser-launch-plan').map(
|
|
175
|
+
...validateSchema(plan, schema, 'audrey-browser-launch-plan').map(
|
|
176
|
+
failure => `browser launch plan schema: ${failure}`,
|
|
177
|
+
),
|
|
164
178
|
];
|
|
165
179
|
|
|
166
180
|
if (!publicationReport.ok) {
|
|
@@ -170,7 +184,8 @@ export async function verifyBrowserLaunchPlan(options = {}) {
|
|
|
170
184
|
failures.push('browser launch plan must point at docs/paper/publication-pack.json');
|
|
171
185
|
}
|
|
172
186
|
for (const command of REQUIRED_PREFLIGHT_COMMANDS) {
|
|
173
|
-
if (!(plan.preflightCommands ?? []).includes(command))
|
|
187
|
+
if (!(plan.preflightCommands ?? []).includes(command))
|
|
188
|
+
failures.push(`Missing browser-launch preflight command: ${command}`);
|
|
174
189
|
}
|
|
175
190
|
for (const target of plan.targets ?? []) {
|
|
176
191
|
const targetFailures = [];
|
|
@@ -192,7 +207,9 @@ export async function verifyBrowserLaunchPlan(options = {}) {
|
|
|
192
207
|
for (const id of REQUIRED_TARGETS) {
|
|
193
208
|
if (!ids.has(id)) failures.push(`Missing browser-launch target: ${id}`);
|
|
194
209
|
}
|
|
195
|
-
const ordered = [...(plan.targets ?? [])]
|
|
210
|
+
const ordered = [...(plan.targets ?? [])]
|
|
211
|
+
.sort((a, b) => a.order - b.order)
|
|
212
|
+
.map(target => target.id);
|
|
196
213
|
if (ordered.join('|') !== REQUIRED_TARGETS.join('|')) {
|
|
197
214
|
failures.push(`Browser-launch target order must be ${REQUIRED_TARGETS.join(', ')}`);
|
|
198
215
|
}
|