Skip to content

Commit d079e11

Browse files
authored
Merge pull request #27 from Boring-Software-Inc/feat/forge-opengit-oauth
Feat/forge opengit oauth
2 parents 68ef1b3 + aeb6d9a commit d079e11

141 files changed

Lines changed: 9589 additions & 747 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.env.example

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ BETTER_AUTH_SECRET=
3131
BETTER_AUTH_URL=http://localhost:3000
3232
GITHUB_OAUTH_CLIENT_ID=
3333
GITHUB_OAUTH_CLIENT_SECRET=
34+
WEBHOOK_PUBLIC_URL=
3435

3536
# ── Review agent — VERIFICATION-QUEUE.md #7 ──────────────────────────────
3637
# OpenRouter (https://openrouter.ai/keys). Unset = ai-review skips (and
@@ -132,3 +133,39 @@ TEST_BRANCH=fix-typo
132133
# DATABASE_URL=<PlanetScale POOLED url> # the DB the prod worker reads
133134
# TEST_CONTRIBUTOR=<non-exempt alt gh username>
134135
# TEST_MAINTAINER=<maintainer gh username, restored/merged as>
136+
137+
# open-git OAuth. Sign-in is separate from ingest: this pair only authenticates
138+
# a person; the bot credentials below are what post checks. Redirect URL:
139+
# <BETTER_AUTH_URL>/api/auth/oauth2/callback/opengit
140+
OPEN_GIT_OAUTH_CLIENT_ID=
141+
OPEN_GIT_OAUTH_CLIENT_SECRET=
142+
# Verifies POST /webhooks/opengit. Unset ⇒ the route 503s, never accepts.
143+
OPEN_GIT_BOT_WEBHOOK_SECRET=
144+
# open-git bot credentials — the worker signs an RS256 JWT (iss = bot id, max
145+
# 10m) and exchanges it for a one-hour `ogi_` installation token. Unset ⇒
146+
# open-git events still normalize and persist, but no check is ever posted.
147+
# The bot's SPKI public key must be registered on open-git.
148+
OPEN_GIT_BOT_ID=
149+
OPEN_GIT_BOT_PRIVATE_KEY=
150+
# The bot's marketplace path, used to build the CONNECT REPOS install url:
151+
# <OPEN_GIT_URL>/integrations/<OWNER>/<SLUG>/install
152+
# Both come from the bot's page under Settings -> Organizations -> Developer on
153+
# open-git. Unset ⇒ the open-git cell in the connect grid says "not configured"
154+
# rather than sending an admin to a 404.
155+
OPEN_GIT_BOT_OWNER=
156+
OPEN_GIT_BOT_SLUG=
157+
# Self-hosted open-git only — omit for open-git.com
158+
OPEN_GIT_URL=
159+
160+
# ── open-git live E2E (`bun run scripts/e2e/opengit.ts`) ─────────────────
161+
# Drives REAL pull requests on a sacrificial open-git repo. It NEVER closes
162+
# them: open-git has no closed-pull-request surface, so a closed one cannot be
163+
# inspected. The run prints the urls; you close them by hand.
164+
# The token is a personal access token (ugp_) with repo:write — git push uses
165+
# it as basic-auth, so the username is needed too.
166+
OPEN_GIT_TEST_REPO=
167+
OPEN_GIT_TEST_TOKEN=
168+
OPEN_GIT_TEST_USER=
169+
# Defaults: base "main", workdir "$TEST_WORKDIR-opengit"
170+
OPEN_GIT_TEST_BASE=
171+
OPEN_GIT_TEST_WORKDIR=

.github/workflows/ci.yml

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,11 +10,20 @@ jobs:
1010
runs-on: ubuntu-latest
1111
steps:
1212
- uses: actions/checkout@v4
13+
with:
14+
# Full history so the anti-slop gate can diff a PR against its base.
15+
fetch-depth: 0
1316

1417
- uses: oven-sh/setup-bun@v2
1518
with:
1619
bun-version: latest
1720

21+
# The anti-slop oxlint plugin is TypeScript; oxlint loads it through
22+
# Node's ESM loader, which needs >=22.6 + type-stripping. Pin Node 22.
23+
- uses: actions/setup-node@v4
24+
with:
25+
node-version: 22
26+
1827
- name: Install
1928
run: bun install --frozen-lockfile
2029

@@ -27,5 +36,14 @@ jobs:
2736
- name: Boundary check
2837
run: bun run check:boundaries
2938

39+
# Gate NEW code only: anti-slop runs over the lines this PR ADDED, not the
40+
# whole tree and not whole touched files. The 471 legacy violations are
41+
# burned down separately and never block an unrelated PR.
42+
- name: Anti-slop (changed lines)
43+
if: github.event_name == 'pull_request'
44+
run: |
45+
git fetch --no-tags origin "${{ github.base_ref }}"
46+
bun run scripts/lint-slop-changed.ts "origin/${{ github.base_ref }}...HEAD"
47+
3048
- name: Tests
3149
run: bun test

.gitignore

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,6 @@ coverage/
1515
.turbo/
1616
/docs
1717
/repo_docs
18+
19+
# local QA artifacts (screenshots, videos, db dumps) — never committed
20+
dogfood-output/

.oxlintrc.json

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
{
2+
"$schema": "./node_modules/oxlint/configuration_schema.json",
3+
"plugins": [],
4+
"categories": {
5+
"correctness": "off"
6+
},
7+
"jsPlugins": ["./scripts/oxlint/anti-slop/index.ts"],
8+
"ignorePatterns": [
9+
"**/node_modules/**",
10+
"**/dist/**",
11+
"**/.output/**",
12+
"**/.tanstack/**",
13+
"**/.turbo/**",
14+
"**/.vercel/**",
15+
"**/drizzle/**",
16+
"**/*.gen.ts",
17+
"**/routeTree.gen.ts",
18+
".agents/**",
19+
".claude/**",
20+
"scripts/oxlint/anti-slop/**"
21+
],
22+
"rules": {
23+
"anti-slop/no-chained-type-assertions": "error",
24+
"anti-slop/no-conditional-empty-object-spread": "error",
25+
"anti-slop/no-known-value-widening": "error",
26+
"anti-slop/no-object-parameters": "error",
27+
"anti-slop/no-runtime-typeof": "error",
28+
"anti-slop/no-shape-in-symbol-names": "off",
29+
"anti-slop/no-unknown-parameters": "error",
30+
"anti-slop/no-unknown-type-aliases": "error",
31+
"anti-slop/no-unsafe-dictionary-type": "error",
32+
"anti-slop/no-widen-then-assert": "error"
33+
},
34+
"overrides": [
35+
{
36+
"files": [
37+
"**/test/**",
38+
"**/tests/**",
39+
"**/*.test.ts",
40+
"**/*.test.tsx",
41+
"**/*.spec.ts",
42+
"**/*.spec.tsx",
43+
"scripts/eval/**"
44+
],
45+
"rules": {
46+
"anti-slop/no-chained-type-assertions": "off"
47+
}
48+
}
49+
]
50+
}

LICENSE

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
MIT License
2+
3+
Copyright (c) 2026 Boring Software Inc.
4+
5+
Permission is hereby granted, free of charge, to any person obtaining a copy
6+
of this software and associated documentation files (the "Software"), to deal
7+
in the Software without restriction, including without limitation the rights
8+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9+
copies of the Software, and to permit persons to whom the Software is
10+
furnished to do so, subject to the following conditions:
11+
12+
The above copyright notice and this permission notice shall be included in all
13+
copies or substantial portions of the Software.
14+
15+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21+
SOFTWARE.

OVERVIEW.md

Lines changed: 201 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,201 @@
1+
# tripwire
2+
3+
a firewall for your repo.
4+
5+
tripwire reads every incoming change request and decides whether it is safe.
6+
it then blocks it, passes it, or sends it to a human. this happens before a
7+
maintainer ever opens it.
8+
9+
it works on github and on open-git.
10+
11+
---
12+
13+
## the problem
14+
15+
an open repository takes changes from strangers. most are fine. some are spam,
16+
some are a wallet address swapped into a config file, and some are an account
17+
made an hour ago.
18+
19+
a maintainer reads all of them. that is the cost of being open, and it is the
20+
part that does not scale.
21+
22+
---
23+
24+
## what tripwire does
25+
26+
1. a change request arrives. the forge sends a webhook.
27+
2. tripwire runs a set of **rules** against it.
28+
3. a **workflow** combines those results into one verdict.
29+
4. tripwire writes a **check** on the commit and comments on the thread.
30+
31+
three verdicts:
32+
33+
| verdict | what happens |
34+
| --- | --- |
35+
| pass | the check goes green. nobody is interrupted. |
36+
| block | the check fails. the merge button is dead. |
37+
| review | a human is asked to decide. |
38+
39+
the check is the gate. a failing check means the change cannot merge.
40+
41+
### it fails closed
42+
43+
if tripwire cannot read what a rule needs, that rule declines. it does not
44+
guess, and it never reports a pass for something it did not examine. a change
45+
tripwire could not judge goes to a human, not through.
46+
47+
### every run is auditable
48+
49+
each verdict keeps its inputs, the result of each rule, and the evidence.
50+
you can open a run and see why. you can replay history against new rule code
51+
and see which verdicts would change, before you ship the change.
52+
53+
---
54+
55+
## agentic use
56+
57+
tripwire is built to be driven by coding agents, and most of it was.
58+
59+
### why it suits an agent
60+
61+
the repository is arranged so an agent can work in it without breaking things
62+
it cannot see.
63+
64+
- **the rules are pure.** rule code does no i/o. effects are passed in. a rule
65+
is a plain function over a fixed input, so an agent can test one without a
66+
database, a network, or a forge.
67+
- **the dependency arrows are enforced.** `bun run check:boundaries` fails the
68+
build on a wrong-direction import. an agent cannot quietly couple two
69+
packages that must stay apart.
70+
- **the type rules are strict on purpose.** a custom lint suite rejects the
71+
shortcuts a language model reaches for first, such as widening a known value
72+
or casting through `unknown`. see `docs/agent-guidelines.md`.
73+
- **the checks are one command each.** lint, typecheck, boundaries, tests. an
74+
agent can run all four and read a clear pass or fail.
75+
76+
### using tripwire from an agent
77+
78+
```
79+
bun run rule-check # evaluate rules against a change request
80+
bun run replay # re-run stored history against current rule code
81+
```
82+
83+
`replay` is the important one. it answers "what would this rule change have
84+
done to the last thousand change requests" before the change reaches anyone.
85+
86+
### contributing with an agent
87+
88+
agents are welcome here. the rules are the same as for a person, and they are
89+
written down in `docs/agent-guidelines.md`. the short version:
90+
91+
1. run lint and typecheck before every push. no exceptions.
92+
2. no `any`. no casting around a type error.
93+
3. a ui change needs a before-and-after screenshot.
94+
4. a commit message needs the right shape.
95+
5. a huge pull request gets split, not reviewed.
96+
97+
read `docs/CONTRIBUTING.md` first. read `docs/agent-guidelines.md` second.
98+
99+
---
100+
101+
## local development
102+
103+
### the demo, one command, no docker
104+
105+
```
106+
bun run dev:demo
107+
```
108+
109+
a seeded, presentable app at `http://localhost:3000`. the web head only. the
110+
database is embedded, in-process postgres, running the same schema and
111+
migrations as production.
112+
113+
it seeds a realistic story across all three verdicts and drops you on a full
114+
dashboard. re-running resets to the same clean state.
115+
116+
a dev build also has a persona switcher, at the bottom left. it jumps between
117+
real product states: a fresh maintainer, one repo, many repos, an empty
118+
dashboard, and the anonymous stranger view. it is excluded from production
119+
builds at compile time.
120+
121+
### the full stack
122+
123+
```
124+
bun run db:up # postgres in docker
125+
bun run db:migrate # apply migrations
126+
bun run dev # web, api, worker, and a tunnel
127+
```
128+
129+
`bun run dev` opens one terminal ui. arrow keys move between the web, api,
130+
worker, and tunnel panes.
131+
132+
the tunnel pane prints a public url routed to the api. point your forge app's
133+
webhook at `<that-url>/webhooks/github` or `<that-url>/webhooks/opengit` to
134+
receive local deliveries.
135+
136+
smaller pieces:
137+
138+
```
139+
bun run dev:local # the same, without the tunnel
140+
bun run dev:web # the web head only
141+
bun run dev:api # the api head only
142+
bun run dev:worker # the queue consumer only
143+
```
144+
145+
set `BETTER_AUTH_SECRET` to turn on real sign-in and the auth gates. leave it
146+
unset for an open local posture.
147+
148+
---
149+
150+
## checks
151+
152+
run these four before you push. continuous integration runs the same four.
153+
154+
```
155+
bun run check # format and lint
156+
bun run typecheck # every package
157+
bun run check:boundaries # the dependency arrows
158+
bun test # unit and integration tests
159+
```
160+
161+
---
162+
163+
## live end-to-end tests
164+
165+
`bun test` proves the logic against a fake forge. a separate harness proves the
166+
real thing against a real forge, on a sacrificial repository.
167+
168+
it needs real credentials, a running worker, and a tunnel. it is a pre-release
169+
tool. it does not run on each pull request.
170+
171+
```
172+
bun run test --list # the github scenarios
173+
bun run test --everything # all of them, with a summary
174+
```
175+
176+
open-git has its own harness. it never closes a pull request, because open-git
177+
has no page for a closed one, so a closed pull request cannot be read.
178+
179+
```
180+
bun run scripts/e2e/opengit.ts --list
181+
```
182+
183+
what is deliberately not automated: whether the comment copy READS well. the
184+
harness proves the mechanics. a human reads the thread once. taste stays human.
185+
186+
---
187+
188+
## docs
189+
190+
| file | what it covers |
191+
| --- | --- |
192+
| `docs/CONTRIBUTING.md` | setup, checks, commits, tests |
193+
| `docs/agent-guidelines.md` | rules for agent contributors |
194+
| `docs/opengit-progress.md` | what open-git still needs |
195+
| `docs/LICENSE-EXPLAINED.md` | the license in plain words |
196+
197+
---
198+
199+
## license
200+
201+
mit. see `LICENSE`, and `docs/LICENSE-EXPLAINED.md` for what it means.

apps/api/package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
"@tripwire/contracts": "workspace:*",
1818
"@tripwire/db": "workspace:*",
1919
"@tripwire/forge-github": "workspace:*",
20+
"@tripwire/forge-opengit": "workspace:*",
2021
"@tripwire/utils": "workspace:*",
2122
"hono": "^4.12.29",
2223
"pg": "^8.22.0",

apps/api/src/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ if (import.meta.main) {
5353
secret,
5454
baseUrl: process.env.BETTER_AUTH_URL ?? "http://localhost:3000",
5555
github: null,
56+
opengit: null,
5657
// This head only verifies sessions (SSE gating) — it doesn't
5758
// mount /dash/*. Pass the key anyway so the shared dash()
5859
// isn't in missing-key mode if it's set on this service.

0 commit comments

Comments
 (0)