Skip to content

feat: route-issue reusable workflow + template (public dispatch leg)#57

Merged
dkastl merged 5 commits into
mainfrom
feat/56-route-issue
Jun 11, 2026
Merged

feat: route-issue reusable workflow + template (public dispatch leg)#57
dkastl merged 5 commits into
mainfrom
feat/56-route-issue

Conversation

@dkastl

@dkastl dkastl commented Jun 11, 2026

Copy link
Copy Markdown
Contributor

Closes #56

What

Public dispatch leg for org-wide issue auto-routing (Phase 1). Lets any
Geolonia repo opt in to having its issues auto-added to a team project board
based on the org Department issue field — without exposing any credential or
routing config publicly.

Files

  • .github/workflows/reusable-route-issue.yml — reusable workflow: mints the
    low-priv OPS_DISPATCH app token and repository_dispatches the issue to
    geolonia-operations.
  • workflow-templates/route-issue.yml + .properties.json — one-click adoption
    in the New workflow picker (thin caller on issues:[field_added,field_removed]).
  • docs/workflows/route-issue.md + nav/table entry.

Why dispatch-to-central

Mirrors the existing sync-team-access pattern — individual repos never see
the org token
. This public side is a dumb forwarder: no project IDs, no routing
logic, no privileged token. All of that lives in private geolonia-operations
(see geolonia/geolonia-operations#156, the receiver half).

Notes

  • Reuses the existing OPS_DISPATCH_CLIENT_ID / OPS_DISPATCH_APP_PRIVATE_KEY
    org secrets (already wired for sync-team-access).
  • Actions SHA-pinned to the same create-github-app-token SHA the repo already uses (pinact clean).
  • Caller pins the reusable workflow to @v1a v1 tag must exist before consumers adopt it (cut after merge, per release-auto-on-tag).

Not in this PR

  • The privileged receiver, routing.yml, and the "Issue Router" GitHub App live in geolonia-operations.

Summary by CodeRabbit

  • New Features

    • Reusable workflow to forward issue events to a central operations repo for org-wide board routing.
    • Workflow template to opt repositories into automatic issue routing, with optional exclusive vs additive routing.
  • Documentation

    • Added setup guide, behavior notes, troubleshooting tips, and a new Workflows navigation entry for the routing template.

Public dispatch leg for org-wide issue auto-routing. A thin caller in any
participating repo forwards issues:[field_added,field_removed] events to the
reusable workflow, which mints the low-priv OPS_DISPATCH app token and sends a
repository_dispatch to the private geolonia-operations repo. No project IDs,
routing logic, or privileged credentials live here.

Mirrors the reusable-sync-team-access.yml dispatch-to-central pattern.

Refs #56
@coderabbitai

coderabbitai Bot commented Jun 11, 2026

Copy link
Copy Markdown

Review Change Stack

Important

Review skipped

This PR was authored by the user configured for CodeRabbit reviews. CodeRabbit does not review PRs authored by this user. It's recommended to use a dedicated user account to post CodeRabbit review feedback.

⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 4cb012c8-7896-4011-b47d-b56dd684945d

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Walkthrough

Adds a reusable GitHub Actions workflow that mints a low-privilege app token and repository_dispatches issue field events to a central operations repo, plus a one-click workflow template, properties, and documentation with prerequisites and troubleshooting.

Changes

Issue routing via dispatch

Layer / File(s) Summary
Reusable workflow dispatch logic
.github/workflows/reusable-route-issue.yml
Defines the reusable workflow interface (inputs: operations_repo, event_type, exclusive_routing), requires OPS_DISPATCH_CLIENT_ID/OPS_DISPATCH_APP_PRIVATE_KEY, sets permissions: contents: read, mints an app token via actions/create-github-app-token, validates issue.node_id, builds a repository_dispatch payload (includes exclusive_routing) via jq, and sends it to the target operations repo.
Workflow template and properties
workflow-templates/route-issue.yml, workflow-templates/route-issue.properties.json
Adds a consumer workflow that triggers on issues field_added/field_removed and delegates to the reusable workflow with inherited secrets; the properties JSON registers the template in the GitHub UI (icon, categories, file pattern).
Documentation and navigation
docs/workflows/route-issue.md, docs/workflows.md, mkdocs.yml
Publishes docs describing opt-in usage, the exclusive_routing option and its semantics, the dispatch-to-central pattern (no privileged tokens in participating repos), prerequisites (secret visibility and default-branch trigger), troubleshooting, and adds site navigation/table entries.

Sequence Diagram(s)

sequenceDiagram
  participant ParticipatingRepo as Participating Repo
  participant ReusableWorkflow as Reusable Workflow
  participant AppToken as actions/create-github-app-token
  participant OperationsRepo as geolonia-operations
  ParticipatingRepo->>ReusableWorkflow: Call with issue event
  ReusableWorkflow->>AppToken: Generate low-privilege token
  AppToken-->>ReusableWorkflow: App token
  ReusableWorkflow->>OperationsRepo: repository_dispatch (issue context)
  OperationsRepo-->>ReusableWorkflow: dispatch accepted
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related issues

  • geolonia/geolonia-operations#156 — Implements the public dispatch leg that repository_dispatches issue routing events to geolonia-operations.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Linked Issues check ⚠️ Warning The PR addresses all coding requirements from issue #56: the reusable workflow is implemented with proper token scoping, workflow templates are added with documentation, and all files are present. However, one substantive bug remains unresolved: the jq call uses --arg instead of --argjson for ISSUE_NUMBER, serializing it as a string rather than an integer. Fix the ISSUE_NUMBER serialization in .github/workflows/reusable-route-issue.yml by changing the jq --arg call to use --argjson to emit a proper integer value.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately describes the PR's main change: adding a reusable workflow and template for routing issues, and specifically calls out this is the public dispatch leg of a two-part feature.
Description check ✅ Passed The PR description comprehensively covers what is being added (files, purpose), why the architectural choice was made, and what is not included. However, it does not follow the repository's template structure with Summary and Checklist sections.
Out of Scope Changes check ✅ Passed All changes are directly related to implementing the public dispatch leg of issue auto-routing: reusable workflow, templates, and documentation. No extraneous modifications are present.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.


Comment @coderabbitai help to get the list of available commands and usage tips.

@github-actions

Copy link
Copy Markdown

Secret Leak Check

OK No secrets detected in this PR's diff.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In @.github/workflows/reusable-route-issue.yml:
- Around line 65-72: The GitHub App token created by the "Generate dispatch
token" step (id: app_token, uses: actions/create-github-app-token) currently
inherits full installation permissions; restrict it by adding permission inputs
so it only has Metadata: read and Contents: read/write — i.e., add
permission-metadata: read and permission-contents: write to the step inputs to
minimize token scope for the repos/{owner}/{repo}/dispatches call.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 0fd47d3a-4913-4d3f-a171-e03b2fe16d20

📥 Commits

Reviewing files that changed from the base of the PR and between 649b590 and 1774998.

📒 Files selected for processing (6)
  • .github/workflows/reusable-route-issue.yml
  • docs/workflows.md
  • docs/workflows/route-issue.md
  • mkdocs.yml
  • workflow-templates/route-issue.properties.json
  • workflow-templates/route-issue.yml

Comment thread .github/workflows/reusable-route-issue.yml
Per-repo opt-in: when a repo sets remove_on_field_removed: true, a cleared
Department field also removes the issue from its board. Default false (keep on
board). The flag is forwarded in the dispatch payload; the central receiver in
geolonia-operations performs the removal.

Refs #56
@github-actions

Copy link
Copy Markdown

Secret Leak Check

OK No secrets detected in this PR's diff.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@docs/workflows/route-issue.md`:
- Around line 47-50: The sentence "A configured master board is never removed
from" is unclear to public readers; either replace it with a brief definition of
"configured master board" (e.g., "a board designated in routing settings as the
authoritative source for items, set in the private geolonia-operations repo") or
remove the sentence if it's an internal implementation detail. Update the
sentence near the line that currently reads "A configured master board is never
removed from" to include the short definition or delete it so the doc only
describes observable user behavior (that moving between Department options
triggers field_added on the new board but not field_removed on the old).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 3981e5ca-2083-4116-8449-ccb27fa2d7c9

📥 Commits

Reviewing files that changed from the base of the PR and between 1774998 and 91e5ec3.

📒 Files selected for processing (3)
  • .github/workflows/reusable-route-issue.yml
  • docs/workflows/route-issue.md
  • workflow-templates/route-issue.yml

Comment thread docs/workflows/route-issue.md Outdated
Default behavior is now: an issue lives on only the board for its current
Department. Changing the department removes the issue from the other department
boards; clearing it removes from all of them. This fixes accidental double-add
when a mis-set department is corrected (field_added on update no longer leaves
the stale board item behind).

Renames the per-repo input remove_on_field_removed -> exclusive_routing
(default true; set false for additive routing). Only department boards listed
in routing.yml are ever touched -- never a master board or unrelated project.

Refs #56
@github-actions

Copy link
Copy Markdown

Secret Leak Check

OK No secrets detected in this PR's diff.

No master/all-work board exists or is planned. Reword the exclusive_routing
docs to say only department boards are ever removed from (any other project is
left alone), without referencing a master board.

Refs #56
@github-actions

Copy link
Copy Markdown

Secret Leak Check

OK No secrets detected in this PR's diff.

repository_dispatch needs only Metadata: read + Contents: write, so request
exactly those on the minted token instead of inheriting the OPS_DISPATCH app's
full installation permissions.

Refs #56
@dkastl

dkastl commented Jun 11, 2026

Copy link
Copy Markdown
Contributor Author

@coderabbitai review

Addressed the dispatch-token least-privilege finding (now requests permission-metadata: read + permission-contents: write). The earlier docs/workflows/route-issue.md "configured master board" comment is resolved — the master board concept was removed entirely in a later commit.

@github-actions

Copy link
Copy Markdown

Secret Leak Check

OK No secrets detected in this PR's diff.

@coderabbitai

coderabbitai Bot commented Jun 11, 2026

Copy link
Copy Markdown
🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
echo "=== .github/workflows/reusable-route-issue.yml ==="
cat .github/workflows/reusable-route-issue.yml

echo ""
echo "=== workflow-templates/route-issue.yml ==="
cat workflow-templates/route-issue.yml

echo ""
echo "=== workflow-templates/route-issue.properties.json ==="
cat workflow-templates/route-issue.properties.json

echo ""
echo "=== docs/workflows/route-issue.md ==="
cat docs/workflows/route-issue.md

echo ""
echo "=== docs/workflows.md (tail) ==="
tail -30 docs/workflows.md

echo ""
echo "=== mkdocs.yml (relevant section) ==="
cat mkdocs.yml

Length of output: 13355


🏁 Script executed:

#!/bin/bash
# Check the create-github-app-token action's action.yml to see if permission-* inputs are declared
gh api \
  "repos/actions/create-github-app-token/contents/action.yml?ref=bcd2ba49218906704ab6c1aa796996da409d3eb1" \
  --jq '.content' | base64 -d | grep -A2 'permission'

Length of output: 10646



@dkastl: Good progress — the permission inputs (permission-metadata: read / permission-contents: write) are confirmed supported at the pinned SHA, and both values are correct (contents: write is exactly what GitHub requires for repository_dispatch). The "master board" docs issue is clearly resolved. The overall design is solid.

One real bug remains before merge:


🐛 issue_number serialised as a JSON string, not an integer

In .github/workflows/reusable-route-issue.yml, the jq call uses --arg for the issue number:

--arg number "$ISSUE_NUMBER" \

--arg always produces a JSON string ("123"), so the receiver gets "issue_number": "123" instead of "issue_number": 123. If geolonia-operations' route.sh (or any downstream logic) compares or filters by issue number arithmetically or with strict JSON typing, this will silently fail.

Fix — use --argjson to emit a proper integer:

--argjson number "$ISSUE_NUMBER" \

$EXCLUSIVE_ROUTING already uses --argjson (correctly), so this is just making number consistent with that pattern.


Everything else looks clean:

Item Status
permission-metadata: read + permission-contents: write supported at pinned SHA bcd2ba49
Token scoped to operations_repo only (repositories: input)
--argjson exclusive for boolean
set -euo pipefail + ISSUE_NODE_ID guard
No master board references in docs
mkdocs.yml nav + docs/workflows.md table entry
workflow-templates template properties
✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
.github/workflows/reusable-route-issue.yml (1)

108-126: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use --argjson for issue_number to emit an integer instead of a string.

Line 111 uses --arg number, which serializes issue_number as a JSON string ("123") rather than an integer (123). The receiver in geolonia-operations may expect the native integer type. Note that exclusive_routing on line 115 already uses --argjson correctly for the same reason.

Proposed fix
           REQUEST_BODY=$(jq -n \
             --arg event_type "$EVENT_TYPE" \
             --arg repo "$SOURCE_REPO" \
-            --arg number "$ISSUE_NUMBER" \
+            --argjson number "$ISSUE_NUMBER" \
             --arg node_id "$ISSUE_NODE_ID" \
             --arg url "$ISSUE_URL" \
             --arg action "$ACTION" \
             --argjson exclusive "$EXCLUSIVE_ROUTING" \
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/reusable-route-issue.yml around lines 108 - 126, The
REQUEST_BODY construction currently uses --arg number "$ISSUE_NUMBER" which
makes issue_number a JSON string; change that to --argjson number
"$ISSUE_NUMBER" so the issue_number field is emitted as a native integer. Update
the jq arg for issue_number in the REQUEST_BODY block (the --arg/---argjson
invocations around event_type, repo, number, node_id, url, action, exclusive) to
use --argjson for number (and only number) while leaving other --arg calls
unchanged so issue_number becomes an integer in the produced JSON.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In @.github/workflows/reusable-route-issue.yml:
- Around line 108-126: The REQUEST_BODY construction currently uses --arg number
"$ISSUE_NUMBER" which makes issue_number a JSON string; change that to --argjson
number "$ISSUE_NUMBER" so the issue_number field is emitted as a native integer.
Update the jq arg for issue_number in the REQUEST_BODY block (the
--arg/---argjson invocations around event_type, repo, number, node_id, url,
action, exclusive) to use --argjson for number (and only number) while leaving
other --arg calls unchanged so issue_number becomes an integer in the produced
JSON.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 2d886cda-0e84-4d4d-bad7-b5e11965ea58

📥 Commits

Reviewing files that changed from the base of the PR and between 1edf87e and 31eb703.

📒 Files selected for processing (1)
  • .github/workflows/reusable-route-issue.yml

@dkastl dkastl merged commit 92c8c6d into main Jun 11, 2026
3 checks passed
@dkastl dkastl deleted the feat/56-route-issue branch June 11, 2026 23:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add reusable workflow: route tagged issues to team boards (public dispatch leg)

1 participant