@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.
@@ -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: "testprov/testmodel" },
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], "testprov/testmodel");
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
+ });
@@ -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
  }