tui/update/apply.go: consent gate (no record -> zero fetches); asset URL re-resolved from feed (consent never carries a URL) + same-origin enforcement (third-party refusal); 64MiB-capped temp download hashed while streaming; sha256 verify — mismatch refuses + rolls back (target byte-identical, restart never fires, refusal journaled); restore point via the restore-points engine before swap (fail closed); atomic-rename swap; journal; PostStartup confirms or honestly names the rewind point. Red-first: consent->download->verify->swap->restart->version round-trip (fake Gitea), tampered-digest rollback, no-silent-offer, third-party refusal, fail-closed, feed-moved refusals. 28/28 green, race-clean. Main-session verify: green + race own run. D27 complete: offer -> apply -> verify -> swap -> restart -> honest version.
117 lines
4.4 KiB
Python
117 lines
4.4 KiB
Python
"""Admission dispatch for the K26 update-apply wave (task 0.18, D27).
|
|
|
|
classify + activate + admit (the headless scaffolding entry law,
|
|
scripts/admit_wave_settings_surfaces.py pattern): the update APPLY flow
|
|
in tui/update — consent → digest-verified download from the owner's
|
|
Gitea release asset → restore point before the swap (restore-points
|
|
engine, landed) → binary swap → restart → honest post-restart version.
|
|
D27 pinned by frozen tests: tampered digest refuses + rolls back, no
|
|
third-party channels, no-silent-offer conformance — user-dispatch
|
|
approved.
|
|
"""
|
|
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 the task-0.18 update APPLY flow in tui/update on top of the "
|
|
"landed offer/consent groundwork: a recorded consent gates a "
|
|
"digest-verified download from the owner's Gitea release asset "
|
|
"(asset URL re-resolved from the feed, same-origin enforced — no "
|
|
"third-party channels), a restore point is captured via the landed "
|
|
"restore-points engine BEFORE the swap (capture failure fails "
|
|
"closed, no swap), one atomic rename swaps the binary, a journal "
|
|
"records applied/refused next to the binary, the restart re-execs "
|
|
"in place (unix), and PostStartup makes the post-restart version "
|
|
"honest (confirmed vs said mismatch naming the rewind point). "
|
|
"Tampered digest refuses and rolls back byte-for-byte. Frozen "
|
|
"tests: consent→download→verify→swap→restart→version round-trip "
|
|
"(fake server); tampered-digest rollback fixture; no-silent-offer "
|
|
"conformance. Red-first."
|
|
)
|
|
AFFECTED = [
|
|
"tui/update/apply.go",
|
|
"tui/update/apply_test.go",
|
|
"tui/update/restart_unix.go",
|
|
"tui/update/restart_fallback.go",
|
|
"tui/update/offer.go",
|
|
"tui/update/offer_test.go",
|
|
"tui/update/update.go",
|
|
"tui/update/README.md",
|
|
]
|
|
|
|
|
|
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-K26-update-apply",
|
|
"objective": OBJECTIVE,
|
|
"origin": "worker",
|
|
"project": "rune",
|
|
"task_ref": "wave-K26-update-apply",
|
|
"affected_surface": AFFECTED,
|
|
"evidence_refs": [
|
|
"docs/openspec/changes/add-native-rune-tui/tasks.md",
|
|
"docs/openspec/changes/add-native-rune-tui/design.md",
|
|
"src/go/restore-points/internal/restorepoints/cli.go",
|
|
"tui/update/offer.go",
|
|
],
|
|
}
|
|
act = cact.activate(md, need=need, scaffold=scaffold,
|
|
builder="wave-K26-update-apply")
|
|
print("activated:", act.get("activated"), act.get("mode"))
|
|
|
|
env = cadm.admit(
|
|
act,
|
|
objective=OBJECTIVE,
|
|
builder="wave-K26-update-apply",
|
|
affected_surface=AFFECTED,
|
|
evidence_refs=need["evidence_refs"],
|
|
origin="worker",
|
|
task_ref="wave-K26-update-apply",
|
|
expected_new_entities=[
|
|
"tui/update apply flow (Applier, journal, PostStartup, restart seam)",
|
|
],
|
|
expected_runtime_effects=[
|
|
"on recorded consent only, tui/update may download+verify the "
|
|
"release asset, capture a restore point, swap the binary, and "
|
|
"re-exec; without consent nothing is fetched or run; the offer "
|
|
"card copy reflects the landed apply",
|
|
],
|
|
verification_obligations=[
|
|
"go test ./update/ -count=1 green in tui/ (frozen round-trip, "
|
|
"tampered rollback, no-silent-offer conformance)",
|
|
"gofmt -l and go vet clean on tui/update",
|
|
"go build ./... green in tui/",
|
|
],
|
|
approval_ref="user:wave-K26-update-apply dispatch",
|
|
contract_id="ADMIT-wave-K26-update-apply",
|
|
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())
|