Skip to content
Merged
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
436a7f8
feat(install): add --vendor for self-contained workflow and agent assets
ifireball Jun 10, 2026
0a0561b
feat(vendor): add manifest-driven cleanup and split analyze reporting
ifireball Jun 10, 2026
f19f1e3
fix: address remaining PR review nits for vendor work
ifireball Jun 10, 2026
32aaf9d
fix(binary): restore download tests dropped in prior commit
ifireball Jun 10, 2026
b5baa69
fix(vendor): batch stale cleanup and address review nits
ifireball Jun 10, 2026
8a9681e
docs(vendor): note --vendor-fullsend-binary removal without alias
ifireball Jun 10, 2026
0b50f96
fix(vendor): restore layer docs and normalize cleanup step messages
ifireball Jun 11, 2026
1f678e7
fix(vendor): single-commit upload and address Bugbot findings
ifireball Jun 11, 2026
1881e3b
fix(forge): include mode and type in DeleteFiles tree entries
ifireball Jun 11, 2026
88ecef4
test(forge): assert DeleteFiles tree entry mode and type
ifireball Jun 11, 2026
0013c75
Merge upstream/main into feat/distribution-mode-layered-standalone
ifireball Jun 11, 2026
893d1af
fix(vendor): address post-review findings from fullsend-ai-review
ifireball Jun 11, 2026
52dc9d2
Merge upstream/main into feat/distribution-mode-layered-standalone
ifireball Jun 11, 2026
b7b04f5
docs: address review feedback on ADR 0046 and testing guide
ifireball Jun 11, 2026
7d71e38
chore: re-trigger fullsend-ai-review after doc fixes
ifireball Jun 11, 2026
d330766
fix(scaffold): include check-e2e-authorization in vendored infra paths
ifireball Jun 11, 2026
99ddc9d
docs(forge): note base64 encoding in CommitFiles comment
ifireball Jun 11, 2026
fed552c
fix(install): combine vendor commit with scaffold and retry enrollmen…
ifireball Jun 11, 2026
1d3da39
fix(install): wait for workflow registration and activate repo-mainte…
ifireball Jun 11, 2026
73dea45
fix(forge): write text files as UTF-8 in CommitFiles, blob API for bi…
ifireball Jun 11, 2026
602f75b
merge: integrate main into feat/distribution-mode-layered-standalone
ifireball Jun 14, 2026
d1baca8
fix(docs): renumber vendored-install ADR to 0047 after main merge
ifireball Jun 14, 2026
47e61b6
fix: address review feedback on dispatch retry and vendor docs
ifireball Jun 14, 2026
368890e
fix(test): wrap dispatch retry stub errors as APIError
ifireball Jun 14, 2026
7ecf899
merge: integrate main into feat/distribution-mode-layered-standalone
ifireball Jun 16, 2026
5e3d932
fix(vendor): harden vendoring and address PR review findings
ifireball Jun 16, 2026
ecf5175
test(vendor): cover appendVendorTreeFiles and VendorBinary helpers
ifireball Jun 16, 2026
4c360c8
test(vendor): raise PR patch coverage above 80% threshold
ifireball Jun 16, 2026
ac64c91
test(cli): cover admin per-repo vendor dry-run path
ifireball Jun 16, 2026
387968a
test(cli): cover runDryRun, runAnalyze, and per-org setup dry-run
ifireball Jun 16, 2026
a9bd135
test(cli): cover runInstall mint check and skip path
ifireball Jun 16, 2026
2b93fff
test: raise patch coverage for install, vendor, and download paths
ifireball Jun 16, 2026
3fb219c
Signed-off-by: Barak Korren <bkorren@redhat.com>
ifireball Jun 16, 2026
22d710d
docs(adr): document trust boundary for vendored defaults gate
ifireball Jun 16, 2026
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
2 changes: 2 additions & 0 deletions .github/workflows/reusable-code.yml
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,7 @@ jobs:
uses: actions/checkout@v6

- name: Checkout upstream defaults
if: hashFiles('.defaults/action.yml') == ''
Comment thread
ifireball marked this conversation as resolved.
Resolved
uses: actions/checkout@v6
with:
repository: fullsend-ai/fullsend
Expand Down Expand Up @@ -102,6 +103,7 @@ jobs:
mkdir -p .github/scripts
cp "${SRC}/.github/scripts/setup-agent-env.sh" .github/scripts/setup-agent-env.sh


- name: Validate enrollment and extract repo metadata
id: repo-parts
uses: ./.defaults/.github/actions/validate-enrollment
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/reusable-fix.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@ jobs:
uses: actions/checkout@v6

- name: Checkout upstream defaults
if: hashFiles('.defaults/action.yml') == ''
uses: actions/checkout@v6
with:
repository: fullsend-ai/fullsend
Expand Down Expand Up @@ -114,6 +115,7 @@ jobs:
mkdir -p .github/scripts
cp "${SRC}/.github/scripts/setup-agent-env.sh" .github/scripts/setup-agent-env.sh


- name: Validate enrollment and extract repo metadata
id: repo-parts
uses: ./.defaults/.github/actions/validate-enrollment
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/reusable-prioritize.yml
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ jobs:
uses: actions/checkout@v6

- name: Checkout upstream defaults
if: hashFiles('.defaults/action.yml') == ''
uses: actions/checkout@v6
with:
repository: fullsend-ai/fullsend
Expand Down Expand Up @@ -104,6 +105,7 @@ jobs:
mkdir -p .github/scripts
cp "${SRC}/.github/scripts/setup-agent-env.sh" .github/scripts/setup-agent-env.sh


- name: Validate enrollment and extract repo metadata
id: repo-parts
uses: ./.defaults/.github/actions/validate-enrollment
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/reusable-retro.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ jobs:
uses: actions/checkout@v6

- name: Checkout upstream defaults
if: hashFiles('.defaults/action.yml') == ''
uses: actions/checkout@v6
with:
repository: fullsend-ai/fullsend
Expand Down Expand Up @@ -100,6 +101,7 @@ jobs:
mkdir -p .github/scripts
cp "${SRC}/.github/scripts/setup-agent-env.sh" .github/scripts/setup-agent-env.sh


- name: Validate enrollment and extract repo metadata
id: repo-parts
uses: ./.defaults/.github/actions/validate-enrollment
Expand Down
1 change: 1 addition & 0 deletions .github/workflows/reusable-review.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ jobs:
uses: actions/checkout@v6

- name: Checkout upstream defaults
if: hashFiles('.defaults/action.yml') == ''
uses: actions/checkout@v6
with:
repository: fullsend-ai/fullsend
Expand Down
2 changes: 2 additions & 0 deletions .github/workflows/reusable-triage.yml
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@ jobs:
uses: actions/checkout@v6

- name: Checkout upstream defaults
if: hashFiles('.defaults/action.yml') == ''
uses: actions/checkout@v6
with:
repository: fullsend-ai/fullsend
Expand Down Expand Up @@ -100,6 +101,7 @@ jobs:
mkdir -p .github/scripts
cp "${SRC}/.github/scripts/setup-agent-env.sh" .github/scripts/setup-agent-env.sh


- name: Validate enrollment and extract repo metadata
id: repo-parts
uses: ./.defaults/.github/actions/validate-enrollment
Expand Down
2 changes: 2 additions & 0 deletions .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,8 @@ repos:
- property "workflow_repository" is not defined
- -ignore
- SC2016
- -ignore
- '__REUSABLE_(WORKFLOW|DISPATCH)__'

- repo: local
hooks:
Expand Down
2 changes: 1 addition & 1 deletion action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ runs:
done
}

# Use vendored binary if present (placed by fullsend admin install --vendor-fullsend-binary).
# Use vendored binary if present (placed by fullsend admin install --vendor).
# Per-org mode stores it at bin/fullsend (in .fullsend config repo);
# per-repo mode stores it at .fullsend/bin/fullsend (in the target repo).
# GitHub Contents API does not preserve the executable bit, so check -f not -x.
Expand Down
4 changes: 3 additions & 1 deletion docs/ADRs/0035-layered-content-resolution.md
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,9 @@ they are populated at runtime from upstream.
replaced the earlier checkout at `@v0` with a checkout at a
caller-controlled ref), copies them into the main dirs (`agents/`, `skills/`,
etc.), then copies customizations on top so override files replace upstream
defaults. The workflow inspects `install_mode` to resolve the correct
defaults. When `--vendor` has committed upstream mirror content under
`.defaults/`, the sparse checkout is skipped (see
[ADR 0047](0047-vendored-installs-with-vendor-flag.md)). The workflow inspects `install_mode` to resolve the correct
customization base:

- `per-org`: reads from `customized/`
Expand Down
116 changes: 116 additions & 0 deletions docs/ADRs/0047-vendored-installs-with-vendor-flag.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
---
title: "47. Vendored installs with --vendor"
status: Accepted
relates_to:
- testing-agents
topics:
- vendor
- layered-content
- workflows
---

# ADR 0047: Vendored installs with `--vendor`

## Status

Accepted

## Context

Layered installs (the default) fetch reusable workflows and agent content from
`fullsend-ai/fullsend@v0` at runtime via sparse checkout. That keeps config repos
small and picks up upstream fixes automatically.

Some workflows need to run unreleased fullsend changes (forks, local workflow
edits, pre-release CI) without publishing tags. A single install flag should
vendor binary + workflow/agent assets at install time; runtime should detect
vendored files without `config.yaml` distribution settings.
Comment thread
ifireball marked this conversation as resolved.
Resolved

## Decision

### Install-time: `--vendor`

`fullsend admin install`, `fullsend github setup`, and
`fullsend github sync-scaffold` accept:

| Flag | Purpose |
|------|---------|
| `--vendor` | Vendor linux/amd64 binary, reusable workflows, composite actions, and agent content |
| `--fullsend-source <dir>` | Explicit fullsend checkout for content walks and binary cross-compile |
| `--fullsend-binary <path>` | Explicit Linux ELF; skips cross-compile (requires `--vendor`) |

Source resolution (shared by binary and content) in `internal/binary`:

1. `--fullsend-source` (validated checkout: `go.mod`, `cmd/fullsend/`)
2. `ModuleRoot()` when CWD is inside a checkout
3. GitHub source fetch at CLI version (released CLI only)

Without `--vendor`, install removes stale vendored binary and content paths and
renders thin callers with upstream `uses: fullsend-ai/fullsend/.../reusable-*.yml@v0`.

### Vendor manifest

`--vendor` writes `vendor-manifest.yaml` listing every vendored path plus
`binary_path`:

| Install mode | Manifest path |
|--------------|---------------|
| Per-org (`.fullsend` config repo) | `vendor-manifest.yaml` |
| Per-repo | `.fullsend/vendor-manifest.yaml` |

The manifest is committed in the same batch as vendored content. Cleanup when
`--vendor` is off reads the manifest from the target repo (via forge API) and
deletes listed paths — no local fullsend checkout required. Legacy installs
without a manifest fall back to embed-derived path enumeration.

### Analyze behavior

Scaffold and vendored assets are reported separately:

- **Workflows layer** — always checks embed-derived managed paths
(`ManagedPaths(false)`): thin callers, shim, `customized/` gitkeeps, and
`CODEOWNERS`. Vendored marker presence does not expand this list.
- **Vendor layer** — reports vendored binary/marker presence, manifest
alignment (missing paths, legacy installs without manifest), and optional
source alignment when `--fullsend-source` is passed to `fullsend admin analyze`
(or when the CLI version can resolve a source tree).

Vendored misalignment surfaces under the **vendor** layer, not workflows.

### Runtime: file-presence detection

Reusable workflows detect vendored installs before sparse checkout:

- **All modes:** `.defaults/action.yml` in the checked-out repo (committed by `--vendor`, or populated by sparse checkout at runtime)

When present, upstream sparse checkout is skipped. Infra is referenced from
`.defaults/` (`uses: ./.defaults/.github/actions/...`, `uses: ./.defaults/`).
Layered agent content is copied from `.defaults/internal/scaffold/fullsend-repo/`
onto the workspace root at job start (inline prepare step).

Thin caller `uses:` paths are rendered at install/sync time (local `./...` when
`--vendor`, upstream `@v0` when layered).

### What this PR removes

These existed on earlier iterations of the distribution-mode branch and are
dropped in favor of `--vendor` plus runtime marker detection:

- `distribution.mode` / `distribution.upstream.ref` in org and per-repo config
Comment thread
ifireball marked this conversation as resolved.
Resolved
- `--distribution-mode`, `--upstream-ref` CLI flags
- `distribution_mode` workflow input
- `upstreamembed.go` (content read from resolved source tree instead)

## Consequences

- **Positive:** One flag, no config block, runtime auto-detect; dev/CI can test unreleased workflow changes.
- **Negative:** Deleting vendored files without re-install leaves broken local `uses:` paths until sync-scaffold or re-install.
- **Neutral:** Default layered behavior unchanged for installs without `--vendor`.

## References

- [Installation guide](../reference/installation.md)
- [Testing workflows](../guides/dev/testing-workflows.md)
- ADR 0031 (reusable workflows for distribution)
- ADR 0033 (per-repo installation mode)
- ADR 0035 (layered content resolution)
10 changes: 6 additions & 4 deletions docs/architecture.md
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ Infrastructure platform choice and configuration are specified in the adopting o
- Shim workflow security: `pull_request_target` prevents PR authors from modifying the shim workflow. No long-lived secrets flow through the shim — OIDC tokens are issued by the GitHub runtime and scoped to the workflow run ([ADR 0009](ADRs/0009-pull-request-target-in-shim-workflows.md)).
- Repo maintenance: a workflow in `.fullsend` (`.github/workflows/repo-maintenance.yml`) reconciles enrollment shims in target repos when `config.yaml` changes or on manual dispatch. The CLI's `EnrollmentLayer.Install()` dispatches this workflow via `workflow_dispatch` and monitors it for completion, then reports any enrollment PRs created in target repos.
- Installer scaffold: the `WorkflowsLayer` deploys content from an embedded scaffold (`internal/scaffold/`), keeping deployable files as real files under version control rather than Go string constants.
- 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)).
- 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)).

**Open questions:**
Expand Down Expand Up @@ -345,9 +345,11 @@ See [ADR 0003](ADRs/0003-org-config-repo-convention.md) for the config repo conv
**Decided:**

- Layered content resolution: upstream defaults (agents, skills, schemas,
harness, policies, scripts) are provided at runtime via a full checkout of
`fullsend-ai/fullsend` at the ref passed via `fullsend_ai_ref`. The scaffold
installs only org-specific files and a `customized/` directory for org
harness, policies, scripts) are provided at runtime via sparse checkout of
`fullsend-ai/fullsend@v0`, or from vendored files when `--vendor` was used at
install (detected via `.defaults/action.yml` — see
[ADR 0047](ADRs/0047-vendored-installs-with-vendor-flag.md)). The
scaffold installs only org-specific files and a `customized/` directory for org
overrides. Org files in `customized/` overwrite upstream defaults at runtime
([ADR 0035](ADRs/0035-layered-content-resolution.md)).

Expand Down
10 changes: 6 additions & 4 deletions docs/guides/dev/cli-internals.md
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ Install: process 1→7 (forward)
Uninstall: process 7→1 (reverse)
```

Per-repo mode does not use the layer stack — it runs the same phases inline in `runPerRepoInstall()` and `runGitHubSetupPerRepo()` since there's no need for composable uninstall ordering with a single repo. Binary vendoring (when `--vendor-fullsend-binary` is set) and stale binary cleanup are handled inline or via shared helpers; per-org mode uses `VendorBinaryLayer`.
Per-repo mode does not use the layer stack — it runs the same phases inline in `runPerRepoInstall()` and `runGitHubSetupPerRepo()` since there's no need for composable uninstall ordering with a single repo. Vendoring (when `--vendor` is set) and stale asset cleanup are handled inline or via shared helpers; per-org mode uses `VendorBinaryLayer`.

### Binary acquisition (`internal/binary`)

Expand All @@ -258,7 +258,7 @@ Linux binary resolution for `fullsend run` and vendoring lives in `internal/bina
| `ResolveForVendor` | Cross-compile → matching release (released CLI only) → fail (no latest) |
| `ResolveExplicit` | Validate linux/{arch} ELF for `--fullsend-binary` |

Vendoring commit messages use title + body (upload and stale delete). `admin analyze` reports stale vendored binaries at `bin/fullsend` or `.fullsend/bin/fullsend` without install-intent flags.
Vendoring commit messages use title + body (upload and stale delete). `admin analyze` reports stale vendored assets at `bin/fullsend` or `.fullsend/bin/fullsend` without install-intent flags.

---

Expand Down Expand Up @@ -441,8 +441,10 @@ fullsend-repo/ (embedded template)
| Category | Installed? | Source | Purpose |
|----------|-----------|--------|---------|
| **Installed** | Yes | Scaffold → `.fullsend` repo | Workflows, configs, static files |
| **Layered** | No (runtime) | Upstream reusable workflows | agents/, skills/, harness/, plugins/, policies/, scripts/, schemas/, env/ |
| **Upstream-only** | No | Referenced directly | .github/actions/, .github/scripts/ |
| **Layered** | No (runtime) or yes with `--vendor` | Upstream `@v0` sparse checkout, or vendored at install | agents/, skills/, harness/, plugins/, policies/, scripts/, schemas/, env/ |
| **Upstream-only** | No (layered) or yes with `--vendor` | Referenced directly or vendored at install | .github/actions/, .github/scripts/ |

Runtime skips upstream fetch when `.defaults/action.yml` is present (vendored); layered installs sparse-checkout `fullsend-ai/fullsend@v0` into `.defaults/`.

### File Mode Tracking

Expand Down
59 changes: 43 additions & 16 deletions docs/guides/dev/testing-workflows.md
Original file line number Diff line number Diff line change
Expand Up @@ -12,17 +12,53 @@ There are independent version reference inputs that control different parts of t
| `fullsend_ai_ref` | Which ref composite actions (`action.yml`) and defaults are loaded from at runtime | Passed as a `with:` input |
| `fullsend_version` | Which fullsend CLI binary is installed | Passed as a `with:` input |

When no release exists for `fullsend_version`, `action.yml` falls back to cloning
and building from source at that ref (see the `install-method=source` path).

If `uses:`, `fullsend_ai_ref` and `fullsend_version` diverge, the workflows, agents and harnesses, and
CLI diverge, potentially causing mismatch in behavior and failures.

## Per-repo mode
## Vendored installs (recommended for PR testing)

Install or re-install with `--vendor` to copy reusable workflows, actions, agent
definitions, and the CLI binary from your local checkout into the config repo or
`.fullsend/` directory:

```bash
fullsend admin install "$ORG" \
--vendor \
--fullsend-source "$PWD" \
--skip-app-setup \
--skip-mint-check \
--mint-url "$MINT_URL" \
# ... other flags
```

After changing reusable workflows or agent content, re-run install (or
`fullsend github setup`) with `--vendor` to refresh vendored files.
`fullsend github sync-scaffold` updates thin caller templates and auto-detects
vendored vs layered mode from `.defaults/action.yml` presence.

In your repository modify the dispatch job at `.github/workflows/fullsend.yaml` to
use the ref you want to test:
Runtime skips the upstream sparse checkout when `.defaults/action.yml` is
present (vendored install) and stages content from `.defaults/` instead.

See [ADR 0047](../../ADRs/0047-vendored-installs-with-vendor-flag.md) for the
full distribution model.

## Layered installs: pin upstream ref

In layered mode (default), thin callers reference upstream reusable workflows at
`fullsend-ai/fullsend@v0`. To test a specific upstream ref without vendoring,
change the `uses:` ref and matching `with:` inputs in the thin caller workflows.

**Note**: for forks, change the `fullsend-ai/fullsend` portion to point to your fork.

### Per-repo mode

In your repository modify the dispatch job at `.github/workflows/fullsend.yaml`:

```yaml
Comment thread
ifireball marked this conversation as resolved.
Resolved
# .github/workflows/fullsend.yaml
# [...]
jobs:
dispatch:
# [...]
Expand All @@ -34,23 +70,16 @@ jobs:
# [...]
```

Then push this change and trigger a Fullsend action: `/fs-triage`, `/fs-code`, ... When the ref is
deleted from fullsend-ai/fullsend (branch deleted or commit amended), revert this back to the
desired reference.

**Note**: for forks, change the `fullsend-ai/fullsend` portion to point to your fork.

## Per-org mode
### Per-org mode

**WARNING**: this impacts all repositories, so proceed with care. You can install your test repository
using the repository install mode to avoid this problem.
**WARNING**: this impacts all repositories, so proceed with care. You can install
your test repository using per-repo mode to avoid this problem.

In your `.fullsend` repository change the references for the `reusable-<stage>.yml` you want to
test (triage in the example below):

```yaml
# .github/workflows/triage.yml
# [...]
jobs:
triage:
# [...]
Expand All @@ -65,5 +94,3 @@ jobs:
Then push this change and trigger a Fullsend action on your test repository: `/fs-triage`, `/fs-code`, ...
When the ref is deleted from fullsend-ai/fullsend (branch deleted or commit amended), revert this back
to the desired reference.

**Note**: for forks, change the `fullsend-ai/fullsend` portion to point to your fork.
Loading
Loading