Skip to content

feat(dispatch): cross-repo grants + reusable Actions (starter/queuer) + workflow call-site migration #2672

Closed

Description

@dezren39

Drew's directive (2026-10-30): keep the pm-dispatch permission system, but establish grants so any allowlisted repo may target any other allowlisted repo; paper over the dispatch plumbing with centrally-updated reusable GitHub Actions referenced @main; migrate the fleet's workflow call sites onto them.

Part 1 — Cross-repo grants (pm-stack/dispatch)

  • OIDC rule changes from same-repo-only to a grant mesh: caller ∈ allowlist AND target ∈ allowlist (allowlist = the four fleet repos). Bearer unchanged.
  • Grants model explicit in code + tests: cross-repo allowed, non-allowlisted caller rejected, non-allowlisted target rejected, same-repo still allowed, bearer unaffected.
  • Small API extension: POST /v1/tasks accepts optional issue_number — attach to an existing issue in the target repo (task-id comment + queue/queued + source/dispatch labels) instead of creating a new one. Preserves tracker no-spam semantics for Part 3.
  • Deploy to the laptop via install.sh; health version must move to the new main HEAD.

Part 2 — Reusable actions (@main, this repo)

  • .github/actions/pm-dispatch-start ("the starter"): OIDC fetch (aud pm-dispatch), POST /v1/sessions, outputs task_id/session_id/issue_number/issue_url.
  • .github/actions/pm-dispatch-queue ("the queuer"): same plumbing over POST /v1/tasks, incl. issue / issue_number inputs.
  • examples/dispatch-workflow.yml + dispatch README updated to the actions and the grants mesh.

Part 3 — Call-site migration (all four repos)

Every Jules session-creation call in workflows → the starter; every direct issue creation → the queuer. Incidents that did both (tracker issue + Jules session) collapse to the starter alone — one issue per incident. Tracker-maintenance workflows keep no-spam: look up the open tracker, attach via the queuer's issue_number. Affected (enumerated 2026-10-30): code flake-inputs-update.yml, main-broken.yml, jules.yml, jules-daily-codebase-ideation.yml, jules-daily-flake-sync.yml; opencode jules-daily-upstream-sync.yml, main-broken.yml; openchamber opencode-update.yml, jules-daily-upstream-sync.yml, main-broken.yml; nix main-broken-watcher.yml (hybrid: dispatch's GitHub App is not installed on dezren39/nix, so its issue creation cannot move — the queue enqueue does).
Live proof: a workflow_dispatch-only pm-dispatch-smoke.yml in openchamber runs the queuer targeting developing-today/opencode (cross-repo), verified end-to-end, then cleaned up.

Activity

  1. added 18 commits that reference this issue on Oct 30, 2026
  2. dezren39 commented on Oct 30, 2026

    @dezren39
    MemberAuthor

    Completion record — #2672 (Parts 1–5, 5b, 7 built + merged; live proofs + Part 6 outstanding, see end)

    Part 1 — cross-repo grants (PR #2676, merged 9d91d38). OIDC rule is now a grants mesh over the allowlist (src/auth.ts assertCallerRepo): caller ∈ allowlist AND target ∈ allowlist; bearer unchanged. POST /v1/tasks gained issue_number (attach: task-id comment + queue/queued + source/dispatch labels, canonical dedupe). Tests 34 pass (was 28), tsc clean. Deployed via install.sh; health version tracked main HEAD at each deploy.

    Part 2 — reusable actions (PR #2676). .github/actions/pm-dispatch-start (starter) + pm-dispatch-queue (queuer), pure bash+curl+jq composites over OIDC (aud pm-dispatch); example workflow + README updated.

    Part 3 — call-site migration (all merged). code #2678 (flake-inputs-update collapse to starter + tracker attach, main-broken → queuer, jules.yml's four jules-action jobs → starter, both jules-daily-* → starter, repo-sweeper comment), opencode #975 (upstream-sync + main-broken), openchamber #904 (opencode-update, upstream-sync, main-broken + the standing pm-dispatch-smoke.yml), dezren39/nix #1182 (main-broken-watcher hybrid: gh issue create kept for its exact label set, enqueue moved to the queuer at filing, priority critical). Judged NOT call sites: bot/pr-review comment-and-reaction workflows (they create nothing).

    Part 4 — prompt sources (PR #2682). prompt-file > prompt-url > prompt resolution on both primitives.

    Part 5a — allowlist extension (PR #2683, deployed; /health = c9f0108 = this PR's merge). developing-today/zettel + dezren39/dezren39 added to the mesh with queues (repo-health-zettel-pilot / repo-health-dezren39-pilot) and laptop checkouts (~/git/zettel pre-existed; ~/git/dezren39 cloned). APP_INSTALLED_REPOS corrected to include dezren39/nix (App "developing.today" 5454422, installations 174792676 + 175747567 — verified live by App-JWT probe).

    Parts 5/5b — scheduled automation, final consolidated shape. Central library in .github/actions/pm-dispatch-task/prompts/ (bolt/palette/suggestions/ci-fix) + task action (PR #2692; 38/38 functional tests) + central reusable workflow .github/workflows/pm-scheduled.yml (PR #2704; slot table, 23/23 resolve cases). Every repo has exactly ONE new workflow, .github/workflows/agents.yml, a thin caller (triggers + one job calling pm-scheduled@main): code #2702, openchamber #909, opencode #979, nix #1183, zettel #25, dezren39/dezren39 #18+#19. All six repos run Bolt + Palette daily and Suggestions daily, staggered America/Chicago (tasks: code 20:00, zettel 08:30, nix 00:00, dezren39 00:00, opencode 19:00, openchamber 21:00; suggestions 08:00–13:00 hourly by repo). No per-repo prompt files.

    Part 7 — CI fixer, final placement. ci-fix mode lives in pm-dispatch-task (dedupe on ci-fix: <workflow> failing on <branch>; default branch → starter/high, other branch → queuer/ci_investigation). Thin self-filling wrapper .github/actions/pm-dispatch-ci-fix (PR #2701; default-branch passthrough bug found + fixed in PR #2736). In-CI ci-fix jobs (needs main jobs, if: failure(), continue-on-error step): code nix-ci.yml (#2702, also removed jules.yml's superseded workflow_run ci-failure-fix job), openchamber oc-review.yml (#909), opencode test.yml (#979), nix flake-checks-build.yml (#1183), zettel ci.yaml (#25). dezren39/dezren39 has no real CI (readme-scribe only) — no hook. Code also has pm-dispatch-smoke.yml (dispatch-only ci-fix proof harness).

    Outstanding — live proofs (blocked by a GitHub platform failure, 2026-10-30). Four of four runs that download an action from this repo @main failed identically in runner staging ("Could not find file …/_staging/code-/archive"): openchamber smoke ×2, code agents, code ci-fix smoke. The repo tarball was verified healthy from the sandbox via codeload AND the api tarball endpoint (26.8 MB, untars clean, public repo, no LFS/submodules) — the failure is on GitHub's runner side, amid multi-hour queue backlogs. Owed once it recovers: (1) openchamber pm-dispatch-smoke → verify opencode task+issue, cancel/close; (2) code agents dispatch (all tasks) + nix agents dispatch (task=bolt) → verify session/task ids, leave sessions running; (3) code pm-dispatch-smoke ×2 (starter path, then dedupe-attach) → verify, close issue, cancel tasks. The in-CI ci-fix hook itself is proven by the next real main-CI failure, as designed.

    Outstanding — Part 6 (cron retarget). Blocked: the runtime's injection check flagged the cron.view results for jules-daily-top-issue / jules-daily-top-issue-code and stopped action on them; no edits were made. Escalated to the main chat with options (clean re-retrieval on the main side / Drew's explicit go-ahead / defer to the keeper-deprecation pass).

    Cutover reminders (Drew): the 6 Jules UI scheduled tasks still need pausing/deleting to avoid double-running alongside agents.yml; GitHub App installation grants for developing-today/zettel + dezren39/dezren39 are still pending (dispatch warn-skips issue creation there until granted).

  3. dezren39 commented on Oct 30, 2026

    @dezren39
    MemberAuthor

    Addendum / corrections to the completion record (from the late-arriving Part 4 + Part 5a reports)

    Part 5a — corrections:

    • Checkouts: ~/git/zettel did NOT pre-exist — it was cloned by the Part 5a work (origin main @ 9a4372b). dezren39/dezren39's primary pre-existed at /home/user/dezren39 (home dir, not ~/git/) — that is the path REPO_DIRS points at, so dispatch sessions for dezren39/dezren39 run there. (A second clone at ~/git/dezren39 was later made by the dezren39 agents.yml work; harmless, not the session dir.)
    • Database gate found + fixed during the deploy probes: the first zettel enqueue returned 502 — the Supabase function agent.enqueue_repo_task_fleet hardcodes the fleet-repo list server-side (verified via pg_get_functiondef; it is the only agent function gating on the list). Migration 0015 (CREATE OR REPLACE with the six-repo list, body otherwise byte-identical; the code-only enqueue_repo_task deliberately untouched) was applied via ~/tools/supabase-exec.py, after which both probes passed: zettel → task 3915 (repo-health-zettel-pilot), dezren39/dezren39 → task 3916 (repo-health-dezren39-pilot), both cancelled afterwards (SQL-confirmed status=cancelled). The migration file is saved at ~/workspace/supabase/muse-dev/migrations/0015_fleet_repos_zettel_dezren39.sql and is not committed — committing it (home-repo) is the parent's call.
    • Warn-skip verdict (with line evidence): maybeCreateIssue in src/server.ts returns null + warning on a set miss and catches create failures the same way; handleSessions proceeds to enqueue + launch and returns 201/202. The skip path cannot hard-fail a launch. Side effect of the nix app fix: the /v1/tasks issue_number attach path (comment + labels in handleTasks) now also works for dezren39/nix targets.
    • DISPATCH_REPO_ALLOWLIST is NOT pinned in either laptop env file — the FLEET_REPOS default governs.
    • Tests after Part 5a: 39 pass / 0 fail, tsc clean (new test/config.test.ts; auth grants fixture extended with a cross-repo zettel→dezren39/dezren39 + dezren39/nix test).

    Follow-up candidates (not done, by design of the briefs):

    1. Client-side repo lists still name four repos: ~/tools/repo-queue.py (FLEET_REPOS + dedupe REPO_ALT) and ~/tools/repo-task-v1.schema.json (repo enum) — direct repo-queue.py enqueue/schema validation for zettel/dezren39 would fail client-side. Dispatch paths and cancel are unaffected.
    2. Two stale README lines remain (outside the sections the Part 5a brief allowed): the /v1/sessions flow step and the issue_number paragraph still say dezren39/nix has no app installation.

    Part 4 — evidence detail (for the record): the extracted step scripts were harness-tested with a stubbed curl: 20/20 cases across both actions (precedence incl. multi-line file content verbatim in the payload, absolute paths, no-fallthrough on a selected-but-broken source, all failure modes exiting before any dispatch POST). The harness caught one real bug pre-merge (an unquoted /* glob test breaking relative prompt-file resolution when CWD ≠ workspace) — fixed with a case pattern in both actions.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions