CodeIssuesDiscussionsWikiPull RequestsProjectsCommitsActionsReleasesContributorsPulse● GatesSecuritySettingsDeploymentsPipelineInsightsAgents✨ Explain✨ Ask AI✨ Workspace✨ Spec✨ Tests▓ Debt Map✨ NL Search🏛 Archaeology
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 | # GateTest ↔ GlueCron integration
GlueCron exposes **two** inbound paths for GateTest to report scan results back.
Use the primary path for normal traffic; the backup path exists so you always
have a way in if the shared secret is misconfigured.
Base URL: `https://<your-gluecron-host>` (e.g. `https://gluecron.com`)
---
## Primary: shared-secret hook
**URL:** `POST /api/hooks/gatetest`
### Auth (pick one)
**Option A — Bearer token**
```
Authorization: Bearer <GATETEST_CALLBACK_SECRET>
```
or if the GateTest side can't set `Authorization`:
```
X-GateTest-Token: <GATETEST_CALLBACK_SECRET>
```
**Option B — HMAC-SHA256 over raw body**
```
X-GateTest-Signature: sha256=<hex(hmac_sha256(GATETEST_HMAC_SECRET, rawBody))>
```
Both are compared with a timing-safe equality check.
### Request body (JSON)
```json
{
"repository": "owner/name",
"sha": "0123abcd0123abcd0123abcd0123abcd01234567",
"ref": "refs/heads/main",
"pullRequestNumber": 42,
"status": "passed",
"summary": "12 tests passed, 0 failed, 3.4s",
"details": { "...": "arbitrary JSON, persisted as-is" },
"durationMs": 3400
}
```
| Field | Required | Notes |
|---|---|---|
| `repository` | ✅ | `"owner/name"` |
| `sha` | ✅ | full 40-char commit SHA |
| `status` | ✅ | `"passed"` \| `"failed"` \| `"error"` \| `"success"` |
| `ref` | optional | defaults to `refs/heads/main` |
| `pullRequestNumber` | optional | links the gate run to a PR |
| `summary` | optional | shown on the gates UI |
| `details` | optional | arbitrary JSON, stored verbatim |
| `durationMs` | optional | for metrics |
### Response
```json
{ "ok": true, "gateRunId": "uuid-of-the-inserted-row" }
```
On failure:
- `400` — invalid JSON or missing required fields
- `401` — missing / invalid credentials
- `404` — repository not known to GlueCron
- `500` — DB error (retry-safe, idempotent on sha + gate_name)
### Side effects on a `failed` status
1. Row inserted in `gate_runs`
2. In-app notification to the repo owner (kind `gate_failed`)
3. Entry in `audit_log` (action `gate_callback`)
---
## Backup: personal access token
**URL:** `POST /api/v1/gate-runs`
### Auth
Standard GlueCron PAT, created at `/settings/tokens`:
```
Authorization: Bearer glc_<64-hex-chars>
```
Alternative header if Bearer isn't possible:
```
X-API-Token: glc_<64-hex-chars>
```
### Request body
Identical to the primary path, plus optional `gateName`:
```json
{
"repository": "owner/name",
"sha": "...",
"status": "passed",
"gateName": "GateTest",
"summary": "...",
"details": { }
}
```
### Authorisation rules
- The PAT's owner must match the repo's owner (MVP rule — will expand with orgs/teams in Block B).
- Token is rejected if expired.
- Successful use updates `api_tokens.last_used_at`.
### Listing prior runs
```
GET /api/v1/gate-runs?repository=owner/name&limit=20
Authorization: Bearer glc_...
```
Returns:
```json
{ "ok": true, "runs": [ {...}, ... ] }
```
---
## Liveness probe (no auth)
```
GET /api/hooks/ping
```
Returns:
```json
{
"ok": true,
"service": "gluecron",
"hooks": ["gatetest", "gatetest/recent", "api/v1/gate-runs (backup)"],
"timestamp": "2026-04-14T..."
}
```
Use this in GateTest's connectivity check before trying an authenticated call.
---
## Configuring the secrets
On the GlueCron host (Railway / Fly / Docker) set either or both:
```
GATETEST_CALLBACK_SECRET=<long random bearer token>
GATETEST_HMAC_SECRET=<long random HMAC key>
```
Generate with:
```bash
openssl rand -hex 32
```
Share the same value with GateTest. **If neither is set, the callback endpoint
refuses all requests** — there is no anonymous write path by design.
---
## Example curl (primary path)
```bash
curl -X POST "https://gluecron.com/api/hooks/gatetest" \
-H "Authorization: Bearer $GATETEST_CALLBACK_SECRET" \
-H "Content-Type: application/json" \
-d '{
"repository": "alice/webapp",
"sha": "9a8b7c6d5e4f3a2b1c0d9e8f7a6b5c4d3e2f1a0b",
"ref": "refs/heads/main",
"status": "failed",
"summary": "2 tests failed in src/auth.test.ts",
"durationMs": 4812
}'
```
Response:
```json
{ "ok": true, "gateRunId": "b7f3e2a1-..." }
```
The repo owner immediately sees a red notification; the run shows up at
`/<owner>/<repo>/gates`.
|