From 2583862d2f10cb659438b66d8757664e3c003b02 Mon Sep 17 00:00:00 2001 From: Val Alexander Date: Mon, 6 Jul 2026 19:48:48 -0500 Subject: [PATCH 1/2] feat(demo): offline self-verifying reference demo of the operating loop (#19) Replay the full ClawSweeper-style loop against the real adapter binary with an in-memory GitHub API stub and a headless-contract-conformant fake coven-code runtime: issue assignment -> Check Run -> edited-in-place status comment -> familiar-voice draft PR, plus maintainer steering (status, retry, unknown-verb clarification), the write-access permission gate, casual-mention and self-comment suppression, and a per-token-role audit trail. Every property is asserted programmatically; a green exit proves the loop. Signed-off-by: Val Alexander --- .../fake-coven-codecpython-314.pyc | Bin 0 -> 5821 bytes .../__pycache__/github-stub.cpython-314.pyc | Bin 0 -> 16726 bytes examples/demo/fake-coven-code | 137 +++++++ examples/demo/github-stub.py | 296 ++++++++++++++++ examples/demo/run-demo.sh | 335 ++++++++++++++++++ 5 files changed, 768 insertions(+) create mode 100644 examples/demo/__pycache__/fake-coven-codecpython-314.pyc create mode 100644 examples/demo/__pycache__/github-stub.cpython-314.pyc create mode 100755 examples/demo/fake-coven-code create mode 100755 examples/demo/github-stub.py create mode 100755 examples/demo/run-demo.sh diff --git a/examples/demo/__pycache__/fake-coven-codecpython-314.pyc b/examples/demo/__pycache__/fake-coven-codecpython-314.pyc new file mode 100644 index 0000000000000000000000000000000000000000..99b3af016ff35cfb32055f74b4aa0b2b06d92340 GIT binary patch literal 5821 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 UrdD+IAJeA482emD6(;BZ0;?d)@&Et; literal 0 HcmV?d00001 diff --git a/examples/demo/fake-coven-code b/examples/demo/fake-coven-code new file mode 100755 index 0000000..22f807b --- /dev/null +++ b/examples/demo/fake-coven-code @@ -0,0 +1,137 @@ +#!/usr/bin/env python3 +"""Contract-conformant stand-in for `coven-code --headless` (demo, issue #19). + +Speaks headless contract v2 (`docs/headless-contract.md`): reads the tokenless +session brief from `--context`, writes the structured result envelope to +`--output`, and exits 0. Instead of running a real agent session, it fabricates +a small, plausible fix in the familiar's voice so the adapter's publication +path (Check Run, status comment, draft PR) can be demonstrated offline. + +It also proves two security invariants of the real integration on every run: + + * the brief is tokenless — no credential material appears in the JSON; + * git authority arrives only via the COVEN_GIT_TOKEN environment variable. +""" + +import json +import os +import sys + + +def parse_args(argv): + args = {"headless": False, "context": None, "output": None} + it = iter(argv) + for arg in it: + if arg == "--headless": + args["headless"] = True + elif arg == "--context": + args["context"] = next(it, None) + elif arg == "--output": + args["output"] = next(it, None) + if not (args["headless"] and args["context"] and args["output"]): + sys.exit("usage: fake-coven-code --headless --context BRIEF --output RESULT") + return args + + +def no_review(): + return { + "mode": "none", + "evidence_status": "not_applicable", + "reviewed_files": [], + "supporting_files": [], + "findings": [], + "tests_run": [ + { + "command": "cargo test -p auth refresh", + "status": "passed", + "output_summary": "9/9 passing", + } + ], + "no_findings_reason": None, + "limitations": [], + } + + +def main(): + args = parse_args(sys.argv[1:]) + with open(args["context"], encoding="utf-8") as f: + raw_brief = f.read() + brief = json.loads(raw_brief) + + if brief.get("contract_version") != "2": + sys.exit(f"unsupported brief contract_version: {brief.get('contract_version')}") + + # Invariant checks (issue #4): tokenless brief, env-injected git authority. + git_token = os.environ.get("COVEN_GIT_TOKEN", "") + if not git_token: + sys.exit("COVEN_GIT_TOKEN missing — the worker must inject git authority") + if git_token in raw_brief: + sys.exit("SECURITY: git token leaked into the session brief") + print( + "fake-coven-code: brief is tokenless; git authority arrived via env only", + file=sys.stderr, + ) + + familiar = brief["familiar"]["display_name"] + task = brief["task"] + kind = task["kind"] + + if kind == "fix_issue": + number = task["issue_number"] + branch = f"cody/fix-issue-{number}" + summary = ( + f"Fixed the OAuth token refresh by adding a 60-second " + f"clock-skew buffer (issue #{number})." + ) + pr_body = ( + f"## Hey, I'm {familiar} \U0001f43e\n\n" + f"Issue #{number} traced to the OAuth refresh path not accounting " + f"for clock skew between the client and the auth server. I added a " + f"60-second buffer to the expiry check and regression tests for " + f"both skew directions.\n\n" + f"**Changed:** `src/auth/refresh.rs` (+14 / -3)\n" + f"**Tests:** 9/9 passing (2 new regression cases)\n\n" + f"I kept the diff scoped to the refresh path; the token store " + f"itself is untouched. Shout if you want the buffer configurable." + ) + commits = [ + { + "sha": "f1e2d3c4b5a6", + "message": f"fix: add clock-skew buffer to OAuth refresh (#{number})", + } + ] + files = ["src/auth/refresh.rs", "src/auth/refresh_test.rs"] + elif kind == "address_review_comment": + number = task["pr_number"] + branch = f"cody/pr-{number}-feedback" + summary = f"Addressed review feedback on PR #{number}." + pr_body = ( + f"## {familiar} here \U0001f43e\n\n" + f"Follow-up for the review feedback on #{number} — renamed the " + f"helper and added the missing error-path test." + ) + commits = [ + {"sha": "0a1b2c3d4e5f", "message": "refactor: address review feedback"} + ] + files = ["src/auth/refresh.rs"] + else: + sys.exit(f"demo runtime does not handle task kind: {kind}") + + result = { + "contract_version": "2", + "status": "success", + "branch": branch, + "commits": commits, + "files_changed": files, + "summary": summary, + "pr_body": pr_body, + "review": no_review(), + "exit_reason": None, + } + with open(args["output"], "w", encoding="utf-8") as f: + json.dump(result, f, indent=2) + print(f"fake-coven-code: wrote result for {kind} -> {branch}", file=sys.stderr) + + +if __name__ == "__main__": + main() diff --git a/examples/demo/github-stub.py b/examples/demo/github-stub.py new file mode 100755 index 0000000..8e1a40f --- /dev/null +++ b/examples/demo/github-stub.py @@ -0,0 +1,296 @@ +#!/usr/bin/env python3 +"""In-memory GitHub API stub for the reference demo (issue #19). + +Stands in for api.github.com so the full operating loop — scoped token +minting, Check Runs, marker-backed status comments, draft PRs, and the +maintainer permission gate — runs end to end with zero network access and +zero real credentials. + +The stub keeps one world of state and records every API call in an audit +trail. Two demo-only endpoints expose it: + + GET /_demo/state full world state as JSON (assertions read this) + GET /_demo/audit the audit trail as JSON + +Run: python3 github-stub.py --port 8091 +""" + +import argparse +import json +import re +import threading +import time +from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer + +# Who may command the familiar. Everyone else defaults to read — the worker's +# pre-flight permission gate declines their commands (issue #13). +COLLABORATOR_PERMISSIONS = { + "octocat": "admin", + "coven-cody[bot]": "write", +} +DEFAULT_PERMISSION = "read" + +BRANCH_SHA = "a1b2c3d4e5f6a7b8c9d0e1f2a3b4c5d6e7f8a9b0" + + +class World: + """All mutable stub state, guarded by one lock.""" + + def __init__(self): + self.lock = threading.Lock() + self.tokens = {} # token value -> role + self.token_seq = 0 + self.check_runs = {} # id -> dict + self.check_seq = 1000 + self.comments = [] # [{id, issue_number, body, user, edits}] + self.comment_seq = 5000 + self.pulls = {} # number -> dict + self.pull_seq = 100 + self.audit = [] # [{seq, at, actor, action}] + self.audit_seq = 0 + + def record(self, actor, action): + self.audit_seq += 1 + self.audit.append( + { + "seq": self.audit_seq, + "at": time.strftime("%H:%M:%S"), + "actor": actor, + "action": action, + } + ) + + def state(self): + return { + "tokens_minted": [ + {"role": role} for _, role in sorted(self.tokens.items()) + ], + "check_runs": list(self.check_runs.values()), + "comments": self.comments, + "pulls": list(self.pulls.values()), + "audit_len": len(self.audit), + } + + +WORLD = World() + + +def token_role(permissions): + """Names the scoped-token role from its permission set (issue #4).""" + if permissions.get("checks") == "write": + return "orchestration" + if permissions.get("pull_requests") == "write": + return "publication" + if permissions == {"contents": "write"}: + return "agent-git" + return "unknown" + + +class Handler(BaseHTTPRequestHandler): + protocol_version = "HTTP/1.1" + server_version = "github-stub/0.1" + + def log_message(self, fmt, *args): # quiet the default access log + pass + + def _json(self, status, payload): + body = json.dumps(payload).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 _read_body(self): + length = int(self.headers.get("Content-Length") or 0) + raw = self.rfile.read(length) if length else b"" + return json.loads(raw) if raw else {} + + def _actor(self): + auth = self.headers.get("Authorization", "") + token = auth.removeprefix("Bearer ").strip() + return WORLD.tokens.get(token, "app-jwt") + + # ── Routing ────────────────────────────────────────────────────────── + + def do_GET(self): + path = self.path.split("?")[0] + with WORLD.lock: + if path == "/_demo/state": + return self._json(200, WORLD.state()) + if path == "/_demo/audit": + return self._json(200, WORLD.audit) + + m = re.fullmatch(r"/repos/([^/]+)/([^/]+)", path) + if m: + WORLD.record(self._actor(), f"read repo metadata {m[1]}/{m[2]}") + return self._json(200, {"default_branch": "main"}) + + m = re.fullmatch(r"/repos/[^/]+/[^/]+/branches/([^/]+)", path) + if m: + WORLD.record(self._actor(), f"resolve branch '{m[1]}' head SHA") + return self._json(200, {"commit": {"sha": BRANCH_SHA}}) + + m = re.fullmatch( + r"/repos/[^/]+/[^/]+/collaborators/([^/]+)/permission", path + ) + if m: + perm = COLLABORATOR_PERMISSIONS.get(m[1], DEFAULT_PERMISSION) + WORLD.record( + self._actor(), + f"permission check for @{m[1]} -> {perm}", + ) + return self._json(200, {"permission": perm}) + + m = re.fullmatch(r"/repos/[^/]+/[^/]+/issues/(\d+)/comments", path) + if m: + number = int(m[1]) + items = [ + {"id": c["id"], "body": c["body"], "user": {"login": c["user"]}} + for c in WORLD.comments + if c["issue_number"] == number + ] + WORLD.record( + self._actor(), + f"list #{number} comments ({len(items)} found)", + ) + return self._json(200, items) + + self._json(404, {"message": f"stub: no route for GET {path}"}) + + def do_POST(self): + path = self.path.split("?")[0] + body = self._read_body() + with WORLD.lock: + m = re.fullmatch(r"/app/installations/(\d+)/access_tokens", path) + if m: + role = token_role(body.get("permissions", {})) + WORLD.token_seq += 1 + token = f"demo-token-{role}-{WORLD.token_seq}" + WORLD.tokens[token] = role + repos = ",".join(body.get("repositories", [])) + WORLD.record( + "app-jwt", + f"mint {role} token (repo-scoped: {repos})", + ) + return self._json(201, {"token": token}) + + m = re.fullmatch(r"/repos/([^/]+)/([^/]+)/check-runs", path) + if m: + WORLD.check_seq += 1 + run = { + "id": WORLD.check_seq, + "name": body.get("name", ""), + "head_sha": body.get("head_sha", ""), + "status": body.get("status", "queued"), + "conclusion": None, + "title": None, + "summary": None, + "details_url": body.get("details_url"), + } + WORLD.check_runs[run["id"]] = run + WORLD.record( + self._actor(), + f"create check run {run['id']} '{run['name']}' " + f"on {run['head_sha'][:12]} (queued)", + ) + return self._json(201, {"id": run["id"]}) + + m = re.fullmatch(r"/repos/[^/]+/[^/]+/issues/(\d+)/comments", path) + if m: + WORLD.comment_seq += 1 + comment = { + "id": WORLD.comment_seq, + "issue_number": int(m[1]), + "body": body.get("body", ""), + "user": "coven-cody[bot]", + "edits": 0, + } + WORLD.comments.append(comment) + WORLD.record( + self._actor(), + f"post comment {comment['id']} on #{m[1]}", + ) + return self._json(201, {"id": comment["id"]}) + + m = re.fullmatch(r"/repos/([^/]+)/([^/]+)/pulls", path) + if m: + WORLD.pull_seq += 1 + pull = { + "number": WORLD.pull_seq, + "title": body.get("title", ""), + "body": body.get("body", ""), + "head": body.get("head", ""), + "base": body.get("base", ""), + "draft": bool(body.get("draft")), + } + WORLD.pulls[pull["number"]] = pull + WORLD.record( + self._actor(), + f"open draft PR #{pull['number']} " + f"({pull['head']} -> {pull['base']})", + ) + return self._json(201, {"number": pull["number"]}) + + self._json(404, {"message": f"stub: no route for POST {path}"}) + + def do_PATCH(self): + path = self.path.split("?")[0] + body = self._read_body() + with WORLD.lock: + m = re.fullmatch(r"/repos/[^/]+/[^/]+/check-runs/(\d+)", path) + if m: + run = WORLD.check_runs.get(int(m[1])) + if not run: + return self._json(404, {"message": "no such check run"}) + run["status"] = body.get("status", run["status"]) + if "conclusion" in body: + run["conclusion"] = body["conclusion"] + output = body.get("output") or {} + run["title"] = output.get("title", run["title"]) + run["summary"] = output.get("summary", run["summary"]) + detail = run["conclusion"] or run["status"] + WORLD.record( + self._actor(), + f"check run {run['id']} -> {detail} ('{run['title']}')", + ) + return self._json(200, {"id": run["id"]}) + + m = re.fullmatch(r"/repos/[^/]+/[^/]+/issues/comments/(\d+)", path) + if m: + cid = int(m[1]) + for comment in WORLD.comments: + if comment["id"] == cid: + comment["body"] = body.get("body", comment["body"]) + comment["edits"] += 1 + first_line = next( + ( + line + for line in comment["body"].splitlines() + if line and not line.startswith("