Ticket graphs

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.

← all ticket-graphs

ACTIVE#423 forge-refactor

Rewrite agent_tools_suite:crankd.py as behavior-preserving, installable, control-config'd Python [forge-rw:e63eaa8c7c306f03]

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:e63eaa8c7c306f03 (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 : 19522 bytes EXTENSION : .py CANONICAL SOURCE (the location this ticket is named for): repo : agent_tools_suite path : crankd.py ref : HEAD (HEAD) blob : 2d7fa16472882af65efb8aca792ad98ab500585d (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 2d7fa16472882af65efb8aca792ad98ab500585d 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 — 19522 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 """crankd — the crank service. The ONLY thing that runs a crank. The agent never spawns a crank; it POSTs here. crankd verifies that the crank's ticket carries a valid WALLET signature over the ticket's CONTENT (unforgeable by the agent — it has no wallet), then runs the crank as ITS OWN child process, off the agent's process tree, and records provenance. Because the agent has no subprocess capability at all and crankd only runs wallet-approved cranks, the escape (a crank subprocess reaching back into the agent's ~/.claude / permission layer) has no path. POST /crank {"component": "...", "intent": "..."} -> verify approval -> run -> {ok, stdout_tail} GET /digest?component=... -> the digest a human must wallet-sign GET /health env: CONGRUENCY_URL (default :8899), CRANK_APPROVER_PUBKEY (ssh-ed25519 ... of wallet 'crank-approver'), CONGRUENCY_API_KEY (X-Api-Key for provenance writes). Secrets are read from ~/.secrets/ats.env. Follow-on isolation (not yet): run the crank under a separate user / the workstation VM so even an approved crank cannot touch the operator's home. Today it runs as crankd's child (off the agent tree), which already removes the agent-owned-subprocess escape; congruency patches stay audit-veto-confined inside mint_crank. """ import hashlib import json import os import re import subprocess import sys import tempfile import threading import urllib.error import urllib.request from datetime import datetime, timezone from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer from urllib.parse import parse_qs, urlparse from cryptography.hazmat.primitives.serialization import load_ssh_public_key HERE = os.path.dirname(os.path.abspath(__file__)) sys.path.insert(0, HERE) import crank_discipline # the audit-veto (run_confined) used to apply the approved patch # noqa: E402 HOST = os.environ.get("CRANKD_HOST", "127.0.0.1") PORT = int(os.environ.get("CRANKD_PORT", "8812")) CONG = (os.environ.get("CONGRUENCY_URL") or "http://127.0.0.1:8899").rstrip("/") def _secret(name): """Read a value from ~/.secrets/ats.env (KEY=VALUE), else the process env; None if absent.""" path = os.path.expanduser("~/.secrets/ats.env") if os.path.isfile(path): for line in open(path): line = line.strip() if line.startswith(name + "="): return line.split("=", 1)[1] return os.environ.get(name) APPROVER_PUBKEY = _secret("CRANK_APPROVER_PUBKEY") # ssh-ed25519 pubkey of wallet 'crank-approver' CONGRUENCY_KEY = _secret("CONGRUENCY_API_KEY") # component -> how to crank it. The crank runs as crankd's child, in the component's repo. COMPONENTS = { "workstation": { "repo": os.path.expanduser("~/code/workstation"), "argv": lambda intent: [sys.executable, "install.py", "--mint", "-m", intent], "version": lambda repo: json.load(open(os.path.join(repo, "install.json"))).get("version", "0.0"), }, "congruency": { # crankd applies the agent's patch (audit-veto), then mint_crank --no-patch captures it as the # next version-4.0NN (self-hosting: make_state snapshots the live db to forge, shadow gate, tag). "repo": os.path.expanduser("~/code/congruency"), "argv": lambda intent: ["python3", "checkouts/current/congruency/tools/mint_crank.py", "--no-patch", "-m", intent], "version": lambda repo: json.load(open(os.path.join(repo, "install.json"))).get("version", "0.0"), }, } _crank_lock = threading.Lock() # serialize: one crank at a time class CrankError(Exception): def __init__(self, status, message): self.status = status super().__init__(message) def _now(): return datetime.now(timezone.utc).isoformat() def _cong(method, path, obj=None): data = json.dumps(obj).encode() if obj is not None else None headers = {"Content-Type": "application/json"} if CONGRUENCY_KEY: headers["X-Api-Key"] = CONGRUENCY_KEY req = urllib.request.Request(CONG + path, data=data, method=method, headers=headers) with urllib.request.urlopen(req, timeout=5) as r: return json.loads(r.read().decode() or "{}") def fetch_ticket(component): """The component's latest ticket (incl. approval_sig) via the congruency named route, or None.""" rows = _cong("GET", "/?route=ticket.approval&component=%s" % component).get("rows") or [] return rows[0] if rows else None def digest(ticket): """The canonical content digest a human signs — binds id+component+title+body, so any post-approval edit to the ticket invalidates the signature (defeats bait-and-switch).""" canon = "\n".join(str(ticket.get(k, "")) for k in ("id", "component", "title", "body", "patch")) return hashlib.sha256(canon.encode()).hexdigest() def verify_approval(ticket): """True iff ticket.approval_sig is a valid wallet signature over digest(ticket).""" if not APPROVER_PUBKEY or not ticket.get("approval_sig"): return False try: pub = load_ssh_public_key(APPROVER_PUBKEY.encode()) pub.verify(bytes.fromhex(ticket["approval_sig"]), digest(ticket).encode()) return True except Exception: # noqa: BLE001 — bad sig / bad hex / wrong key all mean "not approved" return False def apply_patch(patch_src, repo): """Apply the approved patch under the audit-veto (confined to the repo — no shell-out / out-of-repo writes), off the agent's process tree. Raises CrankError(403) on escape.""" with tempfile.TemporaryDirectory() as d: patch_file = os.path.join(d, "patch.py") with open(patch_file, "w") as fh: fh.write(patch_src) try: return crank_discipline.run_confined(patch_file, repo) except crank_discipline.CrankRefused as e: raise CrankError(403, "patch refused by the audit-veto: %s" % e) def _version_tuple(v): return tuple(int(x) for x in str(v).split(".") if x.strip().isdigit()) def current_version(component): try: return COMPONENTS[component]["version"](COMPONENTS[component]["repo"]) except Exception: # noqa: BLE001 return "0.0" def fetch_policy(component): rows = _cong("GET", "/?route=policy.active&component=%s" % component).get("rows") or [] return rows[0] if rows else None def policy_digest(policy): """The canonical digest the human wallet-signs for a standing policy.""" canon = "\n".join(str(policy.get(k, "")) for k in ("component", "max_version", "expires")) return hashlib.sha256(canon.encode()).hexdigest() def policy_allows(component): """True iff a wallet-signed standing policy covers this crank: valid signature, not expired, and the component's current version is still below the policy's max_version. The audit-veto still confines every patch, so this bounds WHICH cranks run autonomously, not what a patch may do.""" if not APPROVER_PUBKEY: return False policy = fetch_policy(component) if not policy or not policy.get("sig"): return False try: load_ssh_public_key(APPROVER_PUBKEY.encode()).verify(bytes.fromhex(policy["sig"]), policy_digest(policy).encode()) except Exception: # noqa: BLE001 return False expires = policy.get("expires") or 0 if expires and datetime.now(timezone.utc).timestamp() > float(expires): return False return _version_tuple(current_version(component)) < _version_tuple(policy.get("max_version", "0")) def _shadows(repo): """The shadow files declared in the repo's install.json (empty list if unreadable).""" try: return json.load(open(os.path.join(repo, "install.json"))).get("shadows", []) except Exception: # noqa: BLE001 return [] def _harvest_guarantees(ticket_id, repo, version, new_shadows): """For each shadow this crank INTRODUCED, run it and record its green ✓ labels as guarantees attributed to the ticket. Applies the author's declared bindings (binding.list) so each guarantee's clause_seq is set where declared (else unbound). Best-effort; never raises. Returns count recorded.""" binds = {} # declared guarantee-label -> clause_seq try: rows = _cong("GET", "/?route=binding.list&ticket_id=%s" % ticket_id).get("rows") or [] binds = {r["guarantee_label"]: r["clause_seq"] for r in rows} except Exception: # noqa: BLE001 pass n = 0 for shadow in new_shadows or []: try: r = subprocess.run([sys.executable, shadow], cwd=repo, capture_output=True, text=True, timeout=120) except Exception: # noqa: BLE001 continue if r.returncode != 0: # only harvest a shadow that held green continue for line in r.stdout.splitlines(): line = re.sub(r"\x1b\[[0-9;]*m", "", line).strip() if not line.startswith("✓"): continue g = line[1:].strip() dig = hashlib.sha256(("%s|%s|%s" % (ticket_id, shadow, g)).encode()).hexdigest() try: _cong("POST", "/?route=guarantee.record", {"ticket_id": int(ticket_id), "version": version, "shadow": shadow, "guarantee": g, "clause_seq": binds.get(g), "digest": dig}) n += 1 except Exception: # noqa: BLE001 pass return n def _run_shadows_green(repo): """Run every shadow declared in the repo and collect the green ✓ labels it CURRENTLY asserts — the candidate contract this crank would ship.""" green = set() for shadow in _shadows(repo): try: r = subprocess.run([sys.executable, shadow], cwd=repo, capture_output=True, text=True, timeout=120) except Exception: # noqa: BLE001 continue if r.returncode != 0: continue for line in r.stdout.splitlines(): line = re.sub(r"\x1b\[[0-9;]*m", "", line).strip() if line.startswith("✓"): green.add(line[1:].strip()) return green def _held_guarantees(component): """Every guarantee label the component has previously recorded — the contract it must not erode.""" try: rows = _cong("GET", "/?route=guarantee.held&component=%s" % component).get("rows") or [] return {r["guarantee"] for r in rows} except Exception: # noqa: BLE001 return set() def _revert(repo): """Undo an applied-but-unminted patch so a refused crank leaves the repo at HEAD (not dirty).""" for argv in (["git", "checkout", "--", "."], ["git", "clean", "-fd"]): try: subprocess.run(argv, cwd=repo, capture_output=True, timeout=30) except Exception: # noqa: BLE001 pass def run_crank(component, intent, patch=None): """Apply the approved patch (confined), then run the crank — both as crankd's OWN children, off the agent's process tree. Returns a result dict.""" cfg = COMPONENTS[component] before = set(_shadows(cfg["repo"])) # shadows before the patch, to spot what it introduces if patch and patch.strip(): apply_patch(patch, cfg["repo"]) # audit-veto confined; makes the change in the repo held = _held_guarantees(component) # regression gate: a crank must not DROP a held guarantee if held: dropped = held - _run_shadows_green(cfg["repo"]) if dropped: _revert(cfg["repo"]) # undo the patch — the crank is refused, not minted raise CrankError(409, "contract regression: %d held guarantee(s) would be dropped: %s" % (len(dropped), "; ".join(sorted(dropped))[:300])) result = subprocess.run(cfg["argv"](intent), cwd=cfg["repo"], capture_output=True, text=True, timeout=900) if result.returncode != 0: _revert(cfg["repo"]) # undo the patch — the mint failed, so a retry sees a clean tree raise CrankError(500, "crank failed (rc=%s): %s" % (result.returncode, (result.stderr or result.stdout).strip()[-400:])) return {"ok": True, "component": component, "patched": bool(patch and patch.strip()), "stdout": (result.stdout or ""), # full output — recorded, then popped from the response "stdout_tail": (result.stdout or "").strip().splitlines()[-5:], "new_shadows": [s for s in _shadows(cfg["repo"]) if s not in before]} def _journal(ticket_id, kind, text): """Append a journey event (success/blunder/pivot/note) to a ticket's history. Best-effort.""" if not ticket_id: return try: _cong("POST", "/?route=journey.record", {"ticket_id": int(ticket_id), "kind": kind, "text": text}) except Exception: # noqa: BLE001 pass def _publish_artifact(ticket_id, version, blob_text, name="workstation-run", kind="run-output"): """Store a crank's run-evidence in forge (content-addressed) and reference it on the ticket. forge addresses by sha256(content), so we compute the sha locally. Best-effort; returns the sha or None.""" if not ticket_id or not blob_text: return None sha = hashlib.sha256(blob_text.encode()).hexdigest() try: fd, tmp = tempfile.mkstemp() with os.fdopen(fd, "w") as fh: fh.write(blob_text) subprocess.run(["python3", "-m", "forge.cli", "artifact", "put", tmp, "--name", name, "--version", str(version), "--kind", kind], cwd=os.path.expanduser("~/code/jazz-forge"), capture_output=True, timeout=60) os.unlink(tmp) _cong("POST", "/?route=artifact.record", {"ticket_id": int(ticket_id), "name": name, "version": str(version), "forge_sha": sha, "kind": kind}) return sha except Exception: # noqa: BLE001 return None def crank(component, intent): """The whole gated flow: wallet-approved-ticket check -> run -> provenance. Raises CrankError.""" if component not in COMPONENTS: raise CrankError(404, "unknown component %r" % component) if not intent: raise CrankError(400, "intent required") ticket = fetch_ticket(component) if not ticket: raise CrankError(403, "no ticket for %r — file one first" % component) approved = verify_approval(ticket) # per-crank wallet signature (manual) by_policy = policy_allows(component) if not approved else False # standing scope (autonomous) if not (approved or by_policy): raise CrankError(403, "ticket %s not authorized: no wallet approval and no covering policy for %s" % (ticket.get("id"), component)) try: with _crank_lock: result = run_crank(component, intent, ticket.get("patch")) except CrankError as e: # a refused/failed crank is a blunder in the ticket's journey _journal(ticket.get("id"), "blunder", str(e)) raise try: # provenance best-effort _cong("POST", "/?api=memories", {"ts": _now(), "kind": "crank", "component": component, "intent": intent, "session": "crankd", "note": "ticket=%s auth=%s" % (ticket.get("id"), "signature" if approved else "policy")}) except Exception: # noqa: BLE001 pass result["ticket"] = ticket.get("id") result["authorized_by"] = "ticket-signature" if approved else "policy" try: # persist the FULL run (durable history + the source phase-2 harvests guarantees from) out = result.get("stdout", "") cfg = COMPONENTS[component] _cong("POST", "/?route=run.record", { "component": component, "version": cfg["version"](cfg["repo"]), "ticket_id": ticket.get("id"), "authorized_by": result["authorized_by"], "ok": 1 if result.get("ok") else 0, "shadow_output": out, "digest": hashlib.sha256(out.encode()).hexdigest()}) except Exception: # noqa: BLE001 pass try: # iteration 5: auto-harvest guarantees from the shadow(s) this crank introduced cfg = COMPONENTS[component] result["guarantees"] = _harvest_guarantees( ticket.get("id"), cfg["repo"], cfg["version"](cfg["repo"]), result.get("new_shadows")) except Exception: # noqa: BLE001 pass _journal(ticket.get("id"), "success", # iteration 10: a mint is a success in the journey "minted %s v%s" % (component, COMPONENTS[component]["version"](COMPONENTS[component]["repo"]))) try: # iteration 11: publish the run evidence to forge, reference it on the ticket _publish_artifact(ticket.get("id"), COMPONENTS[component]["version"](COMPONENTS[component]["repo"]), result.get("stdout", ""), name=component + "-run") except Exception: # noqa: BLE001 pass result.pop("stdout", None) # keep the crank response small (tail only) result.pop("new_shadows", None) return result class Handler(BaseHTTPRequestHandler): def log_message(self, *a): # quiet the default access log pass def _send(self, status, obj): body = json.dumps(obj).encode() self.send_response(status) self.send_header("Content-Type", "application/json") self.send_header("Content-Length", str(len(body))) self.end_headers() self.wfile.write(body) def do_GET(self): parsed = urlparse(self.path) if parsed.path == "/health": return self._send(200, {"ok": True, "components": list(COMPONENTS), "approver": bool(APPROVER_PUBKEY)}) if parsed.path == "/digest": component = (parse_qs(parsed.query).get("component") or [""])[0] ticket = fetch_ticket(component) if component else None if not ticket: return self._send(404, {"error": "no ticket for component %r" % component}) return self._send(200, {"component": component, "ticket": ticket.get("id"), "digest": digest(ticket)}) return self._send(404, {"error": "not found"}) def do_POST(self): if urlparse(self.path).path != "/crank": return self._send(404, {"error": "not found"}) try: length = int(self.headers.get("Content-Length") or 0) body = json.loads(self.rfile.read(length) or "{}") except Exception: # noqa: BLE001 return self._send(400, {"error": "bad json"}) try: return self._send(200, crank(body.get("component"), body.get("intent"))) except CrankError as e: return self._send(e.status, {"error": str(e)}) except Exception as e: # noqa: BLE001 return self._send(500, {"error": "crankd: %s" % e}) def main(): server = ThreadingHTTPServer((HOST, PORT), Handler) sys.stderr.write("[crankd] http://%s:%d — components=%s approver=%s\n" % (HOST, PORT, list(COMPONENTS), bool(APPROVER_PUBKEY))) server.serve_forever() if __name__ == "__main__": 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: _secret(name) CrankError.__init__(self, status, message) _now() _cong(method, path, obj=None) fetch_ticket(component) digest(ticket) verify_approval(ticket) apply_patch(patch_src, repo) _version_tuple(v) current_version(component) fetch_policy(component) policy_digest(policy) policy_allows(component) _shadows(repo) _harvest_guarantees(ticket_id, repo, version, new_shadows) _run_shadows_green(repo) _held_guarantees(component) _revert(repo) run_crank(component, intent, patch=None) _journal(ticket_id, kind, text) _publish_artifact(ticket_id, version, blob_text, name='workstation-run', kind='run-output') crank(component, intent) Handler.log_message(self, *a) Handler._send(self, status, obj) Handler.do_GET(self) Handler.do_POST(self) main() CAPTURED BEHAVIORAL EXEMPLARS — real (input, output) pairs mined from THIS module with criteria_miner (#200's "verified, not asserted"), running the ORIGINAL as the oracle. THE REWRITE MUST REPRODUCE EVERY PAIR BELOW, exactly: current_version(component) — 14 exemplar(s) captured: current_version('repo') == '0.0' current_version('version') == '0.0' current_version('0.0') == '0.0' current_version(0) == '0.0' current_version(1) == '0.0' current_version(-1) == '0.0' ... 8 more exemplar(s) for this function — re-mine them with criteria_miner.mine() on the source above _journal(ticket_id, kind, text) — 30 exemplar(s) captured: _journal('POST', 'POST', 'POST') == None _journal('POST', 'POST', 'text') == None _journal('POST', 'POST', '/?route=journey.record') == None _journal('POST', 'POST', 'kind') == None _journal('POST', 'POST', 'ticket_id') == None _journal('POST', 'POST', 0) == None ... 24 more exemplar(s) for this function — re-mine them with criteria_miner.mine() on the source above HONEST LIMIT (criteria_miner's own): these cover the SAMPLED inputs, probed around the function's own branch constants. They are a strong refactor-drift net, NOT a proof of total equivalence — so treat them as the FLOOR of this ticket's equivalence evidence and add the edge cases they miss (see ACCEPTANCE). 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.

patch

none

approval

unapproved

runs

no runs recorded

mandate (clauses)

not yet recorded — lands with the ticket-class build

manifestations

not yet recorded — lands with the ticket-class build

guarantees

not yet recorded — lands with the ticket-class build

journey (blunders & successes)

not yet recorded — lands with the ticket-class build

artifacts (forge)

checkouts/current/forge2/agent_tools_suite/crankd.py:cff8c13e08 forge-source · forge 68b703c2ab70
checkouts/current/forge2/agent_tools_suite/pyproject.toml:cff8c13e08 forge-source · forge 953c98d77672
checkouts/current/forge2/agent_tools_suite/registry.json:cff8c13e08 forge-source · forge ecbe9edfeeaf

source (forge tree)

browse congruency source (forge-parked tree)

children

not yet recorded — lands with the ticket-class build