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.
- substratum_cli/README.md +51 -0
- substratum_cli/__init__.py +6 -0
- substratum_cli/__main__.py +7 -0
- substratum_cli/_vendor/__init__.py +0 -0
- substratum_cli/_vendor/_pro_test_derive.py +169 -0
- substratum_cli/account.py +62 -0
- substratum_cli/cli.py +185 -0
- substratum_cli/codes.py +114 -0
- substratum_cli/commands/__init__.py +0 -0
- substratum_cli/commands/apply.py +49 -0
- substratum_cli/commands/auth.py +37 -0
- substratum_cli/commands/commit.py +40 -0
- substratum_cli/commands/explain.py +63 -0
- substratum_cli/commands/failing.py +80 -0
- substratum_cli/commands/fix.py +48 -0
- substratum_cli/commands/history.py +15 -0
- substratum_cli/commands/init_cmd.py +155 -0
- substratum_cli/commands/replay.py +68 -0
- substratum_cli/commands/scaffold.py +71 -0
- substratum_cli/commands/show.py +18 -0
- substratum_cli/commands/undo.py +28 -0
- substratum_cli/commands/verify.py +193 -0
- substratum_cli/commands/write.py +42 -0
- substratum_cli/config.py +49 -0
- substratum_cli/diffs.py +106 -0
- substratum_cli/engine.py +154 -0
- substratum_cli/gitio.py +102 -0
- substratum_cli/langscan.py +110 -0
- substratum_cli/product_ops.py +639 -0
- substratum_cli/refusals.py +127 -0
- substratum_cli/remote.py +140 -0
- substratum_cli/render.py +332 -0
- substratum_cli/result.py +185 -0
- substratum_cli/run_store.py +162 -0
- substratum_cli/runid.py +69 -0
- substratum_cli/session.py +558 -0
- substratum_cli/style.py +128 -0
- substratum_cli-0.1.0.dist-info/METADATA +69 -0
- substratum_cli-0.1.0.dist-info/RECORD +42 -0
- substratum_cli-0.1.0.dist-info/WHEEL +5 -0
- substratum_cli-0.1.0.dist-info/entry_points.txt +2 -0
- 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})
|