Every ticket as the root of its provenance graph — goal, patch, approval, runs and (as the ticket-class build lands) mandate, manifestations, guarantees, journey, artifacts and children. Rendered through the <<<TicketGraphList>>> tag.
goal
Rewrite ONE unique forge module as entirely new, behavior-preserving, installable,
control-config'd Python, per the FORGE-wide refactor standard #200 (#std).
This ticket covers ONE module — the one identified below — and nothing else. It was filed
against the module's DEDUPLICATED identity, so if that module was copy-pasted across the
forge, every copy is covered by this single ticket.
MODULE KEY : forge-rw:00b88a696e353714
(the ticketer's idempotency marker — it lives in this ticket's TITLE. Do
not edit the title, or the module will be re-filed as a duplicate.)
IDENTITY : AST-NORMALIZED — codebean's `_ast_norm` (#183), i.e.
ast.dump(ast.parse(ast.unparse(ast.parse(src)))).
Copies of this module that differ only in comments, blank lines,
quoting or spacing carry the SAME module-sha and are covered by THIS
ticket. A differing docstring is real content, so a module whose
docstring differs is a DIFFERENT module with its own ticket.
SIZE : 22268 bytes
EXTENSION : .py
CANONICAL SOURCE (the location this ticket is named for):
repo : agent_tools_suite
path : deploy_test.py
ref : HEAD (HEAD)
blob : 381ef6db02b3f154cf40f7dce14f61f0363bb7b1 (git blob sha1)
REFERENCE ONLY — you do NOT need this command. This module's bytes are embedded
COMPLETE in THE ORIGINAL SOURCE block below, and that is what the rewrite is based
on. For a reader who does have forge access:
git --git-dir=/Users/stevenpeterson/code/installed/jazz-project/config/forge/remotes/agent_tools_suite.git cat-file blob 381ef6db02b3f154cf40f7dce14f61f0363bb7b1
THE SAME MODULE ALSO LIVES AT THESE FORGE TIPS: none — this module is unique to
agent_tools_suite among the repos scanned so far.
HISTORICAL OCCURRENCES (omni-git `occ`): none to add — either the index has no
row for this blob (it is newer than the index's last snapshot, or the index does
not cover its repo), or every place it records is already listed live above.
THE ORIGINAL SOURCE, EMBEDDED — YOU NEED NO FORGE ACCESS TO DO THIS TICKET.
The module's own bytes are reproduced below, inside this ticket. THEY are the
authoritative copy and the behavior-preserving basis: rewrite from them. The
`git --git-dir=... cat-file blob` command under CANONICAL SOURCE above is kept only as a
REFERENCE for a reader who happens to have forge access — running it is NOT part of this
ticket. (Why the bytes are here at all: the pair_loop worker that executes this ticket is
sandboxed to the ticket's own congruency worktree and cannot read the forge's bare repos.
Ticket #346 is the fix that put them on the ticket.)
The block BEGINS at the line
===ORIGINAL SOURCE (verbatim, the behavior-preserving basis)===
and ENDS at the first line
===END SOURCE===
Everything strictly between those two lines is the module — 22268 byte(s), byte for byte, with no re-indentation, no re-wrapping and nothing
elided.
===ORIGINAL SOURCE (verbatim, the behavior-preserving basis)===
#!/usr/bin/env python3
# -*- coding: utf-8 -*-
"""deploy_test.py — the autonomous deploy+test worker that CLOSES the crank loop.
The mint half is already autonomous: crankd authors -> gates -> commits + tags `version-4.0NN`
in the MINT tree (~/code/congruency) and records a green `crank_runs` row. But NOTHING pushed
that tag to the gitlab origin the DEPLOY tree (~/code/congruency-run) fetches from, so a minted
version never went live. This worker is the missing handoff.
Each cycle it:
1. DETECT — newest `version-*` tag in the mint tree vs the deployed tree's checked-out tag.
2. PREFLIGHT— re-run the crank's own shadow(s) ON THE MINT TREE (self-isolated: throwaway db +
tempdir; never touches gitlab or the live server). RED here => STOP, never push,
the prior version stays live. This is the real "don't ship a broken build" gate.
3. SNAPSHOT — `db_backup.py run --db congruency` (zero-side-effect sqlite online backup) = the
rollback point before anything live is touched.
4. PUSH — `git -C <mint> push origin version-4.0NN` (SSH, BatchMode). The one missing line.
5. DEPLOY — `congruency_up.py up` (fetches the tag from gitlab, provisions php, serves; it
already `preserve_db`-s the live DB and self-gates on ?page=catalog==200).
6. TEST — keyless GETs on the live server: the deploy tree advanced to the tag; ?page=catalog
renders real content (the router returns 200 even for MISSING pages, so we check
the BODY, not the code); no core page 5xx; run.recent's newest row is ok=1 at the
deployed version; every bound guarantee clause maps to a real mandate clause.
Fully autonomous: green => the version is live. RED at any gate => file a bug (best-effort to
congruency `bug.record` + always a local jsonl) and STOP that version (never re-loop a broken
deploy). The pre-deploy DB snapshot is the rollback artifact.
python3 deploy_test.py once # one cycle (detect + process if a new version exists), exit
python3 deploy_test.py loop # standing poll loop (the "waiting deploy terminal")
python3 deploy_test.py status # show newest-minted vs deployed + per-version state
Config resolves env -> launcher registry -> expanduser default (no hardcoded logic paths):
CONGRUENCY_URL live server base url (default http://127.0.0.1:<registry port>)
DEPLOY_TEST_MINT_DIR mint tree (default ~/code/congruency)
DEPLOY_TEST_LAUNCHER congruency_up.py (default ~/code/congruency-launch/congruency_up.py)
DEPLOY_TEST_DB_BACKUP db_backup.py (default ~/bin/db_backup.py)
DEPLOY_TEST_STATE_DIR per-version outcome state (default ~/bin/config/deploy_test)
DEPLOY_TEST_LOG_DIR events + bug jsonl (default ~/bin/logs/deploy_test)
DEPLOY_TEST_INTERVAL loop poll seconds (default 300)
CONGRUENCY_API_KEY X-Api-Key for bug.record (default: read from ~/.secrets/ats.env)
No secret value is ever printed. Congruency source is NEVER edited here — this worker only pushes
already-minted tags and drives the launcher.
"""
import json
import os
import re
import subprocess
import sys
import time
import urllib.error
import urllib.request
from datetime import datetime, timezone
COMPONENT = "congruency"
CORE_PAGES = ["catalog", "forge", "omni", "bugs", "graphs", "shadowforge"]
NOT_FOUND_MARK = "Page not found" # the router's body marker for an unregistered ?page=
HTTP_TIMEOUT = 8
# --------------------------------------------------------------------------- config
def rv(env, default):
"""env override -> expanduser default. (None env name => just the default.)"""
val = os.environ.get(env) if env else None
return os.path.abspath(os.path.expanduser(val or default))
def read_ats_secret(name):
"""Read KEY=VALUE from ~/.secrets/ats.env, else the process env; None if absent. Same source
crankd uses. The value is a secret — it is only ever placed in a request header, never printed."""
path = os.path.expanduser("~/.secrets/ats.env")
if os.path.isfile(path):
try:
for line in open(path):
line = line.strip()
if line and not line.startswith("#") and "=" in line:
k, v = line.split("=", 1)
if k.strip() == name:
return v.strip().strip('"').strip("'")
except OSError:
pass
return os.environ.get(name)
def load_registry(launcher):
reg = os.path.join(os.path.dirname(launcher), "registry.json")
with open(reg) as fh:
return json.load(fh)
MINT_DIR = rv("DEPLOY_TEST_MINT_DIR", "~/code/congruency")
LAUNCHER = rv("DEPLOY_TEST_LAUNCHER", "~/code/congruency-launch/congruency_up.py")
DB_BACKUP = rv("DEPLOY_TEST_DB_BACKUP", "~/bin/db_backup.py")
STATE_DIR = rv("DEPLOY_TEST_STATE_DIR", "~/bin/config/deploy_test")
LOG_DIR = rv("DEPLOY_TEST_LOG_DIR", "~/bin/logs/deploy_test")
INTERVAL = int(os.environ.get("DEPLOY_TEST_INTERVAL") or 300)
_REG = load_registry(LAUNCHER)
DEPLOY_DIR = os.path.abspath(os.path.expanduser(_REG["source_dir"]))
PORT = int(_REG.get("host_port", _REG["port"]))
CONG = (os.environ.get("CONGRUENCY_URL") or ("http://127.0.0.1:%d" % PORT)).rstrip("/")
API_KEY = read_ats_secret("CONGRUENCY_API_KEY")
# --------------------------------------------------------------------------- helpers
def now_iso():
return datetime.now(timezone.utc).isoformat()
def ensure_dir(path, mode=0o700):
os.makedirs(path, exist_ok=True)
try:
os.chmod(path, mode)
except OSError:
pass
def git(cwd, *args, ssh=False):
env = dict(os.environ)
if ssh:
env["GIT_SSH_COMMAND"] = env.get("GIT_SSH_COMMAND", "ssh -o BatchMode=yes")
return subprocess.run(["git", "-C", cwd, *args], capture_output=True, text=True, env=env)
_VRE = re.compile(r"version-(\d+(?:\.\d+)*)")
def vkey(tag):
"""Order version tags numerically. 'version-4.121' -> (4, 121); non-matching -> ()."""
m = _VRE.search(tag or "")
return tuple(int(x) for x in m.group(1).split(".")) if m else ()
def strip_v(tag):
m = _VRE.search(tag or "")
return m.group(1) if m else (tag or "")
def newest_mint_tag():
tags = [t for t in git(MINT_DIR, "tag", "-l", "version-*").stdout.split() if vkey(t)]
return max(tags, key=vkey) if tags else None
def deployed_tag():
out = git(DEPLOY_DIR, "describe", "--tags", "--always").stdout.strip()
return out or None
# --------------------------------------------------------------------------- http
def http_get(path):
"""GET CONG+path -> (code, body_text). Never raises (network/HTTP errors become a 0/message)."""
try:
with urllib.request.urlopen(CONG + path, timeout=HTTP_TIMEOUT) as r:
return r.getcode(), r.read().decode(errors="replace")
except urllib.error.HTTPError as e:
try:
body = e.read().decode(errors="replace")
except Exception: # noqa: BLE001
body = ""
return e.code, body
except Exception as exc: # noqa: BLE001
return 0, "%s: %s" % (type(exc).__name__, exc)
def route(name, **params):
"""GET a named public route -> parsed JSON dict ({} on failure)."""
qs = "&".join("%s=%s" % (k, urllib.request.quote(str(v))) for k, v in params.items())
code, body = http_get("/?route=%s%s" % (name, ("&" + qs) if qs else ""))
if code != 200:
return {}
try:
return json.loads(body or "{}")
except json.JSONDecodeError:
return {}
def page_ok(slug):
"""A page RENDERS iff it returns 200 AND its body is not the router's 'Page not found' shell."""
code, body = http_get("/?page=%s" % slug)
if code != 200:
return False, code, "HTTP %s" % code
if NOT_FOUND_MARK in body:
return False, code, "not registered (Page not found)"
return True, code, "ok"
def cong_post(path, obj):
"""POST JSON to a token route with X-Api-Key. Returns (ok, detail). Best-effort."""
if not API_KEY:
return False, "no CONGRUENCY_API_KEY (ats.env)"
data = json.dumps(obj).encode()
headers = {"Content-Type": "application/json", "X-Api-Key": API_KEY}
req = urllib.request.Request(CONG + path, data=data, method="POST", headers=headers)
try:
with urllib.request.urlopen(req, timeout=HTTP_TIMEOUT) as r:
r.read()
return True, "ok"
except Exception as exc: # noqa: BLE001
return False, "%s: %s" % (type(exc).__name__, exc)
# --------------------------------------------------------------------------- state + logs
def _state_path():
return os.path.join(STATE_DIR, "state.json")
def load_state():
try:
with open(_state_path()) as fh:
return json.load(fh)
except (OSError, json.JSONDecodeError):
return {"tags": {}}
def save_state(state):
ensure_dir(STATE_DIR)
tmp = _state_path() + ".part"
with open(tmp, "w") as fh:
json.dump(state, fh, indent=2)
os.replace(tmp, _state_path())
def mark(state, tag, outcome, reason=None, extra=None):
rec = {"outcome": outcome, "reason": reason, "ts": now_iso()}
if extra:
rec.update(extra)
state.setdefault("tags", {})[tag] = rec
save_state(state)
def _heartbeat_path():
return os.path.join(STATE_DIR, "heartbeat.json")
def write_heartbeat(note):
"""Overwrite a single-line liveness stamp each cycle so the loop's health is observable without
`ps` (and without unbounded log growth on idle cycles)."""
ensure_dir(STATE_DIR)
rec = {"ts": now_iso(), "pid": os.getpid(), "note": note,
"deployed": deployed_tag(), "newest": newest_mint_tag()}
tmp = _heartbeat_path() + ".part"
try:
with open(tmp, "w") as fh:
json.dump(rec, fh)
os.replace(tmp, _heartbeat_path())
except OSError:
pass
def read_heartbeat():
try:
with open(_heartbeat_path()) as fh:
return json.load(fh)
except (OSError, json.JSONDecodeError):
return None
def log_event(kind, rec):
ensure_dir(LOG_DIR)
rec = dict(rec, kind=kind, ts=now_iso())
path = os.path.join(LOG_DIR, "events.jsonl")
with open(path, "a") as fh:
fh.write(json.dumps(rec) + "\n")
try:
os.chmod(path, 0o600)
except OSError:
pass
def file_bug(tag, phase, reason, detail=None):
"""Record a deploy/test failure: best-effort to congruency bug.record, ALWAYS to a local jsonl."""
ver = strip_v(tag)
bug = {
"bug_id": "deploy-%s-%s" % (ver, phase),
"source": "deploy_test",
"title": "deploy/test failed for %s at %s" % (tag, phase),
"severity": "high",
"location": "%s (%s)" % (phase, tag),
"repro": reason,
"success": "",
"fanout": 0,
"repos": 1,
"status": "open",
"pattern": "",
"meta": json.dumps({"tag": tag, "version": ver, "phase": phase, "detail": detail}),
}
ensure_dir(LOG_DIR)
bpath = os.path.join(LOG_DIR, "bugs.jsonl")
with open(bpath, "a") as fh:
fh.write(json.dumps(dict(bug, ts=now_iso())) + "\n")
try:
os.chmod(bpath, 0o600)
except OSError:
pass
ok, why = cong_post("/?route=bug.record", bug)
log_event("bug", {"tag": tag, "phase": phase, "reason": reason,
"bug_record": "ok" if ok else why, "local": bpath})
return bpath
# --------------------------------------------------------------------------- provenance lookups
def ticket_for_version(ver):
"""The ticket_id that minted `ver` (bare, e.g. '4.121'), via run.recent, or None."""
for r in route("run.recent", component=COMPONENT, n=10).get("rows", []):
if str(r.get("version")) == ver:
return r.get("ticket_id")
return None
def shadows_for_ticket(ticket_id):
"""Distinct shadow script paths the ticket's guarantees were harvested from (repo-relative)."""
seen, out = set(), []
for g in route("guarantee.list", ticket_id=ticket_id).get("rows", []):
sh = g.get("shadow")
if sh and sh not in seen:
seen.add(sh)
out.append(sh)
return out
# --------------------------------------------------------------------------- the gates
def run_preflight(tag, ticket_id):
"""Re-prove the crank green on the MINT tree before anything live is touched. Returns
(ok, results). A shadow that RUNS and fails => (False, ...). A shadow that can't be located is
skipped with a warning (the mint gate already ran it); infra gaps never block a proven mint."""
results = []
if not ticket_id:
return True, [{"shadow": None, "status": "skip", "detail": "no ticket for version (mint gate covered)"}]
shadows = shadows_for_ticket(ticket_id)
if not shadows:
return True, [{"shadow": None, "status": "skip", "detail": "no harvested shadows"}]
ok_all = True
for sh in shadows:
abspath = os.path.join(MINT_DIR, sh)
if not os.path.isfile(abspath):
results.append({"shadow": sh, "status": "skip", "detail": "not on disk"})
continue
p = subprocess.run([sys.executable, abspath], cwd=MINT_DIR, capture_output=True, text=True)
passed = p.returncode == 0
ok_all = ok_all and passed
results.append({"shadow": sh, "status": "pass" if passed else "FAIL",
"rc": p.returncode, "tail": (p.stdout or p.stderr).strip()[-300:]})
return ok_all, results
def db_snapshot():
"""Zero-side-effect rollback point for the live congruency db. Returns (ok, detail)."""
p = subprocess.run([sys.executable, DB_BACKUP, "run", "--db", COMPONENT],
capture_output=True, text=True)
return p.returncode == 0, (p.stdout or p.stderr).strip()[-300:]
def push_tag(tag):
"""Push the minted tag to the gitlab origin the deploy tree fetches from (SSH, no prompt)."""
p = git(MINT_DIR, "push", "origin", tag, ssh=True)
ok = p.returncode == 0
return ok, (p.stdout + p.stderr).strip()[-300:]
def deploy():
"""Drive the launcher: fetch the tag, provision php, serve (self-gates on catalog==200)."""
p = subprocess.run([sys.executable, LAUNCHER, "up"], capture_output=True, text=True)
return p.returncode == 0, (p.stdout or p.stderr).strip()[-400:]
def run_acceptance(tag, ticket_id):
"""Keyless post-deploy smoke + provenance test against the live server. Returns (ok, checks)."""
ver = strip_v(tag)
checks = []
def add(name, ok, detail):
checks.append({"check": name, "ok": bool(ok), "detail": detail})
dep = deployed_tag()
add("deploy-tree advanced to tag", dep == tag, "deployed=%s want=%s" % (dep, tag))
cok, ccode, cdetail = page_ok("catalog")
add("catalog renders", cok, cdetail)
server_errors = [p for p in CORE_PAGES if http_get("/?page=%s" % p)[0] >= 500]
add("no core page 5xx", not server_errors, "5xx: %s" % (server_errors or "none"))
rows = route("run.recent", component=COMPONENT, n=1).get("rows", [])
newest = rows[0] if rows else {}
add("run.recent newest is green at deployed version",
str(newest.get("version")) == ver and newest.get("ok") == 1,
"version=%s ok=%s" % (newest.get("version"), newest.get("ok")))
if ticket_id:
mand = {m.get("seq") for m in route("mandate.list", ticket_id=ticket_id).get("rows", [])}
gl = route("guarantee.list", ticket_id=ticket_id).get("rows", [])
bound = [g.get("clause_seq") for g in gl if g.get("clause_seq") is not None]
dangling = [s for s in bound if s not in mand]
add("guarantees bound to real mandate clauses",
bool(gl) and not dangling,
"guarantees=%d bound=%s dangling=%s mandate_seqs=%s"
% (len(gl), bound, dangling or "none", sorted(mand)))
else:
add("guarantees bound to real mandate clauses", True, "no ticket (skipped)")
return all(c["ok"] for c in checks), checks
# --------------------------------------------------------------------------- orchestration
def process_target(tag, state):
"""Preflight -> snapshot -> push -> deploy -> test one minted tag. Returns an outcome dict.
TERMINAL failures (preflight/deploy/test) mark the tag failed + file a bug + STOP (no re-loop).
INFRASTRUCTURE failures (snapshot/push) are non-terminal: logged, left for the next cycle."""
ver = strip_v(tag)
ticket_id = ticket_for_version(ver)
log_event("begin", {"tag": tag, "version": ver, "ticket_id": ticket_id, "deployed": deployed_tag()})
# 2. PREFLIGHT — the only gate before we ever touch gitlab/live. RED => prior stays live.
pf_ok, pf = run_preflight(tag, ticket_id)
if not pf_ok:
file_bug(tag, "preflight", "crank shadow(s) failed on the mint tree — NOT pushed", pf)
mark(state, tag, "failed", "preflight shadow failed", {"phase": "preflight", "results": pf})
return {"tag": tag, "outcome": "failed", "phase": "preflight", "detail": pf}
# 3. SNAPSHOT — rollback point (non-terminal on failure).
s_ok, s_detail = db_snapshot()
if not s_ok:
log_event("abort", {"tag": tag, "phase": "snapshot", "detail": s_detail})
return {"tag": tag, "outcome": "retry", "phase": "snapshot", "detail": s_detail}
# 4. PUSH — the missing handoff (non-terminal on failure).
p_ok, p_detail = push_tag(tag)
if not p_ok:
log_event("abort", {"tag": tag, "phase": "push", "detail": p_detail})
return {"tag": tag, "outcome": "retry", "phase": "push", "detail": p_detail}
# 5. DEPLOY — go live (terminal on failure: the deploy mechanism itself broke).
d_ok, d_detail = deploy()
if not d_ok:
file_bug(tag, "deploy", "congruency_up.py up failed", d_detail)
mark(state, tag, "failed", "deploy (up) failed", {"phase": "deploy", "detail": d_detail})
return {"tag": tag, "outcome": "failed", "phase": "deploy", "detail": d_detail}
# 6. TEST — keyless acceptance (terminal on failure; the new version is live-but-flagged).
t_ok, checks = run_acceptance(tag, ticket_id)
if not t_ok:
file_bug(tag, "test", "post-deploy acceptance failed (version is LIVE but flagged)", checks)
mark(state, tag, "failed", "acceptance failed", {"phase": "test", "checks": checks})
return {"tag": tag, "outcome": "failed", "phase": "test", "checks": checks}
mark(state, tag, "deployed", None, {"phase": "test", "ticket_id": ticket_id, "checks": checks})
log_event("deployed", {"tag": tag, "version": ver, "ticket_id": ticket_id, "checks": checks})
return {"tag": tag, "outcome": "deployed", "ticket_id": ticket_id, "checks": checks}
def find_target(state):
"""The newest minted tag that is ahead of the deployed tree and not already terminal in state.
Returns (tag_or_None, reason)."""
newest = newest_mint_tag()
if not newest:
return None, "no version-* tags in mint tree"
dep = deployed_tag()
if vkey(newest) <= vkey(dep or ""):
return None, "up-to-date (deployed %s >= newest %s)" % (dep, newest)
prior = state.get("tags", {}).get(newest, {}).get("outcome")
if prior in ("deployed", "failed"):
return None, "newest %s already %s — STOP (clear state to retry)" % (newest, prior)
return newest, "target %s (deployed %s)" % (newest, dep)
def process_once():
state = load_state()
target, reason = find_target(state)
if not target:
print(" · %s" % reason)
return {"outcome": "noop", "reason": reason}
print(" → %s" % reason)
result = process_target(target, state)
icon = {"deployed": "✓", "failed": "✗", "retry": "…"}.get(result["outcome"], "?")
print(" %s %s: %s%s" % (icon, result["outcome"], target,
(" @ " + result["phase"]) if result.get("phase") else ""))
if result["outcome"] == "failed":
for c in result.get("checks", []) or []:
if not c["ok"]:
print(" - %s: %s" % (c["check"], c["detail"]))
print(" bug filed under %s" % LOG_DIR)
return result
# --------------------------------------------------------------------------- verbs
def cmd_once():
r = process_once()
return 0 if r["outcome"] in ("deployed", "noop", "retry") else 1
def cmd_loop():
print("deploy_test loop — mint=%s deploy=%s cong=%s interval=%ds"
% (MINT_DIR, DEPLOY_DIR, CONG, INTERVAL))
while True:
try:
r = process_once()
write_heartbeat(r.get("reason") or r.get("outcome"))
except Exception as exc: # noqa: BLE001 — a cycle must never kill the standing loop
log_event("cycle-error", {"error": "%s: %s" % (type(exc).__name__, exc)})
write_heartbeat("cycle-error: %s" % type(exc).__name__)
print(" ! cycle error: %s: %s" % (type(exc).__name__, exc), file=sys.stderr)
sys.stdout.flush()
time.sleep(INTERVAL)
def cmd_status():
state = load_state()
newest, dep = newest_mint_tag(), deployed_tag()
target, reason = find_target(state)
print(" mint newest : %s" % newest)
print(" deployed : %s" % dep)
print(" live server : %s (%s)" % (CONG, "up" if http_get("/?page=catalog")[0] == 200 else "DOWN"))
print(" next target : %s" % (target or ("none — " + reason)))
hb = read_heartbeat()
if hb:
print(" last poll : %s (%s)" % (hb.get("ts"), hb.get("note")))
tags = state.get("tags", {})
if tags:
print(" state:")
for t in sorted(tags, key=vkey):
r = tags[t]
print(" %-16s %-9s %s" % (t, r.get("outcome"), r.get("reason") or ""))
return 0
def main():
verb = sys.argv[1] if len(sys.argv) > 1 else "status"
verbs = {"once": cmd_once, "loop": cmd_loop, "status": cmd_status}
fn = verbs.get(verb)
if fn is None:
raise SystemExit("usage: deploy_test.py once|loop|status")
return fn() or 0
if __name__ == "__main__":
sys.exit(main())
===END SOURCE===
THE CALLABLE SURFACE — every callable in the original, lowered with codebean's
`lower_node` (#183). The rewrite must present THIS surface (same names, same
parameters, same defaults) so callers of the original keep working:
rv(env, default)
read_ats_secret(name)
load_registry(launcher)
now_iso()
ensure_dir(path, mode=448)
git(cwd, *args, ssh=False)
vkey(tag)
strip_v(tag)
newest_mint_tag()
deployed_tag()
http_get(path)
route(name, **params)
page_ok(slug)
cong_post(path, obj)
_state_path()
load_state()
save_state(state)
mark(state, tag, outcome, reason=None, extra=None)
_heartbeat_path()
write_heartbeat(note)
read_heartbeat()
log_event(kind, rec)
file_bug(tag, phase, reason, detail=None)
ticket_for_version(ver)
shadows_for_ticket(ticket_id)
run_preflight(tag, ticket_id)
db_snapshot()
push_tag(tag)
deploy()
run_acceptance(tag, ticket_id)
process_target(tag, state)
find_target(state)
process_once()
cmd_once()
cmd_loop()
cmd_status()
main()
CAPTURED BEHAVIORAL EXEMPLARS: none. no function in this module is minable — criteria_miner mines only
self-contained pure functions (it refuses anything that mentions a
world-touching or non-deterministic name, plus methods and no-arg
functions), and nothing here qualified
This does NOT weaken the ticket's bar — it MOVES the work: you must write the
equivalence exemplars by hand from the original's behavior before rewriting, and
they are what settles this ticket.
THE STANDARD — #200's four requirements, which this ticket exists to enforce. All four are
the standard's own words; none is optional:
1. BEHAVIOR-PRESERVING, VERIFIED NOT ASSERTED — the deliverable is ENTIRELY NEW source
code (rewritten, not copied). It must reproduce the ORIGINAL's behavior, and the proof
is the exemplars above (captured via codebean lower_* #183 + criteria_miner
IO-exemplars, exactly as #200 specifies).
2. A PYTHON MODULE — regardless of the source language. This module's source is `.py`;
the rewrite is Python either way.
3. INSTALLABLE — a proper package: pyproject.toml, `pip install -e .` works, console
entry points where applicable. WRITE ONLY YOUR OWN MODULE FILE at
forge2/<repo>/<module>.py. Do NOT hand-author pyproject.toml or __init__.py: the
harness DERIVES them ADDITIVELY from the modules present (congruency/tools/
forge_package.py, #410 — the analogue of the tag-registry fix #281), so your unit never
conflicts with, or clobbers, a sibling module of the same forge2/<repo>/ package. A
package-level pyproject includes every .py in the directory, so your module installs
and imports as `<repo>.<module>` the moment it lands.
4. CONTROL-CONFIG'D — NO hardcoded paths or params. A JSON/registry config object drives
it (the install.json / registry.json idiom of this repo), and per note-for-claude a
tool that cannot see its config THROWS rather than guessing a default. Add ONLY your
own `forge2.<repo>.<module>` section to the package's registry.json (create the file if
it is absent); NEVER read, edit or drop a sibling module's section — the sections are
disjoint keys and stay additive that way.
ACCEPTANCE (#200's, per rewrite ticket) — this ticket settles when ALL of:
a. the new source reproduces every captured exemplar above, plus the edge cases you add
for what sampling cannot reach (paste the runs as evidence);
b. it presents the callable surface above, so existing callers keep working;
c. `pip install -e .` installs it clean from its own pyproject.toml;
d. it runs off a config object with ZERO hardcoded settings, and throws without one.
Kind is `build` (a pair: coder + adversarial tester). This ticket hangs off #200 by a
`spawned` edge, so a stop ticket aimed at #200 brakes every rewrite in this programme.
Filed mechanically by checkouts/current/congruency/tools/forge_refactor_ticketer.py under ticket #201, from the forge bare repos at
/Users/stevenpeterson/code/installed/jazz-project/config/forge/remotes. Its original source is embedded above (#346), so this ticket is complete on its own.