cast has a drill gate and a record format, but no instrument — nine legs against two live Coolify instances, run from memory #141
Labels
No labels
blocked
blocker:ci-red
blocker:conflict
blocker:drill-pending
blocker:unrequested
bug
claimed
documentation
enhancement
epic
merge-next
needs-triage
ready
release
scope:apply
scope:capture
scope:coolify-api
scope:fleet
scope:manifest
scope:secrets
stale
state:addressing
state:bots-reviewing
state:building
state:needs-human
No milestone
No project
No assignees
2 participants
Notifications
Due date
No due date set.
Dependencies
No dependencies set.
Reference: heavy-duty/cast#141
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
cast has the gate and the record format. It has no instrument, and
drills/README.mdstates the position deliberately:Agreed on the framing, and the ordering was right — the gate is why releases stopped shipping unproven. This issue is the first problem.
What "by hand" currently means
CONTRIBUTING requires nine legs before a release PR can be handed over:
against two live Coolify instances. None of it is scripted.
scripts/contains one file,restore-db.sh, which is an operator tool and not a drill leg.drills/holds onlyREADME.md— "cast has recorded no drill runs."So the release ceremony asks an operator to stand up two Coolify instances, drive nine legs in order against them, remember what each one asserts, and write down numbers — from memory, months apart, with no previous record to imitate. The first person to do it will be inventing the procedure, and the second will be inventing a different one.
cast's test suite makes the gap sharper rather than softer. There are 40
.test.tsfiles, 167describeblocks,wire.test.tsalone is 64KB — and none of it touches a real Coolify.test/live-lookup.test.tsbuilds a fake that answersGET /projects. That is the correct design for a unit suite, and it is exactly why the drill matters: everything that is mocked is a claim about Coolify's behaviour that the suite cannot check. box'sdrill/README.mdmakes this argument better than I can, and it transfers directly:Substitute Coolify for Incus and it is cast's situation precisely, with 40 files of stubs standing in for one live instance.
The legs that only a live run can prove
Two invariants in cast's README have no possible unit test, and they are the ones a promotion drill exists to assert:
diffleg — a secondapplymust yield zero changes, asserted mechanically. The never-deletes half is the read-only guard leg.src/reserved.ts) andsmoke's probe key.src/smoke.tsis described in-tree as "the one thing in cast that mutates a live resource purely to learn something" — it writesINFRA_SMOKE_KEEP. The drill has to assert that key is cleaned up and never collides with a reserved name. A mocked Coolify will agree with whatever the code believes about this; a real one will not.destroyhas the same character: it must assert absence afterwards, not just that the delete call returned 200. box'swipe.shgets this right and says why — "assert the ABSENCE, don't trust the removals' exit codes" — and it is the single most portable idea in box's harness.What to build
drill/drill.sh(or a TS equivalent — see below), on box's shape:The reporting verbs —
drill/drill.sh:52-58, five one-liners the whole 997-line harness rests on.pass/failcounters, afindingsarray,phasebanners, andok/no/notethat always return 0 so the body can be a flat sequence ofprobe && ok … || no …. Exit 0 ifffailis 0.Continue on failure. box's header is explicit that this is not a style choice: "NOT -e: a failing check is data, not a crash." Nine legs against live infrastructure, aborting on the first problem, means one finding per afternoon of setup.
Assert the refs that are actually under test, the way box re-reads
INSTALLED_FROMand dies on a mismatch. A drill that believes it exercised the candidate and gotmainhas proven nothing about the combination.A record emitter in box#152's shape, so the three repos' records reconcile on the shared run ID instead of three hand-invented ones.
Language. box's harness is bash because box is bash. cast is TypeScript with vitest and a
globalSetup, and it may well be that cast's drill should be a vitest suite gated behind aCAST_DRILL_LIVE=1env var, reusing the existing helpers and getting the leg structure for free. I lean that way but do not have a strong view; worth deciding early since it determines everything else. What must not change either way is the contract — nine legs, continue-on-failure, counted, record emitted, loud skips.Loud skips. A leg that cannot run because an instance is unreachable must not be silently absent from the count. Same defect class as heavy-duty/box#153.
Blocker
The parameterised compose file that stood up instance B for the July drill is not in this repo. CONTRIBUTING references it —
— and cast's legs are described as "the least coupled of the three" specifically because of it. But an operator reading CONTRIBUTING today cannot follow that sentence to anything. Filed separately as heavy-duty/cast#140, and it is the natural first commit: the harness has nothing to run against until instance B is reproducible.
Scope
One script (or suite), nine legs, the verbs, ref assertion, record emitter. Not box's whole
drill/directory —doctor.shandwipe.shaddress a persistent host stack that cast does not create, andmultiuser.shhas no analogue.Not a cross-repo drill framework. heavy-duty/rig#105 is the same shape of issue for rig; the three should converge by copying box's output format, not by sharing code — they assert three different things (box: isolation, rig: convergence, cast: promotion) and the per-repo independence is load-bearing, for the reasons
drill-recorded.shalready sets out at length about cross-repo lookups degrading to "pass".Related: heavy-duty/box#152 (record emitter, defines the shape), heavy-duty/box#153 (skipped legs must not read as passes).
Triage — decisions (2026-08-17)
CAST_DRILL_LIVE=1, per the lean stated above — cast is TypeScript, the leg structure and live-client helpers already exist in the suite's shape, and a second toolchain in-tree is a cost with no offsetting assertion. The contract is unchanged and is the review spec: nine legs, continue-on-failure, pass/fail counted, candidate refs asserted, record emitted in box#152's shape, loud skips.readywhen #140 lands.Triage: the open language question is settled (vitest suite gated behind
CAST_DRILL_LIVE=1) and the dependency is recorded — blocked by #140. Details appended to the body; flips toreadywhen #140 lands.