substratum-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.
Files changed (42) hide show
  1. substratum_cli/README.md +51 -0
  2. substratum_cli/__init__.py +6 -0
  3. substratum_cli/__main__.py +7 -0
  4. substratum_cli/_vendor/__init__.py +0 -0
  5. substratum_cli/_vendor/_pro_test_derive.py +169 -0
  6. substratum_cli/account.py +62 -0
  7. substratum_cli/cli.py +185 -0
  8. substratum_cli/codes.py +114 -0
  9. substratum_cli/commands/__init__.py +0 -0
  10. substratum_cli/commands/apply.py +49 -0
  11. substratum_cli/commands/auth.py +37 -0
  12. substratum_cli/commands/commit.py +40 -0
  13. substratum_cli/commands/explain.py +63 -0
  14. substratum_cli/commands/failing.py +80 -0
  15. substratum_cli/commands/fix.py +48 -0
  16. substratum_cli/commands/history.py +15 -0
  17. substratum_cli/commands/init_cmd.py +155 -0
  18. substratum_cli/commands/replay.py +68 -0
  19. substratum_cli/commands/scaffold.py +71 -0
  20. substratum_cli/commands/show.py +18 -0
  21. substratum_cli/commands/undo.py +28 -0
  22. substratum_cli/commands/verify.py +193 -0
  23. substratum_cli/commands/write.py +42 -0
  24. substratum_cli/config.py +49 -0
  25. substratum_cli/diffs.py +106 -0
  26. substratum_cli/engine.py +154 -0
  27. substratum_cli/gitio.py +102 -0
  28. substratum_cli/langscan.py +110 -0
  29. substratum_cli/product_ops.py +639 -0
  30. substratum_cli/refusals.py +127 -0
  31. substratum_cli/remote.py +140 -0
  32. substratum_cli/render.py +332 -0
  33. substratum_cli/result.py +185 -0
  34. substratum_cli/run_store.py +162 -0
  35. substratum_cli/runid.py +69 -0
  36. substratum_cli/session.py +558 -0
  37. substratum_cli/style.py +128 -0
  38. substratum_cli-0.1.0.dist-info/METADATA +69 -0
  39. substratum_cli-0.1.0.dist-info/RECORD +42 -0
  40. substratum_cli-0.1.0.dist-info/WHEEL +5 -0
  41. substratum_cli-0.1.0.dist-info/entry_points.txt +2 -0
  42. substratum_cli-0.1.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,37 @@
1
+ """`login` / `logout` / `whoami` -- connect the thin client to the hosted engine via GitHub."""
2
+ from __future__ import annotations
3
+
4
+ import sys
5
+
6
+ from .. import account, remote
7
+
8
+
9
+ def cmd_login() -> int:
10
+ if account.is_logged_in():
11
+ sys.stdout.write(f" already logged in as {account.github_login() or 'a user'} "
12
+ f"({account.server()})\n")
13
+ return 0
14
+ sys.stdout.write(f" connecting to {account.server()} ...\n")
15
+ try:
16
+ who = remote.login()
17
+ except remote.RemoteError as e:
18
+ sys.stderr.write(f" login failed: {e}\n")
19
+ return 1
20
+ sys.stdout.write(f"\n logged in as {who.get('github_login') or 'a user'}. "
21
+ f"fix/write/clarify now use the hosted engine.\n")
22
+ return 0
23
+
24
+
25
+ def cmd_logout() -> int:
26
+ account.clear()
27
+ sys.stdout.write(" logged out (local commands still work offline).\n")
28
+ return 0
29
+
30
+
31
+ def cmd_whoami() -> int:
32
+ if account.is_logged_in():
33
+ sys.stdout.write(f" {account.github_login() or 'logged in'} · {account.server()}\n")
34
+ else:
35
+ sys.stdout.write(" not logged in — run substratum login to use the hosted engine "
36
+ "(local commands work offline)\n")
37
+ return 0
@@ -0,0 +1,40 @@
1
+ """`commit [<run>]` -- a proof-carrying git commit of a verified, applied run: stages exactly the
2
+ run's files and commits them with a message that cites the verified run + its provenance. Uses the
3
+ repo's own git author config, never Substratum's."""
4
+ from __future__ import annotations
5
+
6
+ from typing import Optional
7
+
8
+ from .. import codes, gitio, refusals, run_store
9
+ from .. import result as _result
10
+
11
+
12
+ def _proof_message(target: str, run_id: str, prov: list) -> str:
13
+ body = f"substratum: {target}\n\nVerified run {run_id}."
14
+ if prov:
15
+ body += "\nProvenance: " + ", ".join(prov[:4])
16
+ return body
17
+
18
+
19
+ def cmd_commit(repo: str, run_id: str, message: Optional[str] = None) -> "_result.SubstratumResult":
20
+ loaded = run_store.load_run(repo, run_id)
21
+ if not loaded:
22
+ ref = refusals.build(codes.RUN_NOT_FOUND, ctx={"target": run_id, "run_id": run_id})
23
+ return _result.make(codes.REFUSED, run_id, "commit", code=codes.RUN_NOT_FOUND, refusal=ref)
24
+ res, _inp = loaded
25
+ files = list(res.files or [])
26
+ if res.verdict not in (codes.VERIFIED, codes.PASS) or not files:
27
+ ref = refusals.build(codes.GATE_PRECONDITION,
28
+ engine_code="only a verified, applied run can be committed",
29
+ ctx={"target": run_id, "run_id": run_id})
30
+ return _result.make(codes.REFUSED, run_id, "commit", code=codes.GATE_PRECONDITION, refusal=ref)
31
+ target = (res.data or {}).get("target") or run_id
32
+ prov = list(res.provenance.entries) if res.provenance else []
33
+ msg = message or _proof_message(target, run_id, prov)
34
+ ok, sha = gitio.commit_files(repo, files, msg)
35
+ if not ok:
36
+ ref = refusals.build(codes.GATE_PRECONDITION, engine_code=sha,
37
+ ctx={"target": run_id, "run_id": run_id})
38
+ return _result.make(codes.REFUSED, run_id, "commit", code=codes.GATE_PRECONDITION, refusal=ref)
39
+ return _result.make(codes.PASS, run_id, "commit", files=tuple(files),
40
+ data={"sha": sha, "files": files, "subject": msg.splitlines()[0]})
@@ -0,0 +1,63 @@
1
+ """`substratum explain <run-id> [--line file:N]` -- render the provenance manifest for a run.
2
+ `substratum attest <run-id>` -- emit an evidence bundle (transcripts + manifest + versions).
3
+ Both are run-store readers; light (no engine import)."""
4
+ from __future__ import annotations
5
+
6
+ import hashlib
7
+ import json
8
+ import os
9
+ import zipfile
10
+
11
+ from .. import codes
12
+ from .. import refusals
13
+ from .. import result as _result
14
+ from .. import run_store
15
+
16
+
17
+ def cmd_explain(repo: str, run_id: str, line: str | None = None) -> "_result.SubstratumResult":
18
+ repo = os.path.abspath(repo)
19
+ loaded = run_store.load_run(repo, run_id)
20
+ if loaded is None:
21
+ return _result.make(codes.REFUSED, run_id, "explain", code=codes.RUN_NOT_FOUND,
22
+ refusal=refusals.build(codes.RUN_NOT_FOUND,
23
+ ctx={"target": run_id, "run_id": run_id}))
24
+ res, _inputs = loaded
25
+ manifest = {
26
+ "run_id": run_id, "verdict": res.verdict,
27
+ "provenance": list(res.provenance.entries) if res.provenance else [],
28
+ "pattern_id": res.provenance.pattern_id if res.provenance else None,
29
+ "corpus_support": res.provenance.corpus_support if res.provenance else None,
30
+ "grain": "function", # honest: current provenance grain is function-level, not per-line
31
+ "files": list(res.files),
32
+ }
33
+ if line:
34
+ manifest["note"] = ("per-line provenance is not yet recorded; grain is function-level. "
35
+ f"The provenance below covers the whole emission for {line}.")
36
+ return _result.make(codes.PASS, run_id, "explain", data={"manifest": manifest})
37
+
38
+
39
+ def cmd_attest(repo: str, run_id: str) -> "_result.SubstratumResult":
40
+ repo = os.path.abspath(repo)
41
+ d = run_store.run_dir(repo, run_id)
42
+ if not os.path.isdir(d):
43
+ return _result.make(codes.REFUSED, run_id, "attest", code=codes.RUN_NOT_FOUND,
44
+ refusal=refusals.build(codes.RUN_NOT_FOUND,
45
+ ctx={"target": run_id, "run_id": run_id}))
46
+ # per-file sha256 manifest + egress attestation
47
+ file_hashes = {}
48
+ for root, _dirs, files in os.walk(d):
49
+ for f in files:
50
+ p = os.path.join(root, f)
51
+ rel = os.path.relpath(p, d)
52
+ file_hashes[rel] = hashlib.sha256(open(p, "rb").read()).hexdigest()
53
+ att_dir = os.path.join(run_store.store_dir(repo), "attestations")
54
+ os.makedirs(att_dir, exist_ok=True)
55
+ manifest = {"run_id": run_id, "files": file_hashes, "egress": "none",
56
+ "signature": "unsigned (v1; signing hook stubbed)"}
57
+ bundle = os.path.join(att_dir, f"{run_id}.zip")
58
+ with zipfile.ZipFile(bundle, "w", zipfile.ZIP_DEFLATED) as z:
59
+ for rel in file_hashes:
60
+ z.write(os.path.join(d, rel), rel)
61
+ z.writestr("manifest.json", json.dumps(manifest, indent=2))
62
+ return _result.make(codes.PASS, run_id, "attest",
63
+ data={"bundle": bundle, "manifest": manifest})
@@ -0,0 +1,80 @@
1
+ """`failing` / `tests` -- discover failing tests in the repo by RUNNING its own suite and collecting
2
+ the failed node-ids. This is an OBSERVATION (read-only; runs the user's existing test command), not a
3
+ guess -- the failing tests are facts from the runner, and each becomes a ready `fix <id>` target."""
4
+ from __future__ import annotations
5
+
6
+ import os
7
+ import re
8
+ import shlex
9
+ import subprocess
10
+ import sys as _sys
11
+ from typing import Optional
12
+
13
+ from .. import codes, refusals, runid
14
+ from .. import result as _result
15
+
16
+ # pytest -q prints "FAILED path::test - reason" and "ERROR path::test" lines
17
+ _FAIL_RE = re.compile(r"^(?:FAILED|ERROR)\s+(\S+)", re.M)
18
+
19
+
20
+ def _detect_command(repo: str) -> Optional[list]:
21
+ for marker in ("pytest.ini", "conftest.py", "tox.ini"):
22
+ if os.path.exists(os.path.join(repo, marker)):
23
+ return [_sys.executable, "-m", "pytest"]
24
+ for cfg in ("pyproject.toml", "setup.cfg"):
25
+ p = os.path.join(repo, cfg)
26
+ if os.path.isfile(p):
27
+ try:
28
+ if "pytest" in open(p, encoding="utf-8", errors="replace").read().lower():
29
+ return [_sys.executable, "-m", "pytest"]
30
+ except Exception:
31
+ pass
32
+ if os.path.isdir(os.path.join(repo, "tests")):
33
+ return [_sys.executable, "-m", "pytest"]
34
+ try:
35
+ if any(f.startswith("test_") and f.endswith(".py") for f in os.listdir(repo)):
36
+ return [_sys.executable, "-m", "pytest"]
37
+ except Exception:
38
+ pass
39
+ return None
40
+
41
+
42
+ def cmd_failing(repo: str, path: Optional[str] = None, test_command: Optional[str] = None,
43
+ timeout: int = 300) -> "_result.SubstratumResult":
44
+ repo = os.path.abspath(repo)
45
+ rid, _key = runid.compute(repo, "failing", {"path": path or ""})
46
+ argv = shlex.split(test_command) if test_command else _detect_command(repo)
47
+ if not argv:
48
+ ref = refusals.build(codes.NO_DRIVER,
49
+ engine_code="no recognized test harness (looked for pytest)",
50
+ ctx={"target": repo, "run_id": rid})
51
+ return _result.make(codes.REFUSED, rid, "failing", code=codes.NO_DRIVER, refusal=ref)
52
+ is_pytest = "pytest" in " ".join(argv)
53
+ run_argv = list(argv) + (["-q", "-p", "no:cacheprovider"] if is_pytest else [])
54
+ if path:
55
+ run_argv.append(path)
56
+ try:
57
+ r = subprocess.run(run_argv, cwd=repo, capture_output=True, text=True, timeout=timeout,
58
+ env={**os.environ, "TMPDIR": "/tmp"})
59
+ except subprocess.TimeoutExpired:
60
+ ref = refusals.build(codes.GATE_PRECONDITION,
61
+ engine_code=f"the test suite did not finish within {timeout}s",
62
+ ctx={"target": repo, "run_id": rid})
63
+ return _result.make(codes.REFUSED, rid, "failing", code=codes.GATE_PRECONDITION, refusal=ref)
64
+ except (FileNotFoundError, PermissionError) as e:
65
+ ref = refusals.build(codes.NO_DRIVER, engine_code=f"could not run the test command ({e})",
66
+ ctx={"target": repo, "run_id": rid})
67
+ return _result.make(codes.REFUSED, rid, "failing", code=codes.NO_DRIVER, refusal=ref)
68
+ out = (r.stdout or "") + "\n" + (r.stderr or "")
69
+ failing = []
70
+ for m in _FAIL_RE.finditer(out):
71
+ nid = m.group(1)
72
+ if nid not in failing:
73
+ failing.append(nid)
74
+ all_pass = (not failing) and r.returncode == 0
75
+ excerpt = ""
76
+ if not failing and not all_pass: # suite could not run cleanly -> surface why
77
+ excerpt = "\n".join(out.strip().splitlines()[-8:])
78
+ return _result.make(codes.PASS, rid, "failing",
79
+ data={"failing": failing, "command": " ".join(run_argv),
80
+ "all_pass": all_pass, "excerpt": excerpt, "rc": r.returncode})
@@ -0,0 +1,48 @@
1
+ """`substratum fix <test>` -- produce a verified fix for a failing test, or a named refusal.
2
+ Thin over product_ops.op_fix_from_failing_test; streams stage events when --events is set."""
3
+ from __future__ import annotations
4
+
5
+ import os
6
+ import sys
7
+
8
+ from .. import codes
9
+ from .. import gitio
10
+ from .. import refusals
11
+ from .. import result as _result
12
+
13
+
14
+ def cmd_fix(args) -> "_result.SubstratumResult":
15
+ from .. import product_ops
16
+ repo = os.path.abspath(args.repo)
17
+ if not gitio.is_git_repo(repo):
18
+ return _result.make(codes.REFUSED, "sub_000000000000", "fix",
19
+ code=codes.GATE_PRECONDITION,
20
+ refusal=refusals.build(codes.GATE_PRECONDITION,
21
+ ctx={"target": repo, "run_id": "sub_000000000000"}))
22
+ tests = list(getattr(args, "failing_test", []) or [])
23
+ if getattr(args, "target", None):
24
+ tests.append(args.target)
25
+ if not tests:
26
+ return _result.make(codes.REFUSED, "sub_000000000000", "fix",
27
+ code=codes.NO_WARRANT_TESTS,
28
+ refusal=refusals.build(codes.NO_WARRANT_TESTS,
29
+ ctx={"target": "no test given", "run_id": "sub_000000000000"}))
30
+ intent = getattr(args, "intent", "") or ""
31
+ if intent and os.path.isfile(intent):
32
+ intent = open(intent, encoding="utf-8", errors="replace").read()
33
+
34
+ emit = None
35
+ if getattr(args, "events", False):
36
+ import json
37
+
38
+ def emit(stage, text):
39
+ sys.stdout.write(json.dumps({"stage": stage, "text": text}) + "\n")
40
+ sys.stdout.flush()
41
+ elif not getattr(args, "json", False):
42
+ def emit(stage, text):
43
+ sys.stderr.write(f" {stage:7} {text}\n")
44
+ sys.stderr.flush()
45
+
46
+ return product_ops.op_fix_from_failing_test(
47
+ repo, tests, intent=intent,
48
+ test_command=getattr(args, "test_command", None), emit=emit)
@@ -0,0 +1,15 @@
1
+ """`runs` / `log` -- the proof store made visible: the newest runs (every VERIFIED and every
2
+ REFUSED) plus the refusal->VERIFIED conversion ledger. This is the persistent, proof-carrying
3
+ memory that no LLM tool has, surfaced in the terminal."""
4
+ from __future__ import annotations
5
+
6
+ from .. import codes, run_store, runid
7
+ from .. import result as _result
8
+
9
+
10
+ def cmd_runs(repo: str, limit: int = 15) -> "_result.SubstratumResult":
11
+ runs = run_store.list_runs(repo, limit)
12
+ refn, conv = run_store.refusal_stats(repo, window_days=36500) # all-time ledger
13
+ rid, _key = runid.compute(repo, "runs", {"n": len(runs)})
14
+ return _result.make(codes.PASS, rid, "runs",
15
+ data={"runs": runs, "ledger": {"refusals": refn, "converted": conv}})
@@ -0,0 +1,155 @@
1
+ """`substratum init` -- declare the contract before the first refusal.
2
+
3
+ Three things, all light (no library load, no numpy):
4
+ 1. Coverage Contract: bucket the repo's file extensions against the LIVE driver registries
5
+ so every later refusal reads as the contract being honored, not the tool failing.
6
+ 2. Flake scan: run the detected suite K times at base in a worktree; quarantine any test
7
+ whose outcome is not stable (protects the determinism claim from the customer's own infra).
8
+ 3. Air-gap attestation: count socket events during the scan -> "0 sockets opened, checked
9
+ not claimed."
10
+ `doctor` re-runs the checks any time.
11
+ """
12
+ from __future__ import annotations
13
+
14
+ import os
15
+ from collections import defaultdict
16
+ from typing import Optional
17
+
18
+ from .. import codes
19
+ from .. import engine
20
+ from .. import gitio
21
+ from .. import result as _result
22
+ from .. import run_store
23
+ from .. import runid
24
+
25
+ _SKIP_DIRS = {".git", "node_modules", ".venv", "venv", "__pycache__", ".substratum",
26
+ "dist", "build", ".tox", ".mypy_cache", ".pytest_cache"}
27
+
28
+
29
+ def _walk_extensions(repo: str) -> dict:
30
+ counts = defaultdict(lambda: [0, 0]) # ext -> [files, lines]
31
+ for root, dirs, files in os.walk(repo):
32
+ dirs[:] = [d for d in dirs if d not in _SKIP_DIRS]
33
+ for f in files:
34
+ ext = os.path.splitext(f)[1].lower()
35
+ if not ext:
36
+ continue
37
+ p = os.path.join(root, f)
38
+ counts[ext][0] += 1
39
+ try:
40
+ with open(p, "rb") as fh:
41
+ counts[ext][1] += sum(1 for _ in fh)
42
+ except OSError:
43
+ pass
44
+ return counts
45
+
46
+
47
+ def build_coverage_contract(repo: str) -> dict:
48
+ reg = engine.coverage_registries()
49
+ counts = _walk_extensions(repo)
50
+ gated, undriven, unknown = [], [], []
51
+ for ext, (nfiles, nlines) in sorted(counts.items()):
52
+ row = {"ext": ext, "files": nfiles, "lines": nlines}
53
+ if ext in reg["code_langs"]:
54
+ row["lang"] = reg["code_langs"][ext]
55
+ gated.append(row)
56
+ elif ext in reg["driven_noncode"] or ext in reg["template_exts"]:
57
+ gated.append(row)
58
+ elif ext in reg["known_undriven"]:
59
+ row["refusal"] = f"NONCODE_NO_DRIVER({ext})"
60
+ undriven.append(row)
61
+ else:
62
+ unknown.append(row)
63
+ harness = engine.client_gate.resolve_test_command(repo, None, [])
64
+ return {"gated": gated, "no_driver": undriven, "unknown": unknown,
65
+ "harness": " ".join(harness) if harness else None}
66
+
67
+
68
+ def flake_scan(repo: str, tests: Optional[list] = None, rounds: int = 3) -> dict:
69
+ """Run the detected suite `rounds` times at HEAD in isolated worktrees; a test whose
70
+ pass/fail outcome differs across rounds is quarantined. Counts socket events -> air-gap."""
71
+ import socket as _socket
72
+ sock_events = {"n": 0}
73
+ orig = _socket.socket.connect
74
+
75
+ def _counting_connect(self, *a, **k):
76
+ sock_events["n"] += 1
77
+ return orig(self, *a, **k)
78
+
79
+ _socket.socket.connect = _counting_connect
80
+ outcomes = []
81
+ try:
82
+ base = gitio.head_sha(repo)
83
+ for _ in range(rounds):
84
+ sink: dict = {}
85
+ # a no-op "patch" (empty edits) with differential=False just runs the suite once
86
+ cg = engine.client_gate.run_client_gate(repo, base, {}, test_paths=tests or [],
87
+ differential=False, transcript_sink=sink)
88
+ outcomes.append(cg.code)
89
+ finally:
90
+ _socket.socket.connect = orig
91
+ stable = len(set(outcomes)) <= 1
92
+ return {"rounds": rounds, "outcomes": outcomes, "stable": stable,
93
+ "sockets_opened": sock_events["n"],
94
+ "quarantine": [] if stable else ["suite outcome not stable across rounds"]}
95
+
96
+
97
+ def cmd_init(repo: str, run_flake: bool = True) -> "_result.SubstratumResult":
98
+ repo = os.path.abspath(repo)
99
+ rid, key = runid.compute(repo, "init", {"op": "init"})
100
+ if not gitio.is_git_repo(repo):
101
+ from .. import refusals
102
+ return _result.make(codes.REFUSED, rid, "init", code=codes.GATE_PRECONDITION,
103
+ refusal=refusals.build(codes.GATE_PRECONDITION,
104
+ ctx={"target": repo, "run_id": rid}))
105
+ contract = build_coverage_contract(repo)
106
+ payload = {"coverage_contract": contract}
107
+ if run_flake and contract["harness"]:
108
+ payload["flake"] = flake_scan(repo)
109
+ payload["air_gap"] = {"sockets_opened": payload["flake"]["sockets_opened"],
110
+ "attestation": "checked, not claimed"}
111
+ # persist the contract for the VS Code status bar + doctor
112
+ os.makedirs(run_store.store_dir(repo), exist_ok=True)
113
+ import json
114
+ with open(os.path.join(run_store.store_dir(repo), "coverage_contract.json"), "w") as fh:
115
+ json.dump(contract, fh, indent=2)
116
+ res = _result.make(codes.PASS, rid, "init", data=payload, engine=_engine_info())
117
+ run_store.store_run(repo, res, run_key=key)
118
+ return res
119
+
120
+
121
+ def cmd_doctor(repo: str) -> "_result.SubstratumResult":
122
+ repo = os.path.abspath(repo)
123
+ rid, _ = runid.compute(repo, "doctor", {"op": "doctor"})
124
+ checks = {}
125
+ checks["git_repo"] = gitio.is_git_repo(repo)
126
+ harness = None
127
+ if checks["git_repo"]:
128
+ harness = engine.client_gate.resolve_test_command(repo, None, [])
129
+ checks["harness"] = " ".join(harness) if harness else None
130
+ checks["tmpdir_realfs"] = _tmpdir_ok()
131
+ checks["libraries"] = {k: v for k, v in runid.library_versions().items()}
132
+ if checks["git_repo"] and harness:
133
+ fl = flake_scan(repo)
134
+ checks["air_gap_sockets"] = fl["sockets_opened"]
135
+ checks["suite_stable"] = fl["stable"]
136
+ verdict = codes.PASS if checks["git_repo"] else codes.FAIL
137
+ return _result.make(verdict, rid, "doctor", data={"checks": checks})
138
+
139
+
140
+ def _tmpdir_ok() -> bool:
141
+ """The drvfs/9p lesson: pytest capture needs a real-fs TMPDIR for unlinked-open tempfiles."""
142
+ import tempfile
143
+ try:
144
+ with tempfile.TemporaryFile() as fh:
145
+ fh.write(b"x")
146
+ fh.truncate()
147
+ return True
148
+ except OSError:
149
+ return False
150
+
151
+
152
+ def _engine_info() -> "_result.EngineInfo":
153
+ return _result.EngineInfo(engine_version=runid.engine_version(),
154
+ library_versions=tuple(f"{k}:{v}" for k, v in
155
+ runid.library_versions().items()))
@@ -0,0 +1,68 @@
1
+ """`substratum replay <run-id>` -- determinism as a button. Recompute the run key from current
2
+ state; if it matches the stored key, re-execute and byte-compare; report byte-identical yes/no.
3
+ This is the invitation to catch us."""
4
+ from __future__ import annotations
5
+
6
+ import os
7
+
8
+ from .. import codes
9
+ from .. import refusals
10
+ from .. import result as _result
11
+ from .. import run_store
12
+ from .. import runid
13
+
14
+
15
+ def _canonical_core(res) -> str:
16
+ """The stable slice compared on replay: verdict, code, diff, files, provenance. Excludes
17
+ volatile fields (there are none by construction -- run_id is content-addressed)."""
18
+ import hashlib
19
+ import json
20
+ core = {"verdict": res.verdict, "code": res.code, "diff": res.diff,
21
+ "files": list(res.files),
22
+ "provenance": list(res.provenance.entries) if res.provenance else []}
23
+ blob = json.dumps(core, sort_keys=True)
24
+ return hashlib.sha256(blob.encode()).hexdigest()
25
+
26
+
27
+ def cmd_replay(repo: str, run_id: str) -> "_result.SubstratumResult":
28
+ repo = os.path.abspath(repo)
29
+ loaded = run_store.load_run(repo, run_id)
30
+ if loaded is None:
31
+ return _result.make(codes.REFUSED, run_id, "replay", code=codes.RUN_NOT_FOUND,
32
+ refusal=refusals.build(codes.RUN_NOT_FOUND,
33
+ ctx={"target": run_id, "run_id": run_id}))
34
+ stored, inputs = loaded
35
+ stored_key = (inputs.get("run_key") or {})
36
+ op = stored.operation
37
+
38
+ # recompute the run id from CURRENT state + the stored query; if it differs, state drifted
39
+ query = (stored_key.get("query") or {})
40
+ fresh_rid, fresh_key = runid.compute(repo, op, query)
41
+ if stored_key and fresh_key.get("head_sha") != stored_key.get("head_sha"):
42
+ return _result.make(codes.REFUSED, run_id, "replay", code=codes.REPLAY_STATE_DRIFT,
43
+ refusal=refusals.build(codes.REPLAY_STATE_DRIFT,
44
+ ctx={"target": run_id, "run_id": run_id}))
45
+
46
+ # re-execute the operation and compare the canonical core
47
+ re_res = _reexecute(repo, op, stored, inputs)
48
+ if re_res is None:
49
+ # cannot re-execute (e.g. informational op) -> compare id determinism only
50
+ identical = (fresh_rid == run_id)
51
+ return _result.make(codes.PASS if identical else codes.FAIL, run_id, "replay",
52
+ data={"byte_identical": identical, "recomputed_run_id": fresh_rid})
53
+ a, b = _canonical_core(stored), _canonical_core(re_res)
54
+ identical = (a == b) and (re_res.run_id == run_id)
55
+ return _result.make(codes.PASS if identical else codes.FAIL, run_id, "replay",
56
+ data={"byte_identical": identical, "sha": b,
57
+ "recomputed_run_id": re_res.run_id})
58
+
59
+
60
+ def _reexecute(repo, op, stored, inputs):
61
+ inp = inputs.get("inputs") or {}
62
+ if op == "fix" and inp.get("failing_tests"):
63
+ from .. import product_ops
64
+ return product_ops.op_fix_from_failing_test(
65
+ repo, list(inp["failing_tests"]), intent=inp.get("intent", ""),
66
+ test_command=inp.get("test_command"))
67
+ # verify replay would need the original diff bytes; informational ops fall through
68
+ return None
@@ -0,0 +1,71 @@
1
+ """`substratum scaffold-test <file::fn> [--case k=v]` -- the flip-condition engine. Emit a
2
+ RUNNABLE, FAILING test that pins a target/case, so an UNDERDETERMINED/UNVERIFIABLE refusal has
3
+ an executable next step. Expected values come ONLY from a `TODO` that raises (never invented);
4
+ the file runs and fails immediately, which is exactly a warrant waiting to be satisfied."""
5
+ from __future__ import annotations
6
+
7
+ import os
8
+
9
+ from .. import codes
10
+ from .. import gitio
11
+ from .. import refusals
12
+ from .. import result as _result
13
+ from .. import run_store
14
+ from .. import runid
15
+
16
+
17
+ def _parse_target(target: str) -> tuple[str, str] | None:
18
+ if "::" in target:
19
+ path, name = target.split("::", 1)
20
+ return path, name
21
+ return None
22
+
23
+
24
+ def cmd_scaffold_test(repo: str, target: str, cases: list | None = None) -> "_result.SubstratumResult":
25
+ repo = os.path.abspath(repo)
26
+ rid, key = runid.compute(repo, "scaffold_test", {"target": target, "cases": cases or []})
27
+ parsed = _parse_target(target or "")
28
+ if parsed is None:
29
+ return _result.make(codes.REFUSED, rid, "scaffold_test", code=codes.UNDERDETERMINED_INTENT,
30
+ refusal=refusals.build(codes.UNDERDETERMINED_INTENT,
31
+ ctx={"target": target or "<none>", "run_id": rid}))
32
+ mod_path, fn = parsed
33
+ mod_import = mod_path.replace("/", ".").rsplit(".py", 1)[0]
34
+ scaffold_rel = os.path.join(
35
+ os.path.dirname(mod_path) or ".", f"test_substratum_scaffold_{fn}.py")
36
+ scaffold_rel = os.path.normpath(scaffold_rel)
37
+
38
+ body = _emit_scaffold(mod_import, fn, cases or [])
39
+ diff = _new_file_diff(scaffold_rel, body)
40
+ res = _result.make(codes.PASS, rid, "scaffold_test", diff=diff, files=(scaffold_rel,),
41
+ data={"target": target, "scaffold": scaffold_rel,
42
+ "next": f"substratum fix {scaffold_rel}::test_{fn}_scaffold"})
43
+ run_store.store_run(repo, res, run_key=key)
44
+ return res
45
+
46
+
47
+ def _emit_scaffold(mod_import: str, fn: str, cases: list) -> str:
48
+ lines = [
49
+ f"from {mod_import} import {fn}",
50
+ "",
51
+ "",
52
+ "def _TODO(msg):",
53
+ " raise NotImplementedError('pin the expected value: ' + msg)",
54
+ "",
55
+ "",
56
+ f"def test_{fn}_scaffold():",
57
+ ]
58
+ if cases:
59
+ for i, c in enumerate(cases):
60
+ lines.append(f" # case: {c}")
61
+ lines.append(f" result_{i} = {fn}(_TODO('inputs for case {c!r}'))")
62
+ lines.append(f" assert result_{i} == _TODO('expected output for case {c!r}')")
63
+ else:
64
+ lines.append(f" result = {fn}(_TODO('inputs'))")
65
+ lines.append(" assert result == _TODO('expected output')")
66
+ return "\n".join(lines) + "\n"
67
+
68
+
69
+ def _new_file_diff(path: str, content: str) -> str:
70
+ from .. import engine
71
+ return engine.canonical_diff()(path, "", content)
@@ -0,0 +1,18 @@
1
+ """`substratum show <run-id>` -- render a stored run through the SAME renderer as the live
2
+ path. Light command: imports NO backend module (proven by the import-budget test)."""
3
+ from __future__ import annotations
4
+
5
+ from .. import codes
6
+ from .. import refusals
7
+ from .. import result as _result
8
+ from .. import run_store
9
+
10
+
11
+ def cmd_show(repo: str, run_id: str) -> "_result.SubstratumResult":
12
+ loaded = run_store.load_run(repo, run_id)
13
+ if loaded is None:
14
+ return _result.make(codes.REFUSED, run_id, "show", code=codes.RUN_NOT_FOUND,
15
+ refusal=refusals.build(codes.RUN_NOT_FOUND,
16
+ ctx={"target": run_id, "run_id": run_id}))
17
+ res, _inputs = loaded
18
+ return res
@@ -0,0 +1,28 @@
1
+ """`undo [<run>]` -- the inverse of `apply`: reverse-apply a run's verified diff, restoring the
2
+ working tree. Refuses cleanly (never force) if the files changed since apply."""
3
+ from __future__ import annotations
4
+
5
+ from typing import Optional
6
+
7
+ from .. import codes, gitio, refusals, run_store
8
+ from .. import result as _result
9
+
10
+
11
+ def cmd_undo(repo: str, run_id: str) -> "_result.SubstratumResult":
12
+ loaded = run_store.load_run(repo, run_id)
13
+ if not loaded:
14
+ ref = refusals.build(codes.RUN_NOT_FOUND, ctx={"target": run_id, "run_id": run_id})
15
+ return _result.make(codes.REFUSED, run_id, "undo", code=codes.RUN_NOT_FOUND, refusal=ref)
16
+ res, _inp = loaded
17
+ if not res.diff:
18
+ ref = refusals.build(codes.APPLY_STATE_DRIFT,
19
+ engine_code="this run wrote no diff, so there is nothing to undo",
20
+ ctx={"target": run_id, "run_id": run_id})
21
+ return _result.make(codes.REFUSED, run_id, "undo", code=codes.APPLY_STATE_DRIFT, refusal=ref)
22
+ ok, msg = gitio.apply_reverse(repo, res.diff)
23
+ if not ok:
24
+ ref = refusals.build(codes.APPLY_STATE_DRIFT, engine_code=msg,
25
+ ctx={"target": run_id, "run_id": run_id})
26
+ return _result.make(codes.REFUSED, run_id, "undo", code=codes.APPLY_STATE_DRIFT, refusal=ref)
27
+ files = list(res.files or [])
28
+ return _result.make(codes.PASS, run_id, "undo", files=tuple(files), data={"reverted": files})