fix(agent-team): dispatch via GitHub App so P3 reaches CI (run_id resolves) #63

Merged
amoussa1229 merged 8 commits from fix/agent-team-dispatch-run-id into main 2026-06-24 22:22:19 +00:00
amoussa1229 commented 2026-06-24 22:18:52 +00:00 (Migrated from github.com)

Problem

The agent-team P3 pipeline parked every task at verify — dispatch_node produced no run_id ("dispatch unresolved"). Root cause: the dispatcher's default seams shell out to gh (not installed on the R720 box) with a read-only PAT lacking Actions scope. A second, upstream gap: dispatch_node required plan["scope"], which the planner never emits (only summary+phases), so auto-dispatch parked on "empty declared_scope" before ever dispatching.

Fix

  • GitHub-App auth (agent_team/github_app.py, new): the box mints short-lived (~1h) installation tokens from the App private key (RS256 App JWT → /access_tokens), cached + re-minted near expiry. Secret-safe: the JWT/token are never logged, never in an exception message, never persisted.
  • App-authed dispatch seams (dispatcher.py, additive — gh _default_* untouched): app_branch_pusher / app_workflow_dispatcher / app_run_locator. Push auth rides a host-scoped http.extraHeader via GIT_CONFIG_* env (token never in argv), using Basic auth (x-access-token) — git smart-HTTP rejects Bearer. REST locator maps id→databaseId/created_at→createdAt into the pure select_run_id.
  • Wiring (coordinator.py): default_dispatch_node_factory binds the App seams when AGENT_TEAM_GH_APP_ID/_INSTALLATION_ID/_PRIVATE_KEY are all set; partial/unreadable → one warning + gh-default fallback, never crashes serve.
  • Scope (nodes/dispatch_invoker.py): when no plan/operator scope is set, derive declared_scope from the candidate diff's touched paths (ci_gate.diff_touched_paths). CI's denylist + ..-escape + hash-binding + the agent-apply required reviewer remain the independent gates.
  • deps: pin PyJWT, requests, cryptography (48.0.1 — fixes GHSA-537c-gmf6-5ccf).
  • docs: DEPLOY-R720.md (App config, key at ~/.ssh, permission/scope, env-precedence, rotation/incident-response) + README.

Validation

  • 1527 unit tests pass; ruff clean.
  • Gates: /sh-plan-review (GPT-4.1), /sh-security-review (high-recall fan-out + proof-or-kill verifier — no confirmed crit/high; 2 hardening fixes applied), /code-review (findings applied), focused security review of the scope delta (no regression).
  • Box end-to-end: deployed (deploy-then-merge), App mint verified on the box, and a real dispatch resolved run_id 28132804173 → the full agent-team-apply-verify run went green (materialize, guard incl. diff-derived scope, build-test, pure-code gate, App-token mint, draft PR opened) — then cleaned up.

Deploy-then-merge: already deployed + box-verified.

Conscious, mitigated deviation from the dispatcher's old "operator host only" note: the box now holds a write-capable App key (scoped to one repo, Contents+Actions only, short-lived tokens, key mode 600). Follow-up: dedicated least-privilege App.

## Problem The agent-team P3 pipeline parked every task at `verify` — `dispatch_node` produced no `run_id` ("dispatch unresolved"). Root cause: the dispatcher's default seams shell out to `gh` (not installed on the R720 box) with a read-only PAT lacking Actions scope. A second, upstream gap: `dispatch_node` required `plan["scope"]`, which the planner never emits (only `summary`+`phases`), so auto-dispatch parked on "empty declared_scope" before ever dispatching. ## Fix - **GitHub-App auth** (`agent_team/github_app.py`, new): the box mints short-lived (~1h) installation tokens from the App private key (RS256 App JWT → `/access_tokens`), cached + re-minted near expiry. Secret-safe: the JWT/token are never logged, never in an exception message, never persisted. - **App-authed dispatch seams** (`dispatcher.py`, additive — gh `_default_*` untouched): `app_branch_pusher` / `app_workflow_dispatcher` / `app_run_locator`. Push auth rides a host-scoped `http.extraHeader` via `GIT_CONFIG_*` env (token never in argv), using **Basic** auth (`x-access-token`) — git smart-HTTP rejects Bearer. REST locator maps `id→databaseId`/`created_at→createdAt` into the pure `select_run_id`. - **Wiring** (`coordinator.py`): `default_dispatch_node_factory` binds the App seams when `AGENT_TEAM_GH_APP_ID`/`_INSTALLATION_ID`/`_PRIVATE_KEY` are all set; partial/unreadable → one warning + gh-default fallback, never crashes serve. - **Scope** (`nodes/dispatch_invoker.py`): when no plan/operator scope is set, derive `declared_scope` from the candidate diff's touched paths (`ci_gate.diff_touched_paths`). CI's denylist + `..`-escape + hash-binding + the `agent-apply` required reviewer remain the independent gates. - **deps**: pin PyJWT, requests, cryptography (48.0.1 — fixes GHSA-537c-gmf6-5ccf). - **docs**: `DEPLOY-R720.md` (App config, key at `~/.ssh`, permission/scope, env-precedence, rotation/incident-response) + README. ## Validation - **1527 unit tests** pass; ruff clean. - Gates: `/sh-plan-review` (GPT-4.1), `/sh-security-review` (high-recall fan-out + proof-or-kill verifier — no confirmed crit/high; 2 hardening fixes applied), `/code-review` (findings applied), focused security review of the scope delta (no regression). - **Box end-to-end**: deployed (deploy-then-merge), App mint verified on the box, and a real dispatch resolved **run_id 28132804173** → the full `agent-team-apply-verify` run went **green** (materialize, guard incl. diff-derived scope, build-test, pure-code gate, App-token mint, draft PR opened) — then cleaned up. Deploy-then-merge: already deployed + box-verified. Conscious, mitigated deviation from the dispatcher's old "operator host only" note: the box now holds a write-capable App key (scoped to one repo, Contents+Actions only, short-lived tokens, key mode 600). Follow-up: dedicated least-privilege App.
This repo is archived. You cannot comment on pull requests.
No description provided.