zu-cli 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
zu_cli/__init__.py ADDED
File without changes
zu_cli/build.py ADDED
@@ -0,0 +1,111 @@
1
+ """The construction spine — chain the OFFLINE stages of the sequence into one run.
2
+
3
+ ``zu build`` composes what the earlier increments shipped: replay the captured bundle
4
+ offline (stage 3), project the resilient track from that clean run (stage 4), and score
5
+ it against perturbed fixtures (stage 5) — gating the track on the resilience score. The
6
+ output is a production-ready, hardened ``track.json`` next to the agent, produced at $0:
7
+ no model, no network.
8
+
9
+ The two LIVE stages and promotion are deliberately NOT in this spine — they need keys,
10
+ network, or a registry push, and are left behind explicit seams so the cheap, testable
11
+ core stands on its own:
12
+
13
+ * **Stage 2 (capture)** is the one live step; ``zu build`` requires its output
14
+ (``fixtures/capture.json``) and points at ``zu capture`` when it is missing.
15
+ * **Stage 6 (canary)** — one live validation run before promotion — is the live lane,
16
+ guarded by ``_canary`` raising ``NotImplementedError`` so ``--with-canary`` fails
17
+ loudly rather than pretending. It is the next increment.
18
+ * **Stage 7 (promote)** — ``zu pack`` / ``zu deploy`` — is left to its existing commands;
19
+ ``zu build`` prints them as the next step.
20
+ """
21
+
22
+ from __future__ import annotations
23
+
24
+ from dataclasses import dataclass, field
25
+ from pathlib import Path
26
+ from typing import Any
27
+
28
+ from .harden import HardenReport, harden
29
+ from .offline import Bundle, replay_offline
30
+
31
+
32
+ @dataclass
33
+ class StageResult:
34
+ """One stage of the spine: its outcome and a one-line detail for the summary."""
35
+
36
+ name: str
37
+ status: str # "ok" | "failed" | "skipped"
38
+ detail: str
39
+
40
+
41
+ @dataclass
42
+ class BuildReport:
43
+ stages: list[StageResult] = field(default_factory=list)
44
+ track_path: str | None = None
45
+ harden: HardenReport | None = None
46
+
47
+ @property
48
+ def ok(self) -> bool:
49
+ return all(s.status != "failed" for s in self.stages)
50
+
51
+ def _add(self, name: str, status: str, detail: str) -> StageResult:
52
+ s = StageResult(name=name, status=status, detail=detail)
53
+ self.stages.append(s)
54
+ return s
55
+
56
+
57
+ def _canary(spec: Any, cfg: Any) -> None:
58
+ """Stage 6 — the live canary. The seam for the live lane: one real run guarding
59
+ fixture drift before promotion. Not built here (needs keys + network)."""
60
+ raise NotImplementedError(
61
+ "the live canary (stage 6) is the live lane — it needs keys + network and is the "
62
+ "next increment. Validate manually for now with `zu run <agent>` (live), then "
63
+ "promote with `zu pack` / `zu deploy`."
64
+ )
65
+
66
+
67
+ async def build_offline(
68
+ spec: Any, cfg: Any, agent_dir: str | Path, bundle: Bundle, *, min_score: float = 1.0,
69
+ ) -> BuildReport:
70
+ """Run the offline spine — build → record track → harden — and write the hardened
71
+ track. Each stage gates the next: a failed offline build is not tracked, and a track
72
+ that fails the resilience gate is recorded but flagged failed so promotion is held."""
73
+ from zu_core.contracts import Status
74
+ from zu_core.track import record_track
75
+
76
+ report = BuildReport()
77
+
78
+ # Stage 3 — build offline (the keystone). A clean replay is the precondition.
79
+ result, events = await replay_offline(spec, cfg, bundle)
80
+ if result.status is not Status.SUCCESS:
81
+ report._add("build", "failed",
82
+ f"offline run did not succeed ({result.status.value}: {result.reason})")
83
+ return report
84
+ report._add("build", "ok", f"offline run succeeded → {result.value}")
85
+
86
+ # Stage 4 — record the track from the clean offline run.
87
+ track = record_track(events, task=spec.query, model=bundle.model)
88
+ track_path = str(Path(agent_dir) / "track.json")
89
+ track.save(track_path)
90
+ report.track_path = track_path
91
+ climbs = sorted({s.tier for s in track.steps})
92
+ tiers = (f"tiers {min(climbs)}→{max(climbs)}" if len(climbs) > 1
93
+ else f"tier {climbs[0]}" if climbs else "no tools")
94
+ report._add("track", "ok", f"recorded {len(track.steps)} steps ({tiers}) → {track_path}")
95
+
96
+ # Stage 5 — harden: score the track against perturbed fixtures and gate on it.
97
+ hr = await harden(spec, cfg, bundle)
98
+ report.harden = hr
99
+ score = hr.resilience
100
+ if not hr.grounding_load_bearing:
101
+ report._add("harden", "failed",
102
+ "a value-deletion control passed — grounding is not gating; the "
103
+ "resilience score is unreliable")
104
+ elif score < min_score:
105
+ report._add("harden", "failed",
106
+ f"resilience {score:.0%} below --min-score {min_score:.0%} "
107
+ f"({len(hr.findings)} brittle step(s) to fix)")
108
+ else:
109
+ report._add("harden", "ok",
110
+ f"resilience {score:.0%}; {len(hr.findings)} brittle step(s) noted")
111
+ return report