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
+38
View File
@@ -154,3 +154,41 @@ C-4 (α)'s starvation: under load, the console, USB and storage wait until every
4. **O-4:** device servicing under load: (c) accept for the relaunch and (b) interrupt-driven
later (proposed), or (a) treat the device-poll interval as a Hera event.
5. **Discard the uncommitted absolute-heat-share charge** (proposed), or keep it as a safety net.
**RULED 2026-09-24, Captain Bob: "all as proposed."** O-1 through O-4 and the discard (5) all go
as proposed above.
## 7. Raised 2026-09-24: messaging should flow on its own (OPEN, not yet designed)
**Captain Bob, verbatim:** *"Another potential defect is perhaps in messaging itself. We
shouldn't have to actually specifically decide to send a message … that should be autonomous by
the operating system itself … The messages should just flow. … However, I would still have a
manual method of sending and receiving messages … I should be able to create channels manually,
I should be able to destroy channels manually, I should be able to inject messages into the
queue, all of that should be manual, but also able to operate autonomously."*
**What exists today** (traced, not proposed):
- **The kernel side is complete in C:** `sk_hermes_channel_create/destroy/subscribe/unsubscribe/
is_member/member_count`, `sk_hermes_publish`, `sk_hermes_send_one`, and the negotiation calls
(`channel_request/respond/close/open_policy`), in `kernel_hermes.c`.
- **Only `SEND` reaches FORTH.** There are also the fixed-purpose `KH-BLK-ATTACH-SEND` and
`KH-ELEVATE-SEND`. There is no FORTH word to create or destroy a channel, subscribe, publish,
or inspect a queue.
- **Every message today is sent because some code decided to send it:** the console relay,
BLK-ATTACH, `SEND` in a job. Nothing is routed by the OS on its own.
**Two parts, which separate cleanly:**
- **Manual.** FORTH words over the existing C API: create/destroy a channel, subscribe and
unsubscribe, publish, inject a message into a queue, and inspect queues. Mechanical: mostly
exposure, not new mechanism. The ACL decides who may use them (`ACL.4th`, never C).
- **Autonomous.** What should flow without anyone deciding, and on what trigger, is a real design
question. Candidates to discuss:
- events the OS already knows about (a VM born, killed, parked idle; a device attached);
- results a VM leaves on its stack or in a known place, routed to whoever subscribed;
- heat/physics events routed to observers, which fits "observers act on events".
Not designed. It needs Captain Bob's framing first.
**Relation to the O stages:** independent of O-B to O-D. The event-driven DoE driver (O-C) is one
concrete case: a worker's completion notice is today a `SEND` in the job text, and could become an
OS event ("worker parked idle after its job") if autonomous flow lands first.
+48 -2
View File
@@ -22,7 +22,7 @@ Evidence is named **before** each stage runs.
| Stage | Change | Evidence (named in advance) |
|---|---|---|
| **O-A** | Observer role (`FABRIC-3.9.0.md` §2.1). The absolute-heat-share charge is discarded (ruling 5). Observers are left out of the decision's candidates, the work-heat charge and the floor. (Also the absolute charge, if O-2 is ruled so.) Observers with events go ahead at clean boundaries, among themselves by wait stamp (§2.4, per O-1). | The standard sequence. A probe (reverted after capture) shows the decision never returning an observer as a heat candidate, and an observer with mail switched to at the next participant checkpoint. **The busy-wait DoE driver, unchanged, run as a cfg-0 trial with rows on for a fixed window:** Hera's share of rows falls from about ½ (the stopped relaunch's tail) to near 0 while both workers have work. That is the measured symptom this design exists to fix. |
| **O-A** | Observer role (`FABRIC-3.9.0.md` §2.1). The absolute-heat-share charge is discarded (ruling 5). Observers are left out of the decision's candidates, the work-heat charge and the floor, and (O-2) the absolute charge. Observers with events go ahead at clean boundaries, among themselves by wait stamp (§2.4, per O-1). | *(Corrected before starting.)* The standard sequence, with the G interleave unchanged. **Console responsive under load:** `1 2 + .` typed while `wa`/`wb` run prints `3` in the middle of the A/B stream. **Observer precedence:** a message to Hestia sent in the same line as the two worker jobs runs before any `A`/`B`. **Not run: the busy-wait DoE driver.** Under O-1, Hera running `DOE` is an observer acting on an event, and nothing displaces her, so `MU-WAIT-ALL` would wait forever. The originally named evidence ("Hera's share falls on the unchanged busy-wait driver") contradicts the design. `DOE` stays unrun until O-C replaces the driver. |
| **O-B** | Hera's own mail drained at `sk_vm_idle_point()`, with the readline `n > 0` gate; the timer-paced pump drain removed (§2.5). | The standard sequence, plus: a message sent to Hera while she sits idle at the prompt is acted on **without waiting for the idle beat**. A probe records the delay in beats from admission to drain; it must be well under `SK_IDLE_BEAT_INTERVAL`. The relay and BLK-ATTACH flows are unchanged. |
| **O-C** | An event-driven DoE driver (§3): `DOE` starts trial 1 and returns to the prompt; `MU-DONE` counts, ends trials and starts the next; `MU-WAIT-ALL` is deleted. Capsule only. | A 1-rep smoke campaign on amd64 runs all 6 cells to `MU-CAMPAIGN: complete`, with every trial `fail=0` and N/N `KILL … dead`. Hera's rows during trials are only event handling. The prompt answers typed input while trials run. Then the standard sequence on all three ISAs. |
| **O-D** | The DoE relaunch: `12345 3 S" <isa>" DOE`, amd64, then aarch64, then riscv64, with the ring extracted between ISAs and `artemis.img` **not** reset in between. | A fresh time estimate from O-C's measured row rate, **before** launch. Then per ISA: 18 `MU-TRIAL` markers, `fail=0`, `MU-CAMPAIGN: complete`. |
@@ -37,4 +37,50 @@ Evidence is named **before** each stage runs.
## 2. Execution log
*(empty: no stage started)*
- [x] **Stage O-A — the observer role (`FABRIC-3.9.0.md` §2).** 2026-09-24. Captain Bob: *"all as
proposed, go ahead with O-A"* (O-1 to O-4 and the discard, all as proposed).
- **Discarded:** the uncommitted absolute-heat-share charge (`git checkout` of
`capsule_vm_physics.c/.h`), per ruling 5.
- **`capsule_vm_switch_signal.c/.h`:**
- Each slot carries an `observer` flag, with `sk_vm_switch_signal_set_observer()` and
`is_observer()`.
- `sk_vm_switch_decide()` now works in tiers:
1. An observer that is itself in progress (a live non-IDLE frame) or holding an event
keeps the CPU.
2. Otherwise, an observer with an event, meaning mail (or console input, for Hera),
goes ahead of every participant. Among observers, the oldest wait stamp wins.
3. Otherwise, participants are compared by work heat exactly as in FABRIC-3.8.
Observers are never heat candidates.
- `note_work_gained()` never floors an observer and leaves observers out of the floor
set. The running VM counts only if it is a participant.
- **`capsule_birth.c`:** `is_fleet_foundation` is hoisted to function scope. Registration
marks the Tripod as observers from that same birth-time fact.
- **`kernel_main.c`:** Hera is marked an observer where she registers.
- **`vm_runtime.c`:** observers are not charged at all (O-2).
- Nothing a VM executes can set or clear the role.
**Evidence, as corrected before starting (§1).** Ordinary words only, typed at
`[zuse@Hera] ok>`, byte-identical on all three ISAs (`logs/20260924-084904/amd64/`,
`-090107/aarch64/`, `-091424/riscv64/`):
- **G interleave unchanged:** A = 40, B = 40, 48 runs.
- **Observer precedence:** `S" LA" S" wa" SEND . S" LB" S" wb" SEND . S" 72 EMIT 72 EMIT
72 EMIT" S" Hestia" SEND .` gives `HHHABBAABBAA…`. Hestia's mail ran before any
participant output once Hera went idle.
- **Console responsive under load:** with `wa`/`wb` on 200 markers × 20,000-iteration
loops, `1 2 + .` typed mid-run appears as `…ABABAB1 2 + .` / `3 ok` / `ok> ABABBA…`.
Hera, an observer with console input, got the CPU at the next worker boundary, ran the
line, and the workers resumed. This is the C-4 (α) starvation recorded at C-b, fixed.
*(The first amd64 attempt at this check used loops that finished before the line was
typed, so it proved nothing. It was re-run with the heavy loops, and the script was fixed
for the other two ISAs.)*
**Acceptance** (same boots):
- All reach `[zuse@Hera] ok>` with zero `UNKNOWN WORD`, faults and frame corruption.
- The `dict_hash` set is unchanged from Stage I (word_count 531, M7.1a
`0x572daac83b797332`), since no words were added or removed.
- Mint → WIREBIND → `USE` → `1 2 + .` → `3`.
- Idle log growth: 0 bytes.
- Clean build on all three plus hosted, no new warnings.
**Not run, by design:** the busy-wait DoE driver, which would deadlock under O-1 (§1).
Don't run `DOE` until O-C.
+14 -14
View File
@@ -1,5 +1,5 @@
# Capsule Block Manifest — Auto-generated
<!-- Generated by mkcapsule --manifest 2026-09-24T12:13:16Z -->
<!-- Generated by mkcapsule --manifest 2026-09-24T13:13:49Z -->
<!-- DO NOT EDIT — re-run mkcapsule --manifest to refresh. -->
<!-- Hand-written justifications and immutability notes live -->
<!-- in MANIFEST.md alongside this auto-generated index. -->
@@ -25,7 +25,7 @@
| `init-l8-volatile.4th` | 4810, 4811, 4812, 4813 | `0x98caabbbd92abac4` | yes |
| `init.4th` | 2049, 2050, 2057 | `0xea038ba684c53443` | yes |
| `lib.4th` | 4050 | `0x4b216635c359ef73` | yes |
| `multiuser-doe.4th` | 5044, 5045, 5046, 5047, 5048, 5049, 5050, 5051, 5052, 5053, 5054, 5055 | `0x97cd087a36d60cdc` | yes |
| `multiuser-doe.4th` | 5044, 5045, 5046, 5047, 5048, 5049, 5050, 5051, 5052, 5053, 5054, 5055 | `0x2b268decf6065736` | yes |
| `per-isa-doe.4th` | 5117, 5119 | `0xab13edecabfd78db` | yes |
| `sdk.4th` | 5109, 5110, 5111, 5112, 5113, 5114, 5115 | `0x008fdbbb62c94a3a` | yes |
| `turtle.4th` | 5100, 5101, 5102, 5103, 5104, 5105, 5106, 5107, 5108 | `0x4d470418ca543365` | yes |
@@ -334,18 +334,18 @@
| 5001 | `fabric.4th` | `0x9d9489cbeca4099b` | ok |
| 5002 | `fabric.4th` | `0x9d9489cbeca4099b` | ok |
| 5043 | `workload-calib1.4th` | `0x3b9f2d17b554fabc` | ok |
| 5044 | `multiuser-doe.4th` | `0x97cd087a36d60cdc` | ok |
| 5045 | `multiuser-doe.4th` | `0x97cd087a36d60cdc` | ok |
| 5046 | `multiuser-doe.4th` | `0x97cd087a36d60cdc` | ok |
| 5047 | `multiuser-doe.4th` | `0x97cd087a36d60cdc` | ok |
| 5048 | `multiuser-doe.4th` | `0x97cd087a36d60cdc` | ok |
| 5049 | `multiuser-doe.4th` | `0x97cd087a36d60cdc` | ok |
| 5050 | `multiuser-doe.4th` | `0x97cd087a36d60cdc` | ok |
| 5051 | `multiuser-doe.4th` | `0x97cd087a36d60cdc` | ok |
| 5052 | `multiuser-doe.4th` | `0x97cd087a36d60cdc` | ok |
| 5053 | `multiuser-doe.4th` | `0x97cd087a36d60cdc` | ok |
| 5054 | `multiuser-doe.4th` | `0x97cd087a36d60cdc` | ok |
| 5055 | `multiuser-doe.4th` | `0x97cd087a36d60cdc` | ok |
| 5044 | `multiuser-doe.4th` | `0x2b268decf6065736` | ok |
| 5045 | `multiuser-doe.4th` | `0x2b268decf6065736` | ok |
| 5046 | `multiuser-doe.4th` | `0x2b268decf6065736` | ok |
| 5047 | `multiuser-doe.4th` | `0x2b268decf6065736` | ok |
| 5048 | `multiuser-doe.4th` | `0x2b268decf6065736` | ok |
| 5049 | `multiuser-doe.4th` | `0x2b268decf6065736` | ok |
| 5050 | `multiuser-doe.4th` | `0x2b268decf6065736` | ok |
| 5051 | `multiuser-doe.4th` | `0x2b268decf6065736` | ok |
| 5052 | `multiuser-doe.4th` | `0x2b268decf6065736` | ok |
| 5053 | `multiuser-doe.4th` | `0x2b268decf6065736` | ok |
| 5054 | `multiuser-doe.4th` | `0x2b268decf6065736` | ok |
| 5055 | `multiuser-doe.4th` | `0x2b268decf6065736` | ok |
| 5056 | `workload-5-lite.4th` | `0x05c651e140ef667b` | ok |
| 5057 | `workload-5-lite.4th` | `0x05c651e140ef667b` | ok |
| 5058 | `workload-1-lite.4th` | `0xbc13567fea25063a` | ok |
@@ -116,6 +116,17 @@ int sk_vm_switch_signal_unregister(VMUuid vm_id);
* mail is delivered on their own stacks, by a switch). */
int sk_vm_switch_signal_is_participant(VMUuid vm_id);
/* FABRIC-3.9.0.md §2 (Stage O-A, 2026-09-24): the observer role. The
* Tripod (Hera, Artemis, Hestia) is marked by the kernel at registration --
* never by anything a VM executes. Observers are outside the compudynamic
* comparison (never heat candidates, never charged, never in a floor) and
* act on events: at a clean boundary an observer with mail (or, for Hera,
* console input) goes ahead of every participant, and an observer acting
* on an event is not displaced (ruling O-1). is_observer() is 0 for an
* unregistered VM. */
void sk_vm_switch_signal_set_observer(VMUuid vm_id);
int sk_vm_switch_signal_is_observer(VMUuid vm_id);
/* A fresh, unique wait stamp (mainline, single writer). kernel_hermes.c
* stamps each admitted message with one. */
uint64_t sk_vm_switch_signal_next_stamp(void);
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+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();