mirror of
https://github.com/mvschwarz/openrig.git
synced 2026-11-07 05:56:21 -10:00
feat(testbed): 51-04 stub-asset trio + census list — unblock the host build (L0.2)
Resolves the VM-side half of the stub census slot: authors the concrete zero-token
stub payload and populates docker/testbed/stub-assets.list (was empty => the build
verb refused, L0.2 FAIL). Base-image digest slot stays host-resolved (L0.1).
- docker/testbed/stub-assets/{rig.yaml, culture.md, agents/worker/agent.yaml}: a
runtime:stub, one-pod/one-seat topology. Shape grounded on the PROVEN 51-02 runner
fixture (test/fixtures/scenarios/topo-stub-baton.yaml) — member-key + top-key parity
MATCH — which the scenario runner settles headlessly in real tmux with zero LLM
tokens; both YAML files parse clean. Not guessed from memory (L3.3 grounding).
- stub-assets.list: exactly the trio's repo-relative paths (the census scope the
manifest hashes + the build verb stages; census-scope-match-code-path).
RED-first: scripts/testbed-stub-assets.test.mjs pins the L0.2 check (census non-empty,
names exactly the trio, every file exists, receipt each 64-hex) — RED on the empty
list (deriveStubAssetsHash loud-fails), GREEN after. 3/3 + full testbed suites 46/46.
L0.2 receipt stubAssetsHash: 3b24066fc0cc2e0a12ce03b32c7a18c170e62c394add583ffef0c105d8bd8349
Co-Authored-By: Claude Opus 4.8 <[email protected]>
This commit is contained in:
1 parent
b56936a793
commit
261f4fcda2
5 files changed
+110
-4
No files matched your search
@@ -5,7 +5,10 @@
|
||||
#
|
||||
# One repo-relative path per line; comments (#) and blank lines are ignored.
|
||||
#
|
||||
# UNRESOLVED SLOT — the concrete zero-token stub payload (the container-local stub rig.yaml + its
|
||||
# agent fixture + culture.md that boot a stub topology for the L3 daemon-in-container leg) is
|
||||
# finalized in the L3 stub-boot runbook increment, where the exact set is grounded rather than
|
||||
# guessed. Until populated, the build verb REFUSES (deriveStubAssetsHash loud-fails on an empty set).
|
||||
# RESOLVED (2026-08-06 VM-side) — the concrete zero-token stub payload: a runtime:stub rig.yaml +
|
||||
# its agent fixture + culture.md that boot a settled topology for the L3 daemon-in-container leg.
|
||||
# Shape grounded on the proven 51-02 runner fixture (test/fixtures/scenarios/topo-stub-baton.yaml),
|
||||
# not guessed. The base-image digest slot (docker/testbed/base-image) remains host-resolved (L0.1).
|
||||
docker/testbed/stub-assets/rig.yaml
|
||||
docker/testbed/stub-assets/culture.md
|
||||
docker/testbed/stub-assets/agents/worker/agent.yaml
|
||||
@@ -0,0 +1,8 @@
|
||||
name: worker
|
||||
version: "1.0.0"
|
||||
resources:
|
||||
skills: []
|
||||
profiles:
|
||||
default:
|
||||
uses:
|
||||
skills: []
|
||||
@@ -0,0 +1,6 @@
|
||||
# testbed-stub (51-04 container stub payload)
|
||||
|
||||
A minimal zero-token stub-runtime rig baked into the openrig-testbed image so the L3
|
||||
daemon-in-container leg can `rig up` a settled topology without a live LLM. The single
|
||||
seat runs the deterministic 51-01 stub harness. Not a product rig — it exists only to
|
||||
prove the container boots a real daemon and settles a real topology headlessly.
|
||||
@@ -0,0 +1,23 @@
|
||||
# 51-04 testbed image — the container-local zero-token stub topology (plan §1 layer 4).
|
||||
#
|
||||
# One pod (dev) with one stub seat (worker). The stub runtime is the deterministic 51-01
|
||||
# node-script harness, so this topology stands up headlessly in real tmux INSIDE the
|
||||
# testbed container without a live LLM (the L3 daemon-in-container leg: `rig up` settles
|
||||
# it, zero tokens by construction). Shape mirrors the proven 51-02 runner fixture
|
||||
# (test/fixtures/scenarios/topo-stub-baton.yaml), which the scenario runner settles
|
||||
# headlessly end-to-end — not a product rig.
|
||||
#
|
||||
# Canonical session of the seat = <pod>-<member>@<name> = dev-worker@testbed-stub.
|
||||
version: "0.2"
|
||||
name: testbed-stub
|
||||
culture_file: culture.md
|
||||
pods:
|
||||
- id: dev
|
||||
label: Dev
|
||||
members:
|
||||
- id: worker
|
||||
agent_ref: "local:agents/worker"
|
||||
profile: default
|
||||
runtime: stub
|
||||
cwd: .
|
||||
edges: []
|
||||
@@ -0,0 +1,66 @@
|
||||
import { test } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { readFileSync, existsSync } from "node:fs";
|
||||
import { dirname, resolve, join } from "node:path";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { deriveStubAssetsHash } from "./testbed-build-inputs.mjs";
|
||||
|
||||
// 51-04 stub-asset increment — the L0.2 census check as a durable pin. The build verb
|
||||
// (scripts/build-testbed-image.sh) stages EXACTLY the paths named in
|
||||
// docker/testbed/stub-assets.list and feeds that same list to the manifest census
|
||||
// (census-scope-match-code-path). An EMPTY census is an L0.2 FAIL — deriveStubAssetsHash
|
||||
// loud-fails and the build verb refuses. This suite pins that the shipped list names the
|
||||
// concrete zero-token stub trio (a runtime:stub rig.yaml + its agent fixture + culture.md)
|
||||
// and that the receipt is well-formed, so the list can never silently drift back to empty
|
||||
// or reference a missing file.
|
||||
|
||||
const REPO_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "..");
|
||||
const LIST = join(REPO_ROOT, "docker/testbed/stub-assets.list");
|
||||
|
||||
// The SAME comment-tolerant parse the build verb feeds the census (strip #.* , trim, drop
|
||||
// blanks — build-testbed-image.sh's node -e / while-read), so the test's census == the
|
||||
// build's census exactly.
|
||||
function parseCensus(path) {
|
||||
return readFileSync(path, "utf8")
|
||||
.split("\n")
|
||||
.map((s) => s.replace(/#.*/, "").trim())
|
||||
.filter(Boolean);
|
||||
}
|
||||
|
||||
// The concrete stub trio (sorted — deriveStubAssetsHash sorts its receipt by POSIX path).
|
||||
const EXPECTED = [
|
||||
"docker/testbed/stub-assets/agents/worker/agent.yaml",
|
||||
"docker/testbed/stub-assets/culture.md",
|
||||
"docker/testbed/stub-assets/rig.yaml",
|
||||
];
|
||||
|
||||
test("stub-assets.list census is populated (empty census = L0.2 FAIL, build verb refuses)", () => {
|
||||
const files = parseCensus(LIST);
|
||||
assert.ok(files.length > 0, "stub-assets.list must be populated (an empty census fails L0.2)");
|
||||
});
|
||||
|
||||
test("census names EXACTLY the stub trio and every listed file exists", () => {
|
||||
const files = parseCensus(LIST);
|
||||
assert.deepEqual(
|
||||
[...files].sort(),
|
||||
EXPECTED,
|
||||
"census must name exactly the stub trio (rig.yaml + culture.md + agent fixture)",
|
||||
);
|
||||
for (const rel of files) {
|
||||
assert.ok(existsSync(join(REPO_ROOT, rel)), `listed stub asset must exist: ${rel}`);
|
||||
}
|
||||
});
|
||||
|
||||
test("L0.2 receipt: deriveStubAssetsHash lists exactly the trio, each with a 64-hex sha256", () => {
|
||||
const files = parseCensus(LIST);
|
||||
const { hash, receipt } = deriveStubAssetsHash(REPO_ROOT, files);
|
||||
assert.match(hash, /^[0-9a-f]{64}$/, "census hash is a 64-hex sha256");
|
||||
assert.deepEqual(
|
||||
receipt.files.map((f) => f.path).sort(),
|
||||
EXPECTED,
|
||||
"receipt names exactly the trio",
|
||||
);
|
||||
for (const f of receipt.files) {
|
||||
assert.match(f.sha256, /^[0-9a-f]{64}$/, `each receipt entry carries a 64-hex digest: ${f.path}`);
|
||||
}
|
||||
});
|
||||
Reference in new issue
Block a user