Add sk_hermes_alloc(), the heat-coupled allocator -- FABRIC-3.6.md task 2.2, item 28

The piece FABRIC-3.5.md SXXXIII.6 calls "what remains genuinely hard,"
built and proven first per its own recommendation. Added
src/starkernel/vm/kernel_hermes.c (wired into Makefile.starkernel's
LOADER_EXTRA_SRCS -- this repo lists vm/*.c files explicitly, no glob)
and sk_hermes_alloc()'s declaration in kernel_hermes.h.

Checks stadium_reservoir_peek(vm_id) >= SK_HERMES_Q_SLOT before
touching the reservoir at all -- refusal this way needs no rollback,
since nothing was pulled -- with an explicit rollback path
(stadium_reservoir_push) kept defensively for the pull-then-short case,
though nothing in this single-core kernel is expected to reach it.

SK_HERMES_Q_SLOT = Q48_ONE / SK_HERMES_MSG_MAX (2048), deliberately
simpler than messaging.4th's own formula, which reserves a Q.1/3 floor
for COMMON-CH's own Stadium heat -- kernel-Hermes has no such object
(SXXXIII.4/SXXXIII.5's flat membership list carries no heat of its
own), so there is nothing left for that floor to protect.

Self-test in kernel_main.c, same diagnostic-only synthetic-VM pattern
as the existing Stadium quota grant self-test (lo=3, distinct from
that test's lo=1): reads back the actual granted reservoir rather than
assuming a number, derives expected_n from it, allocates to refusal,
and checks the refusal lands at exactly expected_n, the reservoir
doesn't move on the refused attempt (rollback proven, not assumed),
and the final reservoir is exactly reservoir0 minus got_n times
Q_SLOT.

Three-arch boot clean: amd64/aarch64/riscv64 all reach [zuse@Hera] ok>,
zero UNKNOWN WORD, dict_hash unmoved from task 2.1 (pure C, no FORTH
touched). All three print identical self-test arithmetic: reservoir0=
65536 Q_SLOT=2048 expected_n=32 got_n=32 reservoir_after=0. No compiler
warnings.

Noted, not fixed: Q_SLOT's divisor and SK_HERMES_MSG_MAX are the same
32, so reservoir and arena exhaustion land at exactly the same count by
construction -- this test can't distinguish which refusal reason
fired, only that refusal is correct and rolls back correctly.

Deliberately not evidence for stadium_conserved(): allocating alone
(no release yet, task 2.3) leaves pulled heat held off the Stadium
floor, so the two-term check would correctly read false right now if
run mid-hold. That's expected, not a bug -- Stage B (task 2.7) is
defined as "before and after the alloc/free cycle," not "continuously
during." This task's self-test checks reservoir arithmetic directly
instead.

Authorized by Captain Bob ("Yes continue").

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
Robert Allan James
2026-09-19 21:00:20 -04:00
co-authored by Claude Sonnet 5
parent f10fa7ae83
commit 9d129fdb1c
12 changed files with 27610 additions and 7 deletions
+42 -4
View File
@@ -61,18 +61,32 @@
#include <stddef.h>
#include <stdint.h>
#include "starkernel/vm_uuid.h" /* VMUuid */
#include "starkernel/q48_16.h" /* Q48_ONE */
/*
* SK_HERMES_MSG_MAX / SK_HERMES_MEMBER_MAX - sizing only, not yet load-
* bearing (nothing allocates against these until task 2.2). Mirrors
* SK_HERMES_MSG_MAX / SK_HERMES_MEMBER_MAX - sizing. Mirrors
* messaging.4th's own MSG-MAX (32) and MBR-MAX (64) as a starting point --
* kernel-Hermes is a single central pool rather than N per-VM arenas, so
* these may need revisiting once task 2.2's allocator has real traffic to
* size against. Not a ruling, just where the FORTH precedent already was.
* these may need revisiting once real traffic exists to size against. Not
* a ruling, just where the FORTH precedent already was.
*/
#define SK_HERMES_MSG_MAX 32
#define SK_HERMES_MEMBER_MAX 64
/*
* SK_HERMES_Q_SLOT - per-message admission heat, task 2.2 (item 28).
* messaging.4th:44-46 derives Q.SLOT as the reservoir remaining after
* COMMON-CH's own Q.1/3 floor, split across MSG-MAX + (CH-MAX-1) slots --
* a floor that exists to reserve heat for the one live channel object
* itself. Kernel-Hermes has no such object: SXXXIII.4 item 3 and
* SXXXIII.5 replace the channel abstraction with a flat membership list
* that carries no Stadium heat of its own, so there is nothing left for a
* floor to protect. Deliberately simpler here rather than carrying the
* old formula's now-unmotivated term forward: reservoir split evenly
* across message slots only.
*/
#define SK_HERMES_Q_SLOT ((uint64_t)Q48_ONE / SK_HERMES_MSG_MAX)
/*
* SkHermesMessage - one message slot, field-for-field mirror of
* messaging.4th's 9-cell MSG layout.
@@ -133,6 +147,30 @@ typedef struct {
size_t count;
} SkHermesMembership;
/*
* sk_hermes_alloc - Heat-coupled allocate (task 2.2, item 28): pull
* SK_HERMES_Q_SLOT from vm_id's own Stadium reservoir and claim a free
* message slot. Refuses cleanly, touching neither the reservoir nor the
* arena, if either is unavailable -- "roll back on refusal" is satisfied
* by never pulling until affordability is confirmed, not by pulling then
* undoing (though the rollback path exists too, for the pull-then-fail
* case the check-first ordering is not expected to reach).
*
* Wired to nothing outside this file's own self-test (kernel_main.c) as
* of task 2.2 -- no FORTH word, no capsule interaction, no protocol logic
* (send/deliver/reap are later tasks). This function existing and being
* exercised by a synthetic-VM self-test does not change any real VM's
* dictionary or Stadium state.
*
* @param vm_id Caller whose reservoir is charged.
* @param out_msg On success, set to the claimed slot. Untouched on
* refusal.
* @return 0 on success, -1 on refusal (insufficient reservoir or no free
* slot -- task 2.2 does not distinguish the two in the return
* value; both leave all state exactly as it was).
*/
int sk_hermes_alloc(VMUuid vm_id, SkHermesMessage **out_msg);
#endif /* __STARKERNEL__ */
#endif /* STARKERNEL_VM_KERNEL_HERMES_H */