Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions docs/ADRs/0048-automatic-updates.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,7 +56,8 @@ Fullsend must make users aware of the implications of choosing a moving tag:
## Consequences

* `v0` should be migrated to the new moving tag and deleted.
* Current users track the new floating tag automatically to keep behavior consistent.
* Current users track the new floating tag automatically to keep behavior consistent. *(See [ADR 62](0062-dispatch-version-skew.md) for migration approach revisions discovered during implementation.)*
* New users track the version tag they install at.

See [Automatic Updates](../plans/automatic-updates.md) for the design details.
See [Automatic Updates](../plans/automatic-updates.md) for the design details,
including amendments for the dispatch version-skew resolution (added 2026-06-19).
92 changes: 92 additions & 0 deletions docs/ADRs/0062-dispatch-version-skew.md
Comment thread
rh-hemartin marked this conversation as resolved.
Comment thread
rh-hemartin marked this conversation as resolved.
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
---
title: "62. Resolving per-repo dispatch version skew"
status: Accepted
relates_to:
- agent-infrastructure
topics:
Comment thread
rh-hemartin marked this conversation as resolved.
- versioning
- workflows
- per-repo
- dispatch
---

# 62. Resolving per-repo dispatch version skew

Date: 2026-06-25

## Status

Accepted
Comment thread
rh-hemartin marked this conversation as resolved.

## Context

[ADR 48](0048-automatic-updates.md) decided that Fullsend offers two tags to
track updates: version (`vMAJOR.MINOR.PATCH`) and moving (`latest`). During
its implementation a major problem was found: in per-repo mode
`reusable-dispatch.yml` has hardcoded references to `reusable-<stage>.yml`
at `v0`. This is what we call "version skew". The version skew does not happen
on per-org mode, as organizations reference `reusable-<stage>.yml` directly.

This ADR presents a few options to solve this problem and recommends a solution.

## Options

### A. Re-introduce `reusable-dispatch.yml` to the user repository

Move `reusable-dispatch.yml` back into the enrolled repo so the `uses:` ref
can be templated at install time by the CLI.

**Rejected** We extracted dispatch specifically to reduce update noise in
user repos. Re-introducing it undoes that benefit.

### B. Convert dispatch to a composite action

Replace the dispatch workflow with a composite action that handles routing
and agent execution in a single job.

**Rejected** Composite actions cannot spawn separate jobs, so it is not
possible.

### C. Release branches with ref rewriting

Change the release process to create a release branch where `@v0` references
in `reusable-dispatch.yml` are rewritten to `@vX.Y.Z` before tagging.

1. Branch from `main`.
2. Rewrite all `uses: ...@v0` to `uses: ...@vX.Y.Z` in `reusable-dispatch.yml`.
3. Update default values for `fullsend_version`, etc.
4. Commit, tag the branch with `vX.Y.Z` and `latest`.

**Rejected** Introduces significant repository-level complexity.

### D. Merge stage workflows into dispatch

Inline all six stage workflows as conditional jobs directly inside
Comment thread
rh-hemartin marked this conversation as resolved.
`reusable-dispatch.yml`. The `uses:` lines to stage workflows disappear
entirely.

This impacts per-org mode as its workflows reference directly `reusable-<stage>.yml`.

**Accepted** Removes the problem completely at the cost of a large file. This file
can be eventually simplified to make it easier to handle.

## Decision

The decision is to merge the stage workflows into the dispatch workflow to avoid the
version skew it introduced in the first place.

However, as per-org mode is deprecated (see ADR 44),
`reusable-<stage>.yml` will be kept to allow per-org mode to continue to work. When
per-org mode is removed, then `reusable-<stage>.yml` files will be removed. During
Comment thread
rh-hemartin marked this conversation as resolved.
the deprecation period `reusable-<stage>.yml` files need to be in sync
with the merged `reusable-dispatch.yml`.

## Implementation
Comment thread
rh-hemartin marked this conversation as resolved.
Comment thread
rh-hemartin marked this conversation as resolved.
Comment thread
rh-hemartin marked this conversation as resolved.

See [merge-stage-workflows plan](../plans/merge-stage-workflows.md).
Comment thread
rh-hemartin marked this conversation as resolved.

## Consequences

* `reusable-<stage>.yml` stage logic is inlined into `reusable-dispatch.yml`; standalone files are retained until per-org mode is removed per ADR 44.
Comment thread
rh-hemartin marked this conversation as resolved.
* `reusable-dispatch.yml` grows significantly in size.
* `reusable-dispatch.yml` and `reusable-<stage>.yml` must stay in sync during the deprecation period.
1 change: 1 addition & 0 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ Infrastructure platform choice and configuration are specified in the adopting o
- Reusable workflows: agent workflows in `.fullsend` are thin callers (~40-70 lines) that delegate infrastructure logic to upstream reusable workflows (`fullsend-ai/fullsend/.github/workflows/reusable-*.yml`) via `workflow_call`. Infrastructure patches ship once upstream and propagate to all orgs without re-install ([ADR 0031](ADRs/0031-reusable-workflows-for-action-installed-distribution.md)). **`--vendor`** ([ADR 0047](ADRs/0047-vendored-installs-with-vendor-flag.md)) commits workflows and agent content at install time; layered installs (default) fetch upstream at runtime.
- Event-driven stage dispatch: eliminate `workflow_dispatch` + `gh workflow run` fan-out from `dispatch.yml` in favor of synchronous `workflow_call` so the dispatched run stays linked to the caller ([ADR 0041](ADRs/0041-synchronous-workflow-call-event-dispatch.md)).
- Multi-repo management: a `fullsend repos` subcommand group with a declarative `repos.yaml` manifest for managing per-repo installations at scale — bulk install, status, sync, upgrade, and removal across repos and orgs ([ADR 0057](ADRs/0057-repos-management.md)).
- Dispatch version-skew resolution: per-repo `reusable-dispatch.yml` inlines stage workflow jobs directly, eliminating `@v0` references to `reusable-{stage}.yml` ([ADR 0062](ADRs/0062-dispatch-version-skew.md)).

**Open questions:**

Expand Down
52 changes: 52 additions & 0 deletions docs/plans/automatic-updates.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,6 +107,58 @@ with:
fullsend_cli_ref: v0.15.0
```

## 2026-06-19 amendments
Comment thread
rh-hemartin marked this conversation as resolved.
Comment thread
rh-hemartin marked this conversation as resolved.

### Per-repo `reusable-dispatch.yml` hardcoded `v0` overlook

The initial plan to implement this overlooked that `reusable-dispatch.yml` hardcodes
`v0` when triggering other workflows (`reusable-triage.yml` for example). This only
happens on per-repo mode, as per-org uses its own `dispatch.yml`.

This is a problem, as the version transmitted to the `reusable-dispatch.yml` from the
shim can't be used to call the appropriate version of `reusable-<stage>.yml`.

See [ADR 62](../ADRs/0062-dispatch-version-skew.md) for the options considered
and the accepted solution (merging stage workflows into dispatch).

### The `main` branch will use `@main`

Currently `reusable-dispatch.yml` uses `@v0` on the `main` branch, that should be changed
to `@main` on `uses:` and other variables, so pointing to `main` on the shim will have the
desired effect of tracking the development changes.

### Period of migration

The ADR proposed that current users would migrate automatically to follow the new
floating tag `latest`. However at implementation time a limitation has been detected:
if `v0` is changed to the new changes (or dropped) users will break. There seems to
be two solutions:

* Do not update anymore `v0`. Users will keep pulling from `v0` and they will be behind,
so a communication needs to happen so they run install commands again to refresh
their shims. Rejected.
* Update `v0` a last time to these changes. This would mean that workflows would
need to preserve behaviour. Accepted, more details below.

Comment thread
rh-hemartin marked this conversation as resolved.
#### Period of migration by moving `v0`

Moving `v0` one last time to these new changes mean that updated workflows
receive `fullsend_ai_ref` and `fullsend_version` so they can't be removed. Instead
they are used as a fallback to preserve behavior:

```yaml
Comment thread
rh-hemartin marked this conversation as resolved.
# reusable-dispatch.yaml/dispatch.yaml
triage:
uses: ...
with:
fullsend_actions_ref: ${{ inputs.fullsend_actions_ref || inputs.fullsend_ai_ref }}
fullsend_cli_ref: ${{ inputs.fullsend_cli_ref || inputs.fullsend_version }}
```

A deprecation notice needs to happen, so users install again to get their
shims refreshed with the new variables. After a while we can remove the old
variables from the workflows. Everyone not migrated by then will be broken.
Comment thread
rh-hemartin marked this conversation as resolved.

## Some Future Problems

* Currently images are not versioned, they just have the `latest` tag. This needs to
Expand Down
65 changes: 65 additions & 0 deletions docs/plans/merge-stage-workflows.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
# ADR 62: Inline stages into reusable-dispatch.yml (per-repo only)

## Context

Per-repo mode has a version skew problem: `reusable-dispatch.yml` references `reusable-{stage}.yml@v0` via
hardcoded `uses:` lines. During development/testing, changes to stage workflows aren't picked up because `@v0` points
to the released version. ADR 62 decides to inline all stage logic into `reusable-dispatch.yml`, eliminating
the second `uses:` hop.

Per-org mode is unaffected (its flow goes through scaffold `dispatch.yml` → `gh workflow run` →
thin callers → `reusable-{stage}.yml@v0`) and must not change. `reusable-{stage}.yml` files stay for
per-org until it's removed per ADR 44.

## Plan

Branch from main. Per-org scaffold files stay untouched — only `reusable-dispatch.yml` and the new composite
action are changed.

### 1. Inline stage logic into reusable-dispatch.yml

Inline each `reusable-{stage}.yml` job directly into `reusable-dispatch.yml`, eliminating the
`uses: reusable-{stage}.yml@v0` hop. Verify:
- Route job logic matches main (no unintended routing changes)
- Each inlined stage job matches its corresponding `reusable-{stage}.yml` (same steps, permissions, concurrency)
- `uses: reusable-{stage}.yml@v0` lines are gone
- Secrets (`FULLSEND_GCP_WIF_PROVIDER`, `FULLSEND_GCP_PROJECT_ID`) declared in `on.workflow_call.secrets`

### 2. Create prepare-workspace composite action

Add `.github/actions/prepare-workspace/action.yml` to DRY up workspace setup across the six inlined stage jobs.
Comment thread
rh-hemartin marked this conversation as resolved.

### 3. Sync check: inlined stages vs reusable-{stage}.yml

For each stage (triage, code, review, fix, retro, prioritize), verify the inlined job in `reusable-dispatch.yml` matches the standalone `reusable-{stage}.yml`:
- Same permissions
- Same concurrency group pattern (agent-scoped)
- Same steps (checkout, prepare-workspace, mint-token, setup-gcp, setup-agent-env, run agent)
- Same stage-specific logic (fix: fork check, eligibility, review body; code: validation, bot identity; review: prior
review; prioritize: no mint, no checkout)

Key difference allowed: inlined jobs use `prepare-workspace` composite action while standalone files have inline bash. The behavior must be equivalent.

### 4. Verify scaffold tests pass

Run `go test ./internal/scaffold/...` — scaffold files are unchanged so tests must still pass.

### 5. Verify workflow lint

Run any workflow linting (`actionlint` or similar) on both `reusable-dispatch.yml` and the restored `reusable-{stage}.yml` files.

## Files changed

| File | Action |
|------|--------|
| `.github/workflows/reusable-dispatch.yml` | Modify (inline stage jobs) |
| `.github/workflows/reusable-{code,fix,review,triage,retro,prioritize}.yml` | Unchanged (kept for per-org) |
| `.github/actions/prepare-workspace/action.yml` | New (composite action) |
| `internal/scaffold/...` | Unchanged (per-org untouched) |

## Verification

1. `go test ./...` — full test suite passes
2. Diff each inlined stage job against its `reusable-{stage}.yml` counterpart to confirm sync
3. Manual review: per-org flow unchanged (shim → dispatch.yml → thin callers → reusable-{stage}.yml)
4. Manual review: per-repo flow works without version skew (shim → reusable-dispatch.yml with inlined stages)
Loading