Diff a connection's granted permissions and subscribed events against what Scarab needs (ADR-0060)
const url = 'https://example.com/v1/connections/example/preflight';const options = {method: 'GET'};
try { const response = await fetch(url, options); const data = await response.json(); console.log(data);} catch (error) { console.error(error);}curl --request GET \ --url https://example.com/v1/connections/example/preflightThis exists because both halves of a misconfigured GitHub App fail silently:
- No events subscribed. GitHub delivers
installation/installation_repositoriesregardless, so the connection registers itself andGET /v1/reposlooks healthy — while no push ever starts a run. The operator’s only signal is that nothing happens. - No
statuses:write. Every status post 403s while the run itself goes green, so the forge simply never shows a check.
Until now the only defence was documentation. GET /v1/connections already
answers “does the credential resolve”; this is the same question one level
deeper — and is it allowed to do the things Scarab will ask of it.
A live forge round-trip, so it is its own endpoint rather than fields on
the list: rendering Settings must not fan out a call per connection
unasked-for (the same reasoning that made supports_resync kind-derived
rather than probed).
It answers 200 with status: "unknown", not 501, when the adapter cannot
introspect. “Unknown” is a real health state the UI must render next to the
credential line, and the requirement list is still worth showing; a 501 would
force every caller to invent that state itself.
Never returns credential material — only names, levels and event ids.
Parameters
Section titled “Parameters”Path Parameters
Section titled “Path Parameters”Connection id
Responses
Section titled “Responses”The result of checking a connection’s app configuration against what Scarab
needs (ADR-0060 preflight) — the deeper cut of the same question
credential_present answers on the list.
Carries no credential material: granted permissions are names and levels, events are names. Nothing here can authenticate to anything.
object
Did the forge actually answer? false for every unknown.
What the forge says the app is granted, including grants Scarab does not need — an operator comparing against the App settings page wants the whole picture, and an over-broad grant is worth seeing.
A permission the forge reports as granted.
object
The subset of required the forge does not currently grant. Empty on
ok, and (necessarily) empty on unknown — read status, not this.
One capability Scarab needs of a forge app, as the preflight reports it
(scarab_forge::preflight::ForgeRequirement on the wire).
object
permission | event.
Minimum level for a permission (read/write/admin); absent for an
event, which is subscribed or not.
The forge’s own name for it (statuses, push) — the label on the
setting an operator has to go and change.
required | recommended.
What silently breaks without it.
Everything Scarab needs from this forge, so the answer is legible even when nothing could be checked.
One capability Scarab needs of a forge app, as the preflight reports it
(scarab_forge::preflight::ForgeRequirement on the wire).
object
permission | event.
Minimum level for a permission (read/write/admin); absent for an
event, which is subscribed or not.
The forge’s own name for it (statuses, push) — the label on the
setting an operator has to go and change.
required | recommended.
What silently breaks without it.
ok — every required capability is granted; degraded — at least one is
missing (runs will silently not trigger, or checks will silently not
post); unknown — the forge could not be asked. Three values, not two:
“I could not look” must never render as “you are fine”.
The webhook events the forge will deliver, as it reports them.
Why the check could not run, when it could not — an adapter that cannot introspect, a credential that does not resolve, a forge that errored.
Examplegenerated
{ "checked": true, "granted_permissions": [ { "level": "example", "name": "example" } ], "id": "example", "kind": "example", "missing": [ { "kind": "example", "level": "example", "name": "example", "severity": "example", "why": "example" } ], "required": [ { "kind": "example", "level": "example", "name": "example", "severity": "example", "why": "example" } ], "status": "example", "subscribed_events": [ "example" ], "unavailable_reason": "example"}Requires Administer on the org
No such connection, or no registry wired