Skip to content

2026-05-13-host-multi-installation-app-auth


title: Host federates GitHub App auth per repo owner (multi-installation) date: 2026-05-13 status: superseded superseded-by: docs/decisions/2026-05-13-host-installation-auto-discovery.md mr_compliance: [MR-7, MR-13]

Section titled “title: Host federates GitHub App auth per repo owner (multi-installation) date: 2026-05-13 status: superseded superseded-by: docs/decisions/2026-05-13-host-installation-auto-discovery.md mr_compliance: [MR-7, MR-13]”

The fleet registry spans multiple GitHub orgs — eva-hq, skyflow-me, and (per registry.yaml) several more. Until 2026-05-13 the host (host/src/github-auth.ts) resolved GitHub App credentials against a single installation id (PETROVA_APP_INSTALLATION_ID), pinned at runtime to one org. Sources that federate across the registry (acts.ts, audit.ts, eva.ts) all minted one process-wide Octokit from that single installation.

This shipped silently broken for any repo whose owner did not match the configured installation. The host swallowed the resulting 404/403 in each source’s catch block, so /console/acts simply showed no rows for those slugs — skyflow-hq being the immediate offender after open_decision PR #1 merged on 2026-05-13 and never surfaced in the acts grid (host/src/sources/acts.ts lines 99–123 silently drop on the pulls.list throw). The same blind-spot exists for cross-org audit and any future cross-org decision lookup. The CLI side already handled this for sweep-state in PR #66 (6cb49d4); that fix did not extend to the host’s runtime.

host/src/github-auth.ts now exposes resolveAuthForOwner(owner) and makeOctokitForOwner(owner). The resolver reads a new env var PETROVA_APP_INSTALLATIONS containing a JSON map {"<owner-login>": <installation-id>, …} and selects the installation id for the owner whose repo is about to be queried. When the owner is not mapped, the resolver falls back to the legacy PETROVA_APP_INSTALLATION_ID so single-org deployments keep working unchanged. PETROVA_GITHUB_TOKEN (PAT) still takes precedence over both — useful for local dev where the PAT scopes every target.

Octokits are cached per lowercased owner login at module scope; the registry is finite, so the cache is bounded and survives serverless warm starts. All three federating sources (acts.ts, audit.ts, eva.ts) now call makeOctokitForOwner(parsed.owner) at the point they parse the entry’s GitHub URL, rather than holding a single process-wide instance. The owner-agnostic resolveAuth() / makeOctokit() API is retained for code paths without an owner in hand (currently only tests reach it indirectly).

Operationally, petrova.host (Railway) and the Vercel api/rpc.ts function need PETROVA_APP_INSTALLATIONS populated with at minimum the eva-hq and skyflow-me installations. Adding a new org becomes a one-line env-var edit plus a redeploy — no code change.

  • Single PAT covering every org — viable for one operator, but the host runs unattended in two cloud environments. PATs expire, require human rotation, and cannot be scoped per org. The App model already exists; multi-installation extends it without introducing a second auth pathway.
  • Per-source ad-hoc installation env vars (PETROVA_INSTALLATION_ID_<SLUG>) — what the CLI started doing manually. Fragile: every new org requires both a code change (look up the right env var) and a deploy. The JSON-map env var compresses that into config-only changes.
  • Use the GitHub App’s listInstallations API to discover installations at runtime — fewer env vars, but each cold start pays an extra API call before serving any request, and the App needs metadata permissions across every installation. Reserved as a follow-up if env-var sprawl becomes painful.

For code: host/src/github-auth.ts adds resolveAuthForOwner / makeOctokitForOwner; host/src/sources/{acts,audit,eva}.ts swap their per-instance cached octokit for the module-level owner-keyed cache. No new dependencies. Behavior under a single-installation deployment is unchanged.

For docs: This decision; host/README.md should note PETROVA_APP_INSTALLATIONS in the env section on next touch.

For invariants: Acts source contract (act = signed verb PR) is preserved — only the visibility of acts on non-default-installation repos changes.

For phases: Unblocks /console/acts for skyflow-hq once the env var is set on Railway and Vercel.

PETROVA_APP_INSTALLATIONS={"eva-hq":131038933,"skyflow-me":131821512}

Set on both petrova.host (Railway) and the petrova-codes Vercel project. Existing PETROVA_APP_INSTALLATION_ID may remain as the unmapped-owner fallback or be removed once PETROVA_APP_INSTALLATIONS covers every registered owner.