starkernel: item 4.1a -- quota granting, Hermes's one-time birth grant

Punch list §25 item 4.1a complete.
New prerequisite item, found while scoping 4.2: no quota-granting mechanism
existed at all. Adds stadium_grant_quota(new_vm_id, from_vm_id) -- a
one-time initial grant at birth, distinct from item 1.3's still-unbuilt
recurring capacity-transfer arbitration. Splits the donor's free list evenly
by cell count, reassigns stadium_owner[] for every moved cell, and grants
the new VM a fresh Q48_ONE reservoir (not a split of the donor's -- per-VM
conservation, same pattern as Hera's own boot grant). Wired into every baby
VM's birth in capsule_birth.c.

Verified via a boot-time self-test in kernel_main.c using a synthetic
identity (not the real UUID pool, not a real capsule birth -- item 0.1's
Hera-alone pruning stays intact). All three architectures booted to ok> with
identical output: grant OK, Hera reservoir=0 (already fully committed to
resident words, correctly unchanged), test-vm reservoir=65536 (fresh
Q48_ONE). dict_hash identical across all three and unchanged from item 4.1's
baseline (0x3d4e1daf289da94f) -- confirms no dictionary word was added.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Robert Allan James
2026-08-05 15:12:30 -04:00
co-authored by Claude Sonnet 5
parent edfc246579
commit 2981ada2a5
11 changed files with 31456 additions and 17 deletions
+10
View File
@@ -31,6 +31,7 @@
#include "starkernel/capsule_run.h"
#include "starkernel/kmalloc.h"
#include "starkernel/console.h"
#include "starkernel/vm/stadium.h" /* item 4.1a -- stadium_grant_quota() */
#include "vm.h"
#include "platform_alloc.h"
#include "starforth_config.h" /* STADIUM_MAX_VM_COUNT */
@@ -502,6 +503,15 @@ CapsuleRunResult capsule_birth_baby(
capsule_parity_log_birth(vm_id, cap->capsule_id, cap->content_hash, dict_hash);
/* item 4.1a: one-time initial quota grant, from Hera's free list. Placed
* after a live birth is confirmed (not gating IDENTITY exec above) --
* this baby can hold no Stadium resident until item 4.2's own work gives
* her a reason to. Failure is not fatal: a VM with no quota today is the
* status quo every VM had before this item existed, so she is simply
* born without one and every stadium_word_dispatch() for her stays a
* harmless refusal, same as any VM without a quota. */
(void)stadium_grant_quota(vm_id, vm_uuid_hera());
if (out_vm_id) *out_vm_id = vm_id;
if (out_vm_ctx) *out_vm_ctx = new_vm;
+20
View File
@@ -628,6 +628,26 @@ static void kernel_main_deep(BootInfo *boot_info) {
* capsule birth above, so this is non-vacuous by this point. */
stadium_words_print_boot_diagnostics(vm_uuid_hera());
/* item 4.1a self-test: exercises stadium_grant_quota() with a synthetic
* identity, NOT vm_uuid_next()'s real birth pool (would perturb the
* deterministic ID stream real BIRTH calls draw from) and NOT a real
* capsule birth (item 0.1 pruned automatic Hermes birth from init.4th;
* restoring it is item 4.2's job, not this one's). Diagnostic only --
* the synthetic VM is never used for anything else. */
{
VMUuid test_id;
test_id.hi = 0;
test_id.lo = 1; /* distinct from vm_uuid_hera() (all-zero) and
* vm_uuid_none() (all-ones) */
int grant_rc = stadium_grant_quota(test_id, vm_uuid_hera());
console_puts("Stadium quota grant self-test: ");
console_println(grant_rc == 0 ? "OK" : "REFUSED");
if (grant_rc == 0) {
print_uint(" Hera reservoir=", stadium_reservoir_peek(vm_uuid_hera()));
print_uint(" test-vm reservoir=", stadium_reservoir_peek(test_id));
}
}
/*
* Runtime --doe flag: inject "EXEC-DOE BYE" if requested via boot args.
* Checked before SK_STARTUP_FORTH so a runtime --doe takes precedence.
+55
View File
@@ -398,6 +398,61 @@ size_t stadium_admit(VMUuid vm_id, const StadiumPatronHeader *candidate) {
return idx;
}
/*
* FABRIC.md item 4.1a: one-time initial quota grant, not item 1.3's
* (still-unbuilt) recurring transfer. See stadium.h's doc for the full
* argument. Two passes over from_vm_id's free list: the first counts it
* (need the length before deciding where to split), the second detaches the
* first `half` cells into new_vm_id's own list, reassigning owner as it goes.
*/
int stadium_grant_quota(VMUuid new_vm_id, VMUuid from_vm_id) {
int from_slot, new_slot, i;
size_t count, half, idx, last_new, new_head, remainder_head;
if (!stadium_initialized) return -1;
if (quota_slot_for_vm(new_vm_id) >= 0) return -1;
from_slot = quota_slot_for_vm(from_vm_id);
if (from_slot < 0) return -1;
new_slot = -1;
for (i = 0; i < STADIUM_MAX_VM_COUNT; i++) {
if (!stadium_quotas[i].in_use) { new_slot = i; break; }
}
if (new_slot < 0) return -1;
count = 0;
idx = stadium_quotas[from_slot].free_head;
while (idx != STADIUM_CELL_NONE) {
count++;
idx = link_to_size(stadium_cell_array[idx].header.link);
}
half = count / 2;
if (half == 0) return -1; /* fewer than 2 free cells -- nothing to split */
new_head = stadium_quotas[from_slot].free_head;
idx = new_head;
last_new = STADIUM_CELL_NONE;
for (i = 0; i < (int)half; i++) {
stadium_owner[idx] = (uint8_t)new_slot;
last_new = idx;
idx = link_to_size(stadium_cell_array[idx].header.link);
}
/* idx now points to the first cell staying with from_slot (or
* STADIUM_CELL_NONE if half == count, i.e. an even list fully moved). */
remainder_head = idx;
stadium_cell_array[last_new].header.link = size_to_link(STADIUM_CELL_NONE);
stadium_quotas[from_slot].free_head = remainder_head;
stadium_quotas[new_slot].vm_id = new_vm_id;
stadium_quotas[new_slot].in_use = 1;
stadium_quotas[new_slot].free_head = new_head;
stadium_quotas[new_slot].reservoir = Q48_ONE;
return 0;
}
uint64_t stadium_reservoir_pull(VMUuid vm_id, uint64_t amount) {
int slot = quota_slot_for_vm(vm_id);
uint64_t pulled;