personal-agent-benchmark 0.0.0-stage → 0.2.0
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 +128 -2
- package/bin/pab.js +5 -0
- package/dist/src/browser.d.ts +29 -0
- package/dist/src/browser.js +37 -0
- package/dist/src/browser.js.map +1 -0
- package/dist/src/cli.d.ts +20 -0
- package/dist/src/cli.js +331 -0
- package/dist/src/cli.js.map +1 -0
- package/dist/src/core/archive.d.ts +49 -0
- package/dist/src/core/archive.js +213 -0
- package/dist/src/core/archive.js.map +1 -0
- package/dist/src/core/audit.d.ts +15 -0
- package/dist/src/core/audit.js +85 -0
- package/dist/src/core/audit.js.map +1 -0
- package/dist/src/core/bundle.d.ts +42 -0
- package/dist/src/core/bundle.js +80 -0
- package/dist/src/core/bundle.js.map +1 -0
- package/dist/src/core/lookup.d.ts +15 -0
- package/dist/src/core/lookup.js +82 -0
- package/dist/src/core/lookup.js.map +1 -0
- package/dist/src/core/normalize.d.ts +45 -0
- package/dist/src/core/normalize.js +143 -0
- package/dist/src/core/normalize.js.map +1 -0
- package/dist/src/core/rules.d.ts +38 -0
- package/dist/src/core/rules.js +187 -0
- package/dist/src/core/rules.js.map +1 -0
- package/dist/src/core/store.d.ts +26 -0
- package/dist/src/core/store.js +78 -0
- package/dist/src/core/store.js.map +1 -0
- package/dist/src/core/types.d.ts +125 -0
- package/dist/src/core/types.js +3 -0
- package/dist/src/core/types.js.map +1 -0
- package/dist/src/core/util.d.ts +21 -0
- package/dist/src/core/util.js +150 -0
- package/dist/src/core/util.js.map +1 -0
- package/dist/src/core/warc.d.ts +55 -0
- package/dist/src/core/warc.js +258 -0
- package/dist/src/core/warc.js.map +1 -0
- package/dist/src/core/writer.d.ts +26 -0
- package/dist/src/core/writer.js +119 -0
- package/dist/src/core/writer.js.map +1 -0
- package/dist/src/grading.d.ts +63 -0
- package/dist/src/grading.js +173 -0
- package/dist/src/grading.js.map +1 -0
- package/dist/src/judge.d.ts +25 -0
- package/dist/src/judge.js +119 -0
- package/dist/src/judge.js.map +1 -0
- package/dist/src/paths.d.ts +2 -0
- package/dist/src/paths.js +40 -0
- package/dist/src/paths.js.map +1 -0
- package/dist/src/replay/clock.d.ts +19 -0
- package/dist/src/replay/clock.js +60 -0
- package/dist/src/replay/clock.js.map +1 -0
- package/dist/src/replay/engine.d.ts +53 -0
- package/dist/src/replay/engine.js +280 -0
- package/dist/src/replay/engine.js.map +1 -0
- package/dist/src/replay/launcher.d.ts +36 -0
- package/dist/src/replay/launcher.js +163 -0
- package/dist/src/replay/launcher.js.map +1 -0
- package/dist/src/replay/whistle-plugin.d.ts +24 -0
- package/dist/src/replay/whistle-plugin.js +82 -0
- package/dist/src/replay/whistle-plugin.js.map +1 -0
- package/dist/src/report.d.ts +3 -0
- package/dist/src/report.js +143 -0
- package/dist/src/report.js.map +1 -0
- package/dist/src/scoring.d.ts +87 -0
- package/dist/src/scoring.js +232 -0
- package/dist/src/scoring.js.map +1 -0
- package/dist/src/tasks.d.ts +61 -0
- package/dist/src/tasks.js +152 -0
- package/dist/src/tasks.js.map +1 -0
- package/docs/bundle-format.md +114 -0
- package/docs/deploy.md +78 -0
- package/docs/grading.md +81 -0
- package/docs/site-rules.md +114 -0
- package/docs/tasks.md +63 -0
- package/package.json +121 -4
- package/plugins/whistle.bench-replay/index.js +1 -0
- package/plugins/whistle.bench-replay/package.json +7 -0
- package/sites/_default.yaml +80 -0
- package/sites/amazon.com.yaml +125 -0
|
@@ -0,0 +1,213 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.RECORDING_FILES = exports.RECORDING_FORMAT = exports.isBundleFile = exports.PUBLIC_BUNDLE_FILES = exports.PART_BYTES = void 0;
|
|
37
|
+
exports.createArchive = createArchive;
|
|
38
|
+
exports.archiveParts = archiveParts;
|
|
39
|
+
exports.hashParts = hashParts;
|
|
40
|
+
exports.extractArchive = extractArchive;
|
|
41
|
+
exports.publishRecording = publishRecording;
|
|
42
|
+
exports.recordingFiles = recordingFiles;
|
|
43
|
+
exports.unpackRecording = unpackRecording;
|
|
44
|
+
const node_crypto_1 = require("node:crypto");
|
|
45
|
+
const fs = __importStar(require("node:fs"));
|
|
46
|
+
const path = __importStar(require("node:path"));
|
|
47
|
+
const node_stream_1 = require("node:stream");
|
|
48
|
+
const promises_1 = require("node:stream/promises");
|
|
49
|
+
const zlib = __importStar(require("node:zlib"));
|
|
50
|
+
const tar = __importStar(require("tar"));
|
|
51
|
+
const util_1 = require("./util");
|
|
52
|
+
/**
|
|
53
|
+
* Large files travel as gzip-compressed tar files: the WARC of a published recording (below) and
|
|
54
|
+
* the recording tool's hand-off archive. Git hosts reject large files (GitHub: 100 MB, with a
|
|
55
|
+
* warning from 50 MB), so archives above PART_BYTES are split into numbered parts:
|
|
56
|
+
* warc.tar.gz.000, warc.tar.gz.001, ... which are read back in order as one stream.
|
|
57
|
+
*/
|
|
58
|
+
exports.PART_BYTES = 45 * 1024 * 1024;
|
|
59
|
+
/** Files of a bundle that can be replayed and published; everything else stays with the recorder. */
|
|
60
|
+
exports.PUBLIC_BUNDLE_FILES = ['manifest.json', 'archive/data.warc', 'indexes/entries.jsonl', 'pages/pages.jsonl'];
|
|
61
|
+
/** Written by earlier versions and never read by replay; still accepted when unpacking old archives. */
|
|
62
|
+
const LEGACY_BUNDLE_FILES = ['datapackage.json', 'indexes/index.cdxj'];
|
|
63
|
+
const isBundleFile = (p) => exports.PUBLIC_BUNDLE_FILES.includes(p) || LEGACY_BUNDLE_FILES.includes(p);
|
|
64
|
+
exports.isBundleFile = isBundleFile;
|
|
65
|
+
// a fixed timestamp keeps archives of the same files byte-identical
|
|
66
|
+
const ARCHIVE_MTIME = new Date('2000-01-01T00:00:00Z');
|
|
67
|
+
/**
|
|
68
|
+
* Writes `files` (paths relative to `srcDir`, in this order) to `outFile`, a .tar.gz, replacing
|
|
69
|
+
* any earlier archive or parts there. Returns the files written: `outFile` or its parts.
|
|
70
|
+
*/
|
|
71
|
+
async function createArchive(srcDir, files, outFile, partBytes = exports.PART_BYTES) {
|
|
72
|
+
for (const f of files)
|
|
73
|
+
if (!fs.existsSync(path.join(srcDir, f)))
|
|
74
|
+
throw new Error(`missing ${f} in ${srcDir}`);
|
|
75
|
+
for (const old of archiveParts(outFile))
|
|
76
|
+
fs.rmSync(old);
|
|
77
|
+
fs.mkdirSync(path.dirname(outFile), { recursive: true });
|
|
78
|
+
const tmp = `${outFile}.tmp-${process.pid}`;
|
|
79
|
+
await tar.c({ file: tmp, cwd: srcDir, gzip: { level: 9 }, portable: true, mtime: ARCHIVE_MTIME }, files);
|
|
80
|
+
const size = fs.statSync(tmp).size;
|
|
81
|
+
if (size <= partBytes) {
|
|
82
|
+
fs.renameSync(tmp, outFile);
|
|
83
|
+
return [outFile];
|
|
84
|
+
}
|
|
85
|
+
const fd = fs.openSync(tmp, 'r');
|
|
86
|
+
const parts = [];
|
|
87
|
+
try {
|
|
88
|
+
const buf = Buffer.alloc(partBytes);
|
|
89
|
+
for (let offset = 0, n = 0; offset < size; offset += partBytes, n++) {
|
|
90
|
+
const read = fs.readSync(fd, buf, 0, partBytes, offset);
|
|
91
|
+
const part = `${outFile}.${String(n).padStart(3, '0')}`;
|
|
92
|
+
fs.writeFileSync(part, buf.subarray(0, read));
|
|
93
|
+
parts.push(part);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
finally {
|
|
97
|
+
fs.closeSync(fd);
|
|
98
|
+
fs.rmSync(tmp);
|
|
99
|
+
}
|
|
100
|
+
return parts;
|
|
101
|
+
}
|
|
102
|
+
/** The archive at `file` as stored: the file itself, or its numbered parts in order. */
|
|
103
|
+
function archiveParts(file) {
|
|
104
|
+
if (fs.existsSync(file))
|
|
105
|
+
return [file];
|
|
106
|
+
const dir = path.dirname(file);
|
|
107
|
+
if (!fs.existsSync(dir))
|
|
108
|
+
return [];
|
|
109
|
+
const base = path.basename(file);
|
|
110
|
+
return fs
|
|
111
|
+
.readdirSync(dir)
|
|
112
|
+
.filter((name) => name.startsWith(base + '.') && /^\d{3}$/.test(name.slice(base.length + 1)))
|
|
113
|
+
.sort()
|
|
114
|
+
.map((name) => path.join(dir, name));
|
|
115
|
+
}
|
|
116
|
+
function hashParts(parts) {
|
|
117
|
+
const hash = (0, node_crypto_1.createHash)('sha256');
|
|
118
|
+
for (const p of parts)
|
|
119
|
+
hash.update(fs.readFileSync(p));
|
|
120
|
+
return hash.digest('hex');
|
|
121
|
+
}
|
|
122
|
+
/**
|
|
123
|
+
* Extracts an archive (one file or its parts) into `destDir`. Only regular files and directories
|
|
124
|
+
* are extracted, and only paths accepted by `allow`; tar itself refuses absolute paths and "..".
|
|
125
|
+
*/
|
|
126
|
+
async function extractArchive(parts, destDir, allow = () => true) {
|
|
127
|
+
if (!parts.length)
|
|
128
|
+
throw new Error('no archive to extract');
|
|
129
|
+
fs.mkdirSync(destDir, { recursive: true });
|
|
130
|
+
const extracted = [];
|
|
131
|
+
const rejected = [];
|
|
132
|
+
const source = node_stream_1.Readable.from((async function* () {
|
|
133
|
+
for (const p of parts)
|
|
134
|
+
yield* fs.createReadStream(p);
|
|
135
|
+
})());
|
|
136
|
+
await (0, promises_1.pipeline)(source, tar.x({
|
|
137
|
+
cwd: destDir,
|
|
138
|
+
strict: true,
|
|
139
|
+
filter: (p, entry) => {
|
|
140
|
+
const type = 'type' in entry ? entry.type : 'File';
|
|
141
|
+
const name = p.replace(/^\.\//, '');
|
|
142
|
+
const ok = (type === 'File' || type === 'Directory') && !name.split('/').includes('..') && !path.isAbsolute(name) && (type === 'Directory' || allow(name));
|
|
143
|
+
if (ok && type === 'File')
|
|
144
|
+
extracted.push(name);
|
|
145
|
+
if (!ok)
|
|
146
|
+
rejected.push(name);
|
|
147
|
+
return ok;
|
|
148
|
+
},
|
|
149
|
+
}));
|
|
150
|
+
if (rejected.length)
|
|
151
|
+
throw new Error(`archive holds unexpected entries: ${rejected.slice(0, 5).join(', ')}`);
|
|
152
|
+
return extracted;
|
|
153
|
+
}
|
|
154
|
+
/**
|
|
155
|
+
* Layout version of a recording published in a task. Replay reads only this version: a change to
|
|
156
|
+
* the layout or to the files in it bumps the number, and existing tasks are rewritten to match.
|
|
157
|
+
*/
|
|
158
|
+
exports.RECORDING_FORMAT = 2;
|
|
159
|
+
/**
|
|
160
|
+
* A bundle as published in a task (tasks/<id>/recording/):
|
|
161
|
+
* manifest.json the bundle's manifest plus "format"
|
|
162
|
+
* pages.jsonl pages/pages.jsonl
|
|
163
|
+
* entries.jsonl.gz indexes/entries.jsonl, gzip-compressed
|
|
164
|
+
* warc.tar.gz archive/data.warc (warc.tar.gz.000, .001, ... above PART_BYTES)
|
|
165
|
+
* The WARC sits apart from the files whose layout may change, so a new format rewrites only the
|
|
166
|
+
* small files and leaves the large archive as it was.
|
|
167
|
+
*/
|
|
168
|
+
exports.RECORDING_FILES = {
|
|
169
|
+
manifest: 'manifest.json',
|
|
170
|
+
pages: 'pages.jsonl',
|
|
171
|
+
entries: 'entries.jsonl.gz',
|
|
172
|
+
warc: 'warc.tar.gz',
|
|
173
|
+
};
|
|
174
|
+
/** Writes the bundle at `bundleDir` to `outDir` in the published layout, replacing what is there. */
|
|
175
|
+
async function publishRecording(bundleDir, outDir) {
|
|
176
|
+
for (const f of exports.PUBLIC_BUNDLE_FILES)
|
|
177
|
+
if (!fs.existsSync(path.join(bundleDir, f)))
|
|
178
|
+
throw new Error(`missing ${f} in ${bundleDir}`);
|
|
179
|
+
fs.rmSync(outDir, { recursive: true, force: true });
|
|
180
|
+
const out = (name) => path.join(outDir, name);
|
|
181
|
+
(0, util_1.writeJson)(out(exports.RECORDING_FILES.manifest), { ...(0, util_1.readJson)(path.join(bundleDir, 'manifest.json')), format: exports.RECORDING_FORMAT });
|
|
182
|
+
fs.copyFileSync(path.join(bundleDir, 'pages', 'pages.jsonl'), out(exports.RECORDING_FILES.pages));
|
|
183
|
+
fs.writeFileSync(out(exports.RECORDING_FILES.entries), zlib.gzipSync(fs.readFileSync(path.join(bundleDir, 'indexes', 'entries.jsonl')), { level: 9 }));
|
|
184
|
+
const warc = await createArchive(bundleDir, ['archive/data.warc'], out(exports.RECORDING_FILES.warc));
|
|
185
|
+
return [out(exports.RECORDING_FILES.manifest), out(exports.RECORDING_FILES.pages), out(exports.RECORDING_FILES.entries), ...warc];
|
|
186
|
+
}
|
|
187
|
+
/** Files of the recording published in `dir`, in a fixed order; empty when there is none. */
|
|
188
|
+
function recordingFiles(dir) {
|
|
189
|
+
const manifest = path.join(dir, exports.RECORDING_FILES.manifest);
|
|
190
|
+
if (!fs.existsSync(manifest))
|
|
191
|
+
return [];
|
|
192
|
+
return [manifest, path.join(dir, exports.RECORDING_FILES.pages), path.join(dir, exports.RECORDING_FILES.entries), ...archiveParts(path.join(dir, exports.RECORDING_FILES.warc))];
|
|
193
|
+
}
|
|
194
|
+
/** Turns the recording published in `dir` back into a bundle at `destDir`; refuses other formats. */
|
|
195
|
+
async function unpackRecording(dir, destDir) {
|
|
196
|
+
const manifest = (0, util_1.readJson)(path.join(dir, exports.RECORDING_FILES.manifest));
|
|
197
|
+
if (manifest.format !== exports.RECORDING_FORMAT) {
|
|
198
|
+
throw new Error(`${dir} is recording format ${manifest.format ?? '(none)'}; this version reads format ${exports.RECORDING_FORMAT} (docs/bundle-format.md)`);
|
|
199
|
+
}
|
|
200
|
+
const warc = archiveParts(path.join(dir, exports.RECORDING_FILES.warc));
|
|
201
|
+
for (const f of [exports.RECORDING_FILES.pages, exports.RECORDING_FILES.entries])
|
|
202
|
+
if (!fs.existsSync(path.join(dir, f)))
|
|
203
|
+
throw new Error(`missing ${f} in ${dir}`);
|
|
204
|
+
if (!warc.length)
|
|
205
|
+
throw new Error(`missing ${exports.RECORDING_FILES.warc} in ${dir}`);
|
|
206
|
+
await extractArchive(warc, destDir, (p) => p === 'archive/data.warc');
|
|
207
|
+
fs.mkdirSync(path.join(destDir, 'pages'), { recursive: true });
|
|
208
|
+
fs.mkdirSync(path.join(destDir, 'indexes'), { recursive: true });
|
|
209
|
+
fs.copyFileSync(path.join(dir, exports.RECORDING_FILES.manifest), path.join(destDir, 'manifest.json'));
|
|
210
|
+
fs.copyFileSync(path.join(dir, exports.RECORDING_FILES.pages), path.join(destDir, 'pages', 'pages.jsonl'));
|
|
211
|
+
fs.writeFileSync(path.join(destDir, 'indexes', 'entries.jsonl'), zlib.gunzipSync(fs.readFileSync(path.join(dir, exports.RECORDING_FILES.entries))));
|
|
212
|
+
}
|
|
213
|
+
//# sourceMappingURL=archive.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"archive.js","sourceRoot":"","sources":["../../../src/core/archive.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Bundle } from './bundle';
|
|
2
|
+
import type { RuleSet } from './rules';
|
|
3
|
+
export interface AuditFinding {
|
|
4
|
+
check: string;
|
|
5
|
+
detail: string;
|
|
6
|
+
}
|
|
7
|
+
/**
|
|
8
|
+
* Checks that a bundle is safe to publish: no credentials in stored requests, no live cookie
|
|
9
|
+
* values in stored responses, no stored header lines that are not headers (a header sent several
|
|
10
|
+
* times stored as one value joined by newlines hides every cookie after the first from the cookie
|
|
11
|
+
* check), no pages the rules exclude (bot checks), and nothing in the manifest beyond what replay
|
|
12
|
+
* reads (such as who recorded it).
|
|
13
|
+
* Returns one finding per problem; an empty list means the bundle passed.
|
|
14
|
+
*/
|
|
15
|
+
export declare function auditBundle(bundle: Bundle, rules: RuleSet): Promise<AuditFinding[]>;
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.auditBundle = auditBundle;
|
|
37
|
+
const fs = __importStar(require("node:fs"));
|
|
38
|
+
const warc_1 = require("./warc");
|
|
39
|
+
const MANIFEST_KEYS = ['task_id', 'captured_from', 'captured_to', 'not_found', 'locale', 'timezone', 'format'];
|
|
40
|
+
/** Written by recordings made before 2026-10-26; replay ignores it and the next import drops it. */
|
|
41
|
+
const LEGACY_MANIFEST_KEYS = ['start_url'];
|
|
42
|
+
const CREDENTIAL_REQUEST_HEADERS = ['cookie', 'authorization', 'proxy-authorization', 'x-amz-access-token'];
|
|
43
|
+
/**
|
|
44
|
+
* Checks that a bundle is safe to publish: no credentials in stored requests, no live cookie
|
|
45
|
+
* values in stored responses, no stored header lines that are not headers (a header sent several
|
|
46
|
+
* times stored as one value joined by newlines hides every cookie after the first from the cookie
|
|
47
|
+
* check), no pages the rules exclude (bot checks), and nothing in the manifest beyond what replay
|
|
48
|
+
* reads (such as who recorded it).
|
|
49
|
+
* Returns one finding per problem; an empty list means the bundle passed.
|
|
50
|
+
*/
|
|
51
|
+
async function auditBundle(bundle, rules) {
|
|
52
|
+
const findings = [];
|
|
53
|
+
for (const f of [bundle.manifestPath, bundle.archivePath, bundle.entriesPath]) {
|
|
54
|
+
if (!fs.existsSync(f))
|
|
55
|
+
findings.push({ check: 'files', detail: `missing ${f}` });
|
|
56
|
+
}
|
|
57
|
+
if (findings.length)
|
|
58
|
+
return findings;
|
|
59
|
+
const extra = Object.keys(bundle.manifest()).filter((k) => !MANIFEST_KEYS.includes(k) && !LEGACY_MANIFEST_KEYS.includes(k));
|
|
60
|
+
if (extra.length)
|
|
61
|
+
findings.push({ check: 'manifest', detail: `manifest.json records fields replay does not use: ${extra.join(', ')}` });
|
|
62
|
+
const add = (check, detail) => {
|
|
63
|
+
if (findings.filter((f) => f.check === check).length < 20)
|
|
64
|
+
findings.push({ check, detail });
|
|
65
|
+
};
|
|
66
|
+
for await (const ex of (0, warc_1.readExchanges)(bundle.archivePath)) {
|
|
67
|
+
for (const [name] of ex.reqHeaders) {
|
|
68
|
+
if (CREDENTIAL_REQUEST_HEADERS.includes(name.toLowerCase()))
|
|
69
|
+
add('request-credentials', `${name} header on ${ex.method} ${ex.url}`);
|
|
70
|
+
}
|
|
71
|
+
if (ex.malformedHeaderLines)
|
|
72
|
+
add('header-lines', `${ex.malformedHeaderLines} stored header lines are not headers (values joined by newlines?) on ${ex.method} ${ex.url}`);
|
|
73
|
+
for (const [name, value] of ex.respHeaders) {
|
|
74
|
+
if (name.toLowerCase() !== 'set-cookie')
|
|
75
|
+
continue;
|
|
76
|
+
const cookieValue = value.split(';')[0].split('=').slice(1).join('=').trim();
|
|
77
|
+
if (cookieValue && cookieValue !== 'REDACTED')
|
|
78
|
+
add('response-cookies', `unredacted Set-Cookie on ${ex.url}`);
|
|
79
|
+
}
|
|
80
|
+
if (rules.isExcluded(ex.url))
|
|
81
|
+
add('excluded-pages', `excluded URL stored: ${ex.url}`);
|
|
82
|
+
}
|
|
83
|
+
return findings;
|
|
84
|
+
}
|
|
85
|
+
//# sourceMappingURL=audit.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit.js","sourceRoot":"","sources":["../../../src/core/audit.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import type { Entry, Manifest } from './types';
|
|
2
|
+
/**
|
|
3
|
+
* A recording bundle on disk. One recording, one directory:
|
|
4
|
+
* manifest.json, archive/data.warc, indexes/entries.jsonl, pages/pages.jsonl;
|
|
5
|
+
* optionally rules/*.yaml.
|
|
6
|
+
* The recorder also keeps working files next to them (raw/, review/, check/, report.json);
|
|
7
|
+
* those never leave the recording machine.
|
|
8
|
+
*/
|
|
9
|
+
export declare class Bundle {
|
|
10
|
+
readonly dir: string;
|
|
11
|
+
constructor(dir: string);
|
|
12
|
+
get manifestPath(): string;
|
|
13
|
+
/** archive/data.warc; bundles written before the switch to uncompressed records use data.warc.gz. */
|
|
14
|
+
get archivePath(): string;
|
|
15
|
+
get entriesPath(): string;
|
|
16
|
+
get pagesPath(): string;
|
|
17
|
+
get rulesDir(): string;
|
|
18
|
+
get reviewDir(): string;
|
|
19
|
+
get rawDir(): string;
|
|
20
|
+
get checkDir(): string;
|
|
21
|
+
get reportPath(): string;
|
|
22
|
+
isPacked(): boolean;
|
|
23
|
+
manifest(): Manifest;
|
|
24
|
+
entries(): Entry[];
|
|
25
|
+
pages(): PageRecord[];
|
|
26
|
+
}
|
|
27
|
+
/** A top-level page visited during capture (WACZ pages.jsonl shape plus capture notes). */
|
|
28
|
+
export interface PageRecord {
|
|
29
|
+
id: string;
|
|
30
|
+
url: string;
|
|
31
|
+
ts: string;
|
|
32
|
+
title?: string;
|
|
33
|
+
/** e.g. "not-found-probe", "reload", "after-add-to-cart" */
|
|
34
|
+
note?: string;
|
|
35
|
+
/** Saved accessibility snapshot / screenshot names under raw/snapshots. */
|
|
36
|
+
snapshot?: string;
|
|
37
|
+
/**
|
|
38
|
+
* How the page's URL was fetched during capture: "GET" (a real document load), "POST" (form
|
|
39
|
+
* result), or "none" (the address bar was rewritten by page script, nothing was requested).
|
|
40
|
+
*/
|
|
41
|
+
fetched?: 'GET' | 'POST' | 'none';
|
|
42
|
+
}
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.Bundle = void 0;
|
|
37
|
+
const fs = __importStar(require("node:fs"));
|
|
38
|
+
const path = __importStar(require("node:path"));
|
|
39
|
+
const util_1 = require("./util");
|
|
40
|
+
/**
|
|
41
|
+
* A recording bundle on disk. One recording, one directory:
|
|
42
|
+
* manifest.json, archive/data.warc, indexes/entries.jsonl, pages/pages.jsonl;
|
|
43
|
+
* optionally rules/*.yaml.
|
|
44
|
+
* The recorder also keeps working files next to them (raw/, review/, check/, report.json);
|
|
45
|
+
* those never leave the recording machine.
|
|
46
|
+
*/
|
|
47
|
+
class Bundle {
|
|
48
|
+
dir;
|
|
49
|
+
constructor(dir) {
|
|
50
|
+
this.dir = dir;
|
|
51
|
+
}
|
|
52
|
+
get manifestPath() { return path.join(this.dir, 'manifest.json'); }
|
|
53
|
+
/** archive/data.warc; bundles written before the switch to uncompressed records use data.warc.gz. */
|
|
54
|
+
get archivePath() {
|
|
55
|
+
const plain = path.join(this.dir, 'archive', 'data.warc');
|
|
56
|
+
const gz = plain + '.gz';
|
|
57
|
+
return !fs.existsSync(plain) && fs.existsSync(gz) ? gz : plain;
|
|
58
|
+
}
|
|
59
|
+
get entriesPath() { return path.join(this.dir, 'indexes', 'entries.jsonl'); }
|
|
60
|
+
get pagesPath() { return path.join(this.dir, 'pages', 'pages.jsonl'); }
|
|
61
|
+
get rulesDir() { return path.join(this.dir, 'rules'); }
|
|
62
|
+
get reviewDir() { return path.join(this.dir, 'review'); }
|
|
63
|
+
get rawDir() { return path.join(this.dir, 'raw'); }
|
|
64
|
+
get checkDir() { return path.join(this.dir, 'check'); }
|
|
65
|
+
get reportPath() { return path.join(this.dir, 'report.json'); }
|
|
66
|
+
isPacked() {
|
|
67
|
+
return fs.existsSync(this.manifestPath) && fs.existsSync(this.entriesPath) && fs.existsSync(this.archivePath);
|
|
68
|
+
}
|
|
69
|
+
manifest() {
|
|
70
|
+
return (0, util_1.readJson)(this.manifestPath);
|
|
71
|
+
}
|
|
72
|
+
entries() {
|
|
73
|
+
return (0, util_1.readJsonl)(this.entriesPath);
|
|
74
|
+
}
|
|
75
|
+
pages() {
|
|
76
|
+
return (0, util_1.readJsonl)(this.pagesPath);
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
exports.Bundle = Bundle;
|
|
80
|
+
//# sourceMappingURL=bundle.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"bundle.js","sourceRoot":"","sources":["../../../src/core/bundle.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;AAAA,MAAY,EAAE,oCAAgB;AAC9B,MAAY,IAAI,sCAAkB;AAElC,iCAA6C;AAE7C;;;;;;GAMG;AACH;IACuB,GAAG;IAAxB,YAAqB,GAAW;mBAAX,GAAG;IAAW,CAAC;IAEpC,IAAI,YAAY,KAAa,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,eAAe,CAAC,CAAC,CAAC,CAAC;IAC3E,qGAAqG;IACrG,IAAI,WAAW;QACb,MAAM,KAAK,GAAG,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,EAAE,WAAW,CAAC,CAAC;QAC1D,MAAM,EAAE,GAAG,KAAK,GAAG,KAAK,CAAC;QACzB,OAAO,CAAC,EAAE,CAAC,UAAU,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,UAAU,CAAC,EAAE,CAAC,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,KAAK,CAAC;IACjE,CAAC;IACD,IAAI,WAAW,KAAa,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,SAAS,EAAE,eAAe,CAAC,CAAC,CAAC,CAAC;IACrF,IAAI,SAAS,KAAa,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,OAAO,EAAE,aAAa,CAAC,CAAC,CAAC,CAAC;IAC/E,IAAI,QAAQ,KAAa,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;IAC/D,IAAI,SAAS,KAAa,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC,CAAC,CAAC;IACjE,IAAI,MAAM,KAAa,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC;IAC3D,IAAI,QAAQ,KAAa,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,OAAO,CAAC,CAAC,CAAC,CAAC;IAC/D,IAAI,UAAU,KAAa,OAAO,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,EAAE,aAAa,CAAC,CAAC,CAAC,CAAC;IAEvE,QAAQ;QACN,OAAO,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,YAAY,CAAC,IAAI,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC,UAAU,CAAC,IAAI,CAAC,WAAW,CAAC,CAAC;IAChH,CAAC;IAED,QAAQ;QACN,OAAO,IAAA,eAAQ,EAAW,IAAI,CAAC,YAAY,CAAC,CAAC;IAC/C,CAAC;IAED,OAAO;QACL,OAAO,IAAA,gBAAS,EAAQ,IAAI,CAAC,WAAW,CAAC,CAAC;IAC5C,CAAC;IAED,KAAK;QACH,OAAO,IAAA,gBAAS,EAAa,IAAI,CAAC,SAAS,CAAC,CAAC;IAC/C,CAAC;CACF"}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import { type NormalizedRequest } from './normalize';
|
|
2
|
+
import type { IndexedEntry, RecordStore } from './store';
|
|
3
|
+
export interface LookupResult {
|
|
4
|
+
source: 'exact' | 'fuzzy' | 'search' | 'promo';
|
|
5
|
+
ix: IndexedEntry;
|
|
6
|
+
detail?: string;
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* The lookup ladder: exact key, then free-text (search / promo), then same-path fuzzy.
|
|
10
|
+
* When a key was recorded several times, the earliest recording wins ("browse first, act later"),
|
|
11
|
+
* except for pages that follow what was done (carts, `latest` in the rules): there the last does.
|
|
12
|
+
*/
|
|
13
|
+
export declare function lookup(store: RecordStore, norm: NormalizedRequest): LookupResult | undefined;
|
|
14
|
+
/** Share of parameters (by name) whose values agree; 1 when neither side has parameters. */
|
|
15
|
+
export declare function paramOverlap(a: [string, string][], b: [string, string][]): number;
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.lookup = lookup;
|
|
4
|
+
exports.paramOverlap = paramOverlap;
|
|
5
|
+
const normalize_1 = require("./normalize");
|
|
6
|
+
const util_1 = require("./util");
|
|
7
|
+
const DEFAULT_SIMILARITY = 0.6;
|
|
8
|
+
const DEFAULT_FUZZY_MIN = 0.5;
|
|
9
|
+
/**
|
|
10
|
+
* The lookup ladder: exact key, then free-text (search / promo), then same-path fuzzy.
|
|
11
|
+
* When a key was recorded several times, the earliest recording wins ("browse first, act later"),
|
|
12
|
+
* except for pages that follow what was done (carts, `latest` in the rules): there the last does.
|
|
13
|
+
*/
|
|
14
|
+
function lookup(store, norm) {
|
|
15
|
+
const exact = store.byKey.get(norm.key);
|
|
16
|
+
if (exact && exact.length)
|
|
17
|
+
return { source: 'exact', ix: norm.latest ? exact[exact.length - 1] : exact[0] };
|
|
18
|
+
const sameEndpoint = (store.byPathKey.get(norm.pathKey) ?? []).filter((ix) => ix.entry.status !== 0);
|
|
19
|
+
if (norm.kind === 'search' && norm.text !== undefined) {
|
|
20
|
+
const found = bestSearch(sameEndpoint, norm);
|
|
21
|
+
if (found)
|
|
22
|
+
return found;
|
|
23
|
+
}
|
|
24
|
+
if (norm.kind === 'promo') {
|
|
25
|
+
const code = norm.text ?? '';
|
|
26
|
+
const same = sameEndpoint.find((ix) => ix.norm.text === code);
|
|
27
|
+
if (same)
|
|
28
|
+
return { source: 'promo', ix: same, detail: 'known-code' };
|
|
29
|
+
const invalid = norm.endpoint?.invalid_code ? (0, normalize_1.normalizeText)(norm.endpoint.invalid_code) : undefined;
|
|
30
|
+
const fallback = invalid !== undefined ? sameEndpoint.find((ix) => ix.norm.text === invalid) : undefined;
|
|
31
|
+
if (fallback)
|
|
32
|
+
return { source: 'promo', ix: fallback, detail: 'unknown-code' };
|
|
33
|
+
return undefined;
|
|
34
|
+
}
|
|
35
|
+
const fuzzyAllowed = norm.endpoint?.fuzzy ?? norm.bodyKey === '';
|
|
36
|
+
if (!fuzzyAllowed || sameEndpoint.length === 0)
|
|
37
|
+
return undefined;
|
|
38
|
+
const min = norm.endpoint?.fuzzy_min ?? (norm.kind === 'static' ? 0 : DEFAULT_FUZZY_MIN);
|
|
39
|
+
let best;
|
|
40
|
+
// among equally good matches the first one tried wins
|
|
41
|
+
for (const ix of norm.latest ? [...sameEndpoint].reverse() : sameEndpoint) {
|
|
42
|
+
const score = paramOverlap(norm.params, ix.norm.params);
|
|
43
|
+
if (score < min)
|
|
44
|
+
continue;
|
|
45
|
+
if (!best || score > best.score)
|
|
46
|
+
best = { ix, score };
|
|
47
|
+
}
|
|
48
|
+
return best ? { source: 'fuzzy', ix: best.ix, detail: `overlap=${best.score.toFixed(2)}` } : undefined;
|
|
49
|
+
}
|
|
50
|
+
function bestSearch(candidates, norm) {
|
|
51
|
+
const want = (0, util_1.tokens)(norm.text ?? '');
|
|
52
|
+
const textParam = norm.endpoint?.text_param;
|
|
53
|
+
const others = norm.params.filter(([k]) => k !== textParam);
|
|
54
|
+
const threshold = norm.endpoint?.similarity ?? DEFAULT_SIMILARITY;
|
|
55
|
+
let best;
|
|
56
|
+
for (const ix of candidates) {
|
|
57
|
+
if (ix.norm.text === undefined)
|
|
58
|
+
continue;
|
|
59
|
+
const sim = (0, util_1.jaccard)(want, (0, util_1.tokens)(ix.norm.text));
|
|
60
|
+
if (sim < threshold)
|
|
61
|
+
continue;
|
|
62
|
+
const rest = paramOverlap(others, ix.norm.params.filter(([k]) => k !== textParam));
|
|
63
|
+
const score = sim + rest * 0.01;
|
|
64
|
+
if (!best || score > best.score)
|
|
65
|
+
best = { ix, score };
|
|
66
|
+
}
|
|
67
|
+
return best ? { source: 'search', ix: best.ix, detail: `similarity=${best.score.toFixed(2)}` } : undefined;
|
|
68
|
+
}
|
|
69
|
+
/** Share of parameters (by name) whose values agree; 1 when neither side has parameters. */
|
|
70
|
+
function paramOverlap(a, b) {
|
|
71
|
+
const ma = new Map(a);
|
|
72
|
+
const mb = new Map(b);
|
|
73
|
+
const names = new Set([...ma.keys(), ...mb.keys()]);
|
|
74
|
+
if (names.size === 0)
|
|
75
|
+
return 1;
|
|
76
|
+
let same = 0;
|
|
77
|
+
for (const n of names)
|
|
78
|
+
if (ma.has(n) && ma.get(n) === mb.get(n))
|
|
79
|
+
same++;
|
|
80
|
+
return same / names.size;
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=lookup.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lookup.js","sourceRoot":"","sources":["../../../src/core/lookup.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
import type { EndpointRule, Kind, SiteRules } from './types';
|
|
2
|
+
import { RuleSet } from './rules';
|
|
3
|
+
export interface RequestInput {
|
|
4
|
+
method: string;
|
|
5
|
+
url: string;
|
|
6
|
+
contentType?: string;
|
|
7
|
+
body?: Buffer;
|
|
8
|
+
}
|
|
9
|
+
export interface NormalizedRequest {
|
|
10
|
+
method: string;
|
|
11
|
+
host: string;
|
|
12
|
+
/** Path after applying the endpoint key template. */
|
|
13
|
+
path: string;
|
|
14
|
+
/** Kept query parameters, sorted. */
|
|
15
|
+
params: [string, string][];
|
|
16
|
+
bodyKey: string;
|
|
17
|
+
/** METHOD host/path?query #body:... — the exact-match key. */
|
|
18
|
+
key: string;
|
|
19
|
+
/** METHOD host/path — groups requests for fuzzy matching. */
|
|
20
|
+
pathKey: string;
|
|
21
|
+
kind: Kind;
|
|
22
|
+
site?: SiteRules;
|
|
23
|
+
endpoint?: EndpointRule;
|
|
24
|
+
captures: Record<string, string>;
|
|
25
|
+
/** Normalized free text for search and promo endpoints. */
|
|
26
|
+
text?: string;
|
|
27
|
+
/** Answer with the last recording of this request rather than the first (carts). */
|
|
28
|
+
latest: boolean;
|
|
29
|
+
}
|
|
30
|
+
export declare function normalizeText(value: string): string;
|
|
31
|
+
export declare function normalizeRequest(input: RequestInput, rules: RuleSet): NormalizedRequest;
|
|
32
|
+
type ParsedBody = {
|
|
33
|
+
kind: 'none';
|
|
34
|
+
} | {
|
|
35
|
+
kind: 'fields';
|
|
36
|
+
fields: Record<string, unknown>;
|
|
37
|
+
} | {
|
|
38
|
+
kind: 'raw';
|
|
39
|
+
raw: Buffer;
|
|
40
|
+
};
|
|
41
|
+
export declare function parseBody(contentType: string | undefined, body: Buffer | undefined): ParsedBody;
|
|
42
|
+
export declare function getPath(obj: unknown, dotted: string): unknown;
|
|
43
|
+
/** Read a field from the query string or a parsed body. */
|
|
44
|
+
export declare function readField(input: RequestInput, from: 'query' | 'body', field: string): string | undefined;
|
|
45
|
+
export {};
|