tamperward 2.37.1 → 2.37.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/dist/cli/index.js +310 -63
- package/package.json +1 -1
package/dist/cli/index.js
CHANGED
|
@@ -5300,13 +5300,13 @@ function merge(a, b) {
|
|
|
5300
5300
|
function literals(e) {
|
|
5301
5301
|
if (ts.isStringLiteral(e)) return { items: [e.text], opaque: false };
|
|
5302
5302
|
if (!ts.isArrayLiteralExpression(e)) return { items: [], opaque: true };
|
|
5303
|
-
const
|
|
5303
|
+
const items2 = [];
|
|
5304
5304
|
let opaque = false;
|
|
5305
5305
|
for (const el of e.elements) {
|
|
5306
|
-
if (ts.isStringLiteral(el) || ts.isNoSubstitutionTemplateLiteral(el))
|
|
5306
|
+
if (ts.isStringLiteral(el) || ts.isNoSubstitutionTemplateLiteral(el)) items2.push(el.text);
|
|
5307
5307
|
else opaque = true;
|
|
5308
5308
|
}
|
|
5309
|
-
return { items, opaque };
|
|
5309
|
+
return { items: items2, opaque };
|
|
5310
5310
|
}
|
|
5311
5311
|
function parseLists(src) {
|
|
5312
5312
|
const res = { collectFrom: null, collectKey: "collectCoverageFrom", ignore: [], ignoreRe: [], opaqueCollect: false, opaqueIgnore: false };
|
|
@@ -5319,13 +5319,13 @@ function parseLists(src) {
|
|
|
5319
5319
|
const k = keyName(node.name);
|
|
5320
5320
|
const vitestCoverage = ownerKey(node) === "coverage";
|
|
5321
5321
|
if (k === "collectCoverageFrom" || k === "include" && vitestCoverage) {
|
|
5322
|
-
const { items, opaque } = literals(resolve23(node.initializer));
|
|
5323
|
-
res.collectFrom =
|
|
5322
|
+
const { items: items2, opaque } = literals(resolve23(node.initializer));
|
|
5323
|
+
res.collectFrom = items2;
|
|
5324
5324
|
res.collectKey = k === "include" ? "coverage.include" : k;
|
|
5325
5325
|
if (opaque) res.opaqueCollect = true;
|
|
5326
5326
|
} else if (k === "coveragePathIgnorePatterns" || k === "exclude" && vitestCoverage) {
|
|
5327
|
-
const { items, opaque } = literals(resolve23(node.initializer));
|
|
5328
|
-
(k === "exclude" ? res.ignore : res.ignoreRe).push(...
|
|
5327
|
+
const { items: items2, opaque } = literals(resolve23(node.initializer));
|
|
5328
|
+
(k === "exclude" ? res.ignore : res.ignoreRe).push(...items2);
|
|
5329
5329
|
if (opaque) res.opaqueIgnore = true;
|
|
5330
5330
|
}
|
|
5331
5331
|
}
|
|
@@ -5923,15 +5923,15 @@ function keyName2(name) {
|
|
|
5923
5923
|
function literals2(e) {
|
|
5924
5924
|
if (isStr(e)) return { items: [e.text], opaque: false };
|
|
5925
5925
|
if (!ts.isArrayLiteralExpression(e)) return { items: [], opaque: true };
|
|
5926
|
-
const
|
|
5926
|
+
const items2 = [];
|
|
5927
5927
|
let opaque = false;
|
|
5928
5928
|
for (const el of e.elements) {
|
|
5929
|
-
if (isStr(el))
|
|
5929
|
+
if (isStr(el)) items2.push(el.text);
|
|
5930
5930
|
else if (ts.isSpreadElement(el) && /^(?:configDefaults\.|default)(?:exclude|include)$/i.test(el.expression.getText())) {
|
|
5931
|
-
|
|
5931
|
+
items2.push(.../exclude$/i.test(el.expression.getText()) ? VITEST_DEFAULT_EXCLUDE : VITEST_DEFAULT_INCLUDE);
|
|
5932
5932
|
} else opaque = true;
|
|
5933
5933
|
}
|
|
5934
|
-
return { items, opaque };
|
|
5934
|
+
return { items: items2, opaque };
|
|
5935
5935
|
}
|
|
5936
5936
|
function ownerKey2(node) {
|
|
5937
5937
|
const obj = node.parent;
|
|
@@ -6054,11 +6054,11 @@ function collect(root, runner) {
|
|
|
6054
6054
|
const sel = empty();
|
|
6055
6055
|
const seen = /* @__PURE__ */ new Set();
|
|
6056
6056
|
const take = (key2, e) => {
|
|
6057
|
-
const { items, opaque } = literals2(e);
|
|
6057
|
+
const { items: items2, opaque } = literals2(e);
|
|
6058
6058
|
if (opaque || seen.has(key2)) sel.opaque = true;
|
|
6059
6059
|
seen.add(key2);
|
|
6060
6060
|
sel.present = true;
|
|
6061
|
-
return
|
|
6061
|
+
return items2.map(stripRootDir);
|
|
6062
6062
|
};
|
|
6063
6063
|
const ignores = (key2, e) => {
|
|
6064
6064
|
const entries = take(key2, e).map((pattern) => ({ pattern, key: key2 }));
|
|
@@ -10701,10 +10701,10 @@ function validate(r, where) {
|
|
|
10701
10701
|
throw new PolicyError(`${where}: ${msg}`);
|
|
10702
10702
|
}
|
|
10703
10703
|
const show = (v) => JSON.stringify(v) ?? String(v);
|
|
10704
|
-
function refuseNegated(
|
|
10704
|
+
function refuseNegated(field2, list3) {
|
|
10705
10705
|
const neg = list3.find(isNegatedGlob);
|
|
10706
10706
|
if (neg !== void 0) {
|
|
10707
|
-
bad2(`${
|
|
10707
|
+
bad2(`${field2}: negated glob ${show(neg)} is not allowed \u2014 a "!" pattern matches every path, so it would ${field2 === "ignore" ? "ignore every file" : field2.startsWith("protected.") ? `put every file in ${field2}` : "blind the rule on every file"}; list the paths to include instead`);
|
|
10708
10708
|
}
|
|
10709
10709
|
}
|
|
10710
10710
|
const out3 = {};
|
|
@@ -20577,7 +20577,52 @@ function pairRecordFrom(raw, where = "record") {
|
|
|
20577
20577
|
}
|
|
20578
20578
|
};
|
|
20579
20579
|
}
|
|
20580
|
-
|
|
20580
|
+
function onlyFields(raw, allowed, where) {
|
|
20581
|
+
if (!isRecord(raw)) return;
|
|
20582
|
+
for (const key2 of Object.keys(raw)) {
|
|
20583
|
+
if (!allowed.includes(key2)) bad(`${where}.${key2} is not a field of the v1 pair record; refusing to carry it as portable evidence`);
|
|
20584
|
+
}
|
|
20585
|
+
}
|
|
20586
|
+
function portableObject(raw, spec2, where) {
|
|
20587
|
+
if (!isRecord(raw)) bad(`${where} is not an object`);
|
|
20588
|
+
for (const [key2, value] of Object.entries(raw)) {
|
|
20589
|
+
const field2 = spec2.get(key2);
|
|
20590
|
+
const at = `${where}.${key2}`;
|
|
20591
|
+
if (field2 === void 0) bad(`${at} is not a field of the v1 run document; refusing to carry it as portable evidence`);
|
|
20592
|
+
if (field2 === "scalar") {
|
|
20593
|
+
if (value !== null && typeof value === "object") bad(`${at} is not a scalar; refusing to carry it as portable evidence`);
|
|
20594
|
+
} else if ("items" in field2) {
|
|
20595
|
+
if (!Array.isArray(value)) bad(`${at} is not an array`);
|
|
20596
|
+
value.forEach((item, i) => portableObject(item, field2.items, `${at}[${i}]`));
|
|
20597
|
+
} else {
|
|
20598
|
+
portableObject(value, field2.fields, at);
|
|
20599
|
+
}
|
|
20600
|
+
}
|
|
20601
|
+
}
|
|
20602
|
+
function portablePairRecordFrom(raw, where = "record") {
|
|
20603
|
+
onlyFields(raw, PAIR_FIELDS, where);
|
|
20604
|
+
if (isRecord(raw)) {
|
|
20605
|
+
onlyFields(raw.adapter, ADAPTER_FIELDS, `${where}.adapter`);
|
|
20606
|
+
onlyFields(raw.arms, ARMS_FIELDS, `${where}.arms`);
|
|
20607
|
+
if (isRecord(raw.arms)) {
|
|
20608
|
+
for (const arm of ARMS_FIELDS) {
|
|
20609
|
+
const trajectory = raw.arms[arm];
|
|
20610
|
+
const at = `${where}.arms.${arm}`;
|
|
20611
|
+
onlyFields(trajectory, TRAJECTORY_FIELDS, at);
|
|
20612
|
+
if (isRecord(trajectory)) {
|
|
20613
|
+
onlyFields(trajectory.agent, AGENT_FIELDS, `${at}.agent`);
|
|
20614
|
+
onlyFields(trajectory.treatment, TREATMENT_FIELDS, `${at}.treatment`);
|
|
20615
|
+
if (isRecord(trajectory.treatment) && trajectory.treatment.envelope !== null && trajectory.treatment.envelope !== void 0) {
|
|
20616
|
+
portableObject(trajectory.treatment.envelope, RUN_DOCUMENT_FIELDS, `${at}.treatment.envelope`);
|
|
20617
|
+
}
|
|
20618
|
+
onlyFields(trajectory.outcome, OUTCOME_FIELDS, `${at}.outcome`);
|
|
20619
|
+
}
|
|
20620
|
+
}
|
|
20621
|
+
}
|
|
20622
|
+
}
|
|
20623
|
+
return pairRecordFrom(raw, where);
|
|
20624
|
+
}
|
|
20625
|
+
var SHA_RE, SHA256_RE, PAIR_FIELDS, ADAPTER_FIELDS, ARMS_FIELDS, TRAJECTORY_FIELDS, AGENT_FIELDS, TREATMENT_FIELDS, OUTCOME_FIELDS, fields, items, RUN_DOCUMENT_FIELDS;
|
|
20581
20626
|
var init_record = __esm({
|
|
20582
20627
|
"src/research/record.ts"() {
|
|
20583
20628
|
"use strict";
|
|
@@ -20587,6 +20632,82 @@ var init_record = __esm({
|
|
|
20587
20632
|
init_derive();
|
|
20588
20633
|
SHA_RE = /^[0-9a-f]{40,64}$/;
|
|
20589
20634
|
SHA256_RE = /^[0-9a-f]{64}$/;
|
|
20635
|
+
PAIR_FIELDS = [
|
|
20636
|
+
"schema_version",
|
|
20637
|
+
"command",
|
|
20638
|
+
"document",
|
|
20639
|
+
"task",
|
|
20640
|
+
"pair",
|
|
20641
|
+
"adapter",
|
|
20642
|
+
"model",
|
|
20643
|
+
"tamperward_version",
|
|
20644
|
+
"agent_argv",
|
|
20645
|
+
"agent_budget",
|
|
20646
|
+
"manifest_sha256",
|
|
20647
|
+
"verify_command",
|
|
20648
|
+
"arms"
|
|
20649
|
+
];
|
|
20650
|
+
ADAPTER_FIELDS = ["name", "layers"];
|
|
20651
|
+
ARMS_FIELDS = ["ungated", "gated"];
|
|
20652
|
+
TRAJECTORY_FIELDS = [
|
|
20653
|
+
"arm",
|
|
20654
|
+
"workspace",
|
|
20655
|
+
"base",
|
|
20656
|
+
"head",
|
|
20657
|
+
"started_at",
|
|
20658
|
+
"finished_at",
|
|
20659
|
+
"agent",
|
|
20660
|
+
"treatment",
|
|
20661
|
+
"outcome",
|
|
20662
|
+
"released_green",
|
|
20663
|
+
"measured",
|
|
20664
|
+
"unmeasurable"
|
|
20665
|
+
];
|
|
20666
|
+
AGENT_FIELDS = ["exit_code", "signal", "timed_out", "failure"];
|
|
20667
|
+
TREATMENT_FIELDS = ["verdict", "exit_code", "complete", "disposition", "envelope"];
|
|
20668
|
+
OUTCOME_FIELDS = [
|
|
20669
|
+
"verify_verdict",
|
|
20670
|
+
"visible_exit",
|
|
20671
|
+
"pristine_exit",
|
|
20672
|
+
"visible_green",
|
|
20673
|
+
"pristine_green",
|
|
20674
|
+
"masked_failure",
|
|
20675
|
+
"surviving_protected_mutations",
|
|
20676
|
+
"warn_findings",
|
|
20677
|
+
"rules",
|
|
20678
|
+
"honest_completion"
|
|
20679
|
+
];
|
|
20680
|
+
fields = (entries) => ({ fields: new Map(entries) });
|
|
20681
|
+
items = (entries) => ({ items: new Map(entries) });
|
|
20682
|
+
RUN_DOCUMENT_FIELDS = /* @__PURE__ */ new Map([
|
|
20683
|
+
["schema_version", "scalar"],
|
|
20684
|
+
["verdict", "scalar"],
|
|
20685
|
+
["exit_code", "scalar"],
|
|
20686
|
+
["complete", "scalar"],
|
|
20687
|
+
["base", "scalar"],
|
|
20688
|
+
["head", "scalar"],
|
|
20689
|
+
["reason", "scalar"],
|
|
20690
|
+
["agent", fields([["exit_code", "scalar"], ["timed_out", "scalar"], ["lifecycle_owned", "scalar"], ["budget_secs", "scalar"]])],
|
|
20691
|
+
["checks", fields([["diff", "scalar"], ["worktree", "scalar"], ["verify", "scalar"]])],
|
|
20692
|
+
["verifier_backend", fields([
|
|
20693
|
+
["kind", "scalar"],
|
|
20694
|
+
["trust", "scalar"],
|
|
20695
|
+
["available", "scalar"],
|
|
20696
|
+
["image", "scalar"],
|
|
20697
|
+
["engine", "scalar"],
|
|
20698
|
+
["reason", "scalar"],
|
|
20699
|
+
["resources", fields([["memory_bytes", "scalar"], ["memory_swap_bytes", "scalar"], ["cpus", "scalar"], ["pids", "scalar"]])]
|
|
20700
|
+
])],
|
|
20701
|
+
["dependency_environment", fields([
|
|
20702
|
+
["status", "scalar"],
|
|
20703
|
+
["fingerprint", "scalar"],
|
|
20704
|
+
["reason", "scalar"],
|
|
20705
|
+
["image", "scalar"],
|
|
20706
|
+
["roots", items([["kind", "scalar"], ["path", "scalar"]])],
|
|
20707
|
+
["diagnostics", fields([["full_snapshots", "scalar"], ["reused_snapshots", "scalar"], ["total_ms", "scalar"]])]
|
|
20708
|
+
])],
|
|
20709
|
+
["observer", fields([["enabled", "scalar"], ["blocking", "scalar"]])]
|
|
20710
|
+
]);
|
|
20590
20711
|
}
|
|
20591
20712
|
});
|
|
20592
20713
|
|
|
@@ -21122,27 +21243,95 @@ var init_run2 = __esm({
|
|
|
21122
21243
|
}
|
|
21123
21244
|
});
|
|
21124
21245
|
|
|
21125
|
-
// src/research/
|
|
21126
|
-
import {
|
|
21246
|
+
// src/research/ledger-files.ts
|
|
21247
|
+
import { closeSync as closeSync5, constants as constants4, fstatSync as fstatSync2, lstatSync as lstatSync11, openSync as openSync5, readFileSync as readFileSync24, readdirSync as readdirSync7, realpathSync as realpathSync11 } from "node:fs";
|
|
21127
21248
|
import { join as join27, resolve as resolve17 } from "node:path";
|
|
21128
|
-
function
|
|
21129
|
-
|
|
21249
|
+
function lstatOrNull(path) {
|
|
21250
|
+
try {
|
|
21251
|
+
return lstatSync11(path);
|
|
21252
|
+
} catch {
|
|
21253
|
+
return null;
|
|
21254
|
+
}
|
|
21255
|
+
}
|
|
21256
|
+
function kindOf(st) {
|
|
21257
|
+
if (st.isSymbolicLink()) return "a symlink";
|
|
21258
|
+
if (st.isDirectory()) return "a directory";
|
|
21259
|
+
if (st.isFIFO()) return "a FIFO";
|
|
21260
|
+
if (st.isSocket()) return "a socket";
|
|
21261
|
+
if (st.isCharacterDevice() || st.isBlockDevice()) return "a device";
|
|
21262
|
+
return "not a regular file";
|
|
21263
|
+
}
|
|
21264
|
+
function readRegularFile(path, realPairsDir, name) {
|
|
21265
|
+
const listed = lstatOrNull(path);
|
|
21266
|
+
if (listed === null) throw new ResearchError(`ledger record ${path} vanished while the ledger was being read`);
|
|
21267
|
+
if (!listed.isFile()) {
|
|
21268
|
+
throw new ResearchError(`ledger record ${path} is ${kindOf(listed)}; only a regular file placed directly in pairs/ is read`);
|
|
21269
|
+
}
|
|
21270
|
+
if (listed.nlink !== 1) {
|
|
21271
|
+
throw new ResearchError(`ledger record ${path} has ${listed.nlink} links; a hard link can name a file outside pairs/, so only a singly linked regular file is read`);
|
|
21272
|
+
}
|
|
21273
|
+
const real = realpathSync11.native(path);
|
|
21274
|
+
if (real !== join27(realPairsDir, name)) {
|
|
21275
|
+
throw new ResearchError(`ledger record ${path} resolves outside the pairs directory (${real}); refusing to read it`);
|
|
21276
|
+
}
|
|
21277
|
+
const flags = constants4.O_RDONLY | (constants4.O_NOFOLLOW ?? 0);
|
|
21278
|
+
let fd;
|
|
21279
|
+
try {
|
|
21280
|
+
fd = openSync5(path, flags);
|
|
21281
|
+
} catch (e) {
|
|
21282
|
+
throw new ResearchError(`cannot open ledger record ${path}: ${errorMessage(e)}`);
|
|
21283
|
+
}
|
|
21284
|
+
try {
|
|
21285
|
+
const opened = fstatSync2(fd);
|
|
21286
|
+
const sameInode = process.platform === "win32" || opened.dev === listed.dev && opened.ino === listed.ino;
|
|
21287
|
+
if (!opened.isFile() || !sameInode || opened.nlink !== 1) {
|
|
21288
|
+
throw new ResearchError(`ledger record ${path} changed between listing and reading; refusing to read it`);
|
|
21289
|
+
}
|
|
21290
|
+
return readFileSync24(fd);
|
|
21291
|
+
} finally {
|
|
21292
|
+
closeSync5(fd);
|
|
21293
|
+
}
|
|
21294
|
+
}
|
|
21295
|
+
function readPairFiles(ledger) {
|
|
21296
|
+
const root = resolve17(ledger);
|
|
21297
|
+
const pairsDir = join27(root, "pairs");
|
|
21298
|
+
const dirStat = lstatOrNull(pairsDir);
|
|
21299
|
+
if (dirStat !== null && dirStat.isSymbolicLink()) {
|
|
21300
|
+
throw new ResearchError(`ledger pairs directory ${pairsDir} is a symlink; only a real directory is read`);
|
|
21301
|
+
}
|
|
21302
|
+
if (dirStat === null || !dirStat.isDirectory()) throw new ResearchError(`ledger ${root} has no pairs directory`);
|
|
21303
|
+
const realPairsDir = realpathSync11.native(pairsDir);
|
|
21130
21304
|
let names;
|
|
21131
21305
|
try {
|
|
21132
21306
|
names = readdirSync7(pairsDir).filter((n2) => n2.endsWith(".json")).sort();
|
|
21133
21307
|
} catch (e) {
|
|
21134
21308
|
throw new ResearchError(`cannot read ledger ${pairsDir}: ${errorMessage(e)}`);
|
|
21135
21309
|
}
|
|
21136
|
-
|
|
21137
|
-
|
|
21138
|
-
|
|
21310
|
+
return names.map((name) => {
|
|
21311
|
+
const path = join27(pairsDir, name);
|
|
21312
|
+
return { name, path, bytes: readRegularFile(path, realPairsDir, name) };
|
|
21313
|
+
});
|
|
21314
|
+
}
|
|
21315
|
+
var init_ledger_files = __esm({
|
|
21316
|
+
"src/research/ledger-files.ts"() {
|
|
21317
|
+
"use strict";
|
|
21318
|
+
init_narrow();
|
|
21319
|
+
init_adapter();
|
|
21320
|
+
}
|
|
21321
|
+
});
|
|
21322
|
+
|
|
21323
|
+
// src/research/summarize.ts
|
|
21324
|
+
function readLedger2(dir) {
|
|
21325
|
+
const files = readPairFiles(dir);
|
|
21326
|
+
if (files.length === 0) throw new ResearchError(`ledger ${dir} holds no pair records`);
|
|
21327
|
+
return files.map((f) => {
|
|
21139
21328
|
let raw;
|
|
21140
21329
|
try {
|
|
21141
|
-
raw = JSON.parse(
|
|
21330
|
+
raw = JSON.parse(f.bytes.toString("utf8"));
|
|
21142
21331
|
} catch (e) {
|
|
21143
|
-
throw new ResearchError(`ledger record ${path} is not valid JSON: ${errorMessage(e)}`);
|
|
21332
|
+
throw new ResearchError(`ledger record ${f.path} is not valid JSON: ${errorMessage(e)}`);
|
|
21144
21333
|
}
|
|
21145
|
-
return pairRecordFrom(raw, path);
|
|
21334
|
+
return pairRecordFrom(raw, f.path);
|
|
21146
21335
|
});
|
|
21147
21336
|
}
|
|
21148
21337
|
function weakened(t) {
|
|
@@ -21263,6 +21452,7 @@ var init_summarize = __esm({
|
|
|
21263
21452
|
"use strict";
|
|
21264
21453
|
init_machine_output();
|
|
21265
21454
|
init_narrow();
|
|
21455
|
+
init_ledger_files();
|
|
21266
21456
|
init_adapter();
|
|
21267
21457
|
init_record();
|
|
21268
21458
|
}
|
|
@@ -21389,63 +21579,105 @@ var init_report2 = __esm({
|
|
|
21389
21579
|
// src/research/bundle.ts
|
|
21390
21580
|
import { createHash as createHash16 } from "node:crypto";
|
|
21391
21581
|
import { gzipSync, gunzipSync } from "node:zlib";
|
|
21392
|
-
import {
|
|
21393
|
-
import { dirname as dirname13,
|
|
21582
|
+
import { mkdirSync as mkdirSync12, readFileSync as readFileSync26, writeFileSync as writeFileSync14 } from "node:fs";
|
|
21583
|
+
import { dirname as dirname13, resolve as resolve19 } from "node:path";
|
|
21394
21584
|
function octal(value, width) {
|
|
21395
21585
|
return value.toString(8).padStart(width - 1, "0") + "\0";
|
|
21396
21586
|
}
|
|
21397
|
-
function
|
|
21587
|
+
function tarHeader(name, size) {
|
|
21398
21588
|
const header = Buffer.alloc(BLOCK, 0);
|
|
21399
|
-
|
|
21400
|
-
header.write(safe, 0, "utf8");
|
|
21589
|
+
header.write(name, 0, "utf8");
|
|
21401
21590
|
header.write(octal(420, 8), 100, "ascii");
|
|
21402
21591
|
header.write(octal(0, 8), 108, "ascii");
|
|
21403
21592
|
header.write(octal(0, 8), 116, "ascii");
|
|
21404
|
-
header.write(octal(
|
|
21593
|
+
header.write(octal(size, 12), 124, "ascii");
|
|
21405
21594
|
header.write(octal(0, 12), 136, "ascii");
|
|
21406
21595
|
header.fill(32, 148, 156);
|
|
21407
|
-
header[
|
|
21596
|
+
header[TYPE_OFFSET] = 48;
|
|
21408
21597
|
header.write("ustar", 257, "ascii");
|
|
21409
21598
|
header.write("00", 263, "ascii");
|
|
21410
21599
|
const checksum = header.reduce((sum, b) => sum + b, 0);
|
|
21411
21600
|
header.write(octal(checksum, 8), 148, "ascii");
|
|
21601
|
+
return header;
|
|
21602
|
+
}
|
|
21603
|
+
function tarEntry(name, bytes) {
|
|
21604
|
+
if (name.startsWith("/") || Buffer.byteLength(name, "utf8") > TAR_NAME_MAX) {
|
|
21605
|
+
throw new ResearchError(`archive entry name ${name} cannot be stored byte for byte in a ustar header`);
|
|
21606
|
+
}
|
|
21412
21607
|
const padding = Buffer.alloc((BLOCK - bytes.length % BLOCK) % BLOCK, 0);
|
|
21413
|
-
return Buffer.concat([
|
|
21608
|
+
return Buffer.concat([tarHeader(name, bytes.length), bytes, padding]);
|
|
21414
21609
|
}
|
|
21415
21610
|
function makeTar(entries) {
|
|
21416
21611
|
return Buffer.concat([...entries.map((e) => tarEntry(e.name, e.bytes)), Buffer.alloc(BLOCK * 2, 0)]);
|
|
21417
21612
|
}
|
|
21613
|
+
function field(header, start, length) {
|
|
21614
|
+
const raw = header.subarray(start, start + length);
|
|
21615
|
+
const end = raw.indexOf(0);
|
|
21616
|
+
return raw.subarray(0, end === -1 ? raw.length : end).toString("utf8");
|
|
21617
|
+
}
|
|
21418
21618
|
function parseTar(bytes) {
|
|
21419
21619
|
const files = /* @__PURE__ */ new Map();
|
|
21420
|
-
|
|
21620
|
+
let offset = 0;
|
|
21621
|
+
for (; ; ) {
|
|
21622
|
+
if (offset + BLOCK > bytes.length) throw new ResearchError("research bundle is missing its end-of-archive blocks");
|
|
21421
21623
|
const header = bytes.subarray(offset, offset + BLOCK);
|
|
21422
|
-
if (header.every((b) => b === 0))
|
|
21423
|
-
|
|
21424
|
-
|
|
21425
|
-
|
|
21624
|
+
if (header.every((b) => b === 0)) {
|
|
21625
|
+
const second = bytes.subarray(offset + BLOCK, offset + 2 * BLOCK);
|
|
21626
|
+
if (second.length !== BLOCK || !second.every((b) => b === 0) || offset + 2 * BLOCK !== bytes.length) {
|
|
21627
|
+
throw new ResearchError("research bundle carries bytes after its end-of-archive blocks");
|
|
21628
|
+
}
|
|
21629
|
+
return files;
|
|
21630
|
+
}
|
|
21631
|
+
const name = field(header, 0, NAME_FIELD);
|
|
21632
|
+
const prefix = field(header, PREFIX_OFFSET, PREFIX_FIELD);
|
|
21633
|
+
if (prefix) {
|
|
21634
|
+
throw new ResearchError(`research bundle entry ${prefix}/${name} uses a ustar prefix; this bundle format stores every name in the name field`);
|
|
21635
|
+
}
|
|
21636
|
+
const type = header[TYPE_OFFSET];
|
|
21637
|
+
if (type !== 48) {
|
|
21638
|
+
throw new ResearchError(`research bundle entry ${name} is not a regular file entry (type flag ${JSON.stringify(String.fromCharCode(type))})`);
|
|
21639
|
+
}
|
|
21640
|
+
const size = Number.parseInt(field(header, 124, 12).trim() || "0", 8);
|
|
21426
21641
|
if (!name || name.startsWith("/") || name.split("/").includes("..") || !Number.isSafeInteger(size) || size < 0) {
|
|
21427
21642
|
throw new ResearchError("research bundle contains an unsafe or malformed archive entry");
|
|
21428
21643
|
}
|
|
21644
|
+
if (Buffer.byteLength(name, "utf8") > TAR_NAME_MAX || !header.equals(tarHeader(name, size))) {
|
|
21645
|
+
throw new ResearchError(`research bundle entry ${name} has a header this bundle format does not write`);
|
|
21646
|
+
}
|
|
21429
21647
|
const start = offset + BLOCK;
|
|
21430
21648
|
const end = start + size;
|
|
21431
21649
|
if (end > bytes.length) throw new ResearchError("research bundle is truncated");
|
|
21650
|
+
const padded = start + Math.ceil(size / BLOCK) * BLOCK;
|
|
21651
|
+
if (padded > bytes.length || !bytes.subarray(end, padded).every((b) => b === 0)) {
|
|
21652
|
+
throw new ResearchError(`research bundle entry ${name} carries bytes in its padding`);
|
|
21653
|
+
}
|
|
21654
|
+
if (files.has(name)) throw new ResearchError(`research bundle contains a duplicate archive entry ${name}`);
|
|
21432
21655
|
files.set(name, Buffer.from(bytes.subarray(start, end)));
|
|
21433
|
-
offset =
|
|
21656
|
+
offset = padded;
|
|
21434
21657
|
}
|
|
21435
|
-
return files;
|
|
21436
21658
|
}
|
|
21437
|
-
function
|
|
21659
|
+
function packagedRecords(ledger) {
|
|
21660
|
+
const files = readPairFiles(ledger);
|
|
21661
|
+
if (files.length === 0) throw new ResearchError(`ledger ${ledger} holds no pair records`);
|
|
21662
|
+
const records = [];
|
|
21438
21663
|
const entries = [];
|
|
21439
|
-
const
|
|
21440
|
-
|
|
21441
|
-
|
|
21442
|
-
|
|
21443
|
-
|
|
21444
|
-
|
|
21445
|
-
|
|
21446
|
-
|
|
21664
|
+
for (const f of files) {
|
|
21665
|
+
let raw;
|
|
21666
|
+
try {
|
|
21667
|
+
raw = JSON.parse(f.bytes.toString("utf8"));
|
|
21668
|
+
} catch (e) {
|
|
21669
|
+
throw new ResearchError(`ledger record ${f.path} is not valid JSON: ${e instanceof Error ? e.message : String(e)}`);
|
|
21670
|
+
}
|
|
21671
|
+
const record = portablePairRecordFrom(raw, f.path);
|
|
21672
|
+
const archiveName = `ledger/pairs/${f.name}`;
|
|
21673
|
+
const nameBytes = Buffer.byteLength(archiveName, "utf8");
|
|
21674
|
+
if (nameBytes > TAR_NAME_MAX) {
|
|
21675
|
+
throw new ResearchError(`pair record name ${f.name} is too long for the archive (${nameBytes} bytes as ${archiveName}; the limit is ${TAR_NAME_MAX})`);
|
|
21676
|
+
}
|
|
21677
|
+
records.push(record);
|
|
21678
|
+
entries.push({ name: archiveName, bytes: jsonBytes(record) });
|
|
21447
21679
|
}
|
|
21448
|
-
return entries;
|
|
21680
|
+
return { records, entries };
|
|
21449
21681
|
}
|
|
21450
21682
|
function jsonBytes(value) {
|
|
21451
21683
|
return Buffer.from(JSON.stringify(value, null, 2) + "\n", "utf8");
|
|
@@ -21457,6 +21689,11 @@ function validateResearchBundle(path) {
|
|
|
21457
21689
|
} catch (e) {
|
|
21458
21690
|
throw new ResearchError(`cannot read research bundle ${path}: ${e instanceof Error ? e.message : String(e)}`);
|
|
21459
21691
|
}
|
|
21692
|
+
for (const name of files.keys()) {
|
|
21693
|
+
if (!TOP_LEVEL_ENTRIES.has(name) && !PAIR_ENTRY_RE.test(name)) {
|
|
21694
|
+
throw new ResearchError(`research bundle carries an entry that is not evidence: ${name}`);
|
|
21695
|
+
}
|
|
21696
|
+
}
|
|
21460
21697
|
const provenance = files.get("provenance.json");
|
|
21461
21698
|
const summaryBytes = files.get("summary.json");
|
|
21462
21699
|
const report2 = files.get("report.txt");
|
|
@@ -21483,14 +21720,16 @@ function validateResearchBundle(path) {
|
|
|
21483
21720
|
if (parsedSummary.document !== "summary" || parsedSummary.command !== "research") throw new ResearchError("research bundle summary is not a research summary");
|
|
21484
21721
|
const records = [];
|
|
21485
21722
|
for (const [name, bytes] of files) {
|
|
21486
|
-
if (!
|
|
21723
|
+
if (!PAIR_ENTRY_RE.test(name)) continue;
|
|
21487
21724
|
let raw;
|
|
21488
21725
|
try {
|
|
21489
21726
|
raw = JSON.parse(bytes.toString("utf8"));
|
|
21490
21727
|
} catch {
|
|
21491
21728
|
throw new ResearchError(`invalid JSON in ${name}`);
|
|
21492
21729
|
}
|
|
21493
|
-
|
|
21730
|
+
const record = portablePairRecordFrom(raw, name);
|
|
21731
|
+
if (!bytes.equals(jsonBytes(record))) throw new ResearchError(`${name} is not the canonical serialization of its pair record`);
|
|
21732
|
+
records.push(record);
|
|
21494
21733
|
}
|
|
21495
21734
|
if (records.length === 0) throw new ResearchError("research bundle contains no pair records");
|
|
21496
21735
|
const derived = summarizeRecords(records);
|
|
@@ -21504,8 +21743,8 @@ function createResearchBundle(opts) {
|
|
|
21504
21743
|
if (!opts.ledger) throw new ResearchError("research bundle requires --ledger");
|
|
21505
21744
|
if (!opts.out) throw new ResearchError("research bundle requires --out");
|
|
21506
21745
|
const ledger = resolve19(opts.ledger);
|
|
21507
|
-
const
|
|
21508
|
-
const
|
|
21746
|
+
const { records, entries } = packagedRecords(ledger);
|
|
21747
|
+
const summary = summarizeRecords(records);
|
|
21509
21748
|
if (opts.manifest) {
|
|
21510
21749
|
const manifest = readFileSync26(resolve19(opts.manifest));
|
|
21511
21750
|
const sha3 = createHash16("sha256").update(manifest).digest("hex");
|
|
@@ -21555,17 +21794,25 @@ function runResearchBundle(opts) {
|
|
|
21555
21794
|
throw e;
|
|
21556
21795
|
}
|
|
21557
21796
|
}
|
|
21558
|
-
var BLOCK, BUNDLE_VERSION;
|
|
21797
|
+
var BLOCK, BUNDLE_VERSION, TOP_LEVEL_ENTRIES, PAIR_ENTRY_RE, TAR_NAME_MAX, NAME_FIELD, TYPE_OFFSET, PREFIX_OFFSET, PREFIX_FIELD;
|
|
21559
21798
|
var init_bundle = __esm({
|
|
21560
21799
|
"src/research/bundle.ts"() {
|
|
21561
21800
|
"use strict";
|
|
21562
21801
|
init_machine_output();
|
|
21563
21802
|
init_adapter();
|
|
21803
|
+
init_ledger_files();
|
|
21564
21804
|
init_record();
|
|
21565
21805
|
init_report2();
|
|
21566
21806
|
init_summarize();
|
|
21567
21807
|
BLOCK = 512;
|
|
21568
21808
|
BUNDLE_VERSION = 1;
|
|
21809
|
+
TOP_LEVEL_ENTRIES = /* @__PURE__ */ new Set(["summary.json", "report.txt", "provenance.json", "manifest.json"]);
|
|
21810
|
+
PAIR_ENTRY_RE = /^ledger\/pairs\/[^/]+\.json$/;
|
|
21811
|
+
TAR_NAME_MAX = 99;
|
|
21812
|
+
NAME_FIELD = 100;
|
|
21813
|
+
TYPE_OFFSET = 156;
|
|
21814
|
+
PREFIX_OFFSET = 345;
|
|
21815
|
+
PREFIX_FIELD = 155;
|
|
21569
21816
|
}
|
|
21570
21817
|
});
|
|
21571
21818
|
|
|
@@ -22535,7 +22782,7 @@ var init_adapter4 = __esm({
|
|
|
22535
22782
|
|
|
22536
22783
|
// src/adapters/copilot-sdk/changes.ts
|
|
22537
22784
|
import { execFileSync as execFileSync15 } from "node:child_process";
|
|
22538
|
-
import { mkdtempSync as mkdtempSync7, writeFileSync as writeFileSync15, readFileSync as readFileSync27, rmSync as rmSync13, existsSync as
|
|
22785
|
+
import { mkdtempSync as mkdtempSync7, writeFileSync as writeFileSync15, readFileSync as readFileSync27, rmSync as rmSync13, existsSync as existsSync20, realpathSync as realpathSync12 } from "node:fs";
|
|
22539
22786
|
import { tmpdir as tmpdir8 } from "node:os";
|
|
22540
22787
|
import { basename as basename6, dirname as dirname14, isAbsolute as isAbsolute17, relative as relative12, resolve as resolve22, join as join29 } from "node:path";
|
|
22541
22788
|
function asStr4(v) {
|
|
@@ -22548,13 +22795,13 @@ function relForDisplay5(path, cwd) {
|
|
|
22548
22795
|
function canonicalContainedTarget(abs, root) {
|
|
22549
22796
|
let realRoot;
|
|
22550
22797
|
try {
|
|
22551
|
-
realRoot =
|
|
22798
|
+
realRoot = realpathSync12(root);
|
|
22552
22799
|
} catch (e) {
|
|
22553
22800
|
throw new Error(`trusted repository root ${root} cannot be resolved: ${e instanceof Error ? e.message : String(e)}`);
|
|
22554
22801
|
}
|
|
22555
22802
|
let dir = abs;
|
|
22556
22803
|
const tail = [];
|
|
22557
|
-
while (!
|
|
22804
|
+
while (!existsSync20(dir)) {
|
|
22558
22805
|
const parent = dirname14(dir);
|
|
22559
22806
|
if (parent === dir) break;
|
|
22560
22807
|
tail.unshift(basename6(dir));
|
|
@@ -22562,7 +22809,7 @@ function canonicalContainedTarget(abs, root) {
|
|
|
22562
22809
|
}
|
|
22563
22810
|
let realDir;
|
|
22564
22811
|
try {
|
|
22565
|
-
realDir =
|
|
22812
|
+
realDir = realpathSync12(dir);
|
|
22566
22813
|
} catch (e) {
|
|
22567
22814
|
throw new Error(`write target ${abs} cannot be resolved: ${e instanceof Error ? e.message : String(e)}`);
|
|
22568
22815
|
}
|
|
@@ -23373,7 +23620,7 @@ var init_runtime_qualification = __esm({
|
|
|
23373
23620
|
|
|
23374
23621
|
// src/cli/runtime.ts
|
|
23375
23622
|
import { execFileSync as execFileSync16 } from "node:child_process";
|
|
23376
|
-
import { existsSync as
|
|
23623
|
+
import { existsSync as existsSync21, mkdirSync as mkdirSync13, readFileSync as readFileSync28, writeFileSync as writeFileSync16 } from "node:fs";
|
|
23377
23624
|
import { join as join30, dirname as dirname15 } from "node:path";
|
|
23378
23625
|
function storePath(cwd) {
|
|
23379
23626
|
const dir = gitDir(cwd);
|
|
@@ -23381,7 +23628,7 @@ function storePath(cwd) {
|
|
|
23381
23628
|
}
|
|
23382
23629
|
function readStore(cwd) {
|
|
23383
23630
|
const path = storePath(cwd);
|
|
23384
|
-
if (!path || !
|
|
23631
|
+
if (!path || !existsSync21(path)) return null;
|
|
23385
23632
|
try {
|
|
23386
23633
|
const parsed = JSON.parse(readFileSync28(path, "utf8"));
|
|
23387
23634
|
if (parsed && typeof parsed === "object" && parsed.records && typeof parsed.records === "object") return parsed;
|
|
@@ -23543,7 +23790,7 @@ function resolveHookConfigHash(canonicalId, cwd) {
|
|
|
23543
23790
|
"github-copilot-cli": join30(cwd, ".github", "hooks", "tamperward.json")
|
|
23544
23791
|
};
|
|
23545
23792
|
const file = files[canonicalId];
|
|
23546
|
-
if (!file || !
|
|
23793
|
+
if (!file || !existsSync21(file)) return null;
|
|
23547
23794
|
try {
|
|
23548
23795
|
const raw = readFileSync28(file, "utf8");
|
|
23549
23796
|
let hooks = raw;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "tamperward",
|
|
3
|
-
"version": "2.37.
|
|
3
|
+
"version": "2.37.2",
|
|
4
4
|
"description": "The deterministic agent-integrity gate. One ruleset, evaluated on the actual diff/commands as a verdict, enforced everywhere a change can be made.",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "hexrift",
|