Stage B proof: add per-VM consumed term to stadium_conserved -- FABRIC-3.6.md task 2.7

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Robert Allan James
2026-09-20 21:31:45 -04:00
co-authored by Claude Sonnet 5
parent 313ffc89e6
commit 2a2bf6eb35
12 changed files with 55566 additions and 8 deletions
+37
View File
@@ -951,6 +951,43 @@ static void kernel_main_deep(BootInfo *boot_info) {
if (held4 != pulled4 - returned4 - consumed4) ok = 0;
if (stadium_reservoir_peek(alloc_test_id) != reservoir0 - decay_expected) ok = 0;
/* Task 2.7, Stage B proof (SXXXIV.3 as corrected by SXXXIX.4):
* a fresh alloc/decay/free cycle on this VM, checking BOTH the
* ledger and stadium_conserved() at every stage. fleet_conserved
* is deliberately not consulted (cannot see Stadium heat). The
* four-term form must hold before, mid-hold, after decay, and
* after release; and after decay the old two-term form must
* FAIL while the four-term one holds -- proving the consumed
* term is load-bearing, not vacuous. */
{
uint64_t nb = 0, h, p_, r, c;
/* Derived from the CURRENT reservoir: the earlier decay
* cycle consumed heat, so fewer than expected_n fit now. */
uint64_t expected_b = stadium_reservoir_peek(alloc_test_id) / SK_HERMES_Q_SLOT;
int sb = 1;
if (!stadium_conserved(alloc_test_id)) sb = 0; /* before */
while (nb < SK_HERMES_MSG_MAX && sk_hermes_alloc(alloc_test_id, &msg) == 0) {
msgs[nb++] = msg;
}
if (nb != expected_b || nb == 0) sb = 0;
if (!stadium_conserved(alloc_test_id)) sb = 0; /* mid-hold */
for (i = 0; i < nb; i++) if (sk_hermes_decay(msgs[i]) != 0) sb = 0;
if (!stadium_conserved(alloc_test_id)) sb = 0; /* after decay */
if (stadium_consumed_peek(alloc_test_id) == 0) sb = 0;
if (stadium_resident_sum(alloc_test_id) + stadium_reservoir_peek(alloc_test_id)
== (uint64_t)Q48_ONE) sb = 0; /* two-term must fail */
sk_hermes_ledger(&h, &p_, &r, &c);
if (!sk_hermes_audit_values(h, p_, r, c)) sb = 0;
for (i = 0; i < nb; i++) if (sk_hermes_release(msgs[i]) != 0) sb = 0;
if (!stadium_conserved(alloc_test_id)) sb = 0; /* after release */
sk_hermes_ledger(&h, &p_, &r, &c);
if (h != held0 || !sk_hermes_audit_values(h, p_, r, c)) sb = 0;
console_puts("Stage B (ledger + stadium_conserved): ");
console_println(sb ? "PASS" : "FAIL");
print_uint(" vm_consumed=", stadium_consumed_peek(alloc_test_id));
if (!sb) ok = 0;
}
/* Task 2.6: the live audit never fired across both cycles,
* and a ONE-unit corruption of each counter in turn (on a
* copy -- live state untouched) is caught by the pure
+2
View File
@@ -151,6 +151,7 @@ int sk_hermes_alloc(VMUuid vm_id, SkHermesMessage **out_msg) {
memset(&sk_hermes_msgs[slot], 0, sizeof(SkHermesMessage));
sk_hermes_msgs[slot].in_use = 1;
sk_hermes_msgs[slot].stadium_cell = (int32_t)cell;
sk_hermes_msgs[slot].owner = vm_id;
*out_msg = &sk_hermes_msgs[slot];
/* Task 2.4: the one site where held/pulled increment -- only reached
@@ -222,6 +223,7 @@ int sk_hermes_decay(SkHermesMessage *msg) {
* pays for it). SXXXVII.3: decay's delta is exact and in hand here. */
sk_hermes_consumed += before - after;
sk_hermes_held -= before - after;
stadium_consumed_record(msg->owner, before - after);
sk_hermes_audit();
return 0;
+22 -1
View File
@@ -107,6 +107,10 @@ typedef struct {
* Invariant: Σ(resident patron heat) + reservoir ==
* Q48_ONE, checked the same way vm_physics_conserved()
* checks the fleet sum. */
uint64_t consumed; /* FABRIC-3.5.md §XL.4 -- Q48.16, heat this VM's messages
* have decayed away (recorded by kernel-Hermes,
* FABRIC-3.6.md task 2.7). Full invariant:
* Σ(resident) + reservoir + consumed == Q48_ONE. */
} StadiumVMQuota;
/* kmalloc'd at stadium_boot_init() to stadium_max_vm_count_val entries,
@@ -275,6 +279,7 @@ int stadium_boot_init(void) {
quotas[i].free_count = 0;
quotas[i].resident_head = STADIUM_CELL_NONE;
quotas[i].reservoir = 0;
quotas[i].consumed = 0;
}
}
quotas[0].vm_id = vm_uuid_hera();
@@ -642,6 +647,7 @@ int stadium_grant_quota(VMUuid new_vm_id, VMUuid from_vm_id) {
stadium_quotas[new_slot].free_count = half;
stadium_quotas[new_slot].resident_head = STADIUM_CELL_NONE;
stadium_quotas[new_slot].reservoir = Q48_ONE;
stadium_quotas[new_slot].consumed = 0;
return 0;
}
@@ -764,8 +770,23 @@ uint64_t stadium_resident_sum(VMUuid vm_id) {
return sum;
}
void stadium_consumed_record(VMUuid vm_id, uint64_t amount) {
int slot = quota_slot_for_vm(vm_id);
if (slot < 0) return;
stadium_quotas[slot].consumed += amount;
}
uint64_t stadium_consumed_peek(VMUuid vm_id) {
int slot = quota_slot_for_vm(vm_id);
if (slot < 0) return 0;
return stadium_quotas[slot].consumed;
}
int stadium_conserved(VMUuid vm_id) {
return (stadium_resident_sum(vm_id) + stadium_reservoir_peek(vm_id)) == (uint64_t)Q48_ONE;
return (stadium_resident_sum(vm_id) + stadium_reservoir_peek(vm_id) +
stadium_consumed_peek(vm_id)) == (uint64_t)Q48_ONE;
}
/*