Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ reproduced, and changed through PRs instead of server-only edits.
- `src/server.ts` - Node HTTP entrypoint for `/`, `/healthz`, and `/webhook`.
- `tests/webhook-adapter.test.ts` - parity coverage for signature handling,
request body edge cases, and task routing guards.
- `config/example-policy.json` - example installation/repository policy that
connects a GitHub App install to a familiar route.
- `docs/coven-github-connection.md` - operator guide for connecting this
TypeScript deployment bundle to the canonical `coven-github` app manifest.
- `scripts/smoke-webhook.sh` - local HMAC signature smoke test for a running
webhook endpoint.

Expand Down Expand Up @@ -87,6 +91,10 @@ Deployments should provide `coven-github-policy.json` through
`COVEN_GITHUB_POLICY_PATH`. That file is intentionally ignored because it is
environment-specific.

Start from [`config/example-policy.json`](config/example-policy.json) and the
connection guide in
[`docs/coven-github-connection.md`](docs/coven-github-connection.md).

## Current behavior

- Emits headless contract v2 session briefs.
Expand Down
36 changes: 36 additions & 0 deletions config/example-policy.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"version": 1,
"installations": {
"123456": {
"repositories": {
"987654321": {
"enabled_triggers": [
"issues.labeled",
"issue_comment.created",
"pull_request_review_comment.created"
],
"trigger_labels": [
Comment on lines +7 to +12
"coven:fix",
"coven:review"
],
"bot_usernames": [
"coven-cody[bot]"
],
"default_branch": "main",
"familiar": {
"id": "cody",
"display_name": "Cody",
"model": "openai/gpt-5.5",
"skills": [
"systematic-debugging",
"test-driven-development"
]
},
"publication": {
"mode": "record_only"
}
}
}
}
}
}
106 changes: 106 additions & 0 deletions docs/coven-github-connection.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
# Connecting to coven-github

`coven-github-webhook` is the TypeScript deployment bundle for the
[`OpenCoven/coven-github`](https://github.com/OpenCoven/coven-github) product
surface. It receives GitHub App webhooks, verifies signatures, applies
installation/repository routing policy, and starts `coven-code` headless runs.

Use the canonical `coven-github` repo for the product spec, GitHub App
permissions, event list, headless contract, and hosted/self-hosted operating
model. Use this repo when you want the lightweight Node deployment entrypoint.

## App Registration

Register the GitHub App with the manifest from `coven-github`:

- `OpenCoven/coven-github/docs/app-manifest.json`

Set the manifest webhook URL to this service:

```text
https://your-host/webhook
```

The manifest subscribes to the events this adapter can route:

- `issues`
- `issue_comment`
- `pull_request_review`
- `pull_request_review_comment`
- `check_suite`
- `check_run`
Comment on lines +24 to +31

After creating the App, keep these values outside git:

- App ID -> `GITHUB_APP_ID`
- Webhook secret -> `GITHUB_WEBHOOK_SECRET`
- Private key PEM path -> `GITHUB_APP_PRIVATE_KEY_PATH`

## Policy

This adapter uses a local JSON policy file to map a GitHub installation and
repository to a familiar route. Start with:

```bash
cp config/example-policy.json coven-github-policy.json
```

Then replace:

- `123456` with the GitHub App installation ID.
- `987654321` with the repository ID.
- `bot_usernames` with the GitHub App bot login, for example
`coven-cody[bot]`.
- `trigger_labels` with labels that should start tasks.
- `familiar` with the familiar id, display name, model, and skills to pass to
`coven-code`.

Point the service at the file:

```bash
export COVEN_GITHUB_POLICY_PATH="$PWD/coven-github-policy.json"
```

Keep `publication.mode` as `record_only` for first smoke runs. Switch it to
`comment` only after you have verified the App installation, `coven-code`
runtime, Codex token, and workspace permissions.

## Runtime Checklist

```bash
npm ci
npm run build

export GITHUB_APP_ID="123456"
export GITHUB_WEBHOOK_SECRET="replace-with-github-secret"
export GITHUB_APP_PRIVATE_KEY_PATH="$PWD/keys/coven-github.private-key.pem"
export COVEN_GITHUB_POLICY_PATH="$PWD/coven-github-policy.json"
export COVEN_GITHUB_STATE_DIR="$PWD/coven-github-state"
export COVEN_CODE_BIN="$(command -v coven-code)"

npm start
```

In another shell:

```bash
WEBHOOK_SECRET="$GITHUB_WEBHOOK_SECRET" \
scripts/smoke-webhook.sh http://localhost:3000/webhook
```

That proves the HTTP endpoint and HMAC signature path before any GitHub token or
runtime work is attempted.

## Functional App Smoke

On a repository where the App is installed:

1. Copy the installation ID and repository ID into `coven-github-policy.json`.
2. Add one of the configured labels, such as `coven:fix`, to an issue.
3. Confirm a delivery record appears under `COVEN_GITHUB_STATE_DIR/deliveries`.
4. Confirm a task record appears under `COVEN_GITHUB_STATE_DIR/tasks`.
5. Inspect the attempt directory if the runtime fails before enabling comment
publication.

This keeps the first app connection auditable: GitHub delivery, policy route,
task creation, runtime attempt, and publication are separate files.
46 changes: 45 additions & 1 deletion tests/webhook-adapter.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import assert from "node:assert/strict";
import { createHmac } from "node:crypto";
import { mkdtempSync } from "node:fs";
import { mkdtempSync, readFileSync } from "node:fs";
import { tmpdir } from "node:os";
import { join } from "node:path";
import test from "node:test";
Expand Down Expand Up @@ -293,3 +293,47 @@ test("missing familiar policy does not fall back to hardcoded installation", ()
assert.equal(task.state, "ignored");
assert.equal(task.ignored_reason, "missing_familiar_policy");
});

test("example policy routes a labeled issue to the configured familiar", () => {
const policyFile = new URL("../config/example-policy.json", import.meta.url);
const policyRoot = JSON.parse(readFileSync(policyFile, "utf8")) as JsonObject;
const installation = (policyRoot.installations as JsonObject)["123456"] as JsonObject;
const policy = ((installation.repositories as JsonObject)["987654321"]) as JsonObject;

const task = buildTaskFromEvent(
"issues",
"delivery-example-policy",
{
action: "labeled",
installation: {id: 123456},
repository: {
id: 987654321,
full_name: "OpenCoven/example",
clone_url: "https://github.com/OpenCoven/example.git",
default_branch: "main",
},
issue: {
number: 42,
title: "Wire the app",
body: "Make the first app route functional.",
labels: [{name: "coven:fix"}],
},
} as JsonObject,
policy,
);

assert.equal(task.state, "queued");
assert.equal(task.trigger, "issue_mention");
assert.deepEqual(task.task, {
kind: "fix_issue",
issue_number: 42,
issue_title: "Wire the app",
issue_body: "Make the first app route functional.",
});
assert.deepEqual(task.familiar, {
id: "cody",
display_name: "Cody",
model: "openai/gpt-5.5",
skills: ["systematic-debugging", "test-driven-development"],
});
});
Loading