ReviewOS

also looking at this

stacks/drone

test: exercise full host filesystem recovery

#58
Merged chris wants to merge codex/runtime-full-volume-recovery into main
13 files +644 -5

Review threads live on the whole diff, not on one commit, so none are shown here - a thread's line means something in the branch's final form, and painting it into an intermediate step would put it on code it is not about.

docs/runtime-updates.mdmodified+4-2
Changes to docs/runtime-updates.md
@@ -182,8 +182,10 @@ cuts do not cover every write/sync boundary or actual physical power loss.
182182The metadata format is bounded to 256 attempts, 10,000 metadata advances and a 128 MiB
183183state database. Each attempt allows 64 inputs, 32 MiB of metadata and 512 KiB per
184184file. Exhaustion fails closed; history is not automatically deleted or compacted.
185Capacity migration, the full storage-fault campaign, authenticated repair/key
186revocation, durable slot coordination and session-bound health remain in #42.
185Capacity migration, the full storage-fault campaign and authenticated repair/key
186revocation remain in #42. [Owned host slots](#owned-host-slots-and-observed-health)
187below add durable host roles and observed boot/session health. Physical board
188selection, boot health and recovery still require their separate qualification.
187189
188190## Protected signed-image preparation
189191
tests/fixtures/updates/host-volume-full.tsmodified+4-3
Changes to tests/fixtures/updates/host-volume-full.ts
@@ -168,8 +168,9 @@ export async function qualifyHostFullVolume(directory: string) {
168168 assert.ok(rejection.errorCodes.includes('ENOSPC') && rejection.errorCodes.includes('SQLITE_FULL'))
169169 assert.ok(Array.isArray(rejection.phases) && rejection.phases.includes('slot-selected'))
170170 assert.ok(!rejection.phases.includes('slot-image-written') && !rejection.phases.includes('runtime-started') && !rejection.phases.includes('runtime-input-committed'))
171 assert.equal(checkedVolume(volume).freeBytes, 0)
172 await writeFile(join(directory, 'full-volume.json'), JSON.stringify(checkedVolume(volume), null, 2))
171 const fullVolume = checkedVolume(volume)
172 assert.equal(fullVolume.freeBytes, 0)
173 await writeFile(join(directory, 'full-filesystem.json'), JSON.stringify(fullVolume, null, 2))
173174 await archiveVolume(volume, join(directory, 'full-volume'))
174175 await unlink(join(volume, 'filler.bin'))
175176 const failed = await inspectRuntimeFirmwareSlots(reference)
@@ -207,7 +208,7 @@ export async function qualifyHostFullVolume(directory: string) {
207208 assert.notEqual(fresh.knownGood!.health!.sessionId, good.knownGood!.health!.sessionId)
208209 assert.deepEqual(await readFile(partialPath), partial); assert.deepEqual(await readFile(previousImage), image)
209210 assert.deepEqual(await readFile(join(reference.directory, 'host-slots', fresh.knownGood!.id, 'image.bin')), image)
210 const result = { evidence: 'host-actual-enospc-v1', filesystemType: 'tmpfs', filesystemBytes: mounted.capacityBytes, freeBytesAfterFailure: 0, sourceRevision, authorizedImageBytes: image.length, authorizedImageSha256: signed.imageHash, partialImageBytes: partial.length, partialImageSha256: digest(partial), errorCodes: rejection.errorCodes, interruptedPhase: failed.trial!.phase, interruptedAttempt: failed.store.attempt!.phase, acceptedRootVersion: version('root'), acceptedTargetsVersion: version('targets'), releaseFloor: fresh.store.releaseFloor, abandonedPhase: abandoned.trial!.phase, previousSlotId: good.knownGood!.id, failedSlotId: failed.trial!.id, confirmedSlotId: fresh.knownGood!.id, previousBootId: good.knownGood!.health!.bootId, freshBootId: fresh.knownGood!.health!.bootId, physicalDeploymentAllowed: fresh.physicalDeploymentAllowed }
211 const result = { evidence: 'host-actual-enospc-v1', filesystemType: 'tmpfs', filesystemBytes: mounted.capacityBytes, freeBytesAfterFailure: fullVolume.freeBytes, sourceRevision, authorizedImageBytes: image.length, authorizedImageSha256: signed.imageHash, partialImageBytes: partial.length, partialImageSha256: digest(partial), errorCodes: rejection.errorCodes, interruptedPhase: failed.trial!.phase, interruptedAttempt: failed.store.attempt!.phase, acceptedRootVersion: version('root'), acceptedTargetsVersion: version('targets'), releaseFloor: fresh.store.releaseFloor, abandonedPhase: abandoned.trial!.phase, previousSlotId: good.knownGood!.id, failedSlotId: failed.trial!.id, confirmedSlotId: fresh.knownGood!.id, previousBootId: good.knownGood!.health!.bootId, freshBootId: fresh.knownGood!.health!.bootId, physicalDeploymentAllowed: fresh.physicalDeploymentAllowed }
211212 await writeFile(join(directory, 'qualification.json'), JSON.stringify(result, null, 2))
212213 return result
213214 }