diff --git a/FABRIC.md b/FABRIC.md index e4f9e07..4d71b14 100644 --- a/FABRIC.md +++ b/FABRIC.md @@ -598,7 +598,27 @@ works: `adaptive_check_accumulator` / `adaptive_pending` (`include/vm.h:113-114` **DECIDED** unless argued — it is a constraint inherited from what the system already claims, not a new preference. -### 16.5 What this implies about order +#### Corrected by the GAP-A1 ruling — the tick is virtual + +The rule above ("fire on tick count, never on elapsed wall time") was necessary but not +sufficient, and its inference — *same tick ordinal → same hash* — was unsound. The hash +covers `execution_heat`, which is co-written by **two streams**: word executions and engine +ticks. A hardware timer makes the *interleaving* of those streams wall-clock-dependent +under TCG, so same-per-tick actions do not compose into the same hash. See §25.7.1 GAP-A1 +for the full argument. + +**RULED 2026-08-03:** + +> **The engine's tick is a virtual tick — a pure, deterministic function of the execution +> stream.** This is what exists today (`vm_tick()` paced every N executions) and it is why +> parity holds today. The hardware heartbeat is the TIME-TRUST instrument, the idle wake +> source, and the driver of **nothing that feeds patron state.** When the system is idle, +> the REPL poll loop pumps virtual ticks so TTLs still expire in real time — a context in +> which parity was never claimed. + +Phase 0's timer bring-up remains fully justified: it makes the instrument real on three +ISAs instead of one, and it is the substrate SMP will eventually need. What it does not do +is drive the engine. Whatever step one turns out to be, it now has a floor under it: real timer interrupts and a real IRQ return path on all three ISAs. §10's sequencing (Hermes first, as the proving @@ -654,13 +674,18 @@ Mapped per patron: #### Two measures, one clock -This does **not** mean two clocks. Both mechanisms advance off the same tick — the -adaptive heartbeat. TTL decrements on a tick; heat decays on a tick. They are two different +This does **not** mean two clocks. Both mechanisms advance off the same tick — **the +virtual tick of §16.4 as ruled**, a deterministic function of the execution stream, not the +hardware heartbeat. TTL decrements on a tick; heat decays on a tick. They are two different *readings* of one counter, not two independent time sources. -That is not a tidiness preference, it is forced. §16.4 requires the engine to fire on tick -count so that the same input reproduces the same dictionary hash. Two independent clocks -would be two independent sources of nondeterminism and parity would not survive it. +That is not a tidiness preference, it is forced — twice over. §16.4 requires the engine to +fire deterministically so the same input reproduces the same dictionary hash. And the +mechanisms cannot be split across clocks: TTL expiry has side effects on the instruction +stream (a message expiring versus delivered changes what runs next), so a wall-clock TTL +would corrupt heat downstream even if heat itself stayed execution-paced. One virtual +clock for everything that touches patron state; the hardware heartbeat observes and wakes, +never drives. > **One tick. Two measures. Three mechanisms.** @@ -1034,15 +1059,17 @@ dispatch function over a closed tag set, not in the engine asking patrons what t ### 18.4 One tick -L0 advances on the adaptive heartbeat. Everything derived from time is derived from that -one counter: +L0 advances on **the virtual tick** — a deterministic function of the execution stream, per +the §16.4 ruling. Everything derived from time is derived from that one counter: - TTL decrements per tick (messages, ACLs) - Heat decays per tick (blocks, words) -Two measures, one clock — see §17.1. §16.4 forces this: the engine must fire on tick count -for the same input to reproduce the same dictionary hash, and two independent time sources -would be two independent sources of drift. +Two measures, one clock — see §17.1. §16.4 as ruled forces this: patron state must advance +deterministically for the same input to reproduce the same dictionary hash, and the +hardware heartbeat cannot supply that, because its interleaving with the instruction +stream is wall-clock-dependent. The heartbeat's roles are the TIME-TRUST instrument and +the idle wake source; when the system idles, the REPL poll loop pumps the virtual tick. ### 18.5 CLOSED — the adaptive rate does not break determinism, and here is why @@ -2018,12 +2045,14 @@ on until there is a tick on all three architectures (§16.1, §16.5).* *Done when:* `heartbeat_ticks()` advances on aarch64 at the configured rate. - [ ] **0.8 — Converge the three architectures on one tick path.** - One `heartbeat_tick()` call site per architecture; ISR does counter and flag only; - the engine runs in the mainline off the pending flag. Reuse the existing - `adaptive_check_accumulator` / `adaptive_pending` pattern rather than inventing a second. - *Done when:* all three architectures drive the same bottom half, and no loop math runs in - interrupt context. - *Refs:* §18.4, §21.2. + One `heartbeat_tick()` call site per architecture; the ISR does counter, timestamp and + flag only. **Per the GAP-A1 ruling, the hardware tick drives instrumentation only:** the + bottom half services TIME-TRUST bookkeeping, and the engine (`vm_tick()`, decay, + inference) stays on the virtual tick — execution-paced, exactly as today. Nothing that + feeds patron state reads the hardware counter. + *Done when:* all three architectures drive the same TIME-TRUST bottom half; no loop math + runs in interrupt context; and `vm_tick()`'s call sites are unchanged. + *Refs:* §16.4 (as ruled), §18.4, §21.2. - [ ] **0.9 — Write the concurrency constraint at the mutex stub.** Add a comment at `src/starkernel/vm/host/shim.c:415` stating that the no-op is correct @@ -2103,12 +2132,16 @@ document and committing that amendment as its own item.* ## 25.3 Phase 2 — Prepare the existing physics -- [ ] **2.1 — Restate heat transfer on tick count.** +- [ ] **2.1 — Restate heat transfer on the virtual tick.** `vm_physics_touch()` scales transfers by wall-clock elapsed time - (`capsule_vm_physics.c:272`). Restate it on tick count. + (`capsule_vm_physics.c:272`). Restate it on **the virtual tick** — the execution-derived + counter of §16.4 as ruled, not the hardware heartbeat, whose interleaving with execution + is wall-clock-dependent and would leave the acceptance below unachievable (§25.7.1 + GAP-A1). *Done when:* no wall-clock value influences heat, **and the same capsule booted twice - produces an identical fleet heat sum across the two runs.** - *Refs:* §18.5, §19.6 #3. + produces an identical fleet heat sum across the two runs** — achievable now that both + the touch points and the elapsed-tick values are deterministic functions of execution. + *Refs:* §16.4 (as ruled), §18.5, §19.6 #3. > **Acceptance corrected.** This item previously accepted on the dictionary-hash > double-boot check from 0.10. That cannot detect this work: §18.5 establishes that @@ -2219,7 +2252,14 @@ so.* for what would break a lower-capability model working the punch list. Nothing here has been fixed. GAP-A1 needs a ruling before item 0.8 is coded; the rest are queued for triage.* -#### GAP-A1 — §16.4's central inference is unsound. NEEDS RULING BEFORE PHASE 0 CODING. +#### GAP-A1 — §16.4's central inference is unsound. ~~NEEDS RULING~~ **RULED 2026-08-03: virtual tick.** + +> **Applied.** The recommended resolution below was adopted by Captain Bob. §16.4, §17.1 +> and §18.4 now carry the ruling; items 0.8 and 2.1 were reworded to it. Item 0.10 needed +> no change: with the engine staying execution-paced, its double-boot dict-hash check is a +> valid regression guard and the amd64-as-control framing is accurate again, since 0.8 no +> longer touches engine plumbing on any architecture. The argument below is preserved as +> the record of why. §16.4 claims: *"same input → same tick ordinal → same reap and inference events → same hash."* The last arrow is invalid. The hash covers `execution_heat`, which is co-written by