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:
co-authored by
Claude Opus 5.5
parent
04dbc3a5d3
commit
6e933dac38
@@ -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;
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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();
|
||||
|
||||
Reference in New Issue
Block a user