audrey 0.23.1 → 1.0.1
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 +101 -15
- package/LICENSE +21 -21
- package/README.md +232 -6
- package/SECURITY.md +2 -1
- package/benchmarks/adapter-kit.mjs +20 -0
- package/benchmarks/adapter-self-test.mjs +166 -0
- package/benchmarks/adapters/example-allow.mjs +28 -0
- package/benchmarks/adapters/mem0-platform.mjs +267 -0
- package/benchmarks/adapters/registry.json +51 -0
- package/benchmarks/adapters/zep-cloud.mjs +280 -0
- package/benchmarks/baselines.js +169 -0
- package/benchmarks/build-leaderboard.mjs +170 -0
- package/benchmarks/cases.js +537 -0
- package/benchmarks/create-conformance-card.mjs +139 -0
- package/benchmarks/create-submission-bundle.mjs +176 -0
- package/benchmarks/dry-run-external-adapters.mjs +165 -0
- package/benchmarks/guardbench.js +1125 -0
- package/benchmarks/output/adapter-self-test/guardbench-adapter-self-test.json +50 -0
- package/benchmarks/output/external/guardbench-external-dry-run.json +69 -0
- package/benchmarks/output/external/guardbench-external-evidence.json +56 -0
- package/benchmarks/output/guardbench-conformance-card.json +63 -0
- package/benchmarks/output/guardbench-manifest.json +414 -0
- package/benchmarks/output/guardbench-raw.json +1271 -0
- package/benchmarks/output/guardbench-summary.json +2107 -0
- package/benchmarks/output/leaderboard/guardbench-leaderboard.json +93 -0
- package/benchmarks/output/leaderboard/guardbench-leaderboard.md +7 -0
- package/benchmarks/output/submission-bundle/guardbench-conformance-card.json +63 -0
- package/benchmarks/output/submission-bundle/guardbench-manifest.json +414 -0
- package/benchmarks/output/submission-bundle/guardbench-raw.json +1271 -0
- package/benchmarks/output/submission-bundle/guardbench-summary.json +2107 -0
- package/benchmarks/output/submission-bundle/schemas/guardbench-adapter-registry.schema.json +69 -0
- package/benchmarks/output/submission-bundle/schemas/guardbench-adapter-self-test.schema.json +156 -0
- package/benchmarks/output/submission-bundle/schemas/guardbench-conformance-card.schema.json +184 -0
- package/benchmarks/output/submission-bundle/schemas/guardbench-external-dry-run.schema.json +74 -0
- package/benchmarks/output/submission-bundle/schemas/guardbench-external-evidence.schema.json +108 -0
- package/benchmarks/output/submission-bundle/schemas/guardbench-external-run.schema.json +160 -0
- package/benchmarks/output/submission-bundle/schemas/guardbench-leaderboard.schema.json +179 -0
- package/benchmarks/output/submission-bundle/schemas/guardbench-manifest.schema.json +213 -0
- package/benchmarks/output/submission-bundle/schemas/guardbench-publication-verification.schema.json +47 -0
- package/benchmarks/output/submission-bundle/schemas/guardbench-raw.schema.json +184 -0
- package/benchmarks/output/submission-bundle/schemas/guardbench-submission-manifest.schema.json +151 -0
- package/benchmarks/output/submission-bundle/schemas/guardbench-summary.schema.json +249 -0
- package/benchmarks/output/submission-bundle/submission-manifest.json +131 -0
- package/benchmarks/output/submission-bundle/validation-report.json +31 -0
- package/benchmarks/output/summary.json +2354 -0
- package/benchmarks/perf-snapshot.js +304 -0
- package/benchmarks/perf.bench.js +161 -0
- package/benchmarks/public-paths.mjs +78 -0
- package/benchmarks/reference-results.js +70 -0
- package/benchmarks/report.js +259 -0
- package/benchmarks/run-external-guardbench.mjs +281 -0
- package/benchmarks/run.js +682 -0
- package/benchmarks/schemas/guardbench-adapter-registry.schema.json +69 -0
- package/benchmarks/schemas/guardbench-adapter-self-test.schema.json +156 -0
- package/benchmarks/schemas/guardbench-conformance-card.schema.json +184 -0
- package/benchmarks/schemas/guardbench-external-dry-run.schema.json +74 -0
- package/benchmarks/schemas/guardbench-external-evidence.schema.json +108 -0
- package/benchmarks/schemas/guardbench-external-run.schema.json +160 -0
- package/benchmarks/schemas/guardbench-leaderboard.schema.json +179 -0
- package/benchmarks/schemas/guardbench-manifest.schema.json +213 -0
- package/benchmarks/schemas/guardbench-publication-verification.schema.json +47 -0
- package/benchmarks/schemas/guardbench-raw.schema.json +184 -0
- package/benchmarks/schemas/guardbench-submission-manifest.schema.json +151 -0
- package/benchmarks/schemas/guardbench-summary.schema.json +249 -0
- package/benchmarks/snapshots/perf-0.22.2.json +123 -0
- package/benchmarks/snapshots/perf-0.23.0.json +123 -0
- package/benchmarks/validate-adapter-module.mjs +104 -0
- package/benchmarks/validate-adapter-registry.mjs +134 -0
- package/benchmarks/validate-adapter-self-test.mjs +96 -0
- package/benchmarks/validate-guardbench-artifacts.mjs +343 -0
- package/benchmarks/verify-external-evidence.mjs +296 -0
- package/benchmarks/verify-publication-artifacts.mjs +286 -0
- package/benchmarks/verify-submission-bundle.mjs +167 -0
- 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 +1 -1
- package/dist/mcp-server/config.js.map +1 -1
- package/dist/mcp-server/index.d.ts +65 -3
- package/dist/mcp-server/index.d.ts.map +1 -1
- package/dist/mcp-server/index.js +675 -157
- package/dist/mcp-server/index.js.map +1 -1
- package/dist/src/action-key.d.ts +9 -0
- package/dist/src/action-key.d.ts.map +1 -0
- package/dist/src/action-key.js +49 -0
- package/dist/src/action-key.js.map +1 -0
- package/dist/src/adaptive.js +5 -5
- package/dist/src/affect.js +8 -8
- package/dist/src/audrey.d.ts +13 -0
- package/dist/src/audrey.d.ts.map +1 -1
- package/dist/src/audrey.js +68 -3
- package/dist/src/audrey.js.map +1 -1
- package/dist/src/capsule.js +4 -4
- package/dist/src/causal.js +3 -3
- package/dist/src/consolidate.js +48 -48
- package/dist/src/controller.d.ts +78 -6
- package/dist/src/controller.d.ts.map +1 -1
- package/dist/src/controller.js +273 -53
- package/dist/src/controller.js.map +1 -1
- package/dist/src/db.js +172 -172
- package/dist/src/decay.js +8 -8
- package/dist/src/embedding.d.ts +2 -1
- package/dist/src/embedding.d.ts.map +1 -1
- package/dist/src/embedding.js +39 -29
- package/dist/src/embedding.js.map +1 -1
- package/dist/src/encode.js +6 -6
- package/dist/src/feedback.d.ts +6 -0
- package/dist/src/feedback.d.ts.map +1 -1
- package/dist/src/feedback.js +6 -0
- package/dist/src/feedback.js.map +1 -1
- package/dist/src/forget.js +12 -12
- package/dist/src/hybrid-recall.js +9 -9
- package/dist/src/impact.js +6 -6
- package/dist/src/import.d.ts +3 -3
- package/dist/src/import.js +41 -41
- package/dist/src/index.d.ts +5 -4
- package/dist/src/index.d.ts.map +1 -1
- package/dist/src/index.js +3 -3
- package/dist/src/index.js.map +1 -1
- package/dist/src/interference.js +14 -14
- package/dist/src/introspect.js +18 -18
- package/dist/src/preflight.d.ts.map +1 -1
- package/dist/src/preflight.js +41 -0
- package/dist/src/preflight.js.map +1 -1
- package/dist/src/promote.js +7 -7
- package/dist/src/prompts.js +118 -118
- package/dist/src/recall.js +30 -30
- package/dist/src/reflexes.d.ts +1 -0
- package/dist/src/reflexes.d.ts.map +1 -1
- package/dist/src/reflexes.js +3 -0
- package/dist/src/reflexes.js.map +1 -1
- package/dist/src/rollback.js +4 -4
- package/dist/src/routes.d.ts.map +1 -1
- package/dist/src/routes.js +71 -2
- package/dist/src/routes.js.map +1 -1
- package/dist/src/validate.js +25 -25
- package/docs/AUDREY_PAPER_OUTLINE.md +175 -0
- package/docs/MEMORY_BENCHMARKING.md +59 -0
- package/docs/PRODUCTION_BACKLOG.md +304 -0
- package/docs/paper/00-master.md +48 -0
- package/docs/paper/01-introduction.md +27 -0
- package/docs/paper/02-related-work.md +47 -0
- package/docs/paper/03-problem-definition.md +108 -0
- package/docs/paper/04-design.md +164 -0
- package/docs/paper/05-guardbench-spec.md +412 -0
- package/docs/paper/06-implementation.md +113 -0
- package/docs/paper/07-evaluation.md +168 -0
- package/docs/paper/08-discussion-limitations.md +61 -0
- package/docs/paper/09-conclusion.md +11 -0
- package/docs/paper/SUBMISSION_README.md +162 -0
- package/docs/paper/appendix-a-demo-transcript.md +114 -0
- package/docs/paper/arxiv-compile-report.schema.json +116 -0
- package/docs/paper/arxiv-source.schema.json +61 -0
- package/docs/paper/audrey-paper-v1.md +1106 -0
- package/docs/paper/browser-launch-plan.json +209 -0
- package/docs/paper/browser-launch-plan.schema.json +100 -0
- package/docs/paper/browser-launch-results.json +86 -0
- package/docs/paper/browser-launch-results.schema.json +66 -0
- package/docs/paper/claim-register.json +138 -0
- package/docs/paper/claim-register.schema.json +81 -0
- package/docs/paper/evidence-ledger.md +103 -0
- package/docs/paper/output/arxiv/README-arxiv.txt +8 -0
- package/docs/paper/output/arxiv/arxiv-manifest.json +41 -0
- package/docs/paper/output/arxiv/main.tex +949 -0
- package/docs/paper/output/arxiv/references.bib +222 -0
- package/docs/paper/output/arxiv-compile-report.json +24 -0
- package/docs/paper/output/submission-bundle/LICENSE +21 -0
- package/docs/paper/output/submission-bundle/README.md +555 -0
- package/docs/paper/output/submission-bundle/benchmarks/output/adapter-self-test/guardbench-adapter-self-test.json +50 -0
- package/docs/paper/output/submission-bundle/benchmarks/output/external/guardbench-external-dry-run.json +69 -0
- package/docs/paper/output/submission-bundle/benchmarks/output/external/guardbench-external-evidence.json +56 -0
- package/docs/paper/output/submission-bundle/benchmarks/output/guardbench-conformance-card.json +63 -0
- package/docs/paper/output/submission-bundle/benchmarks/output/guardbench-manifest.json +414 -0
- package/docs/paper/output/submission-bundle/benchmarks/output/guardbench-raw.json +1271 -0
- package/docs/paper/output/submission-bundle/benchmarks/output/guardbench-summary.json +2107 -0
- package/docs/paper/output/submission-bundle/benchmarks/output/leaderboard/guardbench-leaderboard.json +93 -0
- package/docs/paper/output/submission-bundle/benchmarks/output/leaderboard/guardbench-leaderboard.md +7 -0
- package/docs/paper/output/submission-bundle/benchmarks/output/submission-bundle/submission-manifest.json +131 -0
- package/docs/paper/output/submission-bundle/benchmarks/output/submission-bundle/validation-report.json +31 -0
- package/docs/paper/output/submission-bundle/benchmarks/output/summary.json +2354 -0
- package/docs/paper/output/submission-bundle/benchmarks/schemas/guardbench-adapter-registry.schema.json +69 -0
- package/docs/paper/output/submission-bundle/benchmarks/schemas/guardbench-adapter-self-test.schema.json +156 -0
- package/docs/paper/output/submission-bundle/benchmarks/schemas/guardbench-conformance-card.schema.json +184 -0
- package/docs/paper/output/submission-bundle/benchmarks/schemas/guardbench-external-dry-run.schema.json +74 -0
- package/docs/paper/output/submission-bundle/benchmarks/schemas/guardbench-external-evidence.schema.json +108 -0
- package/docs/paper/output/submission-bundle/benchmarks/schemas/guardbench-external-run.schema.json +160 -0
- package/docs/paper/output/submission-bundle/benchmarks/schemas/guardbench-leaderboard.schema.json +179 -0
- package/docs/paper/output/submission-bundle/benchmarks/schemas/guardbench-manifest.schema.json +213 -0
- package/docs/paper/output/submission-bundle/benchmarks/schemas/guardbench-publication-verification.schema.json +47 -0
- package/docs/paper/output/submission-bundle/benchmarks/schemas/guardbench-raw.schema.json +184 -0
- package/docs/paper/output/submission-bundle/benchmarks/schemas/guardbench-submission-manifest.schema.json +151 -0
- package/docs/paper/output/submission-bundle/benchmarks/schemas/guardbench-summary.schema.json +249 -0
- package/docs/paper/output/submission-bundle/docs/AUDREY_PAPER_OUTLINE.md +175 -0
- package/docs/paper/output/submission-bundle/docs/paper/00-master.md +48 -0
- package/docs/paper/output/submission-bundle/docs/paper/01-introduction.md +27 -0
- package/docs/paper/output/submission-bundle/docs/paper/02-related-work.md +47 -0
- package/docs/paper/output/submission-bundle/docs/paper/03-problem-definition.md +108 -0
- package/docs/paper/output/submission-bundle/docs/paper/04-design.md +164 -0
- package/docs/paper/output/submission-bundle/docs/paper/05-guardbench-spec.md +412 -0
- package/docs/paper/output/submission-bundle/docs/paper/06-implementation.md +113 -0
- package/docs/paper/output/submission-bundle/docs/paper/07-evaluation.md +168 -0
- package/docs/paper/output/submission-bundle/docs/paper/08-discussion-limitations.md +61 -0
- package/docs/paper/output/submission-bundle/docs/paper/09-conclusion.md +11 -0
- package/docs/paper/output/submission-bundle/docs/paper/SUBMISSION_README.md +162 -0
- package/docs/paper/output/submission-bundle/docs/paper/appendix-a-demo-transcript.md +114 -0
- package/docs/paper/output/submission-bundle/docs/paper/arxiv-compile-report.schema.json +116 -0
- package/docs/paper/output/submission-bundle/docs/paper/arxiv-source.schema.json +61 -0
- package/docs/paper/output/submission-bundle/docs/paper/audrey-paper-v1.md +1106 -0
- package/docs/paper/output/submission-bundle/docs/paper/browser-launch-plan.json +209 -0
- package/docs/paper/output/submission-bundle/docs/paper/browser-launch-plan.schema.json +100 -0
- package/docs/paper/output/submission-bundle/docs/paper/browser-launch-results.json +86 -0
- package/docs/paper/output/submission-bundle/docs/paper/browser-launch-results.schema.json +66 -0
- package/docs/paper/output/submission-bundle/docs/paper/claim-register.json +138 -0
- package/docs/paper/output/submission-bundle/docs/paper/claim-register.schema.json +81 -0
- package/docs/paper/output/submission-bundle/docs/paper/evidence-ledger.md +103 -0
- package/docs/paper/output/submission-bundle/docs/paper/output/arxiv/README-arxiv.txt +8 -0
- package/docs/paper/output/submission-bundle/docs/paper/output/arxiv/arxiv-manifest.json +41 -0
- package/docs/paper/output/submission-bundle/docs/paper/output/arxiv/main.tex +949 -0
- package/docs/paper/output/submission-bundle/docs/paper/output/arxiv/references.bib +222 -0
- package/docs/paper/output/submission-bundle/docs/paper/output/arxiv-compile-report.json +24 -0
- package/docs/paper/output/submission-bundle/docs/paper/paper-submission-bundle.schema.json +70 -0
- package/docs/paper/output/submission-bundle/docs/paper/publication-pack.json +81 -0
- package/docs/paper/output/submission-bundle/docs/paper/publication-pack.schema.json +60 -0
- package/docs/paper/output/submission-bundle/docs/paper/references.bib +222 -0
- package/docs/paper/output/submission-bundle/package.json +212 -0
- package/docs/paper/output/submission-bundle/paper-submission-manifest.json +379 -0
- package/docs/paper/paper-submission-bundle.schema.json +70 -0
- package/docs/paper/publication-pack.json +81 -0
- package/docs/paper/publication-pack.schema.json +60 -0
- package/docs/paper/references.bib +222 -0
- package/package.json +87 -4
- package/scripts/audit-release-completion.mjs +362 -0
- package/scripts/create-arxiv-source.mjs +362 -0
- package/scripts/create-paper-submission-bundle.mjs +210 -0
- package/scripts/finalize-release.mjs +526 -0
- package/scripts/prepare-release-cut.mjs +269 -0
- package/scripts/publish-release-bundle.mjs +209 -0
- package/scripts/publish-release-github-api.mjs +429 -0
- package/scripts/run-vitest.mjs +34 -0
- package/scripts/smoke-cli.js +92 -0
- package/scripts/sync-paper-artifacts.mjs +109 -0
- package/scripts/verify-arxiv-compile.mjs +440 -0
- package/scripts/verify-arxiv-source.mjs +194 -0
- package/scripts/verify-browser-launch-plan.mjs +237 -0
- package/scripts/verify-browser-launch-results.mjs +285 -0
- package/scripts/verify-paper-artifacts.mjs +338 -0
- package/scripts/verify-paper-claims.mjs +226 -0
- package/scripts/verify-paper-submission-bundle.mjs +207 -0
- package/scripts/verify-publication-pack.mjs +196 -0
- package/scripts/verify-python-package.py +201 -0
- package/scripts/verify-release-readiness.mjs +785 -0
|
@@ -0,0 +1,440 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { spawn, spawnSync } from 'node:child_process';
|
|
3
|
+
import { cpSync, existsSync, mkdirSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
|
|
4
|
+
import { createServer } from 'node:http';
|
|
5
|
+
import { dirname, join, relative, resolve } from 'node:path';
|
|
6
|
+
import { Readable } from 'node:stream';
|
|
7
|
+
import { fileURLToPath } from 'node:url';
|
|
8
|
+
import { validateSchema } from '../benchmarks/validate-guardbench-artifacts.mjs';
|
|
9
|
+
import { publicPath } from '../benchmarks/public-paths.mjs';
|
|
10
|
+
import { verifyArxivSourcePackage } from './verify-arxiv-source.mjs';
|
|
11
|
+
|
|
12
|
+
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
13
|
+
const DEFAULT_SOURCE_DIR = 'docs/paper/output/arxiv';
|
|
14
|
+
const DEFAULT_OUT_DIR = 'docs/paper/output/arxiv-compile';
|
|
15
|
+
const DEFAULT_REPORT = 'docs/paper/output/arxiv-compile-report.json';
|
|
16
|
+
const DEFAULT_SCHEMA = 'docs/paper/arxiv-compile-report.schema.json';
|
|
17
|
+
const MAIN_TEX = 'main.tex';
|
|
18
|
+
const REFERENCES_BIB = 'references.bib';
|
|
19
|
+
const TECTONIC_BUNDLE_URL = 'https://data1.fullyjustified.net/tlextras-2022.0r0.tar';
|
|
20
|
+
|
|
21
|
+
function fromRoot(path) {
|
|
22
|
+
return resolve(ROOT, path);
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function readJson(path) {
|
|
26
|
+
return JSON.parse(readFileSync(fromRoot(path), 'utf-8'));
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function sha256File(path) {
|
|
30
|
+
return createHash('sha256').update(readFileSync(path)).digest('hex');
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function pathForReport(path) {
|
|
34
|
+
const rel = relative(ROOT, path);
|
|
35
|
+
if (rel && !rel.startsWith('..')) return rel.replaceAll('\\', '/');
|
|
36
|
+
return publicPath(path);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function commandExists(command) {
|
|
40
|
+
const result = process.platform === 'win32'
|
|
41
|
+
? spawnSync(process.env.ComSpec ?? 'cmd.exe', ['/d', '/c', 'where', command], { encoding: 'utf-8' })
|
|
42
|
+
: spawnSync('sh', ['-lc', `command -v ${command}`], { encoding: 'utf-8' });
|
|
43
|
+
return result.status === 0;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function compilerPlan(exists = commandExists) {
|
|
47
|
+
if (exists('tectonic')) {
|
|
48
|
+
return {
|
|
49
|
+
name: 'tectonic',
|
|
50
|
+
stages: [
|
|
51
|
+
{ command: 'tectonic', args: ['--keep-logs', '--keep-intermediates', MAIN_TEX] },
|
|
52
|
+
],
|
|
53
|
+
};
|
|
54
|
+
}
|
|
55
|
+
if (exists('latexmk')) {
|
|
56
|
+
return {
|
|
57
|
+
name: 'latexmk',
|
|
58
|
+
stages: [
|
|
59
|
+
{ command: 'latexmk', args: ['-pdf', '-interaction=nonstopmode', '-halt-on-error', MAIN_TEX] },
|
|
60
|
+
],
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
if (exists('pdflatex') && exists('bibtex')) {
|
|
64
|
+
return {
|
|
65
|
+
name: 'pdflatex+bibtex',
|
|
66
|
+
stages: [
|
|
67
|
+
{ command: 'pdflatex', args: ['-interaction=nonstopmode', '-halt-on-error', MAIN_TEX] },
|
|
68
|
+
{ command: 'bibtex', args: ['main'] },
|
|
69
|
+
{ command: 'pdflatex', args: ['-interaction=nonstopmode', '-halt-on-error', MAIN_TEX] },
|
|
70
|
+
{ command: 'pdflatex', args: ['-interaction=nonstopmode', '-halt-on-error', MAIN_TEX] },
|
|
71
|
+
],
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
if (exists('uvx')) {
|
|
75
|
+
return {
|
|
76
|
+
name: 'uvx-tecto',
|
|
77
|
+
bundleProxy: true,
|
|
78
|
+
stages: [
|
|
79
|
+
{
|
|
80
|
+
command: 'uvx',
|
|
81
|
+
args: ['tecto', '-X', 'compile', '--bundle', '__TECTONIC_BUNDLE_URL__', '--keep-logs', '--keep-intermediates', '--reruns', '2', MAIN_TEX],
|
|
82
|
+
},
|
|
83
|
+
],
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
return null;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
function parseArgs(argv = process.argv.slice(2)) {
|
|
90
|
+
const args = {
|
|
91
|
+
dir: DEFAULT_SOURCE_DIR,
|
|
92
|
+
outDir: DEFAULT_OUT_DIR,
|
|
93
|
+
report: DEFAULT_REPORT,
|
|
94
|
+
schema: DEFAULT_SCHEMA,
|
|
95
|
+
allowMissing: false,
|
|
96
|
+
json: false,
|
|
97
|
+
};
|
|
98
|
+
|
|
99
|
+
for (let i = 0; i < argv.length; i++) {
|
|
100
|
+
const token = argv[i];
|
|
101
|
+
if ((token === '--dir' || token === '--source-dir') && argv[i + 1]) args.dir = argv[++i];
|
|
102
|
+
else if (token === '--out-dir' && argv[i + 1]) args.outDir = argv[++i];
|
|
103
|
+
else if (token === '--report' && argv[i + 1]) args.report = argv[++i];
|
|
104
|
+
else if (token === '--schema' && argv[i + 1]) args.schema = argv[++i];
|
|
105
|
+
else if (token === '--allow-missing') args.allowMissing = true;
|
|
106
|
+
else if (token === '--json') args.json = true;
|
|
107
|
+
else if (token === '--help' || token === '-h') args.help = true;
|
|
108
|
+
else throw new Error(`Unknown argument: ${token}`);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
return args;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
function usage() {
|
|
115
|
+
return `Usage: node scripts/verify-arxiv-compile.mjs [options]
|
|
116
|
+
|
|
117
|
+
Options:
|
|
118
|
+
--dir <path> arXiv source directory. Default: ${DEFAULT_SOURCE_DIR}.
|
|
119
|
+
--out-dir <path> Compile output directory. Default: ${DEFAULT_OUT_DIR}.
|
|
120
|
+
--report <path> Compile report JSON. Default: ${DEFAULT_REPORT}.
|
|
121
|
+
--schema <path> Compile report schema. Default: ${DEFAULT_SCHEMA}.
|
|
122
|
+
--allow-missing Exit 0 when no supported TeX toolchain is installed.
|
|
123
|
+
--json Print the machine-readable compile report.
|
|
124
|
+
`;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function sourceSnapshot(sourceDir) {
|
|
128
|
+
const manifestPath = join(sourceDir, 'arxiv-manifest.json');
|
|
129
|
+
const mainPath = join(sourceDir, MAIN_TEX);
|
|
130
|
+
const bibPath = join(sourceDir, REFERENCES_BIB);
|
|
131
|
+
return {
|
|
132
|
+
sourceDir: pathForReport(sourceDir),
|
|
133
|
+
manifest: pathForReport(manifestPath),
|
|
134
|
+
manifestSha256: sha256File(manifestPath),
|
|
135
|
+
mainTex: pathForReport(mainPath),
|
|
136
|
+
mainTexSha256: sha256File(mainPath),
|
|
137
|
+
referencesBib: pathForReport(bibPath),
|
|
138
|
+
referencesBibSha256: sha256File(bibPath),
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function writeReport(path, report) {
|
|
143
|
+
mkdirSync(dirname(path), { recursive: true });
|
|
144
|
+
writeFileSync(path, `${JSON.stringify(report, null, 2)}\n`, 'utf-8');
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
async function startTectonicBundleProxy(bundleUrl = TECTONIC_BUNDLE_URL) {
|
|
148
|
+
const bundle = new URL(bundleUrl);
|
|
149
|
+
const tarPath = bundle.pathname;
|
|
150
|
+
const indexPath = `${tarPath}.index.gz`;
|
|
151
|
+
const server = createServer(async (request, response) => {
|
|
152
|
+
try {
|
|
153
|
+
const requestUrl = new URL(request.url ?? '/', 'http://127.0.0.1');
|
|
154
|
+
let remoteUrl = null;
|
|
155
|
+
if (requestUrl.pathname === tarPath) remoteUrl = bundleUrl;
|
|
156
|
+
else if (requestUrl.pathname === indexPath) remoteUrl = `${bundleUrl}.index.gz`;
|
|
157
|
+
if (!remoteUrl) {
|
|
158
|
+
response.writeHead(404);
|
|
159
|
+
response.end('not found');
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const headers = {};
|
|
164
|
+
if (request.headers.range) headers.range = request.headers.range;
|
|
165
|
+
const upstream = await fetch(remoteUrl, { headers });
|
|
166
|
+
response.statusCode = upstream.status;
|
|
167
|
+
for (const header of ['accept-ranges', 'content-length', 'content-range', 'content-type', 'etag', 'last-modified']) {
|
|
168
|
+
const value = upstream.headers.get(header);
|
|
169
|
+
if (value) response.setHeader(header, value);
|
|
170
|
+
}
|
|
171
|
+
if (!upstream.body) {
|
|
172
|
+
response.end();
|
|
173
|
+
return;
|
|
174
|
+
}
|
|
175
|
+
Readable.fromWeb(upstream.body).pipe(response);
|
|
176
|
+
} catch (error) {
|
|
177
|
+
response.writeHead(502, { 'content-type': 'text/plain; charset=utf-8' });
|
|
178
|
+
response.end(error?.message ?? 'bundle proxy error');
|
|
179
|
+
}
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
await new Promise((resolveListen, rejectListen) => {
|
|
183
|
+
server.once('error', rejectListen);
|
|
184
|
+
server.listen(0, '127.0.0.1', () => {
|
|
185
|
+
server.off('error', rejectListen);
|
|
186
|
+
resolveListen();
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
const address = server.address();
|
|
190
|
+
return {
|
|
191
|
+
url: `http://127.0.0.1:${address.port}${tarPath}`,
|
|
192
|
+
close: () => new Promise(resolveClose => server.close(resolveClose)),
|
|
193
|
+
};
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
function stageWithBundle(stage, bundleUrl) {
|
|
197
|
+
return {
|
|
198
|
+
command: stage.command,
|
|
199
|
+
args: stage.args.map(arg => arg === '__TECTONIC_BUNDLE_URL__' ? bundleUrl : arg),
|
|
200
|
+
};
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function runStage(stage, cwd) {
|
|
204
|
+
return new Promise(resolveRun => {
|
|
205
|
+
const child = spawn(stage.command, stage.args, { cwd, windowsHide: true });
|
|
206
|
+
let stdout = '';
|
|
207
|
+
let stderr = '';
|
|
208
|
+
let settled = false;
|
|
209
|
+
const timer = setTimeout(() => {
|
|
210
|
+
child.kill();
|
|
211
|
+
if (!settled) {
|
|
212
|
+
settled = true;
|
|
213
|
+
resolveRun({ status: 1, signal: 'TIMEOUT', stdout, stderr: `${stderr}\nTimed out after 120000ms`.trim() });
|
|
214
|
+
}
|
|
215
|
+
}, 120000);
|
|
216
|
+
|
|
217
|
+
child.stdout?.on('data', chunk => {
|
|
218
|
+
stdout += chunk.toString();
|
|
219
|
+
});
|
|
220
|
+
child.stderr?.on('data', chunk => {
|
|
221
|
+
stderr += chunk.toString();
|
|
222
|
+
});
|
|
223
|
+
child.once('error', error => {
|
|
224
|
+
clearTimeout(timer);
|
|
225
|
+
if (!settled) {
|
|
226
|
+
settled = true;
|
|
227
|
+
resolveRun({ status: 1, error, stdout, stderr: `${stderr}\n${error.message}`.trim() });
|
|
228
|
+
}
|
|
229
|
+
});
|
|
230
|
+
child.once('close', (status, signal) => {
|
|
231
|
+
clearTimeout(timer);
|
|
232
|
+
if (!settled) {
|
|
233
|
+
settled = true;
|
|
234
|
+
resolveRun({ status, signal, stdout, stderr });
|
|
235
|
+
}
|
|
236
|
+
});
|
|
237
|
+
});
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
export async function verifyArxivCompile(options = {}) {
|
|
241
|
+
const sourceDir = fromRoot(options.dir ?? DEFAULT_SOURCE_DIR);
|
|
242
|
+
const outDir = fromRoot(options.outDir ?? DEFAULT_OUT_DIR);
|
|
243
|
+
const reportPath = fromRoot(options.report ?? DEFAULT_REPORT);
|
|
244
|
+
const now = options.now ?? new Date().toISOString();
|
|
245
|
+
const failures = [];
|
|
246
|
+
const blockers = [];
|
|
247
|
+
const sourceReport = verifyArxivSourcePackage({ dir: pathForReport(sourceDir) });
|
|
248
|
+
const source = sourceReport.ok ? sourceSnapshot(sourceDir) : null;
|
|
249
|
+
|
|
250
|
+
if (!sourceReport.ok) {
|
|
251
|
+
failures.push(...sourceReport.failures.map(failure => `arXiv source: ${failure}`));
|
|
252
|
+
const report = {
|
|
253
|
+
schemaVersion: '1.0.0',
|
|
254
|
+
suite: 'Audrey arXiv compile check',
|
|
255
|
+
generatedAt: now,
|
|
256
|
+
source,
|
|
257
|
+
outputDir: pathForReport(outDir),
|
|
258
|
+
status: 'failed',
|
|
259
|
+
compiler: null,
|
|
260
|
+
outputPdf: null,
|
|
261
|
+
outputPdfSha256: null,
|
|
262
|
+
logFile: null,
|
|
263
|
+
blockers,
|
|
264
|
+
failures,
|
|
265
|
+
};
|
|
266
|
+
writeReport(reportPath, report);
|
|
267
|
+
return report;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
const plan = compilerPlan(options.commandExists ?? commandExists);
|
|
271
|
+
if (!plan) {
|
|
272
|
+
blockers.push('Install tectonic, latexmk, or pdflatex+bibtex before final arXiv compile proof');
|
|
273
|
+
const report = {
|
|
274
|
+
schemaVersion: '1.0.0',
|
|
275
|
+
suite: 'Audrey arXiv compile check',
|
|
276
|
+
generatedAt: now,
|
|
277
|
+
source,
|
|
278
|
+
outputDir: pathForReport(outDir),
|
|
279
|
+
status: 'toolchain-missing',
|
|
280
|
+
compiler: null,
|
|
281
|
+
outputPdf: null,
|
|
282
|
+
outputPdfSha256: null,
|
|
283
|
+
logFile: null,
|
|
284
|
+
blockers,
|
|
285
|
+
failures,
|
|
286
|
+
};
|
|
287
|
+
writeReport(reportPath, report);
|
|
288
|
+
return report;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
rmSync(outDir, { recursive: true, force: true });
|
|
292
|
+
mkdirSync(outDir, { recursive: true });
|
|
293
|
+
cpSync(join(sourceDir, MAIN_TEX), join(outDir, MAIN_TEX));
|
|
294
|
+
cpSync(join(sourceDir, REFERENCES_BIB), join(outDir, REFERENCES_BIB));
|
|
295
|
+
|
|
296
|
+
let proxy = null;
|
|
297
|
+
const logLines = [];
|
|
298
|
+
try {
|
|
299
|
+
proxy = plan.bundleProxy ? await startTectonicBundleProxy() : null;
|
|
300
|
+
const stages = proxy ? plan.stages.map(stage => stageWithBundle(stage, proxy.url)) : plan.stages;
|
|
301
|
+
for (const stage of stages) {
|
|
302
|
+
logLines.push(`$ ${stage.command} ${stage.args.join(' ')}`);
|
|
303
|
+
const result = await runStage(stage, outDir);
|
|
304
|
+
if (result.stdout) logLines.push(result.stdout.trim());
|
|
305
|
+
if (result.stderr) logLines.push(result.stderr.trim());
|
|
306
|
+
if (result.status !== 0) {
|
|
307
|
+
failures.push(`${stage.command} exited ${result.status ?? result.signal ?? 'unknown'}`);
|
|
308
|
+
break;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
} finally {
|
|
312
|
+
await proxy?.close();
|
|
313
|
+
}
|
|
314
|
+
const logPath = join(outDir, 'arxiv-compile.log');
|
|
315
|
+
writeFileSync(logPath, `${logLines.filter(Boolean).join('\n\n')}\n`, 'utf-8');
|
|
316
|
+
|
|
317
|
+
const pdfPath = join(outDir, 'main.pdf');
|
|
318
|
+
if (!existsSync(pdfPath)) failures.push('main.pdf was not produced by the TeX compiler');
|
|
319
|
+
const status = failures.length ? 'failed' : 'passed';
|
|
320
|
+
const report = {
|
|
321
|
+
schemaVersion: '1.0.0',
|
|
322
|
+
suite: 'Audrey arXiv compile check',
|
|
323
|
+
generatedAt: now,
|
|
324
|
+
source,
|
|
325
|
+
outputDir: pathForReport(outDir),
|
|
326
|
+
status,
|
|
327
|
+
compiler: {
|
|
328
|
+
name: plan.name,
|
|
329
|
+
stages: plan.stages.map(stage => ({ command: stage.command, args: stage.args })),
|
|
330
|
+
},
|
|
331
|
+
outputPdf: existsSync(pdfPath) ? pathForReport(pdfPath) : null,
|
|
332
|
+
outputPdfSha256: existsSync(pdfPath) ? sha256File(pdfPath) : null,
|
|
333
|
+
logFile: pathForReport(logPath),
|
|
334
|
+
blockers,
|
|
335
|
+
failures,
|
|
336
|
+
};
|
|
337
|
+
writeReport(reportPath, report);
|
|
338
|
+
return report;
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
export function verifyArxivCompileReport(options = {}) {
|
|
342
|
+
const reportPath = fromRoot(options.report ?? DEFAULT_REPORT);
|
|
343
|
+
const schemaPath = fromRoot(options.schema ?? DEFAULT_SCHEMA);
|
|
344
|
+
const allowPending = options.allowPending !== false;
|
|
345
|
+
const failures = [];
|
|
346
|
+
const blockers = [];
|
|
347
|
+
let report = null;
|
|
348
|
+
|
|
349
|
+
try {
|
|
350
|
+
report = JSON.parse(readFileSync(reportPath, 'utf-8'));
|
|
351
|
+
} catch (error) {
|
|
352
|
+
return {
|
|
353
|
+
ok: false,
|
|
354
|
+
report: pathForReport(reportPath),
|
|
355
|
+
status: 'missing',
|
|
356
|
+
blockers: [],
|
|
357
|
+
failures: [`arxiv-compile-report.json: ${error.message}`],
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
try {
|
|
362
|
+
failures.push(...validateSchema(report, readJson(pathForReport(schemaPath)), 'audrey-arxiv-compile-report'));
|
|
363
|
+
} catch (error) {
|
|
364
|
+
failures.push(`schema: ${error.message}`);
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
const source = report.source;
|
|
368
|
+
if (source) {
|
|
369
|
+
const sourceChecks = [
|
|
370
|
+
['manifestSha256', source.manifest],
|
|
371
|
+
['mainTexSha256', source.mainTex],
|
|
372
|
+
['referencesBibSha256', source.referencesBib],
|
|
373
|
+
];
|
|
374
|
+
for (const [hashKey, file] of sourceChecks) {
|
|
375
|
+
const absolute = fromRoot(file);
|
|
376
|
+
if (!existsSync(absolute)) {
|
|
377
|
+
failures.push(`arxiv-compile-report.json: source file missing: ${file}`);
|
|
378
|
+
} else if (report.source?.[hashKey] !== sha256File(absolute)) {
|
|
379
|
+
failures.push(`arxiv-compile-report.json: ${file} changed since compile report`);
|
|
380
|
+
}
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
|
|
384
|
+
if (report.status === 'toolchain-missing') {
|
|
385
|
+
blockers.push(...(report.blockers?.length ? report.blockers : ['TeX toolchain is missing']));
|
|
386
|
+
} else if (report.status === 'failed') {
|
|
387
|
+
failures.push(...(report.failures?.length ? report.failures : ['arXiv compile failed']));
|
|
388
|
+
} else if (report.status === 'passed') {
|
|
389
|
+
if (!report.outputPdf || !existsSync(fromRoot(report.outputPdf))) failures.push('arxiv-compile-report.json: outputPdf is missing');
|
|
390
|
+
if (report.outputPdf && report.outputPdfSha256 && sha256File(fromRoot(report.outputPdf)) !== report.outputPdfSha256) {
|
|
391
|
+
failures.push('arxiv-compile-report.json: outputPdfSha256 is stale');
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
return {
|
|
396
|
+
ok: failures.length === 0 && (allowPending || blockers.length === 0),
|
|
397
|
+
report: pathForReport(reportPath),
|
|
398
|
+
status: report.status,
|
|
399
|
+
compiler: report.compiler?.name ?? null,
|
|
400
|
+
blockers,
|
|
401
|
+
failures,
|
|
402
|
+
};
|
|
403
|
+
}
|
|
404
|
+
|
|
405
|
+
function exitCode(report, allowMissing) {
|
|
406
|
+
if (report.status === 'passed') return 0;
|
|
407
|
+
if (report.status === 'toolchain-missing' && allowMissing) return 0;
|
|
408
|
+
return 1;
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
async function main() {
|
|
412
|
+
const args = parseArgs();
|
|
413
|
+
if (args.help) {
|
|
414
|
+
console.log(usage());
|
|
415
|
+
return;
|
|
416
|
+
}
|
|
417
|
+
|
|
418
|
+
const report = await verifyArxivCompile(args);
|
|
419
|
+
if (args.json) {
|
|
420
|
+
console.log(JSON.stringify(report, null, 2));
|
|
421
|
+
} else if (report.status === 'passed') {
|
|
422
|
+
console.log(`arXiv compile check passed: ${report.outputPdf}`);
|
|
423
|
+
} else if (report.status === 'toolchain-missing') {
|
|
424
|
+
console.log(`arXiv compile check pending: ${report.blockers.join('; ')}`);
|
|
425
|
+
} else {
|
|
426
|
+
console.error('arXiv compile check failed:');
|
|
427
|
+
for (const failure of report.failures) console.error(`- ${failure}`);
|
|
428
|
+
}
|
|
429
|
+
|
|
430
|
+
process.exit(exitCode(report, args.allowMissing));
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
434
|
+
try {
|
|
435
|
+
await main();
|
|
436
|
+
} catch (error) {
|
|
437
|
+
console.error(error.stack ?? error.message);
|
|
438
|
+
process.exit(1);
|
|
439
|
+
}
|
|
440
|
+
}
|
|
@@ -0,0 +1,194 @@
|
|
|
1
|
+
import { createHash } from 'node:crypto';
|
|
2
|
+
import { existsSync, readFileSync, readdirSync } from 'node:fs';
|
|
3
|
+
import { dirname, join, relative, resolve } from 'node:path';
|
|
4
|
+
import { fileURLToPath } from 'node:url';
|
|
5
|
+
import { validateSchema } from '../benchmarks/validate-guardbench-artifacts.mjs';
|
|
6
|
+
|
|
7
|
+
const ROOT = resolve(dirname(fileURLToPath(import.meta.url)), '..');
|
|
8
|
+
const DEFAULT_DIR = 'docs/paper/output/arxiv';
|
|
9
|
+
const DEFAULT_SCHEMA = 'docs/paper/arxiv-source.schema.json';
|
|
10
|
+
const REQUIRED_FILES = ['main.tex', 'references.bib', 'README-arxiv.txt'];
|
|
11
|
+
const SEEDED_SECRET = 'sk-guardbench-secret-0000000000000000000000000000';
|
|
12
|
+
|
|
13
|
+
function fromRoot(path) {
|
|
14
|
+
return resolve(ROOT, path);
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
function readJson(path) {
|
|
18
|
+
return JSON.parse(readFileSync(path, 'utf-8'));
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
function sha256File(path) {
|
|
22
|
+
return createHash('sha256').update(readFileSync(path)).digest('hex');
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
function countBibEntries(text) {
|
|
26
|
+
return [...text.matchAll(/@\w+\s*\{/g)].length;
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function walkFiles(dir, root = dir) {
|
|
30
|
+
return readdirSync(dir, { withFileTypes: true }).flatMap(entry => {
|
|
31
|
+
const path = join(dir, entry.name);
|
|
32
|
+
if (entry.isDirectory()) return walkFiles(path, root);
|
|
33
|
+
return relative(root, path).replaceAll('\\', '/');
|
|
34
|
+
});
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function checkSourceHash(label, sourcePath, expectedHash, failures) {
|
|
38
|
+
if (!sourcePath || !expectedHash) return;
|
|
39
|
+
const absolute = fromRoot(sourcePath);
|
|
40
|
+
if (!existsSync(absolute)) {
|
|
41
|
+
failures.push(`arxiv-manifest.json: missing source file for ${label}: ${sourcePath}`);
|
|
42
|
+
return;
|
|
43
|
+
}
|
|
44
|
+
if (expectedHash !== sha256File(absolute)) failures.push(`arxiv-manifest.json: ${label} hash is stale`);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function parseArgs(argv = process.argv.slice(2)) {
|
|
48
|
+
const args = {
|
|
49
|
+
dir: DEFAULT_DIR,
|
|
50
|
+
schema: DEFAULT_SCHEMA,
|
|
51
|
+
json: false,
|
|
52
|
+
};
|
|
53
|
+
for (let i = 0; i < argv.length; i++) {
|
|
54
|
+
const token = argv[i];
|
|
55
|
+
if ((token === '--dir' || token === '--source-dir') && argv[i + 1]) args.dir = argv[++i];
|
|
56
|
+
else if (token === '--schema' && argv[i + 1]) args.schema = argv[++i];
|
|
57
|
+
else if (token === '--json') args.json = true;
|
|
58
|
+
else if (token === '--help' || token === '-h') args.help = true;
|
|
59
|
+
else throw new Error(`Unknown argument: ${token}`);
|
|
60
|
+
}
|
|
61
|
+
return args;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function usage() {
|
|
65
|
+
return `Usage: node scripts/verify-arxiv-source.mjs [options]
|
|
66
|
+
|
|
67
|
+
Options:
|
|
68
|
+
--dir <path> arXiv source directory. Default: ${DEFAULT_DIR}.
|
|
69
|
+
--schema <path> arXiv source manifest schema. Default: ${DEFAULT_SCHEMA}.
|
|
70
|
+
--json Print the machine-readable verification report.
|
|
71
|
+
`;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function verifyArxivSourcePackage(options = {}) {
|
|
75
|
+
const dir = fromRoot(options.dir ?? DEFAULT_DIR);
|
|
76
|
+
const schemaPath = fromRoot(options.schema ?? DEFAULT_SCHEMA);
|
|
77
|
+
const manifestPath = join(dir, 'arxiv-manifest.json');
|
|
78
|
+
const failures = [];
|
|
79
|
+
let manifest = null;
|
|
80
|
+
|
|
81
|
+
try {
|
|
82
|
+
manifest = readJson(manifestPath);
|
|
83
|
+
} catch (error) {
|
|
84
|
+
return {
|
|
85
|
+
ok: false,
|
|
86
|
+
dir,
|
|
87
|
+
files: [],
|
|
88
|
+
citationCount: 0,
|
|
89
|
+
bibEntries: 0,
|
|
90
|
+
failures: [`arxiv-manifest.json: ${error.message}`],
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
try {
|
|
95
|
+
failures.push(...validateSchema(manifest, readJson(schemaPath), 'audrey-arxiv-source'));
|
|
96
|
+
} catch (error) {
|
|
97
|
+
failures.push(`schema: ${error.message}`);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const listed = new Map((manifest.files ?? []).map(file => [file.path, file]));
|
|
101
|
+
for (const file of REQUIRED_FILES) {
|
|
102
|
+
if (!listed.has(file)) failures.push(`arxiv-manifest.json: missing required file record ${file}`);
|
|
103
|
+
}
|
|
104
|
+
if (listed.has('arxiv-manifest.json')) failures.push('arxiv-manifest.json: must not include a self-hash file record');
|
|
105
|
+
checkSourceHash('sourceMarkdown', manifest.sourceMarkdown, manifest.sourceHashes?.sourceMarkdown, failures);
|
|
106
|
+
checkSourceHash('publicationPack', manifest.publicationPack, manifest.sourceHashes?.publicationPack, failures);
|
|
107
|
+
checkSourceHash('referencesBib', 'docs/paper/references.bib', manifest.sourceHashes?.referencesBib, failures);
|
|
108
|
+
|
|
109
|
+
for (const [file, record] of listed) {
|
|
110
|
+
const path = join(dir, file);
|
|
111
|
+
if (!existsSync(path)) {
|
|
112
|
+
failures.push(`${file}: listed in manifest but missing from package`);
|
|
113
|
+
continue;
|
|
114
|
+
}
|
|
115
|
+
const bytes = readFileSync(path).byteLength;
|
|
116
|
+
if (record.bytes !== bytes) failures.push(`${file}: byte length mismatch`);
|
|
117
|
+
if (record.sha256 !== sha256File(path)) failures.push(`${file}: sha256 mismatch`);
|
|
118
|
+
if (record.source && record.source !== 'generated') {
|
|
119
|
+
const sourcePath = fromRoot(record.source);
|
|
120
|
+
if (existsSync(sourcePath)) {
|
|
121
|
+
const sourceSha = sha256File(sourcePath);
|
|
122
|
+
if (record.source === 'docs/paper/references.bib' && sourceSha !== record.sha256) {
|
|
123
|
+
failures.push(`${file}: source bibliography has changed since arXiv package creation`);
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
const actualFiles = walkFiles(dir).filter(file => file !== 'arxiv-manifest.json').sort();
|
|
130
|
+
const listedFiles = [...listed.keys()].sort();
|
|
131
|
+
const listedSet = new Set(listedFiles);
|
|
132
|
+
for (const file of actualFiles) {
|
|
133
|
+
if (!listedSet.has(file)) failures.push(`${file}: present in package but missing from manifest`);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
const mainPath = join(dir, 'main.tex');
|
|
137
|
+
const bibPath = join(dir, 'references.bib');
|
|
138
|
+
const main = existsSync(mainPath) ? readFileSync(mainPath, 'utf-8') : '';
|
|
139
|
+
const bib = existsSync(bibPath) ? readFileSync(bibPath, 'utf-8') : '';
|
|
140
|
+
const citationCount = [...main.matchAll(/\\cite\{([^}]+)\}/g)].length;
|
|
141
|
+
const citedIds = new Set([...main.matchAll(/\\cite\{([^}]+)\}/g)].flatMap(match => match[1].split(',').map(id => id.trim())));
|
|
142
|
+
const bibIds = new Set([...bib.matchAll(/@\w+\s*\{\s*([^,\s]+)/g)].map(match => match[1].trim()));
|
|
143
|
+
const bibEntries = countBibEntries(bib);
|
|
144
|
+
|
|
145
|
+
if (!main.includes('\\documentclass')) failures.push('main.tex: missing documentclass');
|
|
146
|
+
if (!main.includes('\\begin{abstract}')) failures.push('main.tex: missing abstract');
|
|
147
|
+
if (!main.includes('\\bibliography{references}')) failures.push('main.tex: missing bibliography command');
|
|
148
|
+
if (main.includes('[@')) failures.push('main.tex: contains unconverted Markdown citation syntax');
|
|
149
|
+
if (/^#{1,6}\s/m.test(main)) failures.push('main.tex: contains unconverted Markdown heading syntax');
|
|
150
|
+
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)) failures.push('main.tex: contains a local absolute path');
|
|
152
|
+
if (citationCount < 1) failures.push('main.tex: expected at least one citation');
|
|
153
|
+
if (bibEntries !== 21) failures.push(`references.bib: expected 21 entries, found ${bibEntries}`);
|
|
154
|
+
for (const id of citedIds) {
|
|
155
|
+
if (!bibIds.has(id)) failures.push(`main.tex: cites missing bibliography id ${id}`);
|
|
156
|
+
}
|
|
157
|
+
if (manifest.tex?.citationCount !== citationCount) failures.push('arxiv-manifest.json: citation count is stale');
|
|
158
|
+
if (manifest.tex?.bibEntryCount !== bibEntries) failures.push('arxiv-manifest.json: bibliography count is stale');
|
|
159
|
+
|
|
160
|
+
return {
|
|
161
|
+
ok: failures.length === 0,
|
|
162
|
+
dir,
|
|
163
|
+
manifestPath,
|
|
164
|
+
files: listedFiles,
|
|
165
|
+
citationCount,
|
|
166
|
+
bibEntries,
|
|
167
|
+
failures,
|
|
168
|
+
};
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
function main() {
|
|
172
|
+
const args = parseArgs();
|
|
173
|
+
if (args.help) {
|
|
174
|
+
console.log(usage());
|
|
175
|
+
return;
|
|
176
|
+
}
|
|
177
|
+
const report = verifyArxivSourcePackage(args);
|
|
178
|
+
if (args.json) console.log(JSON.stringify(report, null, 2));
|
|
179
|
+
else if (report.ok) console.log(`arXiv source package verification passed: ${report.dir}`);
|
|
180
|
+
else {
|
|
181
|
+
console.error('arXiv source package verification failed:');
|
|
182
|
+
for (const failure of report.failures) console.error(`- ${failure}`);
|
|
183
|
+
}
|
|
184
|
+
if (!report.ok) process.exit(1);
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (process.argv[1] && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
|
188
|
+
try {
|
|
189
|
+
main();
|
|
190
|
+
} catch (error) {
|
|
191
|
+
console.error(error.stack ?? error.message);
|
|
192
|
+
process.exit(1);
|
|
193
|
+
}
|
|
194
|
+
}
|