Files
rune/scripts/admit_wave_firstrun.py
Nick 5a860bac8c feat(tui): firstrun experience — fresh-state detection, provider/model selector, no-default gate
tui/firstrun: fresh-state detection (no provider record + no checkpoint
= first boot); stage machine (welcome -> provider catalog -> model list
-> connection test -> done) through lib.ModelLister (off-thread, pending/
answer protocol); no-default gate (chat refused until explicit selection
recorded in provider_selection.json, atomic write); scrubbed record
re-gates (leaving configured state re-triggers first-run). 7/7 frozen
red-first green. Main-session verify: green own run. Face wiring deferred
(main.go owned by chat-live wave).
2026-10-18 01:25:51 +01:00

159 lines
6.7 KiB
Python

"""Admission dispatch for wave wave-firstrun: B-FIRST-RUN — the
first-run experience (owner directive 2026-10-17, docs/factory/
spawn-queue.md "NEW PRIORITY 0" + "First-run law" + "Fresh-state
detection").
classify + activate + admit (the headless scaffolding entry law,
tests/test_construction_admission.py pattern). Research exemption:
owner-directive, declared in docs/factory/research-registry.json slug
firstrun (approval_ref 'user:wave-firstrun dispatch') — the behavior
contract is pinned at directive level; this is a bounded build, no new
research questions.
Artifacts this wave admits: the tui/firstrun package — fresh-state
detection (fresh = no provider selection record + no conversation
checkpoint; a checkpoint without a record is gated-but-returning, never
welcomed as first run), the selection record law (provider_selection.
json in the state home, RUNE_HOME -> ~/.rune, atomic write, defensive
read — the same law the live gate ships), the frozen extensive
provider catalog (cloud ten + local four, no default), the first-run
face (welcome -> provider -> model -> connection test -> done stage
machine; listing and probe travel through the lib.ModelLister seam so
the owning shell runs them off its UI thread), and the Shell (renders
the first-run face while gated, the working face once configured;
chat gate enforced on the record — gated sends refused with the
honest line, nothing auto-selects or auto-persists). Red-first frozen
battery tui/firstrun/firstrun_test.go (detection, transition +
connection-test honesty, no-default enforcement, catalog law, face
walk, record path) — proven RED against the stub, GREEN on the
implementation.
Boundary note: tui/cmd/rune-tui/main.go is owned by the concurrent
chat-live wave and was NOT touched; the shell adoption seam (render
firstrun.Shell while unconfigured) is documented in the package law
comment for the file-owning wave.
"""
import json
import os
import sys
sys.path.insert(0, os.path.join(os.path.dirname(__file__), "..", "src"))
from rune import construction_activation as cact # noqa: E402
from rune import construction_admission as cadm # noqa: E402
from rune import construction_mode as cmode # noqa: E402
from rune import scaffold as sc # noqa: E402
ROOT = os.path.join(os.path.dirname(__file__), "..")
OBJECTIVE = (
"build B-FIRST-RUN — implement the first-run experience (owner "
"directive, docs/factory/spawn-queue.md): a brand-new user opens "
"rune and sees the honest welcome (NOT an error, NOT empty-looking "
"lanes), picks a provider from the extensive cloud+local catalog "
"and a model for it, passes the connection test, and lands in the "
"working face. (1) Implement fresh-state detection, wired to the "
"live record law: fresh = no provider selection record + no "
"conversation checkpoint (provider_selection.json in the state "
"home). (2) Create the tui/firstrun package: the welcome surface, "
"the provider selector, the model selector for the picked "
"provider, the connection test (source answers AND carries the "
"picked model — never a silent pass), then the transition to the "
"working face. (3) Implement the Shell that renders the first-run "
"face when fresh/gated and the normal face when configured, with "
"no-default-provider enforcement: chat gated until an explicit "
"selection completes (the record is the authority — never a "
"cursor, a guess, or a silent default). Red-first frozen battery "
"in tui/firstrun/firstrun_test.go"
)
AFFECTED = [
"tui/firstrun/firstrun.go",
"tui/firstrun/face.go",
"tui/firstrun/render.go",
"tui/firstrun/shell.go",
"tui/firstrun/firstrun_test.go",
"docs/factory/research-registry.json",
"scripts/admit_wave_firstrun.py",
]
def main() -> int:
store = os.path.join(ROOT, "store", "scaffold")
scaffold = sc.Scaffold(store, "rune")
md = cmode.classify_mode(
OBJECTIVE,
target_project="rune",
project_exists=True,
affected_surface=AFFECTED,
)
print("classified mode:", md.mode)
need = {
"need_id": "need-wave-firstrun",
"objective": OBJECTIVE,
"origin": "worker",
"project": "rune",
"task_ref": "wave-firstrun",
"affected_surface": AFFECTED,
"evidence_refs": [
"docs/factory/spawn-queue.md",
"tui/firstrun/firstrun_test.go",
"tui/cmd/rune-tui/provider.go",
],
"exemption": (
"owner-directive: B-FIRST-RUN first-run experience per the "
"spawn-queue directives (see docs/factory/research-registry."
"json slug firstrun, approval_ref 'user:wave-firstrun "
"dispatch')"),
}
act = cact.activate(md, need=need, scaffold=scaffold,
builder="wave-firstrun")
print("activated:", act.get("activated"), act.get("mode"))
env = cadm.admit(
act,
objective=OBJECTIVE,
builder="wave-firstrun",
affected_surface=AFFECTED,
evidence_refs=need["evidence_refs"],
origin="worker",
task_ref="wave-firstrun",
expected_new_entities=[
"tui/firstrun (the first-run package: detection, record "
"law, catalog, face, shell)",
"tui/firstrun/firstrun_test.go (frozen battery, red-first)",
"unified tui module member: go test ./firstrun/ green",
],
expected_runtime_effects=[
"a brand-new user (no provider_selection.json, no "
"checkpoint) renders the first-run welcome face — never an "
"error, never empty-looking lanes",
"the provider catalog shows the cloud ten + local four "
"with nothing selected; only enter-picks stage a choice "
"and only a passed connection test persists the record",
"chat is gated until the record exists: gated sends are "
"refused with 'no provider configured — select one to "
"begin' and route nowhere; nothing auto-selects",
"a configured user (record present) renders the working "
"face immediately — no welcome, no selector",
],
verification_obligations=[
"cd tui && go test ./firstrun/ -count=1 green (the frozen "
"battery)",
"cd tui && go vet ./firstrun/ clean",
"cd tui && go build ./... green (the module still builds; "
"cmd/rune-tui untouched)",
],
approval_ref="user:wave-firstrun dispatch",
contract_id="ADMIT-wave-firstrun",
project_store=store,
project_id="rune",
)
print(json.dumps(env, indent=2, default=str)[:2400])
return 0 if env.get("success") else 1
if __name__ == "__main__":
sys.exit(main())