Stage O-A: the Tripod is an observer -- outside the compudynamic comparison, acting on events (FABRIC-3.9.0 §2, 3.9.8)

Observer flag on switch-signal slots, set by the kernel for the Tripod
(is_fleet_foundation, hoisted; Hera in kernel_main). The decision: an
observer in progress or holding an event keeps the CPU; observers with
events (mail, Hera's console input) go ahead of participants, oldest wait
stamp first; participants compared by work heat as before. Observers are
never charged (O-2) nor floored. Uncommitted heat-share charge discarded.

Evidence, byte-identical amd64/aarch64/riscv64: G interleave unchanged;
Hestia's mail runs before any worker output (HHHABB...); a line typed
while two workers run is read and executed mid-stream (...ABAB1 2 + . /
3 ok / ABAB...) -- console responsive under load. Relay 3, dict_hash
unchanged. Busy-wait DOE deliberately not run (deadlocks by design until
O-C). FABRIC-3.9.0 records rulings and Captain Bob's messaging note (§7).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Robert Allan James
2026-09-24 09:28:35 -04:00
co-authored by Claude Opus 5.5
parent 04dbc3a5d3
commit 6e933dac38
14 changed files with 27770 additions and 35 deletions
+18 -10
View File
@@ -835,20 +835,24 @@ CapsuleRunResult capsule_birth_baby(
* session_set_pinned() failures are soft-fail the same way (logged,
* non-fatal) -- same reasoning §H.12 step 4 already established for
* Hera. */
/* FABRIC-3.6.md Phase 4 (Stage E), 2026-09-22: "Hermes" retired
* from this OR-chain -- FABRIC-3.5.md SXXXIV.4's own ruling
* named this exact moment ("removed in Stage E, with the strip,
* not with the Tripod change"). The chain temporarily held four
* names (Hera, Hermes, Artemis, Hestia) through Phases 1-3,
* deliberately, per that same ruling; three names now.
* FABRIC-3.9.0.md §2.1 (Stage O-A, 2026-09-24): hoisted out of the
* Stadium block below so the switch-signal registration further down
* reads the SAME birth-time structural fact (the Tripod members are
* observers) instead of a second copy of this test. */
const int is_fleet_foundation =
vm_name_prefix_eq_nocase(capsule_name, "Hera") ||
vm_name_prefix_eq_nocase(capsule_name, "Artemis") ||
vm_name_prefix_eq_nocase(capsule_name, "Hestia");
{
StadiumPatronHeader vm_patron;
uint8_t *raw = (uint8_t *)&vm_patron;
size_t i;
/* FABRIC-3.6.md Phase 4 (Stage E), 2026-09-22: "Hermes" retired
* from this OR-chain -- FABRIC-3.5.md SXXXIV.4's own ruling
* named this exact moment ("removed in Stage E, with the strip,
* not with the Tripod change"). The chain temporarily held four
* names (Hera, Hermes, Artemis, Hestia) through Phases 1-3,
* deliberately, per that same ruling; three names now. */
int is_fleet_foundation =
vm_name_prefix_eq_nocase(capsule_name, "Hera") ||
vm_name_prefix_eq_nocase(capsule_name, "Artemis") ||
vm_name_prefix_eq_nocase(capsule_name, "Hestia");
for (i = 0; i < sizeof(vm_patron); i++) raw[i] = 0;
vm_patron.identity = 0;
@@ -932,6 +936,10 @@ CapsuleRunResult capsule_birth_baby(
if (sk_vm_switch_signal_register(vm_id) < 0) {
log_message(LOG_WARN, "capsule_birth_baby: switch-signal table full -- %s won't be switched to",
capsule_name);
} else if (is_fleet_foundation) {
/* FABRIC-3.9.0.md §2.1 (Stage O-A): the Tripod observes the
* compudynamics and acts on events; it does not participate. */
sk_vm_switch_signal_set_observer(vm_id);
}
capsule_parity_log_birth(vm_id, cap->capsule_id, cap->content_hash, dict_hash);
@@ -68,6 +68,11 @@ typedef struct {
uint64_t parked_stamp; /* wait stamp taken when this VM last stopped
* running (sk_vm_switch_signal_note_parked());
* 0 = never parked. See sk_vm_switch_decide(). */
int observer; /* FABRIC-3.9.0.md §2 (Stage O-A, 2026-09-24):
* 1 for the Tripod (Hera, Artemis, Hestia) --
* outside the compudynamic comparison, acting on
* events; 0 for every participant. Set by the
* kernel at registration, never by a VM. */
} sk_switch_slot_entry_t;
static sk_switch_slot_entry_t *g_slots = (sk_switch_slot_entry_t *)0;
@@ -129,6 +134,7 @@ int sk_vm_switch_signal_boot_init(void) {
for (i = 0; i < max_vm_count; i++) {
slots[i].vm_id = vm_uuid_none();
slots[i].parked_stamp = 0;
slots[i].observer = 0;
}
g_slots = slots;
@@ -151,9 +157,20 @@ int sk_vm_switch_signal_register(VMUuid vm_id) {
if (g_slot_count >= g_slot_capacity) return -1;
g_slots[g_slot_count].vm_id = vm_id;
g_slots[g_slot_count].parked_stamp = 0;
g_slots[g_slot_count].observer = 0;
return g_slot_count++;
}
void sk_vm_switch_signal_set_observer(VMUuid vm_id) {
int slot = slot_for_vm_id(vm_id);
if (slot >= 0) g_slots[slot].observer = 1;
}
int sk_vm_switch_signal_is_observer(VMUuid vm_id) {
int slot = slot_for_vm_id(vm_id);
return slot >= 0 && g_slots[slot].observer;
}
int sk_vm_switch_signal_unregister(VMUuid vm_id) {
int slot = slot_for_vm_id(vm_id);
if (slot < 0) return -1;
@@ -210,13 +227,12 @@ static int candidate_standing(VM *v, VM *mama, uint64_t parked_stamp,
eligible = 1;
stamp = m->wait_stamp;
}
(void)mama; /* Stage O-A: Hera is an observer, never a candidate here */
if (v->native_stack_saved_sp != 0) { /* parked on its own stack */
const VMFrameStack *fs = &v->frames;
int in_progress = fs->count > 0 &&
fs->entries[fs->count - 1].kind != VM_FRAME_IDLE;
/* §3.13.5 / ruling C-5: pending console input is Hera's mail. */
int console_input = (v == mama) && sk_console_key_available();
if (in_progress || console_input) {
if (in_progress) {
eligible = 1;
if (parked_stamp != 0 && parked_stamp < stamp) stamp = parked_stamp;
}
@@ -230,17 +246,72 @@ static int candidate_standing(VM *v, VM *mama, uint64_t parked_stamp,
return 1;
}
/* FABRIC-3.9.0.md §2.3 (Stage O-A): an observer's events -- pending mail,
* and for Hera pending console input. Returns 1 with the event's wait
* stamp (oldest mail, or when Hera last parked for console input). */
static int observer_event(VM *v, VM *mama, uint64_t parked_stamp, uint64_t *stamp_out) {
SkHermesMessage *m = sk_hermes_pending_peek(v->stadium_vm_id);
uint64_t stamp = UINT64_MAX;
int ev = 0;
if (m) { ev = 1; stamp = m->wait_stamp; }
if (v == mama && sk_console_key_available()) {
ev = 1;
if (parked_stamp != 0 && parked_stamp < stamp) stamp = parked_stamp;
}
*stamp_out = stamp;
return ev;
}
/* An observer acting on an event: a live frame on its stack (a line or a
* drained message being interpreted). Nothing displaces it (§2.4). */
static int observer_in_progress(const VM *v) {
return v->frames.count > 0 &&
v->frames.entries[v->frames.count - 1].kind != VM_FRAME_IDLE;
}
VM *sk_vm_switch_decide(VM *self, int self_competes) {
VM *mama = (VM *)sk_get_mama_vm();
VM *best = (VM *)0;
uint64_t best_heat = 0, best_stamp = 0;
int i;
int i, self_obs = self && sk_vm_switch_signal_is_observer(self->stadium_vm_id);
/* FABRIC-3.9.0.md §2.4, ruling O-1 (Stage O-A): an observer acting on
* an event, or holding one, keeps the CPU -- observers respond to what
* happens; they are not compared with anyone. */
if (self_obs) {
uint64_t s;
if (observer_in_progress(self) || observer_event(self, mama, 0, &s)) return (VM *)0;
}
/* Observers with events go ahead of every participant, at this clean
* boundary; among themselves, the one waiting longest (unique stamps,
* so still independent of walk order). No heat involved. */
for (i = 0; i < g_slot_count; i++) {
VMRegistryEntry e;
VM *v;
uint64_t stamp;
if (!g_slots[i].observer) continue;
if (self && vm_uuid_equal(g_slots[i].vm_id, self->stadium_vm_id)) continue;
if (capsule_vm_registry_get(g_slots[i].vm_id, &e) != 0) continue;
v = (VM *)e.vm_ptr;
if (!v || v == self || e.pending_reap) continue;
if (e.state == VM_STATE_DEAD || e.state == VM_STATE_STILLBORN) continue;
if (v->native_stack_top == 0) continue;
if (!observer_event(v, mama, g_slots[i].parked_stamp, &stamp)) continue;
if (!best || stamp < best_stamp) { best = v; best_stamp = stamp; }
}
if (best) return best;
/* Participants: the FABRIC-3.8 compudynamics, unchanged -- observers
* are never heat candidates. */
for (i = 0; i < g_slot_count; i++) {
VMRegistryEntry e;
VM *v;
uint64_t heat, stamp;
if (g_slots[i].observer) continue;
if (self && vm_uuid_equal(g_slots[i].vm_id, self->stadium_vm_id)) continue;
if (capsule_vm_registry_get(g_slots[i].vm_id, &e) != 0) continue;
v = (VM *)e.vm_ptr;
@@ -263,7 +334,8 @@ VM *sk_vm_switch_decide(VM *self, int self_competes) {
/* A running VM that still has work keeps the CPU unless the best
* candidate is strictly cooler: a tie with the incumbent is "no reason
* to move," not a coin toss (ruling C-3). */
if (best && self_competes && !(best_heat < vm_physics_work_heat_of(self->stadium_vm_id))) {
if (best && self_competes && !self_obs &&
!(best_heat < vm_physics_work_heat_of(self->stadium_vm_id))) {
return (VM *)0;
}
return best;
@@ -281,6 +353,10 @@ void sk_vm_switch_signal_note_work_gained(VMUuid vm_id) {
if (capsule_vm_registry_get(vm_id, &e) != 0 || !e.vm_ptr) return;
v = (VM *)e.vm_ptr;
/* Stage O-A (FABRIC-3.9.0.md §2.2): observers have no work heat to
* floor, and count in nobody's floor. */
if (sk_vm_switch_signal_is_observer(vm_id)) return;
/* Only a VM that had NO work gains some here: not the one running, and
* not one parked mid-task (either already had work, and keeps its own
* work heat). */
@@ -288,9 +364,10 @@ void sk_vm_switch_signal_note_work_gained(VMUuid vm_id) {
if (v->native_stack_saved_sp != 0 && v->frames.count > 0 &&
v->frames.entries[v->frames.count - 1].kind != VM_FRAME_IDLE) return;
/* The floor: the lowest work heat among the VMs that have work right
* now -- the one running, and every other eligible participant. */
if (current) {
/* The floor: the lowest work heat among the participants that have
* work right now -- the one running (if it is a participant), and every
* other eligible participant. */
if (current && !sk_vm_switch_signal_is_observer(current->stadium_vm_id)) {
floor_q48 = vm_physics_work_heat_of(current->stadium_vm_id);
have = 1;
}
@@ -298,6 +375,7 @@ void sk_vm_switch_signal_note_work_gained(VMUuid vm_id) {
VMRegistryEntry o;
VM *ov;
uint64_t heat, stamp;
if (g_slots[i].observer) continue;
if (vm_uuid_equal(g_slots[i].vm_id, vm_id)) continue;
if (capsule_vm_registry_get(g_slots[i].vm_id, &o) != 0) continue;
ov = (VM *)o.vm_ptr;
+3
View File
@@ -1266,6 +1266,9 @@ static void kernel_main_deep(BootInfo *boot_info) {
VMRegistryEntry hera_entry;
if (capsule_vm_registry_get(vm_uuid_hera(), &hera_entry) == 0) {
sk_vm_switch_signal_register(hera_entry.vm_id);
/* FABRIC-3.9.0.md §2.1 (Stage O-A): Hera is an observer, like
* the rest of the Tripod (whose role capsule_birth_baby() sets). */
sk_vm_switch_signal_set_observer(hera_entry.vm_id);
/* Seed the switch mechanism's own "who is running" tracker
* (FABRIC-3.md §XXVIII Stage 3 follow-on, 2026-09-14) -- Hera
* is genuinely the one running here, before any switch has
+7 -1
View File
@@ -150,7 +150,13 @@ void vm_tick(VM* vm)
* vm_physics_touch()'s dispatch law. Kernel-side only: nothing a VM
* executes can ask for, skip, or give back a charge. */
if (vm == sk_vm_switch_current_vm()) {
vm_physics_charge(vm->stadium_vm_id);
/* FABRIC-3.9.0.md §2.2, ruling O-2 (Stage O-A): observers -- the
* Tripod, the fleet's heat bath -- are not charged, neither work
* heat nor absolute heat. Participants pull absolute heat off them
* through their own charges, and retire returns it, so fleet K
* stays conserved. */
if (!sk_vm_switch_signal_is_observer(vm->stadium_vm_id))
vm_physics_charge(vm->stadium_vm_id);
/* Stage C-b (§3.13.3): the running VM's heat just changed, so the
* next owned checkpoint re-decides who runs. */
sk_vm_switch_signal_note_beat();