Stage D: parked-idle VMs are killable, KILL unregisters; fix uninitialized pending_reap (FABRIC-3.8 §3.5, G6)

sk_vm_parked_idle(): saved context + exactly its own IDLE frame.
capsule_vm_kill_refusal() is the single kill rule, shared by
capsule_vm_kill() and the KILL word: refuse SWITCHED_OUT mid-task, parked
with mail pending, or the VM running; allow parked-idle. capsule_vm_kill()
now unregisters the switch slot (G6).

Finding fixed: vm_registry_alloc() never initialized pending_reap (kmalloc
does not zero); C-b's reap_pending walks every participant, so garbage
reaped a healthy VM (seen live before any KILL). Now zeroed at alloc.

Probe (reverted): baseline KILL refused, slots 3->4->4 (FAIL, proves G6);
with Stage D dprobe parked idle, KILL dead, slots 3->4->3, PASS on amd64/
aarch64/riscv64. Acceptance: dict_hash unchanged, relay 3, idle growth 0.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Robert Allan James
2026-09-23 19:14:50 -04:00
co-authored by Claude Opus 5.5
parent 06bcab504d
commit 604ad7aadc
25 changed files with 82548 additions and 15 deletions
+8
View File
@@ -68,6 +68,14 @@ void sk_vm_switch_checkpoint(struct VM *vm);
* switches to the coolest VM with work, if any. */
void sk_vm_idle_point(void);
/* FABRIC-3.8.md §3.5 (Stage D): 1 if `vm` is parked at its own trampoline
* with nothing in progress -- its frame stack holds exactly one frame, its
* own VM_FRAME_IDLE. Its saved context is the bare trampoline loop; no
* payload, no borrowed frame, nothing that would ever need resuming. This
* is what makes a SWITCHED_OUT VM killable. (Hera parked at her idle point
* also has one IDLE frame, but she can never be killed anyway.) */
int sk_vm_parked_idle(const struct VM *vm);
#endif /* __STARKERNEL__ */
#endif /* STARKERNEL_VM_SWITCH_H */