A workflow that merges edge into main on the forced-wipe weekend with no approval: hourly from Thursday 17:00 UTC to Sunday 23:00 UTC, through five gates in order, then a merge commit made with RUNICNPC_TOKEN (that step only) so release.yml cuts the release. 1. Rust's public build changed: dated the wipe day and naming the same depot manifest as staging (the staging build was promoted to live). 2. Oxide's public release caught up, RunicNPC compiles, the swap's fields match. 3. A Carbon boot on public Rust: rust-carbon proves the public manifest is installed, and rnpc.status says framework carbon, navmesh ready, missing=- added=-. 4. The drill's live half on public Rust with edge's plugin and harness. 5. PR checks, run fresh on the cutover PR's head just before the merge. Gates are kept in drill.json for one (wipe, public manifest, edge commit); any change runs them again. A failed Carbon or live attempt cools down for 3 hours. The merge is pinned to the commit the gates ran on. The cutover PR lists every `Closes #N` from the PRs merged into edge since the last cutover, so those issues close on the merge; afterwards each gets the release version. One comment a day while a gate blocks; at the deadline it hands back. The copy on main is only the trigger and step wiring; every step runs edge's code (a dry dispatch may name another branch, code_ref). Dispatches are dry unless dry_run is exactly false. rust-carbon is started again if the job left it stopped. drill.js only gains exports; its report already kept other keys, now tested. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01E14m6SuuY6i1vASFeGDBeY
88 lines
4.6 KiB
JavaScript
88 lines
4.6 KiB
JavaScript
// The drill's pure parts: how it decides the harness has stopped (issue #20), and what it starts clean.
|
|
// node --test tools/drill/drill.test.js
|
|
|
|
const test = require('node:test')
|
|
const assert = require('node:assert')
|
|
const { harnessProgress, unfinishedFinding, FRESH_DATA, KITS_FILE, harnessKits, HARNESS_STALL_MS } = require('./drill.js')
|
|
|
|
const MIN = 60000
|
|
const T0 = 1000000
|
|
|
|
test('a harness that keeps writing is running, and its clock restarts on each new line', () => {
|
|
let v = harnessProgress(['# run all'], { count: 0, at: T0 }, T0 + 30000, HARNESS_STALL_MS)
|
|
assert.equal(v.state, 'running')
|
|
assert.deepEqual(v.seen, { count: 1, at: T0 + 30000 })
|
|
// Four silent minutes, then a line: still running, and the clock restarts.
|
|
v = harnessProgress(['# run all', 'a: PASS '], v.seen, T0 + 30000 + 4 * MIN, HARNESS_STALL_MS)
|
|
assert.equal(v.state, 'running')
|
|
assert.equal(v.seen.at, T0 + 30000 + 4 * MIN)
|
|
})
|
|
|
|
test('no new line for five minutes is a stall; four minutes and a bit is not', () => {
|
|
const seen = { count: 2, at: T0 }
|
|
const lines = ['# run all', 'a: PASS ']
|
|
assert.equal(harnessProgress(lines, seen, T0 + 5 * MIN - 1, HARNESS_STALL_MS).state, 'running')
|
|
assert.equal(harnessProgress(lines, seen, T0 + 5 * MIN, HARNESS_STALL_MS).state, 'stalled')
|
|
})
|
|
|
|
test('a harness that never wrote a line stalls from when the wait began', () => {
|
|
assert.equal(harnessProgress([], { count: 0, at: T0 }, T0 + 5 * MIN, HARNESS_STALL_MS).state, 'stalled')
|
|
})
|
|
|
|
test('done wins, even when the file has been still for a long time', () => {
|
|
const lines = ['# run all', '# done all: 10 pass, 1 fail']
|
|
assert.equal(harnessProgress(lines, { count: 2, at: T0 }, T0 + 30 * MIN, HARNESS_STALL_MS).state, 'done')
|
|
})
|
|
|
|
test('the stall finding names the last line and the counts', () => {
|
|
const lines = ['# run all', 'place.set.each: FAIL position: off the navmesh', 'place.spawned: FAIL each 0, group 3']
|
|
const f = unfinishedFinding({ pass: 40, fail: 2 }, lines, 'stalled')
|
|
assert.equal(f, 'The harness stopped after `place.spawned: FAIL each 0, group 3` (no output for 5 minutes; 40 pass, 2 fail so far).')
|
|
})
|
|
|
|
test('a backtick in the last line cannot break the issue body, and a long line is cut', () => {
|
|
const f = unfinishedFinding({ pass: 0, fail: 0 }, ['x`y' + 'z'.repeat(500)], 'stalled')
|
|
assert.equal(f.split('`').length, 3)
|
|
assert.ok(f.length < 330)
|
|
assert.match(unfinishedFinding({ pass: 0, fail: 0 }, [], 'stalled'), /stopped after `no line at all`/)
|
|
})
|
|
|
|
test('a capped wait keeps the 45-minute message', () => {
|
|
assert.equal(unfinishedFinding({ pass: 3, fail: 1 }, ['# run all'], 'capped'), 'The harness did not finish in 45 minutes (3 pass, 1 fail so far).')
|
|
})
|
|
|
|
test('the clean start deletes files only: every path ends in .json and none is a directory', () => {
|
|
assert.ok(FRESH_DATA.includes('/oxide/data/RunicNPC/state.json'))
|
|
for (const f of FRESH_DATA) assert.match(f, /^\/oxide\/data\/RunicNPC\/[a-z_]+\.json$/)
|
|
})
|
|
|
|
test("Kits' file is replaced, not deleted, by one holding only the harness's kit (#22)", () => {
|
|
assert.ok(!FRESH_DATA.includes(KITS_FILE))
|
|
assert.equal(KITS_FILE, '/oxide/data/Kits/kits_data.json')
|
|
const kits = harnessKits()._kits
|
|
assert.deepEqual(Object.keys(kits), ['rnhrevolver'])
|
|
assert.equal(kits.rnhrevolver.Name, 'rnhrevolver')
|
|
assert.equal(kits.rnhrevolver.IsHidden, true)
|
|
assert.deepEqual(kits.rnhrevolver.BeltItems.map((i) => i.Shortname), ['pistol.revolver'])
|
|
})
|
|
|
|
test('report keeps the state keys it does not own (the cutover, D332) and writes its own', async () => {
|
|
const fs = require('fs')
|
|
const os = require('os')
|
|
const path = require('path')
|
|
const { report } = require('./drill.js')
|
|
delete process.env.GITHUB_STEP_SUMMARY
|
|
const dir = fs.mkdtempSync(path.join(os.tmpdir(), 'drill-report-'))
|
|
const cutover = { wipe: '2026-11-05', manifest: '1', gates: { public: { at: '2026-11-05T17:11:00.000Z' } }, pr: 7 }
|
|
fs.writeFileSync(path.join(dir, 'drill.json'), JSON.stringify({ cutover }))
|
|
const stat = { branch: 'staging', manifest: '42', manifestDate: '10/31/2026 11:48:58', managed: 'ok', oxideBehind: 0, fields: 'same', compile: 'ok' }
|
|
fs.writeFileSync(path.join(dir, 'static.json'), JSON.stringify(stat))
|
|
fs.writeFileSync(path.join(dir, 'live.json'), JSON.stringify({ branch: 'staging', ran: true, findings: [], harness: { done: true, pass: 3, fail: 0, failed: [] } }))
|
|
await report({ state: path.join(dir, 'drill.json'), work: dir })
|
|
const after = JSON.parse(fs.readFileSync(path.join(dir, 'drill.json'), 'utf8'))
|
|
assert.deepEqual(after.cutover, cutover)
|
|
assert.equal(after.checked.manifest, '42')
|
|
assert.equal(after.lastRun.outcome, 'pass')
|
|
fs.rmSync(dir, { recursive: true, force: true })
|
|
})
|