@tachikomagundam/abathur 0.1.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/.github/workflows/ci.yml +29 -0
- package/.github/workflows/publish.yml +74 -0
- package/LICENSE +21 -0
- package/README.md +461 -0
- package/config/abathur.jsonc +17 -0
- package/config/genomes/historian.example.jsonc +124 -0
- package/dist/bench/adapter.js +201 -0
- package/dist/bench/fixture-probe.js +92 -0
- package/dist/bench/fixture-support.js +173 -0
- package/dist/bench/fixture.js +236 -0
- package/dist/bench/toy.js +152 -0
- package/dist/cli.js +110 -0
- package/dist/commands/bundle.js +79 -0
- package/dist/commands/genome.js +94 -0
- package/dist/commands/graft.js +71 -0
- package/dist/commands/kernel.js +47 -0
- package/dist/commands/promote.js +25 -0
- package/dist/commands/run.js +145 -0
- package/dist/commands/self-eval.js +240 -0
- package/dist/commands/status.js +186 -0
- package/dist/commands/tombstone.js +72 -0
- package/dist/config.js +161 -0
- package/dist/core/bundle-common.js +119 -0
- package/dist/core/bundle-export.js +212 -0
- package/dist/core/bundle-inspect.js +143 -0
- package/dist/core/bundle-manifest.js +105 -0
- package/dist/core/bundle-mask.js +75 -0
- package/dist/core/bundle-tar.js +240 -0
- package/dist/core/bundle.js +9 -0
- package/dist/core/evolve/brief.js +45 -0
- package/dist/core/evolve/candidate.js +140 -0
- package/dist/core/evolve/child-track.js +197 -0
- package/dist/core/evolve/friction.js +150 -0
- package/dist/core/evolve/reflect.js +191 -0
- package/dist/core/evolve/run-bench.js +170 -0
- package/dist/core/evolve/run-friction.js +63 -0
- package/dist/core/evolve/run-loop.js +282 -0
- package/dist/core/evolve/run-plan.js +39 -0
- package/dist/core/evolve/run-rows.js +145 -0
- package/dist/core/evolve/self-overlay.js +213 -0
- package/dist/core/evolve/self-snapshot.js +170 -0
- package/dist/core/evolve/stub-mutators.mjs +105 -0
- package/dist/core/evolve/udiff.js +189 -0
- package/dist/core/genome-paths.js +76 -0
- package/dist/core/genome.js +176 -0
- package/dist/core/glob.js +106 -0
- package/dist/core/graft-gates.js +184 -0
- package/dist/core/graft-rebench.js +187 -0
- package/dist/core/graft-support.js +181 -0
- package/dist/core/graft.js +218 -0
- package/dist/core/ids.js +154 -0
- package/dist/core/incumbent.js +46 -0
- package/dist/core/kernel.js +112 -0
- package/dist/core/ledger.js +198 -0
- package/dist/core/locks.js +172 -0
- package/dist/core/promote.js +119 -0
- package/dist/core/snapshot.js +61 -0
- package/dist/core/spec.js +178 -0
- package/dist/core/stats-math.js +102 -0
- package/dist/core/stats-pareto.js +57 -0
- package/dist/core/stats.js +184 -0
- package/dist/core/worktree.js +190 -0
- package/dist/exit.js +32 -0
- package/dist/genomes/toy-smoke/genome.jsonc +30 -0
- package/dist/genomes/toy-smoke/grader.mjs +61 -0
- package/dist/genomes/toy-smoke/init.mjs +63 -0
- package/dist/genomes/toy-smoke/units/add.mjs +17 -0
- package/dist/genomes/toy-smoke/units/explode.mjs +4 -0
- package/dist/genomes/toy-smoke/units/hang.mjs +16 -0
- package/dist/genomes/toy-smoke/units/mul.mjs +16 -0
- package/dist/genomes/toy-smoke/units/mutate.mjs +18 -0
- package/dist/genomes/toy-smoke/units/sub.mjs +16 -0
- package/dist/jsonc.js +77 -0
- package/dist/out.js +5 -0
- package/dist/test/bench-adapter.test.js +33 -0
- package/dist/test/bench-fixture.test.js +407 -0
- package/dist/test/bench-toy.test.js +251 -0
- package/dist/test/bundle.test.js +659 -0
- package/dist/test/config.test.js +185 -0
- package/dist/test/d7-gate.test.js +56 -0
- package/dist/test/fixture-loop.test.js +267 -0
- package/dist/test/fixtures/friction-writer.js +16 -0
- package/dist/test/fixtures-historian.js +82 -0
- package/dist/test/fixtures-self.js +143 -0
- package/dist/test/fixtures-wt.js +64 -0
- package/dist/test/friction.test.js +398 -0
- package/dist/test/genome.test.js +453 -0
- package/dist/test/git.test.js +69 -0
- package/dist/test/graft.test.js +567 -0
- package/dist/test/historian-genome.test.js +134 -0
- package/dist/test/historian-grader-io.test.js +148 -0
- package/dist/test/historian-grader.test.js +209 -0
- package/dist/test/ids.test.js +116 -0
- package/dist/test/include-val.test.js +120 -0
- package/dist/test/ledger-lock.test.js +99 -0
- package/dist/test/ledger.test.js +102 -0
- package/dist/test/promote.test.js +394 -0
- package/dist/test/reflect.test.js +410 -0
- package/dist/test/run-loop.test.js +433 -0
- package/dist/test/self-snapshot.test.js +328 -0
- package/dist/test/snapshot.test.js +86 -0
- package/dist/test/stats.test.js +423 -0
- package/dist/test/stub-mutators.test.js +17 -0
- package/dist/test/testutil.js +30 -0
- package/dist/test/worktree.test.js +198 -0
- package/dist/util/freeze.js +30 -0
- package/dist/util/git.js +85 -0
- package/docs/federation.md +184 -0
- package/docs/immutable-kernel.md +87 -0
- package/graders/historian/grader-core.d.mts +53 -0
- package/graders/historian/grader-core.mjs +276 -0
- package/graders/historian/grader-support.d.mts +57 -0
- package/graders/historian/grader-support.mjs +137 -0
- package/graders/historian/grader.mjs +113 -0
- package/graders/historian/mutate.sh +114 -0
- package/graders/historian/reset-sandbox.sh +60 -0
- package/graders/historian/run-scenario.sh +49 -0
- package/graders/historian/seed-wrapped.sh +32 -0
- package/package.json +42 -0
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
// Todo 1 acceptance pins for src/config.ts (TDD RED first — plan protocol).
|
|
2
|
+
// Given/When/Then throughout; tmpdirs are namespaced per-run via mkdtemp and
|
|
3
|
+
// torn down in t.after so parallel checkouts of this repo never collide.
|
|
4
|
+
import assert from "node:assert/strict";
|
|
5
|
+
import { mkdtemp, mkdir, readFile, rm, writeFile } from "node:fs/promises";
|
|
6
|
+
import * as os from "node:os";
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
import { test } from "node:test";
|
|
9
|
+
import { fileURLToPath } from "node:url";
|
|
10
|
+
import { ConfigError, deepMerge, loadConfig, resolveConfigDir, resolveConfigPath, stripJsonc, } from "../config.js";
|
|
11
|
+
/** Create a throwaway HOME whose ~/.config/abathur does not exist yet. */
|
|
12
|
+
async function freshRoot() {
|
|
13
|
+
return mkdtemp(path.join(os.tmpdir(), "abathur-cfg-test-"));
|
|
14
|
+
}
|
|
15
|
+
function userConfigPath(home) {
|
|
16
|
+
return path.join(home, ".config", "abathur", "config.jsonc");
|
|
17
|
+
}
|
|
18
|
+
async function writeJsonc(filePath, text) {
|
|
19
|
+
await mkdir(path.dirname(filePath), { recursive: true });
|
|
20
|
+
await writeFile(filePath, text, "utf8");
|
|
21
|
+
}
|
|
22
|
+
const repoDefault = fileURLToPath(new URL("../../config/abathur.jsonc", import.meta.url));
|
|
23
|
+
// ---------------------------------------------------------------- stripJsonc
|
|
24
|
+
test("stripJsonc: removes line and block comments and trailing commas", () => {
|
|
25
|
+
const src = `{
|
|
26
|
+
// line comment
|
|
27
|
+
"a": 1, /* block
|
|
28
|
+
comment */
|
|
29
|
+
"b": [1, 2, 3,],
|
|
30
|
+
"c": {"d": true,},
|
|
31
|
+
}`;
|
|
32
|
+
const out = stripJsonc(src);
|
|
33
|
+
assert.deepEqual(JSON.parse(out), { a: 1, b: [1, 2, 3], c: { d: true } });
|
|
34
|
+
});
|
|
35
|
+
test("stripJsonc: preserves comment-like text inside strings", () => {
|
|
36
|
+
const src = `{"url": "http://example.com//x", "note": "a /* b */ c"} // real comment`;
|
|
37
|
+
const out = stripJsonc(src);
|
|
38
|
+
assert.deepEqual(JSON.parse(out), {
|
|
39
|
+
url: "http://example.com//x",
|
|
40
|
+
note: "a /* b */ c",
|
|
41
|
+
});
|
|
42
|
+
});
|
|
43
|
+
// ----------------------------------------------------------------- deepMerge
|
|
44
|
+
test("deepMerge: merges nested objects, replaces arrays and scalars, overlay wins", () => {
|
|
45
|
+
const base = { a: { x: 1, y: 2 }, arr: [1, 2], s: "base", keep: true };
|
|
46
|
+
const overlay = { a: { y: 9, z: 3 }, arr: [9], s: "local" };
|
|
47
|
+
assert.deepEqual(deepMerge(base, overlay), {
|
|
48
|
+
a: { x: 1, y: 9, z: 3 },
|
|
49
|
+
arr: [9],
|
|
50
|
+
s: "local",
|
|
51
|
+
keep: true,
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
// ------------------------------------------------- strict schema (AC-2 core)
|
|
55
|
+
test("loadConfig: unknown key is rejected and named in the error", async (t) => {
|
|
56
|
+
const root = await freshRoot();
|
|
57
|
+
t.after(() => rm(root, { recursive: true, force: true }));
|
|
58
|
+
const cfg = path.join(root, "config.jsonc");
|
|
59
|
+
await writeJsonc(cfg, `{ "unknownField": 1 }`);
|
|
60
|
+
let caught;
|
|
61
|
+
try {
|
|
62
|
+
loadConfig({ ABATHUR_CONFIG: cfg, HOME: root });
|
|
63
|
+
}
|
|
64
|
+
catch (error) {
|
|
65
|
+
caught = error;
|
|
66
|
+
}
|
|
67
|
+
assert.ok(caught instanceof ConfigError, `expected ConfigError, got ${String(caught)}`);
|
|
68
|
+
assert.equal(caught.kind, "unknown-key");
|
|
69
|
+
assert.match(caught.message, /unknownField/);
|
|
70
|
+
assert.ok(caught.keys?.includes("unknownField"));
|
|
71
|
+
assert.equal(caught.filePath, cfg);
|
|
72
|
+
});
|
|
73
|
+
test("loadConfig: unknown key inside the local overlay is rejected too", async (t) => {
|
|
74
|
+
const root = await freshRoot();
|
|
75
|
+
t.after(() => rm(root, { recursive: true, force: true }));
|
|
76
|
+
const cfg = path.join(root, "config.jsonc");
|
|
77
|
+
await writeJsonc(cfg, `{ "opencodeBin": null }`);
|
|
78
|
+
await writeJsonc(path.join(root, "config.local.jsonc"), `{ "oops": true }`);
|
|
79
|
+
assert.throws(() => loadConfig({ ABATHUR_CONFIG: cfg, HOME: root }), (error) => error instanceof ConfigError &&
|
|
80
|
+
error.kind === "unknown-key" &&
|
|
81
|
+
error.keys?.includes("oops"));
|
|
82
|
+
});
|
|
83
|
+
// ------------------------------------------------------------ overlay merge
|
|
84
|
+
test("loadConfig: *.local.jsonc deep-merges over the base file", async (t) => {
|
|
85
|
+
const root = await freshRoot();
|
|
86
|
+
t.after(() => rm(root, { recursive: true, force: true }));
|
|
87
|
+
const cfg = path.join(root, "config.jsonc");
|
|
88
|
+
await writeJsonc(cfg, `{ "opencodeBin": "/usr/bin/opencode", "stateDir": "/srv/state" }`);
|
|
89
|
+
await writeJsonc(cfg.replace(/\.jsonc$/, ".local.jsonc"), `{ "stateDir": "/scratch/state" }`);
|
|
90
|
+
const loaded = loadConfig({ ABATHUR_CONFIG: cfg, HOME: root });
|
|
91
|
+
assert.equal(loaded.config.opencodeBin, "/usr/bin/opencode"); // base survives
|
|
92
|
+
assert.equal(loaded.config.stateDir, "/scratch/state"); // overlay wins
|
|
93
|
+
assert.equal(loaded.path, cfg);
|
|
94
|
+
assert.equal(loaded.overlayPath, path.join(root, "config.local.jsonc"));
|
|
95
|
+
});
|
|
96
|
+
test("loadConfig: no overlay present leaves overlayPath null", async (t) => {
|
|
97
|
+
const root = await freshRoot();
|
|
98
|
+
t.after(() => rm(root, { recursive: true, force: true }));
|
|
99
|
+
const cfg = path.join(root, "config.jsonc");
|
|
100
|
+
await writeJsonc(cfg, `{ /* no overlay */ }`);
|
|
101
|
+
const loaded = loadConfig({ ABATHUR_CONFIG: cfg, HOME: root });
|
|
102
|
+
assert.equal(loaded.overlayPath, null);
|
|
103
|
+
assert.equal(loaded.config.opencodeBin, null); // schema default applied
|
|
104
|
+
});
|
|
105
|
+
// ------------------------------------------------- fallback resolution order
|
|
106
|
+
test("resolveConfigPath: ABATHUR_CONFIG pointing at a missing file fails closed", async (t) => {
|
|
107
|
+
const root = await freshRoot();
|
|
108
|
+
t.after(() => rm(root, { recursive: true, force: true }));
|
|
109
|
+
const missing = path.join(root, "nope.jsonc");
|
|
110
|
+
assert.throws(() => resolveConfigPath({ ABATHUR_CONFIG: missing, HOME: root }), (error) => error instanceof ConfigError && error.kind === "unreadable" &&
|
|
111
|
+
error.message.includes(missing));
|
|
112
|
+
// loadConfig surfaces the same failure class (unreadable => exit 2 contract)
|
|
113
|
+
assert.throws(() => loadConfig({ ABATHUR_CONFIG: missing, HOME: root }), (error) => error instanceof ConfigError && error.kind === "unreadable");
|
|
114
|
+
});
|
|
115
|
+
test("resolveConfigPath: ~/.config/abathur beats the repo default", async (t) => {
|
|
116
|
+
const root = await freshRoot();
|
|
117
|
+
t.after(() => rm(root, { recursive: true, force: true }));
|
|
118
|
+
const user = userConfigPath(root);
|
|
119
|
+
await writeJsonc(user, `{ "stateDir": "/user/abathur-state" }`);
|
|
120
|
+
assert.equal(resolveConfigPath({ HOME: root }), user);
|
|
121
|
+
const loaded = loadConfig({ HOME: root });
|
|
122
|
+
assert.equal(loaded.path, user);
|
|
123
|
+
assert.equal(loaded.config.stateDir, "/user/abathur-state");
|
|
124
|
+
});
|
|
125
|
+
test("resolveConfigPath: falls back to repo config/abathur.jsonc when nothing else exists", async (t) => {
|
|
126
|
+
const root = await freshRoot();
|
|
127
|
+
t.after(() => rm(root, { recursive: true, force: true }));
|
|
128
|
+
assert.equal(resolveConfigPath({ HOME: root }), repoDefault);
|
|
129
|
+
const loaded = loadConfig({ HOME: root }); // must NOT crash — AC: fallback order
|
|
130
|
+
assert.equal(loaded.path, repoDefault);
|
|
131
|
+
assert.equal(loaded.config.opencodeBin, null);
|
|
132
|
+
});
|
|
133
|
+
test("resolveConfigDir: tracks the winner, canonical home when nothing exists", async (t) => {
|
|
134
|
+
const root = await freshRoot();
|
|
135
|
+
t.after(() => rm(root, { recursive: true, force: true }));
|
|
136
|
+
const cfg = path.join(root, "custom", "abathur.jsonc");
|
|
137
|
+
await writeJsonc(cfg, `{}`);
|
|
138
|
+
assert.equal(resolveConfigDir({ ABATHUR_CONFIG: cfg, HOME: root }), path.join(root, "custom"));
|
|
139
|
+
const user = userConfigPath(root);
|
|
140
|
+
await writeJsonc(user, `{}`);
|
|
141
|
+
assert.equal(resolveConfigDir({ HOME: root }), path.join(root, ".config", "abathur"));
|
|
142
|
+
const bare = await freshRoot();
|
|
143
|
+
t.after(() => rm(bare, { recursive: true, force: true }));
|
|
144
|
+
// repo default exists but is lower priority: with only HOME+no files, canonical home wins
|
|
145
|
+
assert.equal(resolveConfigDir({ ABATHUR_CONFIG: undefined, HOME: bare }), path.join(bare, ".config", "abathur"));
|
|
146
|
+
});
|
|
147
|
+
// ------------------------------------------------------------------ malformed
|
|
148
|
+
test("loadConfig: broken syntax yields kind=malformed with the file path, not a raw crash", async (t) => {
|
|
149
|
+
const root = await freshRoot();
|
|
150
|
+
t.after(() => rm(root, { recursive: true, force: true }));
|
|
151
|
+
const cfg = path.join(root, "config.jsonc");
|
|
152
|
+
await writeJsonc(cfg, `{ "opencodeBin": }`); // JSON syntax error
|
|
153
|
+
let caught;
|
|
154
|
+
try {
|
|
155
|
+
loadConfig({ ABATHUR_CONFIG: cfg, HOME: root });
|
|
156
|
+
}
|
|
157
|
+
catch (error) {
|
|
158
|
+
caught = error;
|
|
159
|
+
}
|
|
160
|
+
assert.ok(caught instanceof ConfigError);
|
|
161
|
+
assert.equal(caught.kind, "malformed");
|
|
162
|
+
assert.ok(caught.message.includes(cfg), "error message must name the offending file");
|
|
163
|
+
});
|
|
164
|
+
test("loadConfig: wrong leaf type is kind=invalid-value", async (t) => {
|
|
165
|
+
const root = await freshRoot();
|
|
166
|
+
t.after(() => rm(root, { recursive: true, force: true }));
|
|
167
|
+
const cfg = path.join(root, "config.jsonc");
|
|
168
|
+
await writeJsonc(cfg, `{ "opencodeBin": 42 }`);
|
|
169
|
+
assert.throws(() => loadConfig({ ABATHUR_CONFIG: cfg, HOME: root }), (error) => error instanceof ConfigError && error.kind === "invalid-value");
|
|
170
|
+
});
|
|
171
|
+
// --------------------------------------------------------- readable artefacts
|
|
172
|
+
test("loadConfig: full happy path reads JSONC comments and overlay from disk", async (t) => {
|
|
173
|
+
const root = await freshRoot();
|
|
174
|
+
t.after(() => rm(root, { recursive: true, force: true }));
|
|
175
|
+
const cfg = path.join(root, "config.jsonc");
|
|
176
|
+
await writeJsonc(cfg, `{
|
|
177
|
+
// machine values
|
|
178
|
+
"opencodeBin": "/opt/opencode/bin/opencode",
|
|
179
|
+
"stateDir": null, /* keep default home */
|
|
180
|
+
}`);
|
|
181
|
+
const loaded = loadConfig({ ABATHUR_CONFIG: cfg, HOME: root });
|
|
182
|
+
assert.equal(loaded.config.opencodeBin, "/opt/opencode/bin/opencode");
|
|
183
|
+
const rawOverlay = await readFile(cfg, "utf8"); // sanity: fixture really on disk
|
|
184
|
+
assert.ok(rawOverlay.includes("//"));
|
|
185
|
+
});
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
// D7 grep gate (plan success criterion 7, line 217): shipped sources under src/
|
|
2
|
+
// must never carry machine-bound absolute paths or vendor-specific literals.
|
|
3
|
+
// The README's manual `grep -rnE ... src/` review is not enforcement — this test
|
|
4
|
+
// IS the CI-style gate: it walks every TypeScript source file under src/, minus
|
|
5
|
+
// the src/test/** carve-out the plan grants (fixtures legitimately name such
|
|
6
|
+
// strings), and asserts each file contains neither "/home/lab" nor a
|
|
7
|
+
// case-sensitive "historian". A violation fails naming EVERY offending file and
|
|
8
|
+
// line. Repo sources resolve relative to this module (promote.test.ts pattern),
|
|
9
|
+
// so the gate is robust to the cwd `node --test` runs from.
|
|
10
|
+
import assert from "node:assert/strict";
|
|
11
|
+
import { readdirSync, readFileSync } from "node:fs";
|
|
12
|
+
import path from "node:path";
|
|
13
|
+
import { fileURLToPath } from "node:url";
|
|
14
|
+
import test from "node:test";
|
|
15
|
+
/** src/ of the repo this compiled test belongs to: dist/test/*.js → ../../src. */
|
|
16
|
+
const SRC_DIR = fileURLToPath(new URL("../../src", import.meta.url));
|
|
17
|
+
/** The plan's carve-out: test sources may contain the very literals they ban. */
|
|
18
|
+
const TEST_DIR = path.join(SRC_DIR, "test");
|
|
19
|
+
const FORBIDDEN = [
|
|
20
|
+
{ label: "absolute workspace path", needle: "/home/lab" },
|
|
21
|
+
{ label: "vendor literal", needle: "historian" },
|
|
22
|
+
];
|
|
23
|
+
/** Recursive .ts walk; symlinks are never followed (they could escape src/). */
|
|
24
|
+
function sourceFiles(dir) {
|
|
25
|
+
const found = [];
|
|
26
|
+
for (const entry of readdirSync(dir, { withFileTypes: true })) {
|
|
27
|
+
if (entry.isSymbolicLink())
|
|
28
|
+
continue;
|
|
29
|
+
const full = path.join(dir, entry.name);
|
|
30
|
+
if (entry.isDirectory())
|
|
31
|
+
found.push(...sourceFiles(full));
|
|
32
|
+
else if (entry.isFile() && entry.name.endsWith(".ts"))
|
|
33
|
+
found.push(full);
|
|
34
|
+
}
|
|
35
|
+
return found;
|
|
36
|
+
}
|
|
37
|
+
test("D7 gate: src/** outside src/test/** is free of machine-bound literals", () => {
|
|
38
|
+
const violations = [];
|
|
39
|
+
let scanned = 0;
|
|
40
|
+
for (const file of sourceFiles(SRC_DIR)) {
|
|
41
|
+
if (file.startsWith(`${TEST_DIR}${path.sep}`))
|
|
42
|
+
continue;
|
|
43
|
+
scanned += 1;
|
|
44
|
+
const lines = readFileSync(file, "utf8").split("\n");
|
|
45
|
+
for (const [index, line] of lines.entries()) {
|
|
46
|
+
for (const { label, needle } of FORBIDDEN) {
|
|
47
|
+
if (line.includes(needle)) {
|
|
48
|
+
violations.push(`${path.relative(SRC_DIR, file)}:${String(index + 1)} [${label}] ${line.trim()}`);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
// a vacuous pass (bad path resolution) must be as loud as a violation
|
|
54
|
+
assert.ok(scanned > 20, `gate only scanned ${String(scanned)} sources under ${SRC_DIR} — path resolution broken`);
|
|
55
|
+
assert.equal(violations.length, 0, `D7 grep gate violated (${String(violations.length)} hit(s)):\n${violations.join("\n")}`);
|
|
56
|
+
});
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
// F1-fix2 loop-level regression pins for the val-split bench authority split
|
|
2
|
+
// (plan lines 117-124 / 125-132 / SC4). Commit 3c6bbf9 wired `--include-val` at
|
|
3
|
+
// the fixture adapter seam but left benchTarget iterating EVERY unit, so any
|
|
4
|
+
// default `run` on a val-bearing fixture genome crashed mid-bench with
|
|
5
|
+
// ExitSignal(2) "fixture: unit 'v1' is a val-split scenario and requires the
|
|
6
|
+
// operator flag --include-val"
|
|
7
|
+
// at the first val unit — the SC4/T7 flagship flows were unreachable without the
|
|
8
|
+
// flag. Pins here:
|
|
9
|
+
// (1) default run (NO flag) completes: incumbent AND candidate benches carry
|
|
10
|
+
// scored val replicates (the nomination gate's required input);
|
|
11
|
+
// (2) --include-val stays the honest operator EXPOSURE switch: off, every
|
|
12
|
+
// sandbox manifest keeps val ids/paths opaque; on, they surface;
|
|
13
|
+
// (3) toy + flag fail-closed stays pinned in include-val.test.ts / run-loop.test.ts.
|
|
14
|
+
// No model calls anywhere: fake opencodeBin bash stub + node grader table.
|
|
15
|
+
import assert from "node:assert/strict";
|
|
16
|
+
import { spawnSync } from "node:child_process";
|
|
17
|
+
import { chmodSync, existsSync, mkdirSync, readFileSync, readdirSync, writeFileSync } from "node:fs";
|
|
18
|
+
import { mkdtemp } from "node:fs/promises";
|
|
19
|
+
import * as os from "node:os";
|
|
20
|
+
import path from "node:path";
|
|
21
|
+
import test, {} from "node:test";
|
|
22
|
+
import { registerGenome, requireGenomesByLabel } from "../core/genome.js";
|
|
23
|
+
import { Ledger } from "../core/ledger.js";
|
|
24
|
+
import { decodeGenerationRecord } from "../core/evolve/run-bench.js";
|
|
25
|
+
import { runEvolution } from "../core/evolve/run-loop.js";
|
|
26
|
+
// --------------------------------------------------------------------- stubs
|
|
27
|
+
const VAL_CANARY = "sval-canary-ZZHIDDEN";
|
|
28
|
+
const VAL_PATH = `scenarios/${VAL_CANARY}.md`;
|
|
29
|
+
// Fake opencode: `--version` semver line (probe), `run <path>` scenario echo +
|
|
30
|
+
// final run-metadata JSON line (same contract as bench-fixture.test.ts BIN_GOOD).
|
|
31
|
+
const FAKE_BIN = `#!/usr/bin/env bash
|
|
32
|
+
set -euo pipefail
|
|
33
|
+
if [ "\${1:-}" = "--version" ]; then echo "1.2.3"; exit 0; fi
|
|
34
|
+
if [ -n "\${ABATHUR_TRANSCRIPT:-}" ]; then
|
|
35
|
+
mkdir -p "$(dirname "$ABATHUR_TRANSCRIPT")"
|
|
36
|
+
printf 'ran %s\\n' "\${2:-}" > "$ABATHUR_TRANSCRIPT"
|
|
37
|
+
fi
|
|
38
|
+
echo "ran \${2:-}"
|
|
39
|
+
echo '{"tokensEst":10,"turns":1}'
|
|
40
|
+
`;
|
|
41
|
+
const GRADER = `const [unitId] = process.argv.slice(2);
|
|
42
|
+
const table = {
|
|
43
|
+
t1: { score: 1, pass: true },
|
|
44
|
+
v1: { score: 0.75, pass: true },
|
|
45
|
+
};
|
|
46
|
+
const row = table[unitId];
|
|
47
|
+
if (!row) { console.error("unknown unit " + unitId); process.exit(1); }
|
|
48
|
+
console.log(JSON.stringify({ unit: unitId, score: row.score, pass: row.pass, metrics: { tokensEst: 4, turns: 1 } }));
|
|
49
|
+
`;
|
|
50
|
+
const RESET_SH = `#!/usr/bin/env bash
|
|
51
|
+
set -euo pipefail
|
|
52
|
+
rm -rf "\${1:?sandbox}"
|
|
53
|
+
mkdir -p "\${1:?sandbox}"
|
|
54
|
+
`;
|
|
55
|
+
const SEED_SH = `#!/usr/bin/env bash
|
|
56
|
+
set -euo pipefail
|
|
57
|
+
here=$(cd "$(dirname "$0")" && pwd)
|
|
58
|
+
sandbox="\${1:?sandbox}"
|
|
59
|
+
mkdir -p "$sandbox/scenarios"
|
|
60
|
+
cp "$here"/scenarios/*.md "$sandbox/scenarios/"
|
|
61
|
+
cp "$here"/grader.mjs "$sandbox/grader.mjs"
|
|
62
|
+
`;
|
|
63
|
+
// Two modes: "garbage" delivers one syntactically invalid candidate (rejected at
|
|
64
|
+
// parse, never benched — the loop exits after the incumbent bench); "touch"
|
|
65
|
+
// delivers a valid non-improving diff against the train scenario so the CANDIDATE
|
|
66
|
+
// bench (the second val-bearing seam) runs too and the gate culls it.
|
|
67
|
+
const MUTATOR_STUB = `#!/usr/bin/env node
|
|
68
|
+
import { readFileSync } from "node:fs";
|
|
69
|
+
const args = process.argv.slice(2);
|
|
70
|
+
const opt = (n) => { const i = args.indexOf(n); return i >= 0 ? args[i + 1] : undefined; };
|
|
71
|
+
const mode = opt("--mode");
|
|
72
|
+
const dir = opt("--dir");
|
|
73
|
+
const out = (candidates) => process.stdout.write(JSON.stringify({ candidates }) + "\\n");
|
|
74
|
+
if (mode === "garbage") {
|
|
75
|
+
out([{ id: "garbage-1", rationale: "not a real diff", diffs: ["this is not a unified diff\\n"] }]);
|
|
76
|
+
} else if (mode === "touch") {
|
|
77
|
+
const file = "scenarios/t1.md";
|
|
78
|
+
const lines = readFileSync(dir + "/" + file, "utf8").split("\\n");
|
|
79
|
+
const i = lines.findIndex((l) => l.includes("train alpha"));
|
|
80
|
+
if (i < 0) { process.stderr.write("stub: no anchor in " + file + "\\n"); process.exit(1); }
|
|
81
|
+
const changed = lines[i].replace("train alpha", "train alpha (annotated)");
|
|
82
|
+
const diff = "--- a/" + file + "\\n+++ b/" + file +
|
|
83
|
+
"\\n@@ -" + String(i + 1) + ",1 +" + String(i + 1) + ",1 @@\\n-" + lines[i] + "\\n+" + changed + "\\n";
|
|
84
|
+
out([{ id: "touch-t1", rationale: "annotate the train scenario without changing scores", diffs: [diff] }]);
|
|
85
|
+
} else {
|
|
86
|
+
process.stderr.write("stub: unknown mode\\n");
|
|
87
|
+
process.exit(2);
|
|
88
|
+
}
|
|
89
|
+
`;
|
|
90
|
+
function git(repo, args) {
|
|
91
|
+
const run = spawnSync("git", args, { cwd: repo, encoding: "utf8" });
|
|
92
|
+
if (run.status !== 0)
|
|
93
|
+
throw new Error(`git ${args.join(" ")} failed: ${run.stderr}`);
|
|
94
|
+
}
|
|
95
|
+
/** Git-init'd fixture-scenario genome (train t1 + val v1) registered in a fresh config dir. */
|
|
96
|
+
async function fixtureLoop(t) {
|
|
97
|
+
const root = await mkdtemp(path.join(os.tmpdir(), "abathur-fixloop-"));
|
|
98
|
+
t.after(() => spawnSync("rm", ["-rf", root], { encoding: "utf8" }));
|
|
99
|
+
const binDir = path.join(root, "bin");
|
|
100
|
+
mkdirSync(binDir, { recursive: true });
|
|
101
|
+
const binPath = path.join(binDir, "oc-fake");
|
|
102
|
+
writeFileSync(binPath, FAKE_BIN, "utf8");
|
|
103
|
+
chmodSync(binPath, 0o755);
|
|
104
|
+
const configDir = path.join(root, "config");
|
|
105
|
+
mkdirSync(configDir, { recursive: true });
|
|
106
|
+
// opencodeBin resolution flows loadConfig(env) with env = the loop's WorktreeEnv
|
|
107
|
+
// ({HOME, XDG_CACHE_HOME} only), so the fake HOME carries the user config file
|
|
108
|
+
// — the bench adapter then spawns the fake bin, never a real opencode.
|
|
109
|
+
const userConfigDir = path.join(root, "home", ".config", "abathur");
|
|
110
|
+
mkdirSync(userConfigDir, { recursive: true });
|
|
111
|
+
writeFileSync(path.join(userConfigDir, "config.jsonc"), `${JSON.stringify({ opencodeBin: binPath })}\n`, "utf8");
|
|
112
|
+
const repo = path.join(root, "germ");
|
|
113
|
+
mkdirSync(path.join(repo, "scenarios"), { recursive: true });
|
|
114
|
+
writeFileSync(path.join(repo, "scenarios", "t1.md"), "train alpha scenario\nsecond line\n", "utf8");
|
|
115
|
+
writeFileSync(path.join(repo, "scenarios", `${VAL_CANARY}.md`), "holdout val scenario ZZHIDDEN\n", "utf8");
|
|
116
|
+
writeFileSync(path.join(repo, "grader.mjs"), GRADER, "utf8");
|
|
117
|
+
for (const [name, body] of [
|
|
118
|
+
["reset.sh", RESET_SH],
|
|
119
|
+
["seed.sh", SEED_SH],
|
|
120
|
+
]) {
|
|
121
|
+
const p = path.join(repo, name);
|
|
122
|
+
writeFileSync(p, body, "utf8");
|
|
123
|
+
chmodSync(p, 0o755);
|
|
124
|
+
}
|
|
125
|
+
const specDoc = {
|
|
126
|
+
label: "fixloop-germ",
|
|
127
|
+
repoPath: repo,
|
|
128
|
+
bench: {
|
|
129
|
+
type: "opencode-fixture-scenarios",
|
|
130
|
+
units: [
|
|
131
|
+
{ id: "t1", path: "scenarios/t1.md", split: "train" },
|
|
132
|
+
{ id: "v1", path: VAL_PATH, split: "val" },
|
|
133
|
+
],
|
|
134
|
+
runCommand: `${binPath} run {unit.path}`,
|
|
135
|
+
seedCommand: `${repo}/seed.sh {sandbox}`,
|
|
136
|
+
resetCommand: `${repo}/reset.sh {sandbox}`,
|
|
137
|
+
graderCommand: "node grader.mjs {unit.id}",
|
|
138
|
+
agentModel: "test/agent",
|
|
139
|
+
timeoutS: 10,
|
|
140
|
+
stats: { halfWidth: 0.1, minEffect: 0.05, nReps: { initial: 2, max: 4 } },
|
|
141
|
+
},
|
|
142
|
+
budget: { maxCandidates: 2, maxModelCalls: 8, maxTokens: 100000, maxWallS: 300 },
|
|
143
|
+
kernel: { immutableGlobs: [] },
|
|
144
|
+
requires: [{ cmd: "node", args: ["--version"], probeExit: 0 }],
|
|
145
|
+
opencodeBinVersion: { minVersion: "1.0.0" },
|
|
146
|
+
};
|
|
147
|
+
const specPath = path.join(repo, "genome.jsonc");
|
|
148
|
+
writeFileSync(specPath, `${JSON.stringify(specDoc, null, 2)}\n`, "utf8");
|
|
149
|
+
git(repo, ["init", "-b", "main"]);
|
|
150
|
+
git(repo, ["add", "-A"]);
|
|
151
|
+
// identity pinned via -c BEFORE the subcommand (toy init.mjs convention)
|
|
152
|
+
git(repo, ["-c", "user.name=abathur", "-c", "user.email=abathur@harness.local", "commit", "-m", "fixloop seed"]);
|
|
153
|
+
registerGenome(configDir, specPath);
|
|
154
|
+
const entry = requireGenomesByLabel(configDir, "fixloop-germ").entries[0];
|
|
155
|
+
if (entry === undefined)
|
|
156
|
+
throw new Error("fixture: fixloop-germ registration vanished");
|
|
157
|
+
const stub = path.join(root, "stub.mjs");
|
|
158
|
+
writeFileSync(stub, MUTATOR_STUB, "utf8");
|
|
159
|
+
chmodSync(stub, 0o755);
|
|
160
|
+
return {
|
|
161
|
+
root,
|
|
162
|
+
configDir,
|
|
163
|
+
repo,
|
|
164
|
+
entry,
|
|
165
|
+
env: {
|
|
166
|
+
HOME: path.join(root, "home"),
|
|
167
|
+
XDG_CACHE_HOME: path.join(root, "xdg-cache"),
|
|
168
|
+
},
|
|
169
|
+
sandboxRoot: path.join(root, "sandboxes"),
|
|
170
|
+
stub,
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
function loopRun(f, opts) {
|
|
174
|
+
return runEvolution({
|
|
175
|
+
entry: f.entry,
|
|
176
|
+
configDir: f.configDir,
|
|
177
|
+
mutatorCommand: `node ${f.stub} --mode ${opts.mode} --dir {worktree} --brief {brief}`,
|
|
178
|
+
...(opts.includeVal === undefined ? {} : { includeVal: opts.includeVal }),
|
|
179
|
+
env: f.env,
|
|
180
|
+
sandboxRoot: f.sandboxRoot,
|
|
181
|
+
});
|
|
182
|
+
}
|
|
183
|
+
function generations(repo) {
|
|
184
|
+
return Ledger.open(repo)
|
|
185
|
+
.readAll()
|
|
186
|
+
.filter((r) => r.kind === "generation_complete")
|
|
187
|
+
.map((r) => decodeGenerationRecord(r));
|
|
188
|
+
}
|
|
189
|
+
function manifestsUnder(dir) {
|
|
190
|
+
const found = [];
|
|
191
|
+
const walk = (d) => {
|
|
192
|
+
for (const e of readdirSync(d, { withFileTypes: true })) {
|
|
193
|
+
const p = path.join(d, e.name);
|
|
194
|
+
if (e.isDirectory())
|
|
195
|
+
walk(p);
|
|
196
|
+
else if (e.isFile() && e.name === "manifest.json")
|
|
197
|
+
found.push(p);
|
|
198
|
+
}
|
|
199
|
+
};
|
|
200
|
+
if (existsSync(dir))
|
|
201
|
+
walk(dir);
|
|
202
|
+
return found;
|
|
203
|
+
}
|
|
204
|
+
function valEntry(manifestFile) {
|
|
205
|
+
const doc = JSON.parse(readFileSync(manifestFile, "utf8"));
|
|
206
|
+
const entry = doc.scenarios.find((s) => s.split === "val");
|
|
207
|
+
assert.ok(entry !== undefined, `manifest ${manifestFile} must count the val scenario`);
|
|
208
|
+
return entry;
|
|
209
|
+
}
|
|
210
|
+
// ---------------------------------------------------------------- pin (1): RED at 3c6bbf9
|
|
211
|
+
test("regression: default run (NO --include-val) benches val replicates and completes", async (t) => {
|
|
212
|
+
const f = await fixtureLoop(t);
|
|
213
|
+
const out = await loopRun(f, { mode: "garbage" });
|
|
214
|
+
assert.equal(out.exitCode, 0, `loop must complete, lines: ${out.lines.join("\n")}`);
|
|
215
|
+
assert.match(out.lines.join("\n"), /incumbent baseline: 2 units x 2 reps/);
|
|
216
|
+
const rows = generations(f.repo);
|
|
217
|
+
const incumbent = rows.filter((d) => d.source === "incumbent");
|
|
218
|
+
assert.equal(incumbent.length, 1, "exactly one incumbent baseline row");
|
|
219
|
+
const byId = new Map(incumbent[0]?.units.map((u) => [u.unitId, u]));
|
|
220
|
+
assert.deepEqual(byId.get("t1")?.scores, [1, 1], "train unit scored");
|
|
221
|
+
assert.equal(byId.get("v1")?.split, "val");
|
|
222
|
+
assert.deepEqual(byId.get("v1")?.scores, [0.75, 0.75], "val unit benched under loop authority");
|
|
223
|
+
assert.equal(incumbent[0]?.complete, true);
|
|
224
|
+
// exposure OFF: the val id/path stay opaque in every sandbox manifest the bench wrote
|
|
225
|
+
const manifests = manifestsUnder(f.sandboxRoot);
|
|
226
|
+
assert.ok(manifests.length >= 2, `expected per-unit bench manifests, found ${String(manifests.length)}`);
|
|
227
|
+
for (const m of manifests) {
|
|
228
|
+
const raw = readFileSync(m, "utf8");
|
|
229
|
+
assert.ok(!raw.includes(VAL_CANARY), `val path leaked into ${m} without the operator flag`);
|
|
230
|
+
const val = valEntry(m);
|
|
231
|
+
assert.equal(val.id, undefined, "val id must stay hidden without the operator flag");
|
|
232
|
+
assert.equal(val.path, undefined, "val path must stay hidden without the operator flag");
|
|
233
|
+
}
|
|
234
|
+
});
|
|
235
|
+
// --------------------------------------------------- pin (2): --include-val is real
|
|
236
|
+
test("--include-val completes the run AND exposes val ids/paths in this run's manifests", async (t) => {
|
|
237
|
+
const f = await fixtureLoop(t);
|
|
238
|
+
const out = await loopRun(f, { mode: "garbage", includeVal: true });
|
|
239
|
+
assert.equal(out.exitCode, 0, out.lines.join("\n"));
|
|
240
|
+
const rows = generations(f.repo);
|
|
241
|
+
const incumbent = rows.find((d) => d.source === "incumbent");
|
|
242
|
+
assert.equal(incumbent?.units.find((u) => u.unitId === "v1")?.scores.length, 2, "val benched with the flag too");
|
|
243
|
+
const manifests = manifestsUnder(f.sandboxRoot);
|
|
244
|
+
assert.ok(manifests.length >= 2);
|
|
245
|
+
for (const m of manifests) {
|
|
246
|
+
assert.ok(readFileSync(m, "utf8").includes(VAL_CANARY), `operator asked for exposure: ${m} still hides the val path`);
|
|
247
|
+
const val = valEntry(m);
|
|
248
|
+
assert.equal(val.id, "v1");
|
|
249
|
+
assert.equal(val.path, VAL_PATH);
|
|
250
|
+
}
|
|
251
|
+
});
|
|
252
|
+
// ------------------------------------------- pin (1b): candidate bench seam, no flag
|
|
253
|
+
test("regression: candidate bench (second seam) also carries val replicates without the flag", async (t) => {
|
|
254
|
+
const f = await fixtureLoop(t);
|
|
255
|
+
const out = await loopRun(f, { mode: "touch" });
|
|
256
|
+
// the touch candidate is valid but score-identical ⇒ culled ⇒ run exit 1 (BLOCKED)
|
|
257
|
+
assert.equal(out.exitCode, 1, out.lines.join("\n"));
|
|
258
|
+
const candidate = generations(f.repo)
|
|
259
|
+
.filter((d) => d.source === "candidate")
|
|
260
|
+
.find((d) => d.candidateId === "touch-t1");
|
|
261
|
+
assert.ok(candidate !== undefined, `candidate benched, lines: ${out.lines.join("\n")}`);
|
|
262
|
+
assert.equal(candidate.complete, true);
|
|
263
|
+
const byId = new Map(candidate.units.map((u) => [u.unitId, u]));
|
|
264
|
+
assert.deepEqual(byId.get("v1")?.scores, [0.75, 0.75], "candidate bench carries val replicates for the gate");
|
|
265
|
+
assert.equal(candidate.verdict, "culled");
|
|
266
|
+
assert.ok((candidate.gateFailures ?? []).some((msg) => /minEffect|gain/.test(msg)), `cull reason: ${String(candidate.gateFailures)}`);
|
|
267
|
+
});
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
// Child-process entrypoint for the concurrent friction-append test (AC f).
|
|
2
|
+
// Stands in for two genomes running `abathur` at once — the one place under
|
|
3
|
+
// src/test where touching `process` is legitimate.
|
|
4
|
+
import { appendFriction } from "../../core/ledger.js";
|
|
5
|
+
const [configDir, source, countRaw] = process.argv.slice(2);
|
|
6
|
+
const count = Number(countRaw);
|
|
7
|
+
if (configDir === undefined || source === undefined || !Number.isInteger(count) || count < 0) {
|
|
8
|
+
process.stderr.write("usage: friction-writer <configDir> <source> <count>\n");
|
|
9
|
+
process.exit(2);
|
|
10
|
+
}
|
|
11
|
+
// 200 back-to-back acquisitions in one process; production appends one event
|
|
12
|
+
// per call on the default 15s budget, so the loop raises its own deadline.
|
|
13
|
+
for (let seq = 0; seq < count; seq += 1) {
|
|
14
|
+
appendFriction(configDir, { kind: "friction", data: { source, seq } }, { waitMs: 60_000 });
|
|
15
|
+
}
|
|
16
|
+
process.exit(0);
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
// Shared recorded fixtures for the historian grader tests (task 14). NOT a test
|
|
2
|
+
// file — importing this module registers no tests (unlike importing a .test.js).
|
|
3
|
+
export const FIXTURE5_PATHS = [
|
|
4
|
+
"_sandbox/index",
|
|
5
|
+
"_sandbox/llm-inference/rocm-tuning",
|
|
6
|
+
"_sandbox/mess/untitled",
|
|
7
|
+
"_sandbox/mess/gpu-notes",
|
|
8
|
+
"_sandbox/mess/gpu-stuff",
|
|
9
|
+
];
|
|
10
|
+
export const GOOD_PAGE = `# Qwen 27B Threading Findings
|
|
11
|
+
|
|
12
|
+
> Status: Active | Updated: 2026-09-10 | Scope: llama.cpp decode tuning on the rocm host
|
|
13
|
+
|
|
14
|
+
This page answers: which settings moved qwen-27b decode throughput.
|
|
15
|
+
|
|
16
|
+
## Findings
|
|
17
|
+
|
|
18
|
+
- \`HSA_OVERRIDE_GFX_VERSION=11.0.0\` plus 8 threads lifted decode from 47.4 to 53.7 tok/s.
|
|
19
|
+
|
|
20
|
+
## Method
|
|
21
|
+
|
|
22
|
+
Run under test: \`./llama-server -m qwen-27b.gguf --threads 8 --ctx-size 8192\`
|
|
23
|
+
|
|
24
|
+
## Related Pages
|
|
25
|
+
|
|
26
|
+
- [ROCm tuning](/_sandbox/llm-inference/rocm-tuning)
|
|
27
|
+
`;
|
|
28
|
+
export const GOOD_INCIDENT_PAGE = `# Wiki ES OOM 2026-08-28
|
|
29
|
+
|
|
30
|
+
> Status: Active | Updated: 2026-09-10 | Scope: 502/OOM incident on the wiki host
|
|
31
|
+
|
|
32
|
+
This page answers: what happened, why, and how it is prevented.
|
|
33
|
+
|
|
34
|
+
## Symptoms
|
|
35
|
+
|
|
36
|
+
- Wiki.js returned HTTP 502 for 19 minutes; users saw spinning search boxes.
|
|
37
|
+
|
|
38
|
+
## Root Cause
|
|
39
|
+
|
|
40
|
+
- Elasticsearch JVM heap was unbounded after the host RAM upgrade.
|
|
41
|
+
|
|
42
|
+
## Fix
|
|
43
|
+
|
|
44
|
+
- Capped the heap with \`ES_JAVA_OPTS=-Xms4g -Xmx4g\`; added a restart policy.
|
|
45
|
+
|
|
46
|
+
## Prevention
|
|
47
|
+
|
|
48
|
+
- Review heap caps after host-memory changes; alert on repeated restarts.
|
|
49
|
+
|
|
50
|
+
## Related Pages
|
|
51
|
+
|
|
52
|
+
- [ROCm tuning](/_sandbox/llm-inference/rocm-tuning)
|
|
53
|
+
`;
|
|
54
|
+
export const GOOD_FINAL = "已在 _sandbox/llm-inference/qwen27b-threading 创建调优结论页,链接到 _sandbox/llm-inference/rocm-tuning," +
|
|
55
|
+
"并更新了 _sandbox/index 的 LLM Inference 小节;首 token 没有正式测量,按要求不作为结论写入。";
|
|
56
|
+
export function goodCreated(over = {}) {
|
|
57
|
+
return {
|
|
58
|
+
path: "_sandbox/llm-inference/qwen27b-threading",
|
|
59
|
+
locale: "en",
|
|
60
|
+
title: "Qwen 27B Threading Findings",
|
|
61
|
+
content: GOOD_PAGE,
|
|
62
|
+
...over,
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
export function obs(over = {}) {
|
|
66
|
+
return {
|
|
67
|
+
scenarioNo: 1,
|
|
68
|
+
created: [goodCreated()],
|
|
69
|
+
updated: [],
|
|
70
|
+
moved: [],
|
|
71
|
+
deletedFixturePaths: [],
|
|
72
|
+
outside: { created: [], updated: [], deleted: [] },
|
|
73
|
+
indexUpdated: true,
|
|
74
|
+
indexContent: `## LLM Inference\n- [Qwen 27B Threading Findings](/_sandbox/llm-inference/qwen27b-threading) (Active)\n- [ROCm tuning](/_sandbox/llm-inference/rocm-tuning) (Active)`,
|
|
75
|
+
livePaths: [...FIXTURE5_PATHS, "_sandbox/llm-inference/qwen27b-threading"],
|
|
76
|
+
allPaths: [...FIXTURE5_PATHS, "_sandbox/llm-inference/qwen27b-threading", "infra/network"],
|
|
77
|
+
backlinkBodies: [],
|
|
78
|
+
finalMessage: GOOD_FINAL,
|
|
79
|
+
urlChecks: [],
|
|
80
|
+
...over,
|
|
81
|
+
};
|
|
82
|
+
}
|