FABRIC.md: rule GAP-A1 — the engine tick is virtual

Captain Bob's ruling on §25.7.1 GAP-A1, adopting the recommended resolution.

The engine's tick is a virtual tick: a pure, deterministic function of the
execution stream, which is what exists today and why parity holds today. The
hardware heartbeat is the TIME-TRUST instrument and the idle wake source, and
drives nothing that feeds patron state. When the system idles, the REPL poll
loop pumps virtual ticks so TTLs still expire in real time, in a context where
parity was never claimed.

Applied to: §16.4 (correction appended -- the fire-on-tick-count rule was
necessary but not sufficient, since the hash measures the composition of the
instruction and tick streams, not the engine's schedule alone); §17.1 (the one
clock is the virtual tick, and the mechanisms cannot be split across clocks
because TTL expiry has instruction-stream side effects); §18.4 (L0 advances on
the virtual tick); item 0.8 (disambiguated -- the hardware tick drives
instrumentation only, vm_tick call sites unchanged); item 2.1 (transfer
restates onto the virtual tick, which is what makes its identical-sum
acceptance achievable).

Item 0.10 needed no change: with the engine execution-paced, its double-boot
check is a valid regression guard and amd64 is genuinely a control again.

Phase 0's timer bring-up stands: it makes the instrument real on three ISAs
and is the substrate SMP will need. It does not drive the engine.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Robert Allan James
2026-08-03 11:26:38 -04:00
co-authored by Claude Fable 5
parent e8f8b098ed
commit 11c8678477
+62 -22
View File
@@ -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