Skip to content

Commit 3ea97ec

Browse files
authored
feat: ClawSweeper-style reference demo of the operating loop (#19) (#40)
* 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 <bunsthedev@gmail.com> * docs: narrate the reference demo and link it from README (#19) Signed-off-by: Val Alexander <bunsthedev@gmail.com> --------- Signed-off-by: Val Alexander <bunsthedev@gmail.com>
1 parent 2de2411 commit 3ea97ec

7 files changed

Lines changed: 891 additions & 0 deletions

File tree

README.md

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,12 @@ flowchart LR
3636

3737
See [Architecture Diagrams](docs/architecture.md), [Design](DESIGN.md), [Hosted OpenCoven](HOSTED.md), [Familiar Contract](FAMILIAR-CONTRACT.md), [Roadmap](ROADMAP.md), and [Hosted vs self-hosted](docs/hosted-vs-self-hosted.md) for the operational plan.
3838

39+
**Want to see the whole loop run?** `examples/demo/run-demo.sh` drives the
40+
real adapter through the full operating loop — issue assignment, Check Run,
41+
edited-in-place status comment, maintainer steering commands, permission
42+
gate, familiar-voice draft PR — offline and self-verifying in about a minute.
43+
[docs/demo.md](docs/demo.md) narrates it.
44+
3945
---
4046

4147
## Architecture
@@ -146,6 +152,7 @@ duplicate comments.
146152
| Label trigger | Implemented | Routes configured `trigger_labels` such as `coven:fix`. |
147153
| Maintainer command protocol | Implemented | Typed `@familiar <verb>` grammar; casual mentions ignored; write-access gate; self-comments never re-trigger. |
148154
| Marker-backed status comments | Implemented | One edited-in-place status surface per issue/PR; no duplicate bot comments. |
155+
| Reference demo of the operating loop | Implemented | Offline, self-verifying replay of the full loop with a real adapter binary — see [docs/demo.md](docs/demo.md). |
149156
| PR lifecycle review trigger | Implemented | Policy-gated auto-review on opened/synchronize/reopened/ready_for_review plus label opt-in; familiar-authored PRs are never auto-reviewed. |
150157
| Push / commit review trigger | Partial | Events parsed and typed with fixtures; execution lane needs headless contract v3. |
151158
| GitHub App installation tokens | Implemented | Mints installation access tokens from the App private key. |

docs/demo.md

Lines changed: 116 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,116 @@
1+
# Reference demo: the operating loop, end to end
2+
3+
This is the ClawSweeper-style reference demo of the coven-github operating
4+
loop (issue #19): the **real adapter binary**, driven by signed webhook
5+
deliveries, publishing to an in-memory GitHub API stub through a
6+
contract-conformant fake `coven-code` runtime. No network, no GitHub App
7+
registration, no credentials — one command, about a minute, self-verifying.
8+
9+
```bash
10+
examples/demo/run-demo.sh
11+
```
12+
13+
A green exit code *is* the demo's claim: every property below is asserted
14+
programmatically against the stub's recorded state, not eyeballed. Set
15+
`KEEP=1` to keep the scratch directory (config, server log, stub log) for
16+
inspection.
17+
18+
## Why "ClawSweeper-style"
19+
20+
ClawSweeper is the reference pattern for conservative GitHub automation in the
21+
OpenClaw ecosystem (see [`DESIGN.md`](../DESIGN.md)): narrow promises, durable
22+
state, marker-backed comments edited in place, explicit maintainer commands,
23+
and deterministic gates before mutation. This demo shows coven-github honoring
24+
each of those disciplines with its own machinery.
25+
26+
## What runs
27+
28+
| Piece | Real or stand-in | Role |
29+
|---|---|---|
30+
| `coven-github serve` | **Real** — the same binary you self-host | Webhook receiver, command router, worker pool, publication |
31+
| Webhook deliveries | Real HMAC-SHA256 signatures, GitHub payload shapes | Drive every act |
32+
| GitHub API | [`github-stub.py`](../examples/demo/github-stub.py) — in-memory stand-in | Records every mutation in an audit trail |
33+
| `coven-code --headless` | [`fake-coven-code`](../examples/demo/fake-coven-code) — headless contract v2 conformant | Fabricates a familiar-voice fix instead of running a model |
34+
| App credentials | Throwaway RSA key + random webhook secret, generated per run | Prove the real JWT → installation-token path |
35+
36+
The stub answers exactly the API surface the adapter uses — scoped token
37+
minting, repo metadata, branch resolution, collaborator permission, Check
38+
Runs, issue comments, pull requests — and exposes its world state at
39+
`/_demo/state` and the mutation log at `/_demo/audit`.
40+
41+
## The acts
42+
43+
**Act 1 — issue assigned: the full loop.** `octocat` assigns issue #42 to
44+
`@coven-cody`. The adapter mints an **orchestration** token, resolves the
45+
default branch and head SHA from live state, creates a Check Run against that
46+
immutable SHA, posts the one marker-backed status comment, flips the check to
47+
`in_progress`, mints a separate **agent-git** token injected only via
48+
`COVEN_GIT_TOKEN` (the fake runtime verifies the brief itself is tokenless),
49+
runs the session, then mints a **publication** token and opens a draft PR back
50+
to the issue — in Cody's voice. Asserted: one comment, edited in place to
51+
`Status: done`; Check Run concluded `success`; PR is a draft; three distinct
52+
token scopes minted.
53+
54+
**Act 2 — casual mention.** "thanks @coven-cody, great work on this!" triggers
55+
*nothing*. Asserted: the audit trail did not grow by a single API call.
56+
57+
**Act 3 — self-trigger loop guard.** The familiar's own comment containing
58+
`@coven-cody status` is ignored. Asserted: audit unchanged.
59+
60+
**Act 4 — unknown verb.** `@coven-cody explain` is a command-position mention
61+
with an unknown verb, so the familiar replies with the real command list
62+
instead of guessing. Asserted: the clarification *edited the same status
63+
comment* — still exactly one surface.
64+
65+
**Act 5 — steering: `status`.** Answered from the durable task store (the same
66+
state Cave polls), listing the issue's tasks and their lifecycle states.
67+
68+
**Act 6 — permission gate.** `mallory` (read-only) comments
69+
`@coven-cody retry`. The worker checks collaborator permission *pre-flight*
70+
and declines on the status surface. Asserted: `Status: declined` and **no new
71+
Check Run** — no session was spent below the write-access bar.
72+
73+
**Act 7 — steering: `retry`.** `octocat` (admin) retries. A second full run
74+
executes: second Check Run to `success`, second draft PR. Asserted: **still
75+
exactly one status comment** — repeated runs never stack duplicates.
76+
77+
## The closing surfaces
78+
79+
After the acts, the demo prints two oversight views:
80+
81+
- **The audit trail** — every GitHub mutation in order, attributed to the
82+
token role that performed it. You can watch the authority split from
83+
issue #4 in action: `orchestration` drives checks and comments,
84+
`publication` opens the PR, `agent-git` never touches the API at all.
85+
- **The Cave view**`GET /api/github/tasks`, the adapter's task API that
86+
the CovenCave dashboard (issue #18) polls: task ids, branches, PR links,
87+
Check Run links, session ids, lifecycle status.
88+
89+
Cave *intervention* — pausing or steering a live session from the dashboard —
90+
lands with #18; today the oversight loop closes through the maintainer
91+
commands demonstrated above plus the session deep links on every Check Run
92+
and status comment.
93+
94+
## Sample audit trail
95+
96+
```text
97+
1 app-jwt mint orchestration token (repo-scoped: demo-service)
98+
2 orchestration read repo metadata OpenCoven/demo-service
99+
3 orchestration resolve branch 'main' head SHA
100+
4 orchestration create check run 1001 'Cody — Fix issue #42: …' (queued)
101+
5 orchestration list #42 comments (0 found)
102+
6 orchestration post comment 5001 on #42
103+
7 orchestration check run 1001 -> in_progress ('Running')
104+
8 app-jwt mint agent-git token (repo-scoped: demo-service)
105+
9 app-jwt mint publication token (repo-scoped: demo-service)
106+
10 publication open draft PR #101 (cody/fix-issue-42 -> main)
107+
11 orchestration list #42 comments (1 found)
108+
12 orchestration edit comment 5001 in place (edit #1: 'Status: done')
109+
13 orchestration check run 1001 -> success ('Done')
110+
```
111+
112+
## Requirements
113+
114+
`cargo`, `python3` (stdlib only), `openssl`, `curl`. The script builds the
115+
adapter with `cargo build -p coven-github`, picks free ports, and cleans up
116+
after itself (scratch dir is kept on failure, or with `KEEP=1`).
5.68 KB
Binary file not shown.
16.3 KB
Binary file not shown.

examples/demo/fake-coven-code

Lines changed: 137 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,137 @@
1+
#!/usr/bin/env python3
2+
"""Contract-conformant stand-in for `coven-code --headless` (demo, issue #19).
3+
4+
Speaks headless contract v2 (`docs/headless-contract.md`): reads the tokenless
5+
session brief from `--context`, writes the structured result envelope to
6+
`--output`, and exits 0. Instead of running a real agent session, it fabricates
7+
a small, plausible fix in the familiar's voice so the adapter's publication
8+
path (Check Run, status comment, draft PR) can be demonstrated offline.
9+
10+
It also proves two security invariants of the real integration on every run:
11+
12+
* the brief is tokenless — no credential material appears in the JSON;
13+
* git authority arrives only via the COVEN_GIT_TOKEN environment variable.
14+
"""
15+
16+
import json
17+
import os
18+
import sys
19+
20+
21+
def parse_args(argv):
22+
args = {"headless": False, "context": None, "output": None}
23+
it = iter(argv)
24+
for arg in it:
25+
if arg == "--headless":
26+
args["headless"] = True
27+
elif arg == "--context":
28+
args["context"] = next(it, None)
29+
elif arg == "--output":
30+
args["output"] = next(it, None)
31+
if not (args["headless"] and args["context"] and args["output"]):
32+
sys.exit("usage: fake-coven-code --headless --context BRIEF --output RESULT")
33+
return args
34+
35+
36+
def no_review():
37+
return {
38+
"mode": "none",
39+
"evidence_status": "not_applicable",
40+
"reviewed_files": [],
41+
"supporting_files": [],
42+
"findings": [],
43+
"tests_run": [
44+
{
45+
"command": "cargo test -p auth refresh",
46+
"status": "passed",
47+
"output_summary": "9/9 passing",
48+
}
49+
],
50+
"no_findings_reason": None,
51+
"limitations": [],
52+
}
53+
54+
55+
def main():
56+
args = parse_args(sys.argv[1:])
57+
with open(args["context"], encoding="utf-8") as f:
58+
raw_brief = f.read()
59+
brief = json.loads(raw_brief)
60+
61+
if brief.get("contract_version") != "2":
62+
sys.exit(f"unsupported brief contract_version: {brief.get('contract_version')}")
63+
64+
# Invariant checks (issue #4): tokenless brief, env-injected git authority.
65+
git_token = os.environ.get("COVEN_GIT_TOKEN", "")
66+
if not git_token:
67+
sys.exit("COVEN_GIT_TOKEN missing — the worker must inject git authority")
68+
if git_token in raw_brief:
69+
sys.exit("SECURITY: git token leaked into the session brief")
70+
print(
71+
"fake-coven-code: brief is tokenless; git authority arrived via env only",
72+
file=sys.stderr,
73+
)
74+
75+
familiar = brief["familiar"]["display_name"]
76+
task = brief["task"]
77+
kind = task["kind"]
78+
79+
if kind == "fix_issue":
80+
number = task["issue_number"]
81+
branch = f"cody/fix-issue-{number}"
82+
summary = (
83+
f"Fixed the OAuth token refresh by adding a 60-second "
84+
f"clock-skew buffer (issue #{number})."
85+
)
86+
pr_body = (
87+
f"## Hey, I'm {familiar} \U0001f43e\n\n"
88+
f"Issue #{number} traced to the OAuth refresh path not accounting "
89+
f"for clock skew between the client and the auth server. I added a "
90+
f"60-second buffer to the expiry check and regression tests for "
91+
f"both skew directions.\n\n"
92+
f"**Changed:** `src/auth/refresh.rs` (+14 / -3)\n"
93+
f"**Tests:** 9/9 passing (2 new regression cases)\n\n"
94+
f"I kept the diff scoped to the refresh path; the token store "
95+
f"itself is untouched. Shout if you want the buffer configurable."
96+
)
97+
commits = [
98+
{
99+
"sha": "f1e2d3c4b5a6",
100+
"message": f"fix: add clock-skew buffer to OAuth refresh (#{number})",
101+
}
102+
]
103+
files = ["src/auth/refresh.rs", "src/auth/refresh_test.rs"]
104+
elif kind == "address_review_comment":
105+
number = task["pr_number"]
106+
branch = f"cody/pr-{number}-feedback"
107+
summary = f"Addressed review feedback on PR #{number}."
108+
pr_body = (
109+
f"## {familiar} here \U0001f43e\n\n"
110+
f"Follow-up for the review feedback on #{number} — renamed the "
111+
f"helper and added the missing error-path test."
112+
)
113+
commits = [
114+
{"sha": "0a1b2c3d4e5f", "message": "refactor: address review feedback"}
115+
]
116+
files = ["src/auth/refresh.rs"]
117+
else:
118+
sys.exit(f"demo runtime does not handle task kind: {kind}")
119+
120+
result = {
121+
"contract_version": "2",
122+
"status": "success",
123+
"branch": branch,
124+
"commits": commits,
125+
"files_changed": files,
126+
"summary": summary,
127+
"pr_body": pr_body,
128+
"review": no_review(),
129+
"exit_reason": None,
130+
}
131+
with open(args["output"], "w", encoding="utf-8") as f:
132+
json.dump(result, f, indent=2)
133+
print(f"fake-coven-code: wrote result for {kind} -> {branch}", file=sys.stderr)
134+
135+
136+
if __name__ == "__main__":
137+
main()

0 commit comments

Comments
 (0)