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