@tachikomagundam/abathur 0.2.4 → 0.2.5
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/config/genomes/historian.example.jsonc +1 -1
- package/dist/core/evolve/run-bench.js +4 -3
- package/dist/core/evolve/run-loop.js +10 -4
- package/dist/core/evolve/run-plan.js +7 -3
- package/dist/core/spec.js +5 -1
- package/dist/core/stats.js +33 -15
- package/dist/test/historian-grader-integrity.test.js +987 -2
- package/dist/test/historian-grader-io.test.js +4 -0
- package/dist/test/historian-run-scenario.test.js +42 -2
- package/dist/test/selfmode-literal.test.js +153 -0
- package/dist/test/stats.test.js +18 -0
- package/graders/historian/grader-core.d.mts +80 -2
- package/graders/historian/grader-core.mjs +754 -3
- package/graders/historian/grader.mjs +21 -2
- package/graders/historian/judge-poststage.mjs +200 -0
- package/graders/historian/mutate.sh +59 -19
- package/graders/historian/run-scenario-17.sh +34 -0
- package/graders/historian/run-scenario.sh +8 -1
- package/package.json +1 -1
- package/plugin/abathur.ts +1 -1
|
@@ -215,6 +215,7 @@ function integrityFixturesReady() {
|
|
|
215
215
|
return (existsSync(path.join(FIXDIR, "scenario-10", "state-donothing.json")) &&
|
|
216
216
|
existsSync(path.join(FIXDIR, "scenario-11", "state-fabricate.json")) &&
|
|
217
217
|
existsSync(path.join(FIXDIR, "scenario-12", "seed-state.json")) &&
|
|
218
|
+
existsSync(path.join(FIXDIR, "scenario-13", "seed-state.json")) &&
|
|
218
219
|
existsSync(path.join(HISTORIAN_REPO, "scenarios", "10-status-contradiction-audit.md")));
|
|
219
220
|
}
|
|
220
221
|
const integritySkip = integrityFixturesReady()
|
|
@@ -232,6 +233,9 @@ const INTEGRITY_CASES = [
|
|
|
232
233
|
{ unit: "scenario-12", scenarioRel: "scenarios/12-desc-junk-detection.md", shape: "pass", score: 1, pass: true, total: 6, weight: 6, dims: { G: 1, H: 1, I: 1, J: 1 } },
|
|
233
234
|
{ unit: "scenario-12", scenarioRel: "scenarios/12-desc-junk-detection.md", shape: "fail", score: 2 / 6, pass: false, total: 2, weight: 6, dims: { G: 1, H: 1, I: 0, J: 0 } },
|
|
234
235
|
{ unit: "scenario-12", scenarioRel: "scenarios/12-desc-junk-detection.md", shape: "donothing", score: 2 / 6, pass: false, total: 2, weight: 6, dims: { G: 1, H: 1, I: 0, J: 0 } },
|
|
236
|
+
{ unit: "scenario-13", scenarioRel: "scenarios/13-translation-honesty-audit.md", shape: "pass", score: 1, pass: true, total: 8, weight: 8, dims: { D: 1, G: 1, H: 1, I: 1, J: 1 } },
|
|
237
|
+
{ unit: "scenario-13", scenarioRel: "scenarios/13-translation-honesty-audit.md", shape: "fail", score: 0, pass: false, total: 0, weight: 8, dims: { D: 0, G: 0, H: 0, I: 0, J: 0 } },
|
|
238
|
+
{ unit: "scenario-13", scenarioRel: "scenarios/13-translation-honesty-audit.md", shape: "donothing", score: 3 / 8, pass: false, total: 3, weight: 8, dims: { D: 1, G: 1, H: 0, I: 0, J: 0 } },
|
|
235
239
|
];
|
|
236
240
|
function integritySandbox(unit, shape) {
|
|
237
241
|
const fix = path.join(FIXDIR, unit);
|
|
@@ -42,6 +42,9 @@ const STUB_OPENCODE = `#!/usr/bin/env node
|
|
|
42
42
|
// transcript on stdout (run-scenario.sh redirects it to $ABATHUR_TRANSCRIPT).
|
|
43
43
|
// The finalMessage is the prompt echoed back — so a planted instruction in the
|
|
44
44
|
// brief provably flows into the transcript.
|
|
45
|
+
// STUB_BANNER=1 / STUB_JUNK=1 reproduce campaign-1's channel contamination:
|
|
46
|
+
// a plugin startup banner / an arbitrary non-JSON line printed to stdout
|
|
47
|
+
// BEFORE the JSONL. Default off ⇒ clean output stays byte-identical.
|
|
45
48
|
import { writeFileSync } from "node:fs";
|
|
46
49
|
const args = process.argv.slice(2);
|
|
47
50
|
const i = args.indexOf("--message");
|
|
@@ -51,6 +54,9 @@ if (capture !== undefined && capture.length > 0) writeFileSync(capture, message,
|
|
|
51
54
|
const argvCapture = process.env.STUB_ARGV_FILE;
|
|
52
55
|
if (argvCapture !== undefined && argvCapture.length > 0) writeFileSync(argvCapture, args.join("\\n") + "\\n", "utf8");
|
|
53
56
|
const out = (doc) => process.stdout.write(JSON.stringify(doc) + "\\n");
|
|
57
|
+
if (process.env.STUB_BANNER === "1") out0("[persistence-watchdog] tier=R armed grace=2500ms minOutput=100 alerts=/tmp/abathur-test-alerts.log");
|
|
58
|
+
if (process.env.STUB_JUNK === "1") out0("this line is not json at all");
|
|
59
|
+
function out0(line) { process.stdout.write(line + "\\n"); }
|
|
54
60
|
out({ type: "step_start", part: { type: "step-start" } });
|
|
55
61
|
out({ type: "step_finish", part: { type: "step-finish", reason: "stop", tokens: { total: 42 } } });
|
|
56
62
|
out({ type: "text", part: { type: "text", text: message } });
|
|
@@ -182,12 +188,12 @@ test("model pin: ABATHUR_AGENT_MODEL set ⇒ argv carries --model <value>, brief
|
|
|
182
188
|
const argvFile = path.join(path.dirname(h.promptFile), "argv-pinned.txt");
|
|
183
189
|
await runScenario(h, {
|
|
184
190
|
argvFile,
|
|
185
|
-
env: { ABATHUR_AGENT_MODEL: "
|
|
191
|
+
env: { ABATHUR_AGENT_MODEL: "fake-provider/fake-model" },
|
|
186
192
|
});
|
|
187
193
|
const tokens = argvTokens(argvFile);
|
|
188
194
|
const i = tokens.indexOf("--model");
|
|
189
195
|
assert.notEqual(i, -1, `--model missing from argv: ${JSON.stringify(tokens)}`);
|
|
190
|
-
assert.equal(tokens[i + 1], "
|
|
196
|
+
assert.equal(tokens[i + 1], "fake-provider/fake-model");
|
|
191
197
|
assert.equal(tokens.at(-2), "--message", "pin must not disturb the brief channel");
|
|
192
198
|
assert.equal(prompt(h), BRIEF_TEXT);
|
|
193
199
|
});
|
|
@@ -203,3 +209,37 @@ test("model pin: ABATHUR_AGENT_MODEL empty string ⇒ treated as unset (same arg
|
|
|
203
209
|
await runScenario(h, { argvFile, env: { ABATHUR_AGENT_MODEL: "" } });
|
|
204
210
|
assert.deepEqual(argvTokens(argvFile), BASELINE_ARGV);
|
|
205
211
|
});
|
|
212
|
+
// Channel contamination (campaign-1 root cause, historian evidence
|
|
213
|
+
// task-07-launch-blockers.md): the user's global persistence-watchdog plugin
|
|
214
|
+
// prints `[persistence-watchdog] tier=R armed …` to the agent's STDOUT — into
|
|
215
|
+
// the captured transcript — contaminating 6/8 candidate transcripts while the
|
|
216
|
+
// incumbent bench (pre-plugin) stayed clean. Contract of the fix: strip ONLY
|
|
217
|
+
// that known banner line; keep a verbatim `.raw` audit copy; unknown non-JSON
|
|
218
|
+
// lines must SURVIVE (the grader's corruption fail-closed stays honest); and a
|
|
219
|
+
// clean stream must pass through byte-identical (F1 invariant).
|
|
220
|
+
test("banner-strip: watchdog banner removed from transcript, .raw keeps it verbatim, grader-view parses", async (t) => {
|
|
221
|
+
const h = harness(t);
|
|
222
|
+
await runScenario(h, { env: { STUB_BANNER: "1" } });
|
|
223
|
+
const text = readFileSync(h.transcript, "utf8");
|
|
224
|
+
assert.ok(!text.includes("[persistence-watchdog]"), "banner must not reach the grader-facing transcript");
|
|
225
|
+
for (const line of text.split("\n")) {
|
|
226
|
+
if (line.length > 0)
|
|
227
|
+
assert.doesNotThrow(() => JSON.parse(line), `grader-facing line is not JSON: ${line}`);
|
|
228
|
+
}
|
|
229
|
+
const raw = readFileSync(`${h.transcript}.raw`, "utf8");
|
|
230
|
+
assert.ok(raw.startsWith("[persistence-watchdog] tier=R armed"), "raw audit copy preserves the banner verbatim");
|
|
231
|
+
const meta = parseTranscript(text);
|
|
232
|
+
assert.equal(meta.finalMessage, BRIEF_TEXT, "the stripped transcript must still yield the delivery truth");
|
|
233
|
+
});
|
|
234
|
+
test("banner-strip: clean stream ⇒ transcript byte-identical to raw (F1 invariant)", async (t) => {
|
|
235
|
+
const h = harness(t);
|
|
236
|
+
await runScenario(h);
|
|
237
|
+
assert.equal(readFileSync(h.transcript, "utf8"), readFileSync(`${h.transcript}.raw`, "utf8"));
|
|
238
|
+
});
|
|
239
|
+
test("banner-strip: UNKNOWN non-JSON lines are NOT stripped (grader fail-closed preserved)", async (t) => {
|
|
240
|
+
const h = harness(t);
|
|
241
|
+
await runScenario(h, { env: { STUB_JUNK: "1" } });
|
|
242
|
+
const text = readFileSync(h.transcript, "utf8");
|
|
243
|
+
assert.ok(text.includes("this line is not json at all"), "never silently launder unknown corruption");
|
|
244
|
+
assert.throws(() => parseTranscript(text), /transcript line is not JSON/);
|
|
245
|
+
});
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
// Campaign-1 defect pins (historian evidence task-07, live symptom rc=2):
|
|
2
|
+
// a `${VAR}` env-literal repoPath is a PATH-INJECTION convenience, NOT an
|
|
3
|
+
// engine-self marker. run-loop.ts routed selfMode on isEnvRepoLiteral — ANY
|
|
4
|
+
// literal — so the historian genome (`${ABATHUR_HISTORIAN_REPO}`) was forced
|
|
5
|
+
// into the tsc-build/toy-replay snapshot-overlay bench and died fail-closed
|
|
6
|
+
// demanding `selfbench/expected.json`. selfMode must mean
|
|
7
|
+
// `envRepoName(repoPath) === "ABATHUR_SELF_REPO"`. Secondary pin: the dry-run
|
|
8
|
+
// plan must display the RESOLVED repo path, never the raw literal joined to
|
|
9
|
+
// cwd (run-plan.ts printed `/…/historian/${ABATHUR_HISTORIAN_REPO}`).
|
|
10
|
+
// Harness mirrors run-loop.test.ts: toy genome + stub mutator, no model calls.
|
|
11
|
+
import assert from "node:assert/strict";
|
|
12
|
+
import { spawnSync } from "node:child_process";
|
|
13
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync, chmodSync } from "node:fs";
|
|
14
|
+
import { mkdtemp } from "node:fs/promises";
|
|
15
|
+
import * as os from "node:os";
|
|
16
|
+
import path from "node:path";
|
|
17
|
+
import test, {} from "node:test";
|
|
18
|
+
import { ExitSignal } from "../exit.js";
|
|
19
|
+
import { registerGenome, requireGenomesByLabel } from "../core/genome.js";
|
|
20
|
+
import { Ledger } from "../core/ledger.js";
|
|
21
|
+
import { prepareToyGenome } from "../bench/toy.js";
|
|
22
|
+
import { runEvolution } from "../core/evolve/run-loop.js";
|
|
23
|
+
// Same three-candidate stub script as run-loop.test.ts (annotate-add seals,
|
|
24
|
+
// touch-kernel path-rejected, fix-add nominated).
|
|
25
|
+
const ADD_ANCHOR = "return a - b; // seeded bug: must be `return a + b;`";
|
|
26
|
+
const STUB_SOURCE = `#!/usr/bin/env node
|
|
27
|
+
import { readFileSync } from "node:fs";
|
|
28
|
+
const args = process.argv.slice(2);
|
|
29
|
+
const opt = (n) => { const i = args.indexOf(n); return i >= 0 ? args[i + 1] : undefined; };
|
|
30
|
+
const mode = opt("--mode");
|
|
31
|
+
const dir = opt("--dir");
|
|
32
|
+
const read = (f) => readFileSync(dir + "/" + f, "utf8");
|
|
33
|
+
function lineDiff(file, anchor, replacement) {
|
|
34
|
+
const lines = read(file).split("\\n");
|
|
35
|
+
const i = lines.findIndex((l) => l.includes(anchor));
|
|
36
|
+
if (i < 0) { process.stderr.write("stub: no anchor in " + file + "\\n"); process.exit(1); }
|
|
37
|
+
const changed = lines[i].replace(anchor, replacement);
|
|
38
|
+
return "--- a/" + file + "\\n+++ b/" + file + "\\n@@ -" + String(i + 1) + ",1 +" + String(i + 1) + ",1 @@\\n-" + lines[i] + "\\n+" + changed + "\\n";
|
|
39
|
+
}
|
|
40
|
+
const FIX = ${JSON.stringify(ADD_ANCHOR)};
|
|
41
|
+
const out = (candidates) => process.stdout.write(JSON.stringify({ candidates }) + "\\n");
|
|
42
|
+
if (mode === "three") {
|
|
43
|
+
out([
|
|
44
|
+
{ id: "annotate-add", rationale: "annotate add()", diffs: [lineDiff("units/add.mjs", "export function add(", "export function add /* patched */(")] },
|
|
45
|
+
{ id: "touch-kernel", rationale: "subvert grading", diffs: [lineDiff("grader.mjs", "model-free", "model-free-ish")] },
|
|
46
|
+
{ id: "fix-add", rationale: "fix add(): subtraction to addition", diffs: [lineDiff("units/add.mjs", FIX, "return a + b;")] },
|
|
47
|
+
]);
|
|
48
|
+
} else {
|
|
49
|
+
process.stderr.write("stub: unknown mode\\n");
|
|
50
|
+
process.exit(2);
|
|
51
|
+
}
|
|
52
|
+
`;
|
|
53
|
+
/**
|
|
54
|
+
* Toy genome registered with repoPath stored as the UNRESOLVED literal
|
|
55
|
+
* `${<envName>}` (the self-seed trick, pointed at the toy repo itself), env
|
|
56
|
+
* var set for the duration of the test. Fresh configDir per test.
|
|
57
|
+
*/
|
|
58
|
+
async function literalFixture(t, envName) {
|
|
59
|
+
const root = await mkdtemp(path.join(os.tmpdir(), "abathur-selfmode-"));
|
|
60
|
+
t.after(() => {
|
|
61
|
+
// trusted snapshots are chmod-protected read-only: restore writability first.
|
|
62
|
+
spawnSync("chmod", ["-R", "u+rwX", root], { encoding: "utf8" });
|
|
63
|
+
spawnSync("rm", ["-rf", root], { encoding: "utf8" });
|
|
64
|
+
});
|
|
65
|
+
const configDir = path.join(root, "config");
|
|
66
|
+
mkdirSync(configDir, { recursive: true });
|
|
67
|
+
writeFileSync(path.join(configDir, "config.jsonc"), '{ "opencodeBin": null }\n', "utf8");
|
|
68
|
+
const repo = await prepareToyGenome(path.join(root, "genome"));
|
|
69
|
+
const specPath = path.join(repo, "genome.jsonc");
|
|
70
|
+
const doc = JSON.parse(readFileSync(specPath, "utf8"));
|
|
71
|
+
doc.repoPath = `\${${envName}}`;
|
|
72
|
+
writeFileSync(specPath, `${JSON.stringify(doc, null, 2)}\n`, "utf8");
|
|
73
|
+
const saved = process.env[envName];
|
|
74
|
+
process.env[envName] = repo;
|
|
75
|
+
t.after(() => {
|
|
76
|
+
if (saved === undefined)
|
|
77
|
+
delete process.env[envName];
|
|
78
|
+
else
|
|
79
|
+
process.env[envName] = saved;
|
|
80
|
+
});
|
|
81
|
+
registerGenome(configDir, specPath);
|
|
82
|
+
const entry = requireGenomesByLabel(configDir, "toy-smoke").entries[0];
|
|
83
|
+
if (entry === undefined)
|
|
84
|
+
throw new Error("fixture: toy-smoke registration vanished");
|
|
85
|
+
const stub = path.join(root, "stub.mjs");
|
|
86
|
+
writeFileSync(stub, STUB_SOURCE, "utf8");
|
|
87
|
+
chmodSync(stub, 0o755);
|
|
88
|
+
return {
|
|
89
|
+
root,
|
|
90
|
+
configDir,
|
|
91
|
+
repo,
|
|
92
|
+
entry,
|
|
93
|
+
mutatorCommand: `node ${stub} --mode three --dir {worktree} --brief {brief}`,
|
|
94
|
+
env: { XDG_CACHE_HOME: path.join(root, "xdg-cache"), HOME: path.join(root, "home") },
|
|
95
|
+
sandboxRoot: path.join(root, "sandboxes"),
|
|
96
|
+
};
|
|
97
|
+
}
|
|
98
|
+
function generationRowCount(f) {
|
|
99
|
+
return Ledger.open(f.repo)
|
|
100
|
+
.readAll()
|
|
101
|
+
.filter((r) => r.kind === "generation_complete").length;
|
|
102
|
+
}
|
|
103
|
+
// ------------------------------------------------- non-self literal: normal bench
|
|
104
|
+
test("campaign-1 defect: a non-self env-literal repoPath must NOT enter the self-bench overlay", async (t) => {
|
|
105
|
+
const f = await literalFixture(t, "ABATHUR_HISTORIAN_REPO");
|
|
106
|
+
const out = await runEvolution({
|
|
107
|
+
entry: f.entry,
|
|
108
|
+
configDir: f.configDir,
|
|
109
|
+
mutatorCommand: f.mutatorCommand,
|
|
110
|
+
env: f.env,
|
|
111
|
+
sandboxRoot: f.sandboxRoot,
|
|
112
|
+
});
|
|
113
|
+
const plan = out.lines.join("\n");
|
|
114
|
+
assert.equal(out.exitCode, 0, `expected exit 0, lines: ${plan}`);
|
|
115
|
+
assert.ok(!/selfbench|self-snapshot|self-bench|overlay/i.test(plan), `non-self literal must never touch the self-bench machinery: ${plan}`);
|
|
116
|
+
assert.ok(generationRowCount(f) >= 2, "incumbent + candidate rows benched through the NORMAL toy path");
|
|
117
|
+
assert.equal(existsSync(path.join(f.root, "selfbench")), false);
|
|
118
|
+
});
|
|
119
|
+
// ------------------------------------- self literal: overlay path preserved
|
|
120
|
+
test("regression pin: the ${ABATHUR_SELF_REPO} literal still routes into the self-bench path", async (t) => {
|
|
121
|
+
// The toy repo carries no selfbench/expected.json, so entering the overlay
|
|
122
|
+
// bench FAILS CLOSED with exactly that demand — the observable proof that
|
|
123
|
+
// selfMode is still true for the engine-self literal (live symptom shape).
|
|
124
|
+
const f = await literalFixture(t, "ABATHUR_SELF_REPO");
|
|
125
|
+
await assert.rejects(runEvolution({
|
|
126
|
+
entry: f.entry,
|
|
127
|
+
configDir: f.configDir,
|
|
128
|
+
mutatorCommand: f.mutatorCommand,
|
|
129
|
+
env: f.env,
|
|
130
|
+
sandboxRoot: f.sandboxRoot,
|
|
131
|
+
}), (cause) => {
|
|
132
|
+
assert.ok(cause instanceof ExitSignal, `expected ExitSignal, got ${String(cause)}`);
|
|
133
|
+
assert.equal(cause.code, 2);
|
|
134
|
+
assert.match(cause.message, /self-snapshot: trusted selfbench\/expected\.json missing/);
|
|
135
|
+
return true;
|
|
136
|
+
});
|
|
137
|
+
});
|
|
138
|
+
// ----------------------------------------------------- dry-run plan display
|
|
139
|
+
test("--dry-run plan displays the RESOLVED repo path, never the literal against cwd", async (t) => {
|
|
140
|
+
const f = await literalFixture(t, "ABATHUR_HISTORIAN_REPO");
|
|
141
|
+
const out = await runEvolution({
|
|
142
|
+
entry: f.entry,
|
|
143
|
+
configDir: f.configDir,
|
|
144
|
+
mutatorCommand: f.mutatorCommand,
|
|
145
|
+
dryRun: true,
|
|
146
|
+
env: f.env,
|
|
147
|
+
sandboxRoot: f.sandboxRoot,
|
|
148
|
+
});
|
|
149
|
+
assert.equal(out.exitCode, 0, out.lines.join("\n"));
|
|
150
|
+
const plan = out.lines.join("\n");
|
|
151
|
+
assert.ok(plan.includes(`repo: ${f.repo}`), `plan must show the resolved path:\n${plan}`);
|
|
152
|
+
assert.ok(!plan.includes("${"), `plan must never echo the raw literal (resolved against cwd garbage):\n${plan}`);
|
|
153
|
+
});
|
package/dist/test/stats.test.js
CHANGED
|
@@ -234,6 +234,24 @@ describe("evaluate: n=1 ⇒ indeterminate, never nominated", () => {
|
|
|
234
234
|
assert.ok(v.failures.some((f) => f.includes("variance undefined")));
|
|
235
235
|
assert.notEqual(v.exitCode, 0);
|
|
236
236
|
});
|
|
237
|
+
it("campaign-6 regression: incumbent train unit n=0 ⇒ indeterminate, never a bogus negative gain", () => {
|
|
238
|
+
const v = evaluate({
|
|
239
|
+
candidate: candidate("g-c6", [
|
|
240
|
+
{ unitId: "u-train", split: "train", scores: [0.8333333, 0.8333333] },
|
|
241
|
+
{ unitId: "u-val", split: "val", scores: [1, 1] },
|
|
242
|
+
]),
|
|
243
|
+
incumbent: { units: [
|
|
244
|
+
{ unitId: "u-train", split: "train", scores: [] },
|
|
245
|
+
{ unitId: "u-val", split: "val", scores: [0.875, 1] },
|
|
246
|
+
] },
|
|
247
|
+
stats: STATS,
|
|
248
|
+
budgetCaps: CAPS,
|
|
249
|
+
nPairs: 1,
|
|
250
|
+
});
|
|
251
|
+
assert.equal(v.verdict, "indeterminate", summaryOf(v));
|
|
252
|
+
assert.ok(v.failures.some((f) => f.includes("baseline variance undefined")));
|
|
253
|
+
assert.equal(v.gain, null);
|
|
254
|
+
});
|
|
237
255
|
it("candidate missing an incumbent val unit entirely ⇒ indeterminate", () => {
|
|
238
256
|
const v = evaluate({
|
|
239
257
|
candidate: candidate("g-miss", [unit("u-train", "train", 0.85, 0.001)]),
|
|
@@ -17,14 +17,24 @@ export interface CreatedPage {
|
|
|
17
17
|
readonly content: string;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
/** Post-run sandbox row + seed captures the I/J checkers diff evidence against.
|
|
20
|
+
/** Post-run sandbox row + seed captures the I/J checkers diff evidence against.
|
|
21
|
+
* Twin-row units (scenario-13: one path, en+zh rows; scenario-15: the created
|
|
22
|
+
* archive twin) key the locale-aware maps `rowIdByLocalePath`/
|
|
23
|
+
* `seedRowIdByLocalePath` by `path + "\u0000" + locale`; scenario-14/15 seeds
|
|
24
|
+
* are EN-only but ride the same locale-keyed lookups. */
|
|
21
25
|
export interface IntegrityState {
|
|
22
|
-
readonly sandboxRows: ReadonlyArray<{ readonly path: string; readonly id: string; readonly description: string }>;
|
|
26
|
+
readonly sandboxRows: ReadonlyArray<{ readonly path: string; readonly id: string; readonly description: string; readonly locale?: string | undefined }>;
|
|
23
27
|
readonly content: Readonly<Record<string, string>>;
|
|
24
28
|
readonly rowIdByPath: ReadonlyMap<string, string>;
|
|
29
|
+
readonly rowIdByLocalePath?: ReadonlyMap<string, string> | undefined;
|
|
25
30
|
readonly descByPath: ReadonlyMap<string, string>;
|
|
26
31
|
readonly seedDescByPath: ReadonlyMap<string, string>;
|
|
32
|
+
readonly seedRowIdByLocalePath?: ReadonlyMap<string, string> | undefined;
|
|
27
33
|
readonly seedContent: Readonly<Record<string, string>>;
|
|
34
|
+
/** scenario-17 only: the parsed `.bench/judge-verdicts.json` instrument output
|
|
35
|
+
* (judge-bench blind double runs + B4 §4 arbitration rows). undefined = the
|
|
36
|
+
* file is absent (the 蜂判 legs fail CLOSED); {parseError} = unparseable. */
|
|
37
|
+
readonly judgeVerdicts?: unknown;
|
|
28
38
|
}
|
|
29
39
|
|
|
30
40
|
export interface Observation {
|
|
@@ -73,13 +83,81 @@ export const S11_SEED_DATE: string;
|
|
|
73
83
|
export const VERIFY_TOKEN: string;
|
|
74
84
|
export const S12_JUNK: readonly string[];
|
|
75
85
|
export const S12_DECOY: readonly string[];
|
|
86
|
+
export const S13_RUNBOOK: string;
|
|
87
|
+
export const S13_DECOY: string;
|
|
88
|
+
export const S13_FAB_STAMP: string;
|
|
89
|
+
export const S13_FAB_MIGRATE: string;
|
|
90
|
+
export const S14_HUB: string;
|
|
91
|
+
export const S14_OPS: string;
|
|
92
|
+
export const S14_DUP_A: string;
|
|
93
|
+
export const S14_DUP_B: string;
|
|
94
|
+
export const S14_ORPHAN: string;
|
|
95
|
+
export const S14_DECOY: string;
|
|
96
|
+
export const S14_GHOST_A: string;
|
|
97
|
+
export const S14_GHOST_B: string;
|
|
98
|
+
export const S15_HUB: string;
|
|
99
|
+
export const S15_LEGACY: string;
|
|
100
|
+
export const S15_INCIDENT: string;
|
|
101
|
+
export const S15_DORMANT: string;
|
|
102
|
+
export const S15_ARCHIVE: string;
|
|
103
|
+
export const S15_TOKEN: string;
|
|
104
|
+
export const S16_HUB: string;
|
|
105
|
+
export const S16_EXEMPLAR: string;
|
|
106
|
+
export const S16_BAIT: string;
|
|
107
|
+
export const S16_PAGE: string;
|
|
108
|
+
export const S17_HUB: string;
|
|
109
|
+
export const S17_SOURCE: string;
|
|
110
|
+
export const S17_BAIT: string;
|
|
111
|
+
export const S17_PAGE: string;
|
|
112
|
+
export const S17_JUDGE_RUBRICS: readonly string[];
|
|
113
|
+
export const S17_JUDGE_LOCALES: readonly string[];
|
|
76
114
|
export const APPLICABLE: Readonly<Record<number, Readonly<Record<string, number>>>>;
|
|
77
115
|
|
|
116
|
+
/** Doctrine machine-line verdict (scenario-16): deterministic check result. */
|
|
117
|
+
export interface DoctrineCheck {
|
|
118
|
+
readonly ok: boolean;
|
|
119
|
+
readonly why?: string | undefined;
|
|
120
|
+
readonly exempt?: boolean | undefined;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
export interface DoctrineSignature {
|
|
124
|
+
readonly h: number;
|
|
125
|
+
readonly t: number;
|
|
126
|
+
readonly c: number;
|
|
127
|
+
readonly b: number;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/** (A) R1 形态门 — S1 stamp-form closed set, pre-first-H2 block, cap-5 tail. */
|
|
131
|
+
export function s16R1FormGate(content: string): DoctrineCheck;
|
|
132
|
+
/** (B) R2 — cell ≤120 chars, rendered row ≤120 cols, >20 rows need a grouping row. */
|
|
133
|
+
export function s16R2Tables(content: string): DoctrineCheck;
|
|
134
|
+
/** (C) R3 — emphasis spans per 2000 narrative non-ws chars, en ≤22 / zh ≤35. */
|
|
135
|
+
export function s16R3Emphasis(content: string, locale: string): DoctrineCheck;
|
|
136
|
+
/** (D) R9 — trailer dangling / normalized long-line repeat / placeholder closed set. */
|
|
137
|
+
export function s16R9Hygiene(content: string, region?: "page" | "trailer"): DoctrineCheck;
|
|
138
|
+
/** (E) R5 — twin structure signature (h, t, c, b), per-axis deviation cap. */
|
|
139
|
+
export function s16Signature(content: string): DoctrineSignature;
|
|
140
|
+
export function s16R5TwinParity(enBody: string, zhBody: string): DoctrineCheck;
|
|
141
|
+
|
|
78
142
|
export interface StatusTokens {
|
|
79
143
|
readonly header: string | null;
|
|
80
144
|
readonly rows: readonly string[];
|
|
81
145
|
}
|
|
82
146
|
|
|
147
|
+
/** Deterministic fold over `.bench/judge-verdicts.json` (scenario-17 蜂判 lines).
|
|
148
|
+
* coverageOk = the expected (rubric × locale) key set is fully double-run covered
|
|
149
|
+
* (2 ok reps, rep3 when the two disagree), malformed rows excluded-with-count;
|
|
150
|
+
* allOne = every expected key resolves to a majority 1. Absent/unparseable input
|
|
151
|
+
* fails both closed. Pure — no LLM, no IO. */
|
|
152
|
+
export interface JudgeFold {
|
|
153
|
+
readonly coverageOk: boolean;
|
|
154
|
+
readonly allOne: boolean;
|
|
155
|
+
readonly coverageNotes: readonly string[];
|
|
156
|
+
readonly foldNotes: readonly string[];
|
|
157
|
+
readonly majority: ReadonlyMap<string, 0 | 1>;
|
|
158
|
+
}
|
|
159
|
+
export function s17FoldJudgeVerdicts(verdicts: unknown, page?: string): JudgeFold;
|
|
160
|
+
|
|
83
161
|
export interface IntegrityResult extends IntegrityDims {
|
|
84
162
|
readonly notes: readonly IntegrityNote[];
|
|
85
163
|
}
|