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).
159 lines
6.7 KiB
Python
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())
|