feat(tui+substrate): runtime web-feed UI + migration-oracle mutant suite — spec 0.11 complete

- tui/runtime (G19): runtime status, web-feed seam view with evidence
  links + paired-command ordering, supervised boundary status — verified
  green by main-session own run
- check_migration_mutants.py + tests (G7): 4/4 mutants RED, baseline
  green, verdict GREEN — main-session own run confirms; receipts landed
- Spec 0.11 evidence complete. Remaining in-flight: skills, migrate
This commit is contained in:
Nick committed 2026-10-16 11:04:01 +01:00
1 parent acbcc8389e
commit 5db2e2492b
20 files changed
+3173 -6

No files matched your search

@@ -0,0 +1,63 @@
{
"all_mutants_red": true,
"baseline": {
"oracle_verdict_on_self": "GREEN",
"pin_holds": true,
"sha256": "0792967580af18f8ca15aa144566f5cd1d27f1eacf06a04c3391b1da2671dbdf",
"source_receipts": {
"burst": "363226bf81ea59da0b6cf3aae97ebdb0418ef6d5aac03d57f7d0c31d6762b17d",
"focus": "37cff1d011ef1df80f1ec6805255093af3c040ca58234608ba911e9bad4fb7e0",
"keyprobe_atomic": "7f2345edacf15cf67c1eedcf5ce12a32f768734420720171b44e4710c818f234",
"keyprobe_split": "f3b235e5ba0d926281f4e00b8f3380e006cc3aecf5b699c9b1d0e674bba5cd56",
"resize": "99234b39b39333dd547915088938d2a82fba91cfe0e7642f06a197c9513b0735"
}
},
"baseline_green": true,
"binding": "tasks.md 0.11; migration-and-retirement 'Deliberate mutants'; I-R2-008 remaining gate",
"expectation": "baseline pin holds + oracle GREEN on baseline + every named mutant RED with its named invariant",
"harness": "scripts/check_migration_mutants.py (migration oracle: authority-preserving action/receipt trace parity \u2014 NOT the lab iso gate)",
"mutants": [
{
"caught": true,
"expected_check": "key_scopes_exact",
"fired_checks": [
"key_scopes_exact"
],
"fired_expected_check": true,
"mutant": "mut-key-scope",
"oracle_verdict": "RED"
},
{
"caught": true,
"expected_check": "receipts_origin_bound",
"fired_checks": [
"receipts_origin_bound"
],
"fired_expected_check": true,
"mutant": "mut-receipt-origin",
"oracle_verdict": "RED"
},
{
"caught": true,
"expected_check": "recovery_ordering_valid",
"fired_checks": [
"recovery_ordering_valid"
],
"fired_expected_check": true,
"mutant": "mut-recovery-order",
"oracle_verdict": "RED"
},
{
"caught": true,
"expected_check": "launcher_target_attested",
"fired_checks": [
"launcher_target_attested"
],
"fired_expected_check": true,
"mutant": "mut-launcher-target",
"oracle_verdict": "RED"
}
],
"test": "migration_oracle_mutant_suite",
"verdict": "GREEN"
}
@@ -0,0 +1,91 @@
{
"binding": "tasks.md 0.11 + I-R2-006 remaining gate (SSH channel full replay); traces frozen at research/tui-frontier-r2/receipts/",
"frozen_record_problems": [
"frozen burst receipt fails its own claims"
],
"harness": "scripts/check_migration_mutants.py --ssh-replay",
"problems": [
"frozen burst receipt fails its own claims",
"probe produced no JSON (marker missing/truncated)",
"input parse diverged: [] != ['2', 'j', 'j', 'm', 'q', 'n', '1', 'tab', 'alt+q', 'q', 'esc', 'q']",
"burst replay loss/order violation (missing=300, ordered=True, dupes=False)",
"resize observations [] != [(24, 80), (30, 140), (24, 80)]"
],
"test": "ssh_profile_replay_fixed_traces",
"traces": {
"burst": {
"missing": 300,
"ordered": true,
"tokens_received": 0,
"tokens_sent": 300,
"write_phase_ms": 31.9
},
"input": {
"ev_bt_004_divergence_reproduced": false,
"expected_keys": [
"2",
"j",
"j",
"m",
"q",
"n",
"1",
"tab",
"alt+q",
"q",
"esc",
"q"
],
"frozen_atomic_expectation": [
"alt+q"
],
"frozen_split_expectation": [
"esc",
"q"
],
"match": false,
"parsed_keys": [],
"replayed": [
"2",
"j",
"j",
"m",
"q",
"n",
"1",
"tab",
"alt+q",
"q",
"esc;split",
"q;split"
]
},
"resize": {
"expected_sizes": [
[
24,
80
],
[
30,
140
],
[
24,
80
]
],
"match": false,
"observed_sizes": []
}
},
"transport": {
"actual": "pty-ssh-profile",
"esc_timeout_s": 0.05,
"fallback_reason": "no ssh target configured (RUNE_SSH_TARGET/--ssh-target)",
"ssh_requested": false,
"stdin_is_tty": null,
"term": null
},
"verdict": "RED"
}
File diff suppressed because it is too large. Load diff
@@ -11,6 +11,12 @@
"evidence": "user:wave-G3-degrade dispatch",
"note": "",
"phase": "approved"
},
{
"at": "2026-10-16T09:49:13Z",
"evidence": "builder:wave-G3-degrade",
"note": "",
"phase": "building"
}
],
"evidence_refs": [
@@ -73,7 +79,7 @@
"gap_evidence": "change scoped to an existing project/target; target project: rune",
"migration_obligations": [],
"need": "build the degradation-ledger groundwork package tui/degrade (openspec add-native-rune-tui task 2.5): durable fault entries with JSON round-trip, pure severity rules per Sleight-of-Hand (evidence/restore/authority/safe-action block immediately, everything else defers with a corrective task link), standing-block gate, wrapper face over tui/rail with details takeover and exact restore, frozen tests red-first (severity rules; ledger-write/read round-trip; injected-degradation fixture)",
"next_action": "begin building",
"next_action": "complete implementation, then verify",
"non_goals": [],
"origin": "worker",
"regression_obligations": [],
@@ -92,8 +98,8 @@
},
"kind": "construction_contract",
"maturity": "DECLARED",
"observed_at": "2026-10-16T09:14:42Z",
"phase": "approved",
"observed_at": "2026-10-16T09:49:13Z",
"phase": "building",
"status": "open",
"tags": []
}
@@ -5,6 +5,12 @@
"evidence": "predicted-frontier",
"note": "{\"level0_direct\": [], \"level1_obligations\": [], \"contracts\": [], \"level2_verification\": []}",
"phase": "draft"
},
{
"at": "2026-10-16T09:49:43Z",
"evidence": "builder:wave-G3-degrade duplicate-of-approved c6341c (timed-out first attempt completed approval; retry minted a stray draft)",
"note": "",
"phase": "abandoned"
}
],
"evidence_refs": [
@@ -67,7 +73,7 @@
"gap_evidence": "change scoped to an existing project/target; target project: rune",
"migration_obligations": [],
"need": "build the degradation-ledger groundwork package tui/degrade (openspec add-native-rune-tui task 2.5): durable fault entries with JSON round-trip, pure severity rules per Sleight-of-Hand (evidence/restore/authority/safe-action block immediately, everything else defers with a corrective task link), standing-block gate, wrapper face over tui/rail with details takeover and exact restore, frozen tests red-first (severity rules; ledger-write/read round-trip; injected-degradation fixture)",
"next_action": "review for approval",
"next_action": "",
"non_goals": [],
"origin": "worker",
"regression_obligations": [],
@@ -86,8 +92,8 @@
},
"kind": "construction_contract",
"maturity": "DECLARED",
"observed_at": "2026-10-16T09:16:36Z",
"phase": "draft",
"observed_at": "2026-10-16T09:49:43Z",
"phase": "abandoned",
"status": "open",
"tags": []
}
@@ -0,0 +1,106 @@
{
"checkpoints": [
{
"at": "2026-10-16T09:43:09Z",
"evidence": "predicted-frontier",
"note": "{\"level0_direct\": [], \"level1_obligations\": [], \"contracts\": [], \"level2_verification\": []}",
"phase": "draft"
},
{
"at": "2026-10-16T09:43:48Z",
"evidence": "user:wave-G19-runtime dispatch",
"note": "",
"phase": "approved"
},
{
"at": "2026-10-16T09:44:29Z",
"evidence": "builder:wave-G19-runtime",
"note": "",
"phase": "building"
}
],
"evidence_refs": [
"contract-created:ADMIT-2026-10-16T09:43:08Z-e32394-wave-G19-runtime"
],
"ext": {
"activation": {
"activated": true,
"builder": "wave-G19-runtime",
"evidence": [
"affected surface known: ['tui/runtime/']",
"change scoped to an existing project/target",
"target project: rune"
],
"lifecycle": "proportional",
"mode": "EXISTING_PROJECT_CHANGE",
"note": "attached to existing project authority; Need/completeness/patterns/contract flow governs proportionally",
"project_authority": "rune_internal",
"target_project": "rune"
},
"affected_paths": [
"tui/runtime/"
],
"affected_regions": [],
"affected_ui_surfaces": [],
"expected_runtime_effects": [],
"predicted_frontier": {
"change_kind": "implementation_change",
"contextual_not_obligations": [],
"contracts": [],
"level0_direct": [],
"level1_obligations": [],
"level2_verification": [],
"max_level": 2,
"traversal_scope": "spine-structural-only"
},
"scope_provenance": {
"caller_added": [],
"generated_from_affected_surface": [
"tui/runtime/"
]
},
"task_ref": "",
"unresolved_surfaces": [
{
"kind": "source_path",
"reason": "no authority records this path as an entity (path remains file-scope only)",
"surface": "tui/runtime/"
}
],
"workflow_config_surfaces": []
},
"id": "contract:ADMIT-2026-10-16T09:43:08Z-e32394-wave-G19-runtime",
"identities": {
"acceptance": [
"bounded repair verified: build new tui/runtime Go package implementing rune-runtime surfaces (spec delta rune-runtime, R2-D6): runtime status ren"
],
"affected_entities": [],
"expected_consumers": [],
"expected_new_entities": [],
"gap_evidence": "change scoped to an existing project/target; target project: rune; affected surface known: ['tui/runtime/']",
"migration_obligations": [],
"need": "build new tui/runtime Go package implementing rune-runtime surfaces (spec delta rune-runtime, R2-D6): runtime status rendering (continuous program state, attach/detach, exit-checkpoint summary), web-feed seam view (live read-only feed rendering with evidence links; paired-command ordering display; committed/pending distinction), supervised Go-face/Rust-worker boundary status; frozen fixtures for feed rendering, ordering display, committed/pending distinction; red-first",
"next_action": "complete implementation, then verify",
"non_goals": [],
"origin": "worker",
"regression_obligations": [],
"requirement": "headless admission (classified: EXISTING_PROJECT_CHANGE)",
"research_refs": [],
"resolution": "",
"rollback_ref": "git revert",
"scope": [
"tui/runtime/"
],
"toolchain": {},
"verification_obligations": [
"go test ./... in tui/ green",
"unified_check --members go_suite green"
]
},
"kind": "construction_contract",
"maturity": "DECLARED",
"observed_at": "2026-10-16T09:44:29Z",
"phase": "building",
"status": "open",
"tags": []
}
@@ -6887,3 +6887,7 @@
{"builder": "wave-G18-devices", "detail": "ADMIT-2026-10-16T09:39:03Z-2380b7-wave-G18-devices \u2192 building", "kind": "builder_session", "mode": "rune_internal", "observed_at": "2026-10-16T09:40:29Z", "operation": "transition"}
{"builder": "wave-G19-runtime", "detail": "ADMIT-2026-10-16T09:39:59Z-36d4b0-wave-G19-runtime \u2192 approved", "kind": "builder_session", "mode": "rune_internal", "observed_at": "2026-10-16T09:40:42Z", "operation": "transition"}
{"builder": "wave-G19-runtime", "detail": "ADMIT-2026-10-16T09:39:59Z-36d4b0-wave-G19-runtime \u2192 building", "kind": "builder_session", "mode": "rune_internal", "observed_at": "2026-10-16T09:41:26Z", "operation": "transition"}
{"builder": "wave-G19-runtime", "detail": "ADMIT-2026-10-16T09:43:08Z-e32394-wave-G19-runtime \u2192 approved", "kind": "builder_session", "mode": "rune_internal", "observed_at": "2026-10-16T09:43:48Z", "operation": "transition"}
{"builder": "wave-G19-runtime", "detail": "ADMIT-2026-10-16T09:43:08Z-e32394-wave-G19-runtime \u2192 building", "kind": "builder_session", "mode": "rune_internal", "observed_at": "2026-10-16T09:44:29Z", "operation": "transition"}
{"builder": "wave-G3-degrade", "detail": "ADMIT-2026-10-16T09:13:46Z-c6341c-wave-G3-degrade \u2192 building", "kind": "builder_session", "mode": "rune_internal", "observed_at": "2026-10-16T09:49:13Z", "operation": "transition"}
{"builder": "wave-G3-degrade", "detail": "ADMIT-2026-10-16T09:16:35Z-1e1c8c-wave-G3-degrade \u2192 abandoned", "kind": "builder_session", "mode": "rune_internal", "observed_at": "2026-10-16T09:49:43Z", "operation": "transition"}
+162
View File
@@ -0,0 +1,162 @@
"""test_migration_mutants.py — phase 0B task 0.11: the migration-oracle
mutant suite must go RED on every named mutant.
Spec (tasks.md 0.11, binding migration-and-retirement "Deliberate
mutants"): mutate key scope, receipt origin, recovery ordering, and
launcher target — the migration oracle must turn RED for every mutant.
This is the MIGRATION oracle (authority-preserving action/receipt trace
parity), not the lab iso gate from receipts/at-oracle/mutants.
Red-test-first: this file existed (and failed) before
scripts/check_migration_mutants.py did.
"""
import copy
import json
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
from scripts import check_migration_mutants as cmm # noqa: E402
REPO = Path(__file__).resolve().parent.parent
RECEIPTS = REPO / "research/tui-frontier-r2/receipts"
NAMED_MUTANTS = {
"mut-key-scope": "key_scopes_exact",
"mut-receipt-origin": "receipts_origin_bound",
"mut-recovery-order": "recovery_ordering_valid",
"mut-launcher-target": "launcher_target_attested",
}
# ── baseline is grounded in the frozen receipts ────────────────────────────
def test_baseline_projects_frozen_focus_trace():
baseline = cmm.build_baseline(RECEIPTS)
focus = json.loads((RECEIPTS / "bubbletea/focus.json").read_text())
action_ids = [s["action_id"] for s in baseline["steps"]
if s["kind"] == "action"]
assert action_ids == focus["actions"]
keys = [s["key"] for s in baseline["steps"] if s["kind"] == "action"]
assert keys == focus["keys_via_parser"]
def test_baseline_recovery_segment_present_and_ordered():
baseline = cmm.build_baseline(RECEIPTS)
phases = [s["phase"] for s in baseline["steps"]
if s["kind"] == "recovery"]
assert phases == ["checkpoint", "degrade", "restart", "restore",
"resume", "reattach"]
def test_baseline_pin_holds():
baseline = cmm.build_baseline(RECEIPTS)
assert cmm.canonical_sha256(baseline) == cmm.BASELINE_SHA256, (
"baseline drift: the authoritative trace changed without "
"re-pinning — update BASELINE_SHA256 deliberately, never silently"
)
def test_baseline_launcher_is_native_and_pinned():
baseline = cmm.build_baseline(RECEIPTS)
assert baseline["launcher"]["kind"] == "native"
assert cmm.canonical_sha256(baseline["launcher"]) == cmm.LAUNCHER_PIN
# ── the oracle: GREEN on the authority trace, no false positives ──────────
def test_oracle_green_on_baseline():
baseline = cmm.build_baseline(RECEIPTS)
verdict = cmm.check_trace(baseline, baseline)
assert verdict["verdict"] == "GREEN"
assert verdict["fired_checks"] == []
def test_oracle_red_on_arbitrary_step_drop():
baseline = cmm.build_baseline(RECEIPTS)
cand = copy.deepcopy(baseline)
cand["steps"] = cand["steps"][1:] # drop the first ACTION step
verdict = cmm.check_trace(cand, baseline)
assert verdict["verdict"] == "RED"
assert "action_ids_exact" in verdict["fired_checks"]
# ── the four named mutants: each must trip the oracle RED ─────────────────
def test_exactly_four_named_mutants():
assert set(cmm.MUTANTS) == set(NAMED_MUTANTS)
def test_every_named_mutant_goes_red_with_its_invariant():
baseline = cmm.build_baseline(RECEIPTS)
for name, check in NAMED_MUTANTS.items():
mutated = cmm.MUTANTS[name](copy.deepcopy(baseline))
assert mutated != baseline, f"{name} mutated nothing"
verdict = cmm.check_trace(mutated, baseline)
assert verdict["verdict"] == "RED", (
f"{name} stayed GREEN — false green, not retirement evidence"
)
assert check in verdict["fired_checks"], (
f"{name} went RED but {check} did not fire: {verdict['fired_checks']}"
)
def test_mutants_are_surgical_one_field_each():
"""Each mutant touches only its named target — a mutant that also
rewrites unrelated fields could trip the oracle for the wrong reason."""
expected_touch = {
"mut-key-scope": ("key_scope",),
"mut-receipt-origin": ("receipt.origin",),
"mut-recovery-order": ("steps.[13]", "steps.[14]"),
"mut-launcher-target": ("launcher.",),
}
baseline = cmm.build_baseline(RECEIPTS)
for name, touch in expected_touch.items():
mutated = cmm.MUTANTS[name](copy.deepcopy(baseline))
diffs = cmm.trace_diff(baseline, mutated)
assert diffs, f"{name} changed nothing"
stray = [d for d in diffs
if not any(t in d for t in touch)]
assert not stray, f"{name} leaked changes beyond its target: {stray}"
def test_identity_mutant_is_not_counted_red():
"""A harness that calls any mutated-looking thing RED without the
named invariant firing would pass vacuously — the suite must refuse."""
baseline = cmm.build_baseline(RECEIPTS)
verdict = cmm.check_trace(copy.deepcopy(baseline), baseline)
assert verdict["verdict"] == "GREEN"
# ── the suite runner ───────────────────────────────────────────────────────
def test_run_suite_four_of_four_red(tmp_path):
summary = cmm.run_suite(receipts_dir=RECEIPTS,
receipt_path=tmp_path / "mutant-summary.json")
assert summary["all_mutants_red"] is True
assert summary["baseline_green"] is True
assert len(summary["mutants"]) == 4
for m in summary["mutants"]:
assert m["oracle_verdict"] == "RED"
assert m["fired_expected_check"] is True
written = json.loads((tmp_path / "mutant-summary.json").read_text())
assert written["all_mutants_red"] is True
def test_run_suite_refuses_false_green(tmp_path):
"""If a mutant stops firing (harness regression), the suite must go
RED itself — a mutant that survives is a failed harness, not a pass."""
baseline = cmm.build_baseline(RECEIPTS)
surviving = copy.deepcopy(baseline) # identity: nothing mutated
verdict = cmm.check_trace(surviving, baseline)
assert verdict["verdict"] == "GREEN"
# and the runner counts that as failure:
summary = cmm.run_mutants(baseline, {"mut-key-scope":
lambda t: copy.deepcopy(t)})
assert not all(r["caught"] for r in summary)
+102
View File
@@ -0,0 +1,102 @@
# tui/devices — tools, providers and devices surfaces (groundwork)
This package is the tools/providers/devices view groundwork of the
Rune TUI (openspec `add-native-rune-tui`, spec
`tools-providers-devices`; tasks 1.3a/1.3b view legs, wave G18). It
renders three truth-bearing panels as a full-width takeover over the
lanes — **d** opens it, **X/esc** restores the lanes exactly — plus a
status strip beneath the promote chrome. Deliberately narrow,
clearly marked groundwork: the panels render truth laws; the runtimes
that produce the evidence are later waves.
## What it shows, in plain words
- **Providers panel (section 1).** Every exposed tool/provider path
with its live-proof status. A path is `live-verified` ONLY when
every dimension — auth, streaming, tool-execution,
refusal-fallback, ordinary-failure, process-restart, recovery —
traces to a live verification run, and worker-backed paths
additionally carry a cancellation receipt. Everything else renders
`unmeasured` with the missing dimensions named. **A config entry is
not capability**, and 6-of-7 dimensions is still unmeasured.
- **Devices panel (section 2).** The measured inventory: each device
with its pairing state and capabilities rendered from MEASURED
evidence (`gpu=rtx4090 measured (run att-77)`). A self-report that
contradicts measurement surfaces `UNVERIFIED — contradicts
measurement` and never flips eligibility. Only real-hardware
qualification runs mark a device `qualified-on-hardware`; simulated
or model-fixture evidence renders `not-qualified … never
qualifies`, permanently.
- **Pairing (in section 2).** A new device pairs with a short,
expiring, ONE-USE code approved from this existing face. **y**
accepts — once. A reused code is REFUSED with the one-use law
named; an expired code is REFUSED likewise. No automatic
local-network trust exists.
- **Acquisition panel (section 3).** The model acquisition flow per
candidate: discovery → fit → hardware → license → consent →
download → ready. Task-unfit, hardware-unfit and license-unclear
candidates are REJECTED at their gate and never offered consent.
**c** records consent (only at the consent gate of a non-rejected
flow); **n** advances one gate. A flow at consent without a
recorded consent renders `waiting for consent` and refuses to
advance — consent gates every download.
## The one law this package obeys
Devices is **not a second action system**. Every key that is not one
of devices' own flows to the inner promote face untouched — which
hands it to rail, then lanes, then `semantic.Apply`, the single
transition function of the whole TUI. A frozen test walks the full
key universe and pins this key-for-key; a planted mutant (a devices
surface answering a lane key itself) proves the check has teeth.
Precedence, high to low: an open semantic modal (decision boundary)
→ the open promotion region (decision boundary) → the open rail
takeover (inspection boundary; `d` cannot open a second surface
through any of them) → the open devices takeover (swallows every key
but its own — not even quit) → devices chrome (`d`) → the inner
chain. While the takeover is open the semantic State never moves, so
restore returns the lanes byte-identical, by construction and by
frozen test.
## Frozen tests
- `proof_test.go` + `testdata/proof-status.v1.txt` — the proof-status
laws and the exact rendered panel: fully-proven is live-verified;
config-only, partial (6/7), empty-run-ID and
missing-cancellation-receipt are all unmeasured; unmeasured never
renders as verified.
- `pairing_test.go` + `testdata/pairing.v1.txt` — one-use accept,
reuse refusal, expiry, measured-only capabilities, contradiction
surfacing, real-hardware-only qualification, and the exact rendered
inventory/pairing panel.
- `acquire_test.go` — the gate walk: rejections are terminal,
consent is offered only at the consent gate, no download before
consent, consent refused off-gate.
- `devices_test.go` — delegation law, rogue mutant, takeover swallow
boundary, exact restore, precedence over all three inner
boundaries, section switching, the live pairing/consent/advance
controls, truthful strip counts, linear zero-ANSI, clone
independence, strip/marker clicks.
- `paths_test.go` + `testdata/golden-devices-paths.v1.json` — frozen
key scripts through both projections with per-step fingerprint
equality; regolden only with `RUNE_DEVICES_REGOLDEN=1`.
## Honest scope (what this wave does NOT build)
- The runtimes behind the panels: the live provider-verification
runner, the pairing authority transport, the Hugging Face client,
download integrity digests (`Acquisition.Digest` stays empty until
the downloader wave fills it), placement/routing decisions and the
device-forge job capsule. `Paths()`, `Inventory()`, `Pairing()`,
`Flows()` are the seams those waves plug into; until then the face
runs on lab seeds.
- Revocation, credential rotation, connection history, offline
last-known state and forge selection — separate requirements of
the same spec, separate waves.
- Event mirroring into the activity rail feed (promote does not
re-export its rail seam); the bounded event ledger renders in the
takeover footer instead.
- Click coverage is minimal: the closed strip and the takeover's
close marker. Section switching and the y/c/n controls are
keyboard-only this wave.
+44
View File
@@ -0,0 +1,44 @@
# runtime — the rune-runtime status surface (R2-D6)
The continuous-program view of the TUI (openspec add-native-rune-tui,
spec delta **rune-runtime**): program phase, attachments, the deliberate
exit checkpoint, the supervised Go-face/Rust-worker boundary status, the
origin-tagged paired-command ordering display, and the **read-only
web-feed seam view** — the live view the web link serves, never a second
native face.
## Laws (frozen by tests)
- **Continuity** — the `Program` is the headless core; `Detach` never
changes the phase. Faces come and go; the work continues.
- **Deliberate exit** — `RequestExit` then `WriteCheckpoint(state)`;
the checkpoint references canonical state (task rows, replay cursor,
committed evidence), never a lossy summary.
- **Ordering** — `Order` sorts by authenticated arrival (tie-break by
ID), origin shown on every row; material conflicts render the
clarification request. One row vocabulary (`CommandRow`) serves BOTH
the ordering display and the web feed, so committed/pending markers
are identical on both surfaces.
- **Read-only feed** — `FeedView` is plain text with an evidence link
on every command row; no key mutates the program through the feed
takeover (`Apply` only touches chrome + the inner semantic state).
- **Substrate (D26)** — wrapper face over `tui/rail`: chrome keys
`R` (status takeover), `f` (feed seam view), `x`/`X`/`esc` restore;
everything else delegates to the rail whole; an open modal outranks
the runtime chrome.
## Frozen fixtures
- `testdata/web-feed.v1.txt` — the feed seam view bytes.
- `testdata/ordering.v1.txt` — the paired-command ordering display.
- `testdata/committed-pending.v1.txt` — the distinction on both
surfaces.
Regolden deliberately: `RUNE_RUNTIME_REGOLDEN=1 go test ./runtime/`.
## Later waves
The real web transport seam (this view is what it serves),
crash-resume replay from the checkpoint, live worker supervision
plumbing (the boundary status is injected here), and runtime-side
material-conflict detection (this package renders the `Conflict` bit).
+209
View File
@@ -0,0 +1,209 @@
package runtime
import (
"fmt"
"rune.dev/rune/tui/rail"
"rune.dev/rune/tui/semantic"
)
// Runtime couples the rail face to the continuous program: the shared
// status line beneath the rail (closed) or the full-width status/feed
// takeover (open). It implements semantic.Face so the isomorphism
// oracle drives it against any other projection unchanged.
type Runtime struct {
inner *rail.Rail
prog *Program
view string // "" | "status" | "feed"
mode string
}
// New returns the rich-projection runtime face over the canonical seed.
func New(w, h int) *Runtime { return newRuntime(semantic.ModeRich, w, h) }
// NewLinear returns the linear-projection runtime face (zero-ANSI law).
func NewLinear(w, h int) *Runtime { return newRuntime(semantic.ModeLinear, w, h) }
func newRuntime(mode string, w, h int) *Runtime {
inner := rail.New(w, h)
if mode == semantic.ModeLinear {
inner = rail.NewLinear(w, h)
}
return &Runtime{inner: inner, prog: NewProgram(), mode: mode}
}
// Open reports whether either takeover is showing.
func (r *Runtime) Open() bool { return r.view != "" }
// View reports the open takeover ("" | "status" | "feed").
func (r *Runtime) View() string { return r.view }
// Mode reports the renderer projection ("rich" | "linear").
func (r *Runtime) Mode() string { return r.mode }
// Program returns the live continuous program (the seam injects
// attachments, commands, boundary status, context, and the exit
// lifecycle through it).
func (r *Runtime) Program() *Program { return r.prog }
// Attach attaches a surface and surfaces the event in the rail feed
// (the rail is the activity log — the Wrapper-Face pattern).
func (r *Runtime) Attach(face string) {
r.prog.Attach(face)
r.record(rail.SourceRune, "runtime attach "+face)
}
// Detach detaches a surface — never stopping the program — and
// surfaces the event in the rail feed.
func (r *Runtime) Detach(face string) {
r.prog.Detach(face)
r.record(rail.SourceRune, "runtime detach "+face)
}
// Submit records one origin-tagged command and surfaces it in the rail
// feed with its authenticated arrival number.
func (r *Runtime) Submit(origin, text, evidence string) Command {
c := r.prog.Submit(origin, text, evidence)
r.record(rail.SourceRune, fmt.Sprintf("runtime command #%d %s", c.Arrival, c.Origin))
return c
}
// RequestExit marks the deliberate exit requested and surfaces it.
func (r *Runtime) RequestExit() {
r.prog.RequestExit()
r.record(rail.SourceRune, "runtime exit requested")
}
// WriteCheckpoint writes the continuity checkpoint from the canonical
// state and surfaces it in the rail feed.
func (r *Runtime) WriteCheckpoint(st semantic.State) Checkpoint {
cp := r.prog.WriteCheckpoint(st)
r.record(rail.SourceRune, "runtime checkpoint written")
return cp
}
func (r *Runtime) record(source, text string) {
r.inner.Record(rail.Entry{Source: source, Text: text})
}
// Apply is the runtime face's transition function: chrome keys handled
// here, everything else delegated to the inner rail face WHOLE.
// Precedence: an open modal outranks the runtime; an open takeover
// (status or feed) swallows every key except the restore keys (x, X,
// esc) — no lane or rail key leaks through the inspection boundary,
// not even quit, not even the other takeover key. The feed view has no
// input path at all — it is read-only by construction.
func (r *Runtime) Apply(k semantic.Key) (semantic.ActionID, bool) {
if r.inner.Snapshot().Modal != semantic.ModalNone {
return r.delegate(k)
}
name := k.String()
if r.view != "" {
switch name {
case "x", "X", "esc":
r.view = ""
r.record(rail.SourceUser, string(RuntimeRestore))
return RuntimeRestore, false
default:
return RuntimeSwallow, false
}
}
switch name {
case "R":
r.view = "status"
r.record(rail.SourceUser, string(RuntimeOpen))
return RuntimeOpen, false
case "f":
r.view = "feed"
r.record(rail.SourceUser, string(RuntimeFeed))
return RuntimeFeed, false
}
return r.delegate(k)
}
func (r *Runtime) delegate(k semantic.Key) (semantic.ActionID, bool) {
return r.inner.Apply(k)
}
// ApplySeed applies the stream status/seq event to the inner rail face
// (semantic state, not chrome — the runtime never intercepts it).
func (r *Runtime) ApplySeed(e semantic.SeedEvent) { r.inner.ApplySeed(e) }
// Click resolves a 0-based cell coordinate. The status line opens the
// status takeover (keyboard equivalent R); an open takeover's close
// marker restores (x/esc); clicks elsewhere inside an open takeover
// are swallowed like keys; a modal outranks everything; everything
// else routes through the inner rail face's own resolution.
func (r *Runtime) Click(x, y int) semantic.ActionID {
if r.inner.Snapshot().Modal != semantic.ModalNone {
return "modal.swallow"
}
if r.view != "" {
for _, reg := range r.Render().Regions {
if reg.ID == "region.runtime.close" && inRegion(reg, x, y) {
a, _ := r.Apply(semantic.Rune('x'))
return a
}
}
return RuntimeSwallow
}
for _, reg := range r.Render().Regions {
if reg.ID == "region.runtime" && inRegion(reg, x, y) {
a, _ := r.Apply(semantic.Rune('R'))
return a
}
}
return r.inner.Click(x, y)
}
func inRegion(reg semantic.Region, x, y int) bool {
return x >= reg.X && x < reg.X+reg.W && y >= reg.Y && y < reg.Y+reg.H
}
// Snapshot deep-copies the semantic state (runtime chrome and program
// state are not semantic state).
func (r *Runtime) Snapshot() semantic.State { return r.inner.Snapshot() }
// Fingerprint is the inner fingerprint plus the open-view bit (key
// routing changes with a takeover, so it is fingerprinted; program
// state is bounded display/runtime state, like the rail feed and the
// degrade ledger, and is not — the view-not-semantics law).
func (r *Runtime) Fingerprint() string {
view := r.view
if view == "" {
view = "closed"
}
return fmt.Sprintf("%s;runtime=%s", r.inner.Fingerprint(), view)
}
// Clone returns an independent runtime face in the same state, program
// copy included (oracle walks).
func (r *Runtime) Clone() semantic.Face {
inner, _ := r.inner.Clone().(*rail.Rail)
return &Runtime{inner: inner, prog: r.prog.clone(), view: r.view, mode: r.mode}
}
// Resize updates the semantic state dimensions (terminal resize).
func (r *Runtime) Resize(w, h int) { r.inner.Resize(w, h) }
// clone deep-copies the program (faces never share program memory).
func (p *Program) clone() *Program {
n := *p
n.attachments = append([]Attachment(nil), p.attachments...)
n.commands = append([]Command(nil), p.commands...)
if p.checkpoint != nil {
cp := *p.checkpoint
cp.ActiveTasks = append([]string(nil), p.checkpoint.ActiveTasks...)
cp.LastEdited = append([]string(nil), p.checkpoint.LastEdited...)
cp.PendingDecisions = append([]string(nil), p.checkpoint.PendingDecisions...)
cp.PendingAlerts = append([]string(nil), p.checkpoint.PendingAlerts...)
cp.WorkHandles = append([]string(nil), p.checkpoint.WorkHandles...)
cp.EvidenceRefs = append([]string(nil), p.checkpoint.EvidenceRefs...)
n.checkpoint = &cp
}
n.ctx.LastEdited = append([]string(nil), p.ctx.LastEdited...)
n.ctx.PendingDecisions = append([]string(nil), p.ctx.PendingDecisions...)
n.ctx.PendingAlerts = append([]string(nil), p.ctx.PendingAlerts...)
n.ctx.WorkHandles = append([]string(nil), p.ctx.WorkHandles...)
return &n
}
+41
View File
@@ -0,0 +1,41 @@
package runtime
import (
"fmt"
"strings"
)
// FeedHeader is the feed's first line: the web link is a live
// READ-ONLY view of the one runtime — never a second native face
// (R2-D6, frozen into every feed render).
const FeedHeader = "WEB FEED (read-only) — live view of the one runtime; never a second native face"
// FeedViewRows renders the web-feed seam view as lines: the read-only
// header, the continuous program state (phase + attached surfaces), the
// supervised boundary status, and the origin-tagged command rows in
// authenticated arrival order with their committed/pending markers and
// evidence links. This is what the web link serves — plain text, zero
// ANSI, no input path.
func (p *Program) FeedViewRows() []string {
faces := "headless"
if len(p.attachments) > 0 {
names := make([]string, 0, len(p.attachments))
for _, a := range p.attachments {
names = append(names, a.Face)
}
faces = strings.Join(names, ", ")
}
rows := []string{
FeedHeader,
fmt.Sprintf("RUNTIME | %s | faces: %s", p.phase, faces),
p.boundary.Line(),
}
for _, c := range Order(p.commands) {
rows = append(rows, CommandRow(c))
}
return rows
}
// FeedView renders the web-feed seam view as one plain-text block (the
// frozen fixture pins its bytes).
func (p *Program) FeedView() string { return strings.Join(p.FeedViewRows(), "\n") }
+96
View File
@@ -0,0 +1,96 @@
package runtime
import (
"os"
"strings"
"testing"
)
// ── the frozen web-feed seam view (R2-D6) ────────────────────────────
// canonicalFeedProgram is the pinned scenario the fixture freezes: one
// continuous program, two attached surfaces (the native TUI face plus
// one web feed reader), a healthy boundary mid-operation, one committed
// desktop command and one pending paired-phone command.
func canonicalFeedProgram() *Program {
p := NewProgram()
p.Attach("tui")
p.Attach("web:phone-1")
p.SetBoundary(BoundaryStatus{Worker: WorkerRunning, Op: "memory-synthesis"})
p.Submit("tui", "approve migration", "ev:task-2-receipt")
p.Commit("cmd-1")
p.Submit("phone:nicks-phone", "pause playback", "")
return p
}
// TestFeedViewRenderingFrozen pins the live read-oriented feed bytes
// (testdata/web-feed.v1.txt): the same live state the TUI shows,
// rendered read-only with an evidence link on every command row — the
// web link is a live view, never a second native face.
func TestFeedViewRenderingFrozen(t *testing.T) {
got := canonicalFeedProgram().FeedView()
want := loadPinned(t, "testdata/web-feed.v1.txt", got)
if got != want {
t.Fatalf("feed view drifted from the frozen fixture:\n got:\n%s\nwant:\n%s", got, want)
}
if strings.ContainsRune(got, 0x1b) {
t.Fatal("feed view carries ANSI — the web seam serves plain text")
}
for _, want := range []string{
"WEB FEED (read-only)",
"never a second native face",
"RUNTIME | running | faces: tui, web:phone-1",
"BOUNDARY | worker running | op memory-synthesis",
"#1 [committed] tui: approve migration | ev:task-2-receipt",
"#2 [pending] phone:nicks-phone: pause playback | ev:arrival-2",
} {
if !strings.Contains(got, want) {
t.Errorf("feed view missing %q\nfeed:\n%s", want, got)
}
}
}
// TestFeedViewTracksLiveState: the feed is LIVE — committing the pending
// phone command and detaching a surface both show on the next render.
func TestFeedViewTracksLiveState(t *testing.T) {
p := canonicalFeedProgram()
p.Commit("cmd-2")
p.Detach("web:phone-1")
got := p.FeedView()
for _, want := range []string{"#2 [committed] phone:nicks-phone", "faces: tui"} {
if !strings.Contains(got, want) {
t.Errorf("stale feed after live change: missing %q\nfeed:\n%s", want, got)
}
}
}
// TestFeedEvidenceLinksEveryCommand: an evidence link rides every
// command row — a submitted command with no explicit evidence carries
// its authenticated-arrival record instead.
func TestFeedEvidenceLinksEveryCommand(t *testing.T) {
p := NewProgram()
p.Submit("phone:nicks-phone", "pause playback", "")
for _, row := range p.FeedViewRows() {
if strings.HasPrefix(row, "#") && !strings.Contains(row, "| ev:") {
t.Fatalf("command row without an evidence link: %q", row)
}
}
}
// loadPinned reads a frozen fixture; with RUNE_RUNTIME_REGOLDEN=1 it
// (re)writes the fixture from the implementation so a deliberate change
// can be frozen (same procedure as the rail golden paths).
func loadPinned(t *testing.T, file, got string) string {
t.Helper()
raw, err := os.ReadFile(file)
if err != nil {
if os.Getenv("RUNE_RUNTIME_REGOLDEN") == "1" {
if err := os.WriteFile(file, []byte(got), 0o644); err != nil {
t.Fatalf("regolden %s: %v", file, err)
}
return got
}
t.Fatalf("fixture %s unreadable (regolden with RUNE_RUNTIME_REGOLDEN=1): %v", file, err)
}
return string(raw)
}
+123
View File
@@ -0,0 +1,123 @@
package runtime
import (
"strings"
"testing"
)
// ── origin-tagged ordering: paired commands in authenticated order ───
// pairedCommands is the spec scenario ("Origin-tagged ordering"): a
// paired phone command and the desktop TUI act near-simultaneously, a
// tablet remote follows, and one phone pair conflicts materially. The
// slice order is deliberately NOT the arrival order.
func pairedCommands() []Command {
return []Command{
{ID: "cmd-8", Origin: "phone:nicks-phone", Text: "resume playback", Evidence: "ev:arrival-8", State: CmdPending, Arrival: 8, Conflict: true},
{ID: "cmd-6", Origin: "tui", Text: "approve migration", Evidence: "ev:task-2-receipt", State: CmdCommitted, Arrival: 6},
{ID: "cmd-7", Origin: "phone:nicks-phone", Text: "pause playback", Evidence: "ev:arrival-7", State: CmdCommitted, Arrival: 7, Conflict: true},
{ID: "cmd-9", Origin: "remote:tablet", Text: "open plan view", Evidence: "ev:arrival-9", State: CmdPending, Arrival: 9},
}
}
// TestOrderIsAuthenticatedArrival is the ordering law: commands order by
// authenticated arrival — never by slice position — with the origin
// shown on every row, and a material conflict requests clarification.
func TestOrderIsAuthenticatedArrival(t *testing.T) {
got := Order(pairedCommands())
wantIDs := []string{"cmd-6", "cmd-7", "cmd-8", "cmd-9"}
for i, c := range got {
if c.ID != wantIDs[i] {
t.Fatalf("order[%d] = %s (arrival %d), want %s", i, c.ID, c.Arrival, wantIDs[i])
}
}
d := OrderDisplay(got)
if strings.Index(d, "#6") > strings.Index(d, "#7") || strings.Index(d, "#7") > strings.Index(d, "#8") {
t.Fatalf("display not in arrival order:\n%s", d)
}
for _, row := range []string{
"#6 [committed] tui: approve migration | ev:task-2-receipt",
"#7 [committed] phone:nicks-phone: pause playback | ev:arrival-7 | CONFLICT - clarification requested",
"#8 [pending] phone:nicks-phone: resume playback | ev:arrival-8 | CONFLICT - clarification requested",
"#9 [pending] remote:tablet: open plan view | ev:arrival-9",
} {
if !strings.Contains(d, row) {
t.Errorf("ordering display missing row %q\ndisplay:\n%s", row, d)
}
}
}
// TestOrderDeterministicTieBreak: equal arrival numbers (a seam bug)
// still order deterministically — by ID — and Order never mutates its
// input slice.
func TestOrderDeterministicTieBreak(t *testing.T) {
in := []Command{
{ID: "cmd-b", Arrival: 4, State: CmdPending, Origin: "tui", Text: "b"},
{ID: "cmd-a", Arrival: 4, State: CmdPending, Origin: "tui", Text: "a"},
}
got := Order(in)
if got[0].ID != "cmd-a" || got[1].ID != "cmd-b" {
t.Fatalf("tie break not by ID: %s then %s", got[0].ID, got[1].ID)
}
if in[0].ID != "cmd-b" || in[1].ID != "cmd-a" {
t.Fatal("Order mutated its input slice")
}
}
// TestOrderingDisplayFrozen pins the ordering-display fixture bytes
// (testdata/ordering.v1.txt): origin-tagged paired commands shown in
// authenticated arrival order with committed/pending states distinct.
func TestOrderingDisplayFrozen(t *testing.T) {
got := OrderDisplay(Order(pairedCommands()))
want := loadPinned(t, "testdata/ordering.v1.txt", got)
if got != want {
t.Fatalf("ordering display drifted from the frozen fixture:\n got:\n%s\nwant:\n%s", got, want)
}
}
// ── committed/pending distinction ────────────────────────────────────
// TestCommittedPendingDistinctFrozen pins the committed/pending
// distinction fixture (testdata/committed-pending.v1.txt): the same
// paired commands rendered by the ordering display AND by the web feed
// carry distinct, unambiguous state markers on both surfaces.
func TestCommittedPendingDistinctFrozen(t *testing.T) {
p := canonicalFeedProgram() // one committed desktop + one pending phone command
var b strings.Builder
b.WriteString("ORDERING DISPLAY\n")
b.WriteString(OrderDisplay(Order(p.Commands())))
b.WriteString("\nWEB FEED COMMAND LINES\n")
for _, row := range p.FeedViewRows() {
if strings.HasPrefix(row, "#") {
b.WriteString(row)
b.WriteString("\n")
}
}
got := b.String()
want := loadPinned(t, "testdata/committed-pending.v1.txt", got)
if got != want {
t.Fatalf("committed/pending distinction drifted from the frozen fixture:\n got:\n%s\nwant:\n%s", got, want)
}
if strings.Contains(got, "[unknown]") {
t.Fatal("a command rendered without its committed/pending marker")
}
}
// TestCommitOnlyPending: committing twice is refused, committing an
// unknown id is refused, and only pending commands transition.
func TestCommitOnlyPending(t *testing.T) {
p := NewProgram()
c := p.Submit("tui", "approve migration", "ev:task-2-receipt")
if !p.Commit(c.ID) {
t.Fatal("first commit refused")
}
if p.Commit(c.ID) {
t.Fatal("double commit accepted")
}
if p.Commit("cmd-nope") {
t.Fatal("unknown commit accepted")
}
if got := p.Commands()[0].State; got != CmdCommitted {
t.Fatalf("state after commit = %q, want %q", got, CmdCommitted)
}
}
+234
View File
@@ -0,0 +1,234 @@
package runtime
import (
"fmt"
"strings"
"charm.land/lipgloss/v2"
"rune.dev/rune/tui/semantic"
)
// Render projects the runtime face: closed = the rail frame with the
// runtime status line beneath it; open = the full-width status or feed
// takeover covering the rail. The rich projection colors the runtime
// chrome; the linear projection renders the same text with zero ANSI
// (the oracle enforces both carry the same fingerprints, and the
// linear one carries no escape byte).
func (r *Runtime) Render() semantic.Frame {
if r.view != "" {
if r.view == "feed" {
return r.renderFeedTakeover()
}
return r.renderStatusTakeover()
}
inner := r.inner.Render()
if r.mode == semantic.ModeLinear {
inner.Content = semantic.StripANSI(inner.Content)
}
line := r.StatusLine()
content := line
if inner.Content != "" {
content = inner.Content + "\n" + line
}
railLines := strings.Split(inner.Content, "\n")
regions := append(inner.Regions, semantic.Region{
ID: "region.runtime", X: 0, Y: len(railLines), H: 1,
W: max(r.inner.Snapshot().W, 0), Marker: "RUNTIME",
})
return semantic.Frame{Content: content, Regions: regions}
}
// StatusLine is the shared one-line runtime status beneath the rail:
// continuous program phase, attached surfaces (headless when none),
// the boundary worker state, and the chrome keys.
func (r *Runtime) StatusLine() string {
n := len(r.prog.attachments)
faces := fmt.Sprintf("faces %d", n)
if n == 0 {
faces = "headless"
}
line := fmt.Sprintf("RUNTIME | %s | %s | worker %s | R status, f feed",
r.prog.Phase(), faces, r.prog.Boundary().Worker)
line = clipRunes(line, max(r.inner.Snapshot().W, 0))
if r.mode == semantic.ModeRich && strings.HasPrefix(line, "RUNTIME") {
return runtimeTagStyle.Render("RUNTIME") + line[len("RUNTIME"):]
}
return line
}
// renderStatusTakeover draws the full-width runtime status takeover:
// header with the close marker, the continuous program state (phase,
// attached surfaces), the supervised boundary status, the exit
// checkpoint summary (when one was written), and the restore hint.
func (r *Runtime) renderStatusTakeover() semantic.Frame {
s := r.inner.Snapshot()
W, H := s.W, s.H
if W < 3 || H < 3 {
return blankTakeover(W, H, "region.runtime.takeover")
}
inner := W - 2
header := "RUNTIME STATUS - continuous program | X/esc restores lanes"
faces := "headless"
if n := len(r.prog.attachments); n > 0 {
names := make([]string, 0, n)
for _, a := range r.prog.attachments {
names = append(names, a.Face)
}
faces = strings.Join(names, ", ")
}
phaseLine := "phase: " + r.prog.Phase()
facesLine := "faces: " + faces
boundaryLine := r.prog.Boundary().Line()
cpLine := "exit checkpoint: none written (exit is deliberate)"
if cp, ok := r.prog.Checkpoint(); ok {
cpLine = cp.Summary()
}
hint := "X or esc restores the lanes exactly"
// Priority when the box runs short: header, phase, faces, boundary
// keep; checkpoint lines drop tail-first; the hint drops first.
body := []string{phaseLine, facesLine, boundaryLine}
cpLines := strings.Split(cpLine, "\n")
mid := H - 2
avail := mid - 2 // header + one blank
if avail < 0 {
avail = 0
}
for len(body)+len(cpLines)+1 > avail && len(cpLines) > 0 {
cpLines = cpLines[:len(cpLines)-1]
}
if len(body)+len(cpLines)+1 <= avail {
body = append(body, cpLines...)
body = append(body, hint)
} else if len(body)+len(cpLines) <= avail {
body = append(body, cpLines...)
}
body = body[:min(len(body), max(avail, 0))]
rows := []string{fit(header, inner, "[x]")}
rows = append(rows, strings.Repeat(" ", inner))
for _, row := range body {
rows = append(rows, clipRunes(row, inner))
}
for len(rows) < mid {
rows = append(rows, strings.Repeat(" ", inner))
}
rows = rows[:mid]
frame := border(rows, inner, r.mode)
frame.Regions = []semantic.Region{
{ID: "region.runtime.takeover", X: 0, Y: 0, W: W, H: H, Marker: "RUNTIME STATUS"},
{ID: "region.runtime.close", X: W - 4, Y: 1, W: 3, H: 1, Marker: "[x]"},
}
return frame
}
// renderFeedTakeover draws the full-width web-feed seam view: the live
// read-only feed the web link serves, embedded behind the lanes — one
// view of the one runtime, never a second native face, with no input
// path (every key except restore is swallowed by Apply).
func (r *Runtime) renderFeedTakeover() semantic.Frame {
s := r.inner.Snapshot()
W, H := s.W, s.H
if W < 3 || H < 3 {
return blankTakeover(W, H, "region.runtime.takeover")
}
inner := W - 2
header := "WEB FEED (read-only) - live view | X/esc restores lanes"
hint := "read-only: the feed has no input path - X or esc restores"
feedLines := r.prog.FeedViewRows()
mid := H - 2
avail := mid - 2 // header + one blank
if avail < 0 {
avail = 0
}
// newest command rows stay visible: drop OLDEST feed lines first,
// never the header/state/boundary block's tail before its head.
if len(feedLines)+1 > avail {
drop := len(feedLines) + 1 - avail
if drop < len(feedLines) {
keep := feedLines[drop:]
// the feed's own header line never drops
feedLines = append([]string{feedLines[0]}, keep...)
} else {
feedLines = []string{feedLines[0]}
}
}
body := make([]string, 0, len(feedLines)+1)
for _, l := range feedLines {
body = append(body, clipRunes(l, inner))
}
if len(body) < avail {
body = append(body, clipRunes(hint, inner))
}
body = body[:min(len(body), max(avail, 0))]
rows := []string{fit(header, inner, "[x]")}
rows = append(rows, strings.Repeat(" ", inner))
rows = append(rows, body...)
for len(rows) < mid {
rows = append(rows, strings.Repeat(" ", inner))
}
rows = rows[:mid]
frame := border(rows, inner, r.mode)
frame.Regions = []semantic.Region{
{ID: "region.runtime.takeover", X: 0, Y: 0, W: W, H: H, Marker: "WEB FEED"},
{ID: "region.runtime.feed", X: 1, Y: 2, W: inner, H: max(len(body), 0), Marker: "FEED"},
{ID: "region.runtime.close", X: W - 4, Y: 1, W: 3, H: 1, Marker: "[x]"},
}
return frame
}
// border wraps the box rows in the takeover frame (rich colors it).
func border(rows []string, inner int, mode string) semantic.Frame {
top, side, bottom := "+", "|", "+"
if mode == semantic.ModeRich {
top, side, bottom = takeoverStyle.Render("+"), takeoverStyle.Render("|"), takeoverStyle.Render("+")
}
body := make([]string, 0, len(rows)+2)
body = append(body, top+strings.Repeat("-", inner)+top)
for _, row := range rows {
body = append(body, side+row+side)
}
body = append(body, bottom+strings.Repeat("-", inner)+bottom)
return semantic.Frame{Content: strings.Join(body, "\n")}
}
func blankTakeover(W, H int, id string) semantic.Frame {
rows := make([]string, max(H, 0))
for i := range rows {
rows[i] = strings.Repeat(" ", max(W, 0))
}
return semantic.Frame{Content: strings.Join(rows, "\n"), Regions: []semantic.Region{
{ID: id, X: 0, Y: 0, W: max(W, 0), H: max(H, 0)},
}}
}
func clipRunes(s string, w int) string {
r := []rune(s)
if w < 0 {
w = 0
}
if len(r) <= w {
return s
}
return string(r[:w])
}
func fit(text string, inner int, marker string) string {
if inner < len(marker)+1 {
return clipRunes(marker, inner)
}
left := clipRunes(text, inner-len(marker)-1)
return left + strings.Repeat(" ", max(inner-len([]rune(left))-len(marker), 0)) + marker
}
var (
runtimeTagStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("39"))
takeoverStyle = lipgloss.NewStyle().Foreground(lipgloss.Color("39"))
)
+378
View File
@@ -0,0 +1,378 @@
// Package runtime is the rune-runtime status groundwork of the Rune TUI
// (openspec add-native-rune-tui, spec delta rune-runtime — single-
// interface TUI + web-link live feed, R2-D6): the continuous program
// state surface (attach/detach never stops the work, deliberate exit
// writes the continuity checkpoint), the origin-tagged paired-command
// ordering display with committed/pending distinction, the supervised
// Go-face/Rust-worker boundary status, and the READ-ONLY web-feed seam
// view the web link serves — never a second native face.
//
// THE LAW of this package (D26 substrate law, carried from tui/semantic,
// tui/lanes, tui/rail and tui/degrade): the runtime status is a VIEW
// over the one semantic State and its events — never a parallel action
// system. Every lane key routes through the inner rail face whole; the
// runtime adds only its chrome keys (R opens the status takeover, f
// opens the web-feed seam view, x/X/esc restores) and the program state
// the runtime injects. The delegation is pinned key-for-key by the
// coherence tests.
//
// Continuity law (rune-runtime "Face closes mid-task"): the Program is
// the continuous headless core — attachments come and go, the phase
// never regresses because a face closed. Detach cannot stop work.
//
// Exit law ("Deliberate exit writes continuity checkpoint"): only a
// deliberate exit writes the checkpoint, and the checkpoint references
// canonical state — the semantic State's task rows and replay cursor
// plus the declared context — never a lossy summary.
//
// Ordering law ("Origin-tagged ordering"): paired-device and remote
// input is origin-tagged and ordered by authenticated arrival; a
// material conflict is shown and requests clarification. Order is a
// PURE function — no clock, no RNG — and its row vocabulary is shared
// by the ordering display and the web feed so the committed/pending
// distinction is identical on both surfaces.
//
// Read-only feed law ("Web feed live view", R2-D6): the web feed
// renders the same live state read-only with an evidence link on every
// command row, and has NO input path — no key mutates the program
// through the feed, and the seam view never spawns a native second
// face. Outside-the-TUI access is a live view, nothing more.
//
// Honest scope (groundwork, not the full runtime):
// - HERE: the continuous-program model (phase, attachments,
// origin-tagged commands, boundary status), the exit checkpoint
// summary, the wrapper face with status/feed takeovers, the frozen
// feed/ordering/committed-pending fixtures.
// - LATER WAVES: the real web transport seam (this view is what it
// serves), crash-resume replay from the checkpoint, worker
// supervision plumbing (the boundary status is injected at the live
// seam), material-conflict detection (the Conflict bit is set by
// the runtime; this package renders it).
package runtime
import (
"fmt"
"sort"
"strings"
"rune.dev/rune/tui/semantic"
)
// Continuous program phases (frozen vocabulary).
const (
PhaseRunning = "running" // the one program works headless
PhaseExiting = "exiting" // deliberate exit requested, checkpoint pending
PhaseCheckpointed = "checkpointed" // continuity checkpoint written, program may stop
)
// Command states (frozen vocabulary — the committed/pending distinction
// is pinned by the frozen fixtures on both surfaces).
const (
CmdPending = "pending"
CmdCommitted = "committed"
)
// Supervised Rust worker states (frozen vocabulary).
const (
WorkerRunning = "running" // supervised worker healthy
WorkerRestarting = "restarting" // supervisor restart in flight
WorkerStopped = "stopped" // deliberate stop
WorkerFailed = "failed" // fail-closed (e.g. frame corruption)
)
// Runtime chrome action IDs (the vocabulary the frozen paths pin).
const (
RuntimeOpen = semantic.ActionID("runtime.open")
RuntimeFeed = semantic.ActionID("runtime.feed.open")
RuntimeRestore = semantic.ActionID("runtime.restore")
RuntimeSwallow = semantic.ActionID("runtime.swallow")
)
// Attachment is one attached surface: the single native TUI face
// ("tui") or a web-feed reader ("web:<link-id>"). Attachments are
// observers — closing one never stops the program.
type Attachment struct {
Face string
}
// Command is one origin-tagged input from a paired device or remote:
// who sent it, its authenticated arrival number (the ordering key),
// what it asked, its committed/pending state, its evidence link, and
// whether the runtime flagged a material conflict.
type Command struct {
ID string
Origin string // "tui" | "phone:<id>" | "remote:<id>" | ...
Text string
Evidence string // evidence link; the arrival record when none given
State string // pending | committed
Arrival int64 // authenticated arrival — the ordering key
Conflict bool // material conflict → clarification requested
}
// Context is the declared continuity context the runtime keeps for the
// exit checkpoint (plan revision, last edited files, pending decisions
// and alerts, connected work handles). The runtime seams inject it as
// it changes; the checkpoint freezes it.
type Context struct {
PlanRevision string
LastEdited []string
PendingDecisions []string
PendingAlerts []string
WorkHandles []string
}
// Checkpoint is the canonical continuity checkpoint of a deliberate
// exit (rune-runtime spec): active/incomplete tasks, plan revision,
// last edited files, pending decisions/alerts, connected work handles,
// replay cursor, and source/evidence pointers — all references into
// canonical state, never a lossy summary.
type Checkpoint struct {
ActiveTasks []string
PlanRevision string
LastEdited []string
PendingDecisions []string
PendingAlerts []string
WorkHandles []string
ReplayCursor int64
EvidenceRefs []string
}
// Summary renders the exit-checkpoint summary (the status takeover
// shows it; the restart path reads the same fields).
func (c Checkpoint) Summary() string {
var b strings.Builder
b.WriteString("EXIT CHECKPOINT (canonical refs - never a lossy summary)\n")
fmt.Fprintf(&b, "active tasks: %s\n", joinOrNone(c.ActiveTasks))
fmt.Fprintf(&b, "plan revision: %s\n", orNone(c.PlanRevision))
fmt.Fprintf(&b, "last edited: %s\n", joinOrNone(c.LastEdited))
fmt.Fprintf(&b, "pending decisions: %s\n", joinOrNone(c.PendingDecisions))
fmt.Fprintf(&b, "pending alerts: %s\n", joinOrNone(c.PendingAlerts))
fmt.Fprintf(&b, "work handles: %s\n", joinOrNone(c.WorkHandles))
fmt.Fprintf(&b, "replay cursor: seq %d\n", c.ReplayCursor)
fmt.Fprintf(&b, "evidence: %s", joinOrNone(c.EvidenceRefs))
return b.String()
}
// BoundaryStatus is the supervised Go-face/Rust-worker boundary state
// the runtime injects at the live seam: worker state, the current
// coarse operation, whether cancellation latched (cancel law: the latch
// outlives registration), and whether the boundary failed closed on
// frame corruption.
type BoundaryStatus struct {
Worker string // running | restarting | stopped | failed
Op string // current coarse op ("" = idle)
CancelLatched bool
FrameCorrupt bool
}
// Line renders the one-line boundary status (shared by the status
// line, the status takeover, and the web feed).
func (b BoundaryStatus) Line() string {
var s string
if b.Worker == WorkerFailed && b.FrameCorrupt {
s = "BOUNDARY | worker failed (frame corrupt - no partial effect)"
} else {
s = "BOUNDARY | worker " + b.Worker
}
if b.Op != "" {
s += " | op " + b.Op
}
if b.CancelLatched {
s += " | cancel latched"
}
return s
}
// Program is the continuous headless core: phase, attached surfaces,
// origin-tagged commands, boundary status, declared context, and the
// last deliberate-exit checkpoint. It carries no clock and no renderer
// knowledge — every transition is deterministic and pure over its
// inputs (the family determinism law).
type Program struct {
phase string
attachments []Attachment
nextArrival int64
commands []Command
checkpoint *Checkpoint
boundary BoundaryStatus
ctx Context
}
// NewProgram returns the fresh continuous program: running, headless
// (faces attach to it), boundary healthy, no checkpoint.
func NewProgram() *Program {
return &Program{phase: PhaseRunning, boundary: BoundaryStatus{Worker: WorkerRunning}}
}
// Phase reports the continuous program phase.
func (p *Program) Phase() string { return p.phase }
// Attach attaches one surface (idempotent per face).
func (p *Program) Attach(face string) {
for _, a := range p.attachments {
if a.Face == face {
return
}
}
p.attachments = append(p.attachments, Attachment{Face: face})
}
// Detach detaches one surface (unknown faces are a no-op). Detach NEVER
// changes the phase — the program outlives every face.
func (p *Program) Detach(face string) {
out := p.attachments[:0]
for _, a := range p.attachments {
if a.Face != face {
out = append(out, a)
}
}
p.attachments = out
}
// Attachments returns a copy of the attached surfaces, oldest first.
func (p *Program) Attachments() []Attachment { return append([]Attachment(nil), p.attachments...) }
// Submit records one origin-tagged input and returns it: the
// authenticated arrival number is assigned monotonically, the command
// starts pending, and an evidence-less submission carries its arrival
// record as its evidence link (every command row stays linkable).
func (p *Program) Submit(origin, text, evidence string) Command {
p.nextArrival++
c := Command{
ID: fmt.Sprintf("cmd-%d", p.nextArrival),
Origin: origin,
Text: text,
Evidence: evidence,
State: CmdPending,
Arrival: p.nextArrival,
}
if c.Evidence == "" {
c.Evidence = fmt.Sprintf("ev:arrival-%d", p.nextArrival)
}
p.commands = append(p.commands, c)
return c
}
// Commit transitions one pending command to committed. Double commits
// and unknown ids refuse.
func (p *Program) Commit(id string) bool {
for i := range p.commands {
if p.commands[i].ID == id && p.commands[i].State == CmdPending {
p.commands[i].State = CmdCommitted
return true
}
}
return false
}
// Commands returns a copy of the recorded commands, submit order.
func (p *Program) Commands() []Command { return append([]Command(nil), p.commands...) }
// RequestExit marks the deliberate exit requested (checkpoint pending).
func (p *Program) RequestExit() { p.phase = PhaseExiting }
// SetContext replaces the declared continuity context.
func (p *Program) SetContext(ctx Context) { p.ctx = ctx }
// SetBoundary replaces the boundary status (the live seam injects it).
func (p *Program) SetBoundary(bs BoundaryStatus) { p.boundary = bs }
// Boundary reports the current boundary status.
func (p *Program) Boundary() BoundaryStatus { return p.boundary }
// WriteCheckpoint writes the continuity checkpoint FROM canonical state
// — active tasks and replay cursor derive from the semantic State, the
// declared context and committed-command evidence freeze with it — and
// moves the phase to checkpointed. The checkpoint references canonical
// state; nothing is summarized away.
func (p *Program) WriteCheckpoint(st semantic.State) Checkpoint {
cp := Checkpoint{
ActiveTasks: taskIDs(st),
PlanRevision: p.ctx.PlanRevision,
LastEdited: append([]string(nil), p.ctx.LastEdited...),
PendingDecisions: append([]string(nil), p.ctx.PendingDecisions...),
PendingAlerts: append([]string(nil), p.ctx.PendingAlerts...),
WorkHandles: append([]string(nil), p.ctx.WorkHandles...),
ReplayCursor: st.Seq,
}
seen := map[string]bool{}
for _, c := range Order(p.commands) {
if c.State == CmdCommitted && !seen[c.Evidence] {
seen[c.Evidence] = true
cp.EvidenceRefs = append(cp.EvidenceRefs, c.Evidence)
}
}
cp.EvidenceRefs = append(cp.EvidenceRefs, fmt.Sprintf("state:seq-%d", st.Seq))
p.checkpoint = &cp
p.phase = PhaseCheckpointed
return cp
}
// Checkpoint returns the last written checkpoint, if any.
func (p *Program) Checkpoint() (Checkpoint, bool) {
if p.checkpoint == nil {
return Checkpoint{}, false
}
return *p.checkpoint, true
}
// Order is the ordering law as a pure function: commands sort by
// authenticated arrival, ties (a seam bug) break deterministically by
// ID, and the input slice is never mutated.
func Order(cmds []Command) []Command {
out := append([]Command(nil), cmds...)
sort.SliceStable(out, func(i, j int) bool {
if out[i].Arrival != out[j].Arrival {
return out[i].Arrival < out[j].Arrival
}
return out[i].ID < out[j].ID
})
return out
}
// CommandRow renders one command in the shared row vocabulary:
// authenticated arrival, committed/pending marker, origin, text,
// evidence link — and the clarification request on a material conflict.
// The ordering display and the web feed render the SAME rows, so the
// committed/pending distinction is identical on both surfaces.
func CommandRow(c Command) string {
row := fmt.Sprintf("#%d [%s] %s: %s | %s", c.Arrival, c.State, c.Origin, c.Text, c.Evidence)
if c.Conflict {
row += " | CONFLICT - clarification requested"
}
return row
}
// OrderDisplay renders the origin-tagged ordering display: the paired
// commands in authenticated arrival order (the takeover and the frozen
// fixture show it).
func OrderDisplay(cmds []Command) string {
ordered := Order(cmds)
rows := make([]string, 0, len(ordered))
for _, c := range ordered {
rows = append(rows, CommandRow(c))
}
return strings.Join(rows, "\n")
}
func taskIDs(st semantic.State) []string {
ids := make([]string, 0, len(st.Tasks))
for _, t := range st.Tasks {
ids = append(ids, t.ID)
}
return ids
}
func joinOrNone(parts []string) string {
if len(parts) == 0 {
return "none"
}
return strings.Join(parts, ", ")
}
func orNone(s string) string {
if s == "" {
return "none"
}
return s
}
+403
View File
@@ -0,0 +1,403 @@
package runtime
import (
"strings"
"testing"
"rune.dev/rune/tui/semantic"
)
// ── continuous program state: attach/detach never stops the work ──────
// TestDetachNeverStopsProgram is the spec law (rune-runtime "Face closes
// mid-task"): closing or minimizing the TUI never stops the continuous
// program — detach changes the attachment list, never the phase.
func TestDetachNeverStopsProgram(t *testing.T) {
p := NewProgram()
if p.Phase() != PhaseRunning {
t.Fatalf("fresh program phase = %q, want %q", p.Phase(), PhaseRunning)
}
p.Attach("tui")
p.Detach("tui")
if p.Phase() != PhaseRunning {
t.Fatalf("detach changed phase to %q — the program outlives its faces", p.Phase())
}
if n := len(p.Attachments()); n != 0 {
t.Fatalf("attachments after detach = %d, want 0", n)
}
}
// TestAttachmentsTrackFaces: attach is idempotent per face, detach of an
// unknown face is a no-op, and the list reports what is attached now.
func TestAttachmentsTrackFaces(t *testing.T) {
p := NewProgram()
p.Attach("tui")
p.Attach("tui")
p.Attach("web:phone-1")
if got := p.Attachments(); len(got) != 2 {
t.Fatalf("attachments = %v, want tui + web:phone-1", got)
}
p.Detach("web:tablet-9") // unknown face: no-op, no error path
if got := p.Attachments(); len(got) != 2 {
t.Fatalf("unknown detach mutated attachments: %v", got)
}
p.Detach("web:phone-1")
if got := p.Attachments(); len(got) != 1 || got[0].Face != "tui" {
t.Fatalf("attachments = %v, want [tui]", got)
}
}
// ── deliberate exit writes the continuity checkpoint ─────────────────
// TestExitCheckpointLifecycle is the spec scenario ("Clean exit and
// restart"): request exit, write the checkpoint FROM canonical state,
// and only then may the program report checkpointed.
func TestExitCheckpointLifecycle(t *testing.T) {
p := NewProgram()
p.Attach("tui")
p.Submit("tui", "approve migration", "ev:task-2-receipt")
if p.Phase() != PhaseRunning {
t.Fatalf("phase after submit = %q, want running", p.Phase())
}
p.RequestExit()
if p.Phase() != PhaseExiting {
t.Fatalf("phase after RequestExit = %q, want %q", p.Phase(), PhaseExiting)
}
if _, ok := p.Checkpoint(); ok {
t.Fatal("checkpoint exists before WriteCheckpoint — exit must be deliberate")
}
st := semantic.Seed(80, 24)
st.Seq = 42
cp := p.WriteCheckpoint(st)
if p.Phase() != PhaseCheckpointed {
t.Fatalf("phase after WriteCheckpoint = %q, want %q", p.Phase(), PhaseCheckpointed)
}
got, ok := p.Checkpoint()
if !ok {
t.Fatal("checkpoint missing after WriteCheckpoint")
}
if got.ReplayCursor != 42 {
t.Errorf("replay cursor = %d, want 42 (canonical state seq)", got.ReplayCursor)
}
if len(cp.ActiveTasks) != 3 { // the seed's in-flight tasks, derived
t.Errorf("active tasks = %v, want the 3 seed tasks", cp.ActiveTasks)
}
}
// TestCheckpointSummaryIsCanonicalRefs: the exit-checkpoint summary names
// every spec field group and points at canonical state, never a lossy
// summary (each group present, replay cursor and evidence refs shown).
func TestCheckpointSummaryIsCanonicalRefs(t *testing.T) {
p := NewProgram()
p.SetContext(Context{
PlanRevision: "plan-r3",
LastEdited: []string{"docs/D16.md"},
PendingDecisions: []string{"approve migration"},
PendingAlerts: []string{"calendar handoff due"},
WorkHandles: []string{"bridge/rust-worker"},
})
p.Submit("tui", "approve migration", "ev:task-2-receipt")
p.Commit("cmd-1")
st := semantic.Seed(80, 24)
st.Seq = 42
sum := p.WriteCheckpoint(st).Summary()
for _, want := range []string{
"EXIT CHECKPOINT",
"active tasks: task-1, task-2, task-3",
"plan revision: plan-r3",
"last edited: docs/D16.md",
"pending decisions: approve migration",
"pending alerts: calendar handoff due",
"work handles: bridge/rust-worker",
"replay cursor: seq 42",
"evidence: ev:task-2-receipt, state:seq-42",
} {
if !strings.Contains(sum, want) {
t.Errorf("checkpoint summary missing %q\nsummary:\n%s", want, sum)
}
}
}
// ── supervised Go-face/Rust-worker boundary status ───────────────────
// TestBoundaryStatusLine pins the boundary status vocabulary: the four
// supervised worker states, the latched-cancellation mark, and the
// fail-closed frame-corruption note.
func TestBoundaryStatusLine(t *testing.T) {
cases := []struct {
bs BoundaryStatus
want string
}{
{BoundaryStatus{Worker: WorkerRunning, Op: "memory-synthesis"},
"BOUNDARY | worker running | op memory-synthesis"},
{BoundaryStatus{Worker: WorkerRunning, Op: "memory-synthesis", CancelLatched: true},
"BOUNDARY | worker running | op memory-synthesis | cancel latched"},
{BoundaryStatus{Worker: WorkerRestarting, Op: "memory-synthesis"},
"BOUNDARY | worker restarting | op memory-synthesis"},
{BoundaryStatus{Worker: WorkerStopped},
"BOUNDARY | worker stopped"},
{BoundaryStatus{Worker: WorkerFailed, Op: "memory-synthesis", FrameCorrupt: true},
"BOUNDARY | worker failed (frame corrupt - no partial effect) | op memory-synthesis"},
}
for _, c := range cases {
if got := c.bs.Line(); got != c.want {
t.Errorf("Line() =\n %q\nwant\n %q", got, c.want)
}
}
}
// TestRuntimeFaceSurfacesBoundary: the status line beneath the rail
// carries the continuous program phase, the attachment count (headless
// when no face is attached), and the boundary worker state.
func TestRuntimeFaceSurfacesBoundary(t *testing.T) {
r := New(80, 24)
r.Program().Attach("tui")
r.Program().Attach("web:phone-1")
r.Program().SetBoundary(BoundaryStatus{Worker: WorkerRunning, Op: "memory-synthesis"})
line := r.StatusLine()
for _, want := range []string{"RUNTIME", "running", "faces 2", "worker running"} {
if !strings.Contains(line, want) {
t.Errorf("status line %q missing %q", line, want)
}
}
r.Program().Detach("tui")
r.Program().Detach("web:phone-1")
if line = r.StatusLine(); !strings.Contains(line, "headless") {
t.Errorf("status line with no faces = %q, want headless", line)
}
}
// ── the wrapper face: chrome keys, swallow law, precedence ───────────
// TestChromeKeys: R opens the runtime status takeover, f opens the
// web-feed seam view, x/X/esc restore exactly; while either takeover is
// open every other key is swallowed — not even quit, not even the other
// takeover key.
func TestChromeKeys(t *testing.T) {
r := New(80, 24)
if r.Open() || r.View() != "" {
t.Fatal("fresh face has a takeover open")
}
if a, quit := r.Apply(semantic.Rune('R')); a != RuntimeOpen || quit {
t.Fatalf("R = (%q,%v), want (%q,false)", a, quit, RuntimeOpen)
}
if r.View() != "status" {
t.Fatalf("view after R = %q, want status", r.View())
}
for _, k := range []semantic.Key{semantic.Rune('j'), semantic.Rune('2'), semantic.Rune('y'), semantic.Rune('q'), semantic.Rune('r'), semantic.Rune('f')} {
before := r.inner.Snapshot().Fingerprint()
if a, quit := r.Apply(k); a != RuntimeSwallow || quit {
t.Fatalf("key %s inside takeover = (%q,%v), want swallow/false", k.String(), a, quit)
}
if got := r.inner.Snapshot().Fingerprint(); got != before {
t.Fatalf("key %s leaked into the semantic state inside the takeover", k.String())
}
}
for _, k := range []semantic.Key{semantic.Rune('x'), semantic.Rune('X'), semantic.Named("esc")} {
other := New(80, 24)
other.Apply(semantic.Rune('R'))
if a, _ := other.Apply(k); a != RuntimeRestore {
t.Fatalf("restore key %s = %q, want %q", k.String(), a, RuntimeRestore)
}
if other.Open() {
t.Fatalf("restore key %s left the takeover open", k.String())
}
}
r.Apply(semantic.Named("esc")) // restore the status takeover on r itself
if r.Open() {
t.Fatal("esc did not restore the status takeover")
}
r.Apply(semantic.Rune('f'))
if r.View() != "feed" {
t.Fatalf("view after f = %q, want feed", r.View())
}
if a, _ := r.Apply(semantic.Rune('R')); a != RuntimeSwallow {
t.Fatalf("R inside the feed view = %q, want swallow (one view at a time)", a)
}
if _, quit := r.Apply(semantic.Rune('q')); quit {
t.Fatal("quit leaked through an open takeover")
}
r.Apply(semantic.Named("esc"))
if r.Open() {
t.Fatal("esc did not restore after feed view")
}
}
// TestModalOutranksRuntime: an open modal is a decision boundary — the
// runtime chrome keys do not fire through it.
func TestModalOutranksRuntime(t *testing.T) {
r := New(80, 24)
r.Apply(semantic.Rune('m')) // approve modal
if a, _ := r.Apply(semantic.Rune('R')); a != "modal.swallow" {
t.Fatalf("R through modal = %q, want modal.swallow", a)
}
if r.Open() {
t.Fatal("takeover opened through a modal")
}
}
// TestRestoreReturnsLanesExactly: open/close is pure chrome — the inner
// semantic state is byte-identical before and after a takeover round
// trip, pinned by fingerprint.
func TestRestoreReturnsLanesExactly(t *testing.T) {
r := New(80, 24)
r.Apply(semantic.Rune('2'))
r.Apply(semantic.Rune('j'))
before := r.inner.Snapshot().Fingerprint()
r.Apply(semantic.Rune('R'))
r.Apply(semantic.Rune('j'))
r.Apply(semantic.Rune('f'))
r.Apply(semantic.Named("esc"))
if got := r.inner.Snapshot().Fingerprint(); got != before {
t.Fatalf("takeover round trip mutated the lanes:\n got %s\nwant %s", got, before)
}
}
// TestFingerprintCarriesViewOnly: the open-view bit is fingerprinted
// (key routing changes); program state — attachments, commands,
// boundary, checkpoint — is bounded display/runtime state and is NOT
// (the view-not-semantics law, same as the rail feed and the degrade
// ledger).
func TestFingerprintCarriesViewOnly(t *testing.T) {
r := New(80, 24)
base := r.Fingerprint()
r.Program().Attach("tui")
r.Program().Submit("phone:nicks-phone", "pause playback", "")
r.Program().SetBoundary(BoundaryStatus{Worker: WorkerRestarting, Op: "memory-synthesis"})
if r.Fingerprint() != base {
t.Fatal("program state leaked into the semantic fingerprint")
}
r.Apply(semantic.Rune('f'))
if r.Fingerprint() == base {
t.Fatal("open feed view is not fingerprinted — key routing changed")
}
}
// TestReadOnlyFeedLaw: the web feed is READ-ONLY. While the feed seam
// view is open no key mutates the program — the feed has no input path;
// it is a live view, never a second native face.
func TestReadOnlyFeedLaw(t *testing.T) {
r := New(80, 24)
r.Program().Attach("tui")
r.Program().Submit("phone:nicks-phone", "pause playback", "ev:arrival-1")
feed := r.Program().FeedView()
phase := r.Program().Phase()
r.Apply(semantic.Rune('f'))
for _, k := range []semantic.Key{semantic.Rune('y'), semantic.Rune('j'), semantic.Rune('m'), semantic.Rune('q'), semantic.Rune('R')} {
r.Apply(k)
}
if r.Program().FeedView() != feed {
t.Fatal("a key mutated the feed through the seam view — the feed is read-only")
}
if r.Program().Phase() != phase {
t.Fatal("a key mutated the program phase through the feed view")
}
}
// ── projections ──────────────────────────────────────────────────────
// TestRenderProjections: closed = the rail frame + the runtime status
// line beneath (region.runtime); open = the full-width takeover
// (status or feed) with a close marker. Linear carries zero ANSI.
func TestRenderProjections(t *testing.T) {
r := New(80, 24)
r.Program().SetBoundary(BoundaryStatus{Worker: WorkerRunning, Op: "memory-synthesis"})
fr := r.Render()
lin := NewLinear(80, 24)
lin.Program().SetBoundary(BoundaryStatus{Worker: WorkerRunning, Op: "memory-synthesis"})
lfr := lin.Render()
if !strings.Contains(lfr.Content, "RUNTIME | running") {
t.Fatalf("closed linear frame missing the status line:\n%s", lfr.Content)
}
if !region(fr.Regions, "region.runtime") {
t.Fatalf("closed frame missing region.runtime: %+v", fr.Regions)
}
r.Apply(semantic.Rune('R'))
fr = r.Render()
if !strings.Contains(fr.Content, "RUNTIME STATUS") || !strings.Contains(fr.Content, "BOUNDARY") {
t.Fatalf("status takeover missing program/boundary state:\n%s", fr.Content)
}
if !region(fr.Regions, "region.runtime.takeover") || !region(fr.Regions, "region.runtime.close") {
t.Fatalf("status takeover regions wrong: %+v", fr.Regions)
}
l := NewLinear(80, 24)
l.Apply(semantic.Rune('f'))
flf := l.Render()
if strings.ContainsRune(flf.Content, 0x1b) {
t.Fatal("linear feed projection carries ANSI — zero-ANSI law")
}
if !strings.Contains(flf.Content, "WEB FEED") {
t.Fatalf("feed takeover missing the feed:\n%s", flf.Content)
}
}
// TestOracleIsomorphism: the runtime face implements semantic.Face, so
// the isomorphism oracle drives rich against linear over the runtime's
// own chrome paths.
func TestOracleIsomorphism(t *testing.T) {
paths := []semantic.GoldenPath{
{Name: "status-restore", Steps: []string{"R", "x"}},
{Name: "status-restore-esc", Steps: []string{"R", "esc"}},
{Name: "feed-restore-capital", Steps: []string{"f", "X"}},
{Name: "takeovers-swallow-all", Steps: []string{"2", "j", "R", "j", "2", "y", "m", "q", "f", "x", "j"}},
{Name: "modal-outranks-runtime", Steps: []string{"2", "m", "R", "n", "R", "x"}},
}
rep := semantic.CheckIsomorphism(New(80, 24), NewLinear(80, 24), paths, 3000)
if !rep.OK {
t.Fatalf("isomorphism report not OK: %+v", rep)
}
}
// TestCloneIndependence: clones carry an independent program copy —
// mutating one face's program never reaches the other.
func TestCloneIndependence(t *testing.T) {
r := New(80, 24)
r.Program().Attach("tui")
c, ok := r.Clone().(*Runtime)
if !ok {
t.Fatal("clone is not *Runtime")
}
c.Program().Attach("web:phone-1")
if got := r.Program().Attachments(); len(got) != 1 {
t.Fatalf("original attachments = %v, want [tui] only", got)
}
if got := c.Program().Attachments(); len(got) != 2 {
t.Fatalf("clone attachments = %v, want both", got)
}
}
// TestRuntimeRecordsToRail: program lifecycle events surface in the rail
// feed (the Wrapper-Face pattern — the rail is the activity log).
func TestRuntimeRecordsToRail(t *testing.T) {
r := New(80, 24)
n := len(r.inner.Feed())
r.Attach("web:phone-1")
r.Submit("phone:nicks-phone", "pause playback", "")
r.RequestExit()
st := semantic.Seed(80, 24)
r.WriteCheckpoint(st)
if got := len(r.inner.Feed()) - n; got < 4 {
t.Fatalf("rail feed grew by %d entries, want >=4 (attach, submit, exit, checkpoint)", got)
}
if !railSourceRecorded(r, "runtime attach web:phone-1") {
t.Fatalf("attach not recorded to the rail feed: %+v", r.inner.Feed())
}
}
func railSourceRecorded(r *Runtime, text string) bool {
for _, e := range r.inner.Feed() {
if e.Text == text {
return true
}
}
return false
}
func region(regs []semantic.Region, id string) bool {
for _, r := range regs {
if r.ID == id {
return true
}
}
return false
}
+6
View File
@@ -0,0 +1,6 @@
ORDERING DISPLAY
#1 [committed] tui: approve migration | ev:task-2-receipt
#2 [pending] phone:nicks-phone: pause playback | ev:arrival-2
WEB FEED COMMAND LINES
#1 [committed] tui: approve migration | ev:task-2-receipt
#2 [pending] phone:nicks-phone: pause playback | ev:arrival-2
+4
View File
@@ -0,0 +1,4 @@
#6 [committed] tui: approve migration | ev:task-2-receipt
#7 [committed] phone:nicks-phone: pause playback | ev:arrival-7 | CONFLICT - clarification requested
#8 [pending] phone:nicks-phone: resume playback | ev:arrival-8 | CONFLICT - clarification requested
#9 [pending] remote:tablet: open plan view | ev:arrival-9
+5
View File
@@ -0,0 +1,5 @@
WEB FEED (read-only) — live view of the one runtime; never a second native face
RUNTIME | running | faces: tui, web:phone-1
BOUNDARY | worker running | op memory-synthesis
#1 [committed] tui: approve migration | ev:task-2-receipt
#2 [pending] phone:nicks-phone: pause playback | ev:arrival-2