opencode-longrun-harness 1.2.22
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/LICENSE +21 -0
- package/README.md +390 -0
- package/docs/V1.2.20_EVIDENCE.md +114 -0
- package/docs/V1.2.21_EVIDENCE.md +68 -0
- package/docs/V1.2.22_EVIDENCE.md +52 -0
- package/harness/commissioning/README.md +16 -0
- package/harness/commissioning/inspect-copied-run.mjs +25 -0
- package/harness/commissioning/verify-copied-case.mjs +35 -0
- package/harness/plugin/longrun.js +677 -0
- package/harness/src/cli.mjs +40 -0
- package/harness/src/controller.js +1413 -0
- package/harness/src/evidence.mjs +135 -0
- package/harness/src/execution.mjs +217 -0
- package/harness/src/executor.mjs +21 -0
- package/harness/src/install.mjs +435 -0
- package/harness/src/maintenance.mjs +257 -0
- package/harness/src/memory.mjs +472 -0
- package/harness/test/candidates.test.mjs +73 -0
- package/harness/test/checkpoint.test.mjs +65 -0
- package/harness/test/controller.test.mjs +230 -0
- package/harness/test/evidence.test.mjs +57 -0
- package/harness/test/fixtures/durable-host.mjs +27 -0
- package/harness/test/fixtures/example-app-run.json +1375 -0
- package/harness/test/fixtures/notes-budget-exhausted-run.json +2070 -0
- package/harness/test/fixtures/notes-premature-complete-run.json +1496 -0
- package/harness/test/fixtures/notes-recovery-run.json +622 -0
- package/harness/test/fixtures/presets-readout-run.json +825 -0
- package/harness/test/fixtures/routing-worker.mjs +35 -0
- package/harness/test/fixtures/vitest-failed-receipt.json +33 -0
- package/harness/test/helper.mjs +41 -0
- package/harness/test/install.test.mjs +117 -0
- package/harness/test/lifecycle.test.mjs +102 -0
- package/harness/test/maintenance.test.mjs +204 -0
- package/harness/test/memory.test.mjs +145 -0
- package/harness/test/negative-control.test.mjs +91 -0
- package/harness/test/plugin.test.mjs +169 -0
- package/harness/test/recovery-runner.test.mjs +435 -0
- package/harness/test/recovery.test.mjs +68 -0
- package/harness/test/repair-mechanics.test.mjs +122 -0
- package/harness/test/toolbehavior.test.mjs +75 -0
- package/harness/test/v121-commissioning.test.mjs +177 -0
- package/harness/test/v1210-deadline.test.mjs +134 -0
- package/harness/test/v1211-pause.test.mjs +81 -0
- package/harness/test/v1212-maintenance-pause.test.mjs +76 -0
- package/harness/test/v1213-readout.test.mjs +82 -0
- package/harness/test/v1214-durable.test.mjs +121 -0
- package/harness/test/v1215-guidance.test.mjs +57 -0
- package/harness/test/v1216-test-summary.test.mjs +39 -0
- package/harness/test/v1217-discovery.test.mjs +73 -0
- package/harness/test/v1218-completion-review.test.mjs +203 -0
- package/harness/test/v1219-budget-pause.test.mjs +134 -0
- package/harness/test/v122-lifecycle-resolver.test.mjs +218 -0
- package/harness/test/v1220-budget-amendment.test.mjs +343 -0
- package/harness/test/v1221-negative-fixture-anchor.test.mjs +65 -0
- package/harness/test/v1222-default-evidence-class.test.mjs +75 -0
- package/harness/test/v123-plugin-e2e.test.mjs +120 -0
- package/harness/test/v123-receipt-model.test.mjs +185 -0
- package/harness/test/v124-canonical.test.mjs +147 -0
- package/harness/test/v124-installed.test.mjs +48 -0
- package/harness/test/v125-stability.test.mjs +183 -0
- package/harness/test/v126-execution.test.mjs +183 -0
- package/harness/test/v127-reconciliation.test.mjs +139 -0
- package/harness/test/v128-compaction.test.mjs +156 -0
- package/harness/test/v129-routing.test.mjs +165 -0
- package/harness/tools/audit-receipts.mjs +121 -0
- package/harness/tools/recovery-runner.mjs +499 -0
- package/package.json +49 -0
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# Commissioning case scripts
|
|
2
|
+
|
|
3
|
+
**These are not generic harness tools.** They are the concrete scripts used for one commissioning case,
|
|
4
|
+
kept as evidence of how that case was run. They contain values specific to that case — a particular
|
|
5
|
+
state hash, receipt counts, a loss figure, fixed ports and one application's workspace layout.
|
|
6
|
+
|
|
7
|
+
| Script | What it does | Coupled to |
|
|
8
|
+
| --- | --- | --- |
|
|
9
|
+
| `verify-copied-case.mjs` | Runs declared engineering checks against a physically separate copy of an application, then asserts the run record survived intact | One case's state hash, `receipts.length === 39`, `currentLoss === 10/11`, fixed check ids, ports 5199/8787 and 55199/58787, `packages/web/vite.config.ts`, `scripts/verify-all.sh` |
|
|
10
|
+
| `inspect-copied-run.mjs` | Projects a copied run's state for inspection without touching live evidence | A case's directory layout |
|
|
11
|
+
|
|
12
|
+
The generic, reusable parts of this harness live in `harness/src/` (the engine) and `harness/tools/`
|
|
13
|
+
(the operator utilities). If you want to reuse the copying technique on another project, the things to
|
|
14
|
+
parameterise are: the case root, the workspace layout paths, the port pairs to remap, the log path, the
|
|
15
|
+
check ids, and the expected counts — everything currently written as a literal in
|
|
16
|
+
`verify-copied-case.mjs`.
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
// Offline tool-factory projection only; never OpenCode live evidence.
|
|
2
|
+
import fs from 'node:fs';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
import crypto from 'node:crypto';
|
|
5
|
+
import { pathToFileURL } from 'node:url';
|
|
6
|
+
const [caseRoot, sourceRoot, label = 'before'] = process.argv.slice(2);
|
|
7
|
+
if (!caseRoot || !sourceRoot || !fs.existsSync(path.join(caseRoot,'case.json'))) throw Error('An isolated snapshot case is required');
|
|
8
|
+
const game = path.join(caseRoot, 'game'), sd = path.join(caseRoot, `state-${label}`);
|
|
9
|
+
process.env.LONGRUN_TEST = '1'; process.env.LONGRUN_STATE_DIR = sd;
|
|
10
|
+
process.env.LONGRUN_CONTROLLER_FILE = path.join(sourceRoot,'harness/src/controller.js');
|
|
11
|
+
fs.mkdirSync(sd,{recursive:true});
|
|
12
|
+
const C = await import(pathToFileURL(process.env.LONGRUN_CONTROLLER_FILE));
|
|
13
|
+
const original = JSON.parse(fs.readFileSync(path.join(caseRoot,'state-original/state/982e1f79cff6bc710465d3704c7a947e/run.json')));
|
|
14
|
+
const run = structuredClone(original); run.directory = game;
|
|
15
|
+
const key = C.stateKey(C.projectIdentity(game),run.runId), store = new C.Store(sd);
|
|
16
|
+
store.writeJSON(key,'run.json',run);
|
|
17
|
+
fs.writeFileSync(path.join(sd,'runs.json'),JSON.stringify({offline:{runKey:key,directory:game,runId:run.runId,checkCatalogue:run.checkCatalogue}}));
|
|
18
|
+
const plugin = await import(pathToFileURL(path.join(sourceRoot,'harness/plugin/longrun.js')));
|
|
19
|
+
const t = (await plugin.default.server({client:null})).tool;
|
|
20
|
+
const ctx = {sessionID:'offline',directory:game,worktree:game};
|
|
21
|
+
const read = async action => {const v=await t.longrun.execute({action,runId:run.runId},ctx);try{return JSON.parse(v)}catch{return v}};
|
|
22
|
+
const fp=C.sourceFingerprint(game);
|
|
23
|
+
const report = {kind:'OFFLINE_TOOL_FACTORY',version:C.LIFECYCLE_SCHEMA_VERSION,at:new Date().toISOString(),runId:run.runId,rawState:run.status,fingerprint:fp,contractHash:crypto.createHash('sha256').update(JSON.stringify(run.contract)).digest('hex'),budget:run.budget,receiptCount:run.receipts.length,missingFingerprints:run.receipts.filter(x=>!x.sourceFingerprint).length,status:await read('status'),recovery:await read('resume-context'),verify:await read('verify'),memory:await read('memory_status'),complete:await read('complete'),checks:Object.keys(run.checkCatalogue).map(checkId=>({checkId,...C.effectiveReceipt(run,checkId,fp.hash)}))};
|
|
24
|
+
fs.writeFileSync(path.join(caseRoot,`${label}.json`),JSON.stringify(report,null,2));
|
|
25
|
+
console.log(JSON.stringify({report:path.join(caseRoot,`${label}.json`),fingerprint:fp,status:report.status,recovery:report.recovery,memory:report.memory,missingFingerprints:report.missingFingerprints},null,2));
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
// Runs only the declared engineering commands, against a physically separate application copy.
|
|
2
|
+
import fs from 'node:fs';import path from 'node:path';import assert from 'node:assert/strict';import net from 'node:net';
|
|
3
|
+
const caseRoot=process.argv[2];assert.ok(caseRoot && fs.existsSync(path.join(caseRoot,'case.json')));
|
|
4
|
+
const game=path.join(caseRoot,'game-verify'),sd=path.join(caseRoot,'state-verify'), tmp=path.join(caseRoot,'temp');
|
|
5
|
+
assert.ok(!fs.existsSync(game),'verification copy already exists; use a new isolated case');
|
|
6
|
+
fs.cpSync(path.join(caseRoot,'game'),game,{recursive:true,verbatimSymlinks:true});fs.mkdirSync(sd);fs.mkdirSync(tmp);
|
|
7
|
+
for(const p of [55199,58787]) await new Promise((resolve,reject)=>{const s=net.createServer();s.once('error',reject);s.listen(p,'127.0.0.1',()=>s.close(resolve));});
|
|
8
|
+
const config=path.join(game,'playwright.config.ts'),vite=path.join(game,'packages/web/vite.config.ts'),script=path.join(game,'scripts/verify-all.sh');
|
|
9
|
+
fs.writeFileSync(config,fs.readFileSync(config,'utf8').replaceAll('5199','55199').replaceAll('8787','58787').replaceAll('reuseExistingServer: true','reuseExistingServer: false'));
|
|
10
|
+
fs.writeFileSync(vite,fs.readFileSync(vite,'utf8').replaceAll('8787','58787'));
|
|
11
|
+
fs.writeFileSync(script,fs.readFileSync(script,'utf8').replace('/tmp/sb-build.log',path.join(caseRoot,'sb-build.log')).replace('set -u',`set -u\nexec > '${path.join(caseRoot,'verify-all.log')}' 2>&1`));
|
|
12
|
+
process.env.LONGRUN_TEST='1';process.env.LONGRUN_STATE_DIR=sd;process.env.LONGRUN_CONTROLLER_FILE=path.resolve('harness/src/controller.js');
|
|
13
|
+
process.env.SB_PORT='58787';process.env.SB_DB=path.join(game,'.longrun','test.sqlite');process.env.TMPDIR=tmp;
|
|
14
|
+
const C=await import('../src/controller.js');const P=await import('../plugin/longrun.js');
|
|
15
|
+
const original=JSON.parse(fs.readFileSync(path.join(caseRoot,'state-original/state/982e1f79cff6bc710465d3704c7a947e/run.json')));
|
|
16
|
+
const run=structuredClone(original);run.directory=game;
|
|
17
|
+
const key=C.stateKey(C.projectIdentity(game),run.runId),store=new C.Store(sd);store.writeJSON(key,'run.json',run);
|
|
18
|
+
fs.writeFileSync(path.join(sd,'runs.json'),JSON.stringify({offline:{runKey:key,directory:game,runId:run.runId,checkCatalogue:run.checkCatalogue}}));
|
|
19
|
+
const t=(await P.default.server({client:null})).tool,ctx={sessionID:'offline',directory:game,worktree:game};
|
|
20
|
+
const read=async action=>JSON.parse(await t.longrun.execute({action,runId:run.runId},ctx));
|
|
21
|
+
const before=await read('status');const resumed=await read('resume');assert.equal(resumed.runId,run.runId);
|
|
22
|
+
const results=[];
|
|
23
|
+
for(const checkId of ['c-typecheck','c-build','c-verify-all']) {
|
|
24
|
+
console.log(`Running copied ${checkId}`);
|
|
25
|
+
const result=JSON.parse(await t.longrun_verify.execute({checkId,runId:run.runId,evidenceClass:'INTEGRATION'},ctx));results.push({checkId,...result});
|
|
26
|
+
console.log(JSON.stringify(results.at(-1)));
|
|
27
|
+
}
|
|
28
|
+
const after=await read('status'),completion=await read('complete'),saved=store.readJSON(key,'run.json');
|
|
29
|
+
assert.deepEqual(saved.contract,original.contract);assert.deepEqual(saved.budget,original.budget);assert.deepEqual(saved.state.candidates.slice(0,13),original.state.candidates);
|
|
30
|
+
assert.deepEqual(saved.receipts.slice(0,36),original.receipts);assert.equal(saved.receipts.length,39);
|
|
31
|
+
assert.equal(completion.complete,false,'other missing/stale evidence must continue blocking');
|
|
32
|
+
assert.deepEqual(after.hardGateBlockers,[]);assert.equal(after.currentLoss,10/11);
|
|
33
|
+
const report={kind:'OFFLINE_TOOL_FACTORY_REAL_COPIED_APPLICATION',adaptations:['copied directory and routing key','Playwright ports 55199/58787 and reuseExistingServer false','Vite proxy port 58787','SB_DB inside copy/.longrun/test.sqlite','temporary output/log paths'],before,results,after,completion};
|
|
34
|
+
fs.writeFileSync(path.join(caseRoot,'three-gates.json'),JSON.stringify(report,null,2));
|
|
35
|
+
console.log(JSON.stringify({report:path.join(caseRoot,'three-gates.json'),candidateCount:after.candidateCount,loss:after.currentLoss,bestLoss:after.bestLoss,remaining:after.remaining,complete:completion.complete}));
|