Skip to content

Diff a connection's granted permissions and subscribed events against what Scarab needs (ADR-0060)

GET
/v1/connections/{id}/preflight
curl --request GET \
--url https://example.com/v1/connections/example/preflight

This exists because both halves of a misconfigured GitHub App fail silently:

  • No events subscribed. GitHub delivers installation / installation_repositories regardless, so the connection registers itself and GET /v1/repos looks 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.

id
required
string

Connection id

Media typeapplication/json

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
checked
required

Did the forge actually answer? false for every unknown.

boolean
granted_permissions
required

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.

Array<object>

A permission the forge reports as granted.

object
level
required
string
name
required
string
id
required
string
kind
required
string
missing
required

The subset of required the forge does not currently grant. Empty on ok, and (necessarily) empty on unknown — read status, not this.

Array<object>

One capability Scarab needs of a forge app, as the preflight reports it (scarab_forge::preflight::ForgeRequirement on the wire).

object
kind
required

permission | event.

string
level

Minimum level for a permission (read/write/admin); absent for an event, which is subscribed or not.

string | null
name
required

The forge’s own name for it (statuses, push) — the label on the setting an operator has to go and change.

string
severity
required

required | recommended.

string
why
required

What silently breaks without it.

string
required
required

Everything Scarab needs from this forge, so the answer is legible even when nothing could be checked.

Array<object>

One capability Scarab needs of a forge app, as the preflight reports it (scarab_forge::preflight::ForgeRequirement on the wire).

object
kind
required

permission | event.

string
level

Minimum level for a permission (read/write/admin); absent for an event, which is subscribed or not.

string | null
name
required

The forge’s own name for it (statuses, push) — the label on the setting an operator has to go and change.

string
severity
required

required | recommended.

string
why
required

What silently breaks without it.

string
status
required

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”.

string
subscribed_events
required

The webhook events the forge will deliver, as it reports them.

Array<string>
unavailable_reason

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.

string | null
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