Skip to content

feat(install)!: add --vendor for self-contained workflow and agent assets - #1954

Merged
ifireball merged 34 commits into
fullsend-ai:mainfrom
ifireball:feat/distribution-mode-layered-standalone
Jun 16, 2026
Merged

feat(install)!: add --vendor for self-contained workflow and agent assets#1954
ifireball merged 34 commits into
fullsend-ai:mainfrom
ifireball:feat/distribution-mode-layered-standalone

Conversation

@ifireball

@ifireball ifireball commented Jun 5, 2026

Copy link
Copy Markdown
Member

Fixes #2145
Related: #2152 (action.yml release-path hardening — out of scope for this PR)

Summary

  • Add --vendor to install a self-contained fullsend stack: linux/amd64 binary, reusable workflows, composite actions, and agent content (agents/, skills/, harness/, etc.).
  • Default layered installs are unchanged: thin callers use fullsend-ai/fullsend/.../reusable-*.yml@v0 and reusables sparse-checkout upstream at runtime.
  • Runtime detection uses vendored file presence (.defaults/action.yml) — no distribution block in config.yaml.
  • Write vendor-manifest.yaml on --vendor installs for source-free cleanup and split analyze reporting (workflows layer stays embed-only; vendor layer reports manifest/source alignment).

Breaking change: --vendor-fullsend-binary removed

The old --vendor-fullsend-binary flag (upload binary to .fullsend/bin/fullsend only) is removed with no deprecation alias or silent fallback.

Old New
--vendor-fullsend-binary --vendor (binary + workflow/agent content)
(none) --fullsend-binary <path> — explicit Linux ELF when vendoring (requires --vendor)
(none) --fullsend-source <dir> — pin source tree for content and cross-compile

Known impact: we are not aware of any production usage outside this repo. The only in-repo caller was e2e, updated here to pass --vendor (and the triage smoke path no longer hand-uploads the binary via a separate helper). Scripts or docs referencing --vendor-fullsend-binary must switch to --vendor.

Note: fullsend run --fullsend-binary is unchanged — that flag belongs to the run command, not install vendoring.

How it works

Install Thin caller uses: Runtime content
Default (layered) fullsend-ai/fullsend/.../reusable-*.yml@v0 Sparse checkout fullsend-ai/fullsend@v0 into .defaults/
--vendor Local ./.github/workflows/reusable-*.yml (or .fullsend/... per-repo) Staged from .defaults/ when marker file present

Source resolution (binary cross-compile and content walks):

  1. --fullsend-source <dir> — explicit checkout (go.mod, cmd/fullsend/)
  2. ModuleRoot() when run from a checkout
  3. GitHub source fetch at CLI version (released CLI only)

Flags

Flag Purpose
--vendor Vendor binary + workflow/agent content
--fullsend-source Pin source tree for content and cross-compile
--fullsend-binary Explicit Linux ELF override (requires --vendor)

Without --vendor, re-install removes stale vendored assets (manifest-driven when present, embed fallback for legacy installs).

Test plan

  • go test ./... -short
  • make lint-all
  • E2E admin install with --vendor (binary + vendored content)
  • Layered install regression (no vendored files, upstream checkout)
  • fullsend github sync-scaffold detects vendored marker for local uses: render

@fullsend-ai-review

fullsend-ai-review Bot commented Jun 5, 2026

Copy link
Copy Markdown

Review

Findings

Medium

  • [stale-reference] docs/architecture.md:41 — Layer stack description still references vendor-binary layer name, which was renamed to vendor in this PR (VendorBinaryLayer.Name() now returns "vendor"). This was flagged in the prior review and remains unfixed.
    Remediation: Update line 41 to read vendor instead of vendor-binary in the layer stack listing.

  • [protected-path] .github/workflows/reusable-code.yml, .github/workflows/reusable-fix.yml, .github/workflows/reusable-prioritize.yml, .github/workflows/reusable-retro.yml, .github/workflows/reusable-review.yml, .github/workflows/reusable-triage.yml, .pre-commit-config.yaml — 7 protected files modified. The PR links to issue feat(install): --vendor self-contained workflow and agent assets #2145 and the changes are consistent with the authorized scope (vendored install support). Human approval is always required for protected-path changes, regardless of context.

Low

  • [stale-reference] docs/ADRs/0006-ordered-layer-model.md:28 — Pre-existing stale reference to vendor-binary layer name (file not modified by this PR). The layer is renamed to vendor in production code.

  • [stale-reference] docs/plans/vertex-inference-provisioning.md:235 — Pre-existing stale reference to vendor-binary in layer stack order (file not modified by this PR).

  • [fail-open] .github/workflows/reusable-code.yml:59 — The hashFiles('.defaults/action.yml', '.fullsend/.defaults/action.yml') == '' condition gates the upstream sparse checkout step. Config repo write access is already a trust boundary (the checkout step at line 55–56 checks out the config repo first), so this does not create a new attack surface. Consider documenting that .fullsend repo write access is a trust boundary equivalent to workflow authoring.

  • [naming-consistency] internal/layers/vendorbinary.go — Type VendorBinaryLayer retains Binary in its Go type name despite Name() now returning "vendor" and the layer managing the full vendored stack. Acceptable technical debt for this PR given the breadth of rename impact.

  • [code-organization] internal/cli/vendor.go:244prepareVendorFiles returns a tuple (vendorFileBundle, func(), error) instead of embedding a Cleanup field in the struct. Both patterns exist in the codebase; consider consolidating in a follow-up.

Previous run

Review

Outcome: Comment — medium-severity findings unchanged since prior review; no new issues from test-only delta.

Summary

Re-review of PR #1954 (vendor flag expansion). Only test files changed since the prior review (ecf5175). The test changes are well-aligned with production code — no assertion weakening, adequate coverage additions, and proper test helpers. The challenger confirmed 4 false-positive doc-reference findings (already fixed in PR diff) and downgraded the breaking-CLI finding to info (deprecation alias already implemented). All prior medium findings are on unchanged code and anchored at their prior severity.

Findings

Medium (unchanged from prior review, severity-anchored)

# Category File Description
1 stale-reference docs/architecture.md:41 Stale reference to renamed layer vendor-binary. The PR modifies other parts of this file but line 41's layer stack still reads vendor-binary.
2 error-messages internal/binary/vendorroot.go New error message uses em-dash character (U+2014) in "is a dev build — use --fullsend-source". While em-dashes exist elsewhere in this package, this is a new file — consider standardizing on ASCII.
3 protected-path .github/workflows/reusable-*.yml, .pre-commit-config.yaml 7 protected files modified. Changes add hashFiles conditional for vendored installs and update actionlint config. Authorized by issue #2145.

Low (unchanged from prior review, severity-anchored)

# Category File Description
4 stale-reference docs/ADRs/0006-ordered-layer-model.md:28 Pre-existing stale reference to vendor-binary layer name (file not modified by this PR).
5 stale-reference docs/plans/vertex-inference-provisioning.md:235 Pre-existing stale reference to vendor-binary in layer stack order (file not modified by this PR).
6 error-messages internal/binary/acquire.go:674 Pre-existing em-dash preserved when adding --fullsend-source to the error message.
7 code-organization internal/cli/vendor.go prepareVendorFiles returns (vendorFileBundle, func(), error) tuple; consider returning a struct with a Cleanup field for consistency with VendorRoot.
8 naming-coherence internal/layers/vendorbinary.go Type name VendorBinaryLayer retained despite Name() returning "vendor". Acknowledged in diff comments — intentional.

Info

  • Breaking CLI change mitigated: The --vendor-fullsend-binary--vendor rename includes a hidden deprecated alias with a stderr warning, providing a proper migration path.
  • Security audit: No new security concerns. Prior controls (tar extraction protections, manifest validation, hashFiles gating, blob API routing) remain adequate on unchanged code.
  • Test delta: All test changes since prior review are aligned with production code. No assertion weakening detected.
  • Authorization: PR properly authorized by issue feat(install): --vendor self-contained workflow and agent assets #2145. Scope matches.

Challenger Results

4 findings removed as false positives (doc references already updated by PR diff). 1 finding downgraded from high to info (breaking CLI change has deprecation alias). 1 finding downgraded from low to info (VendorBinaryLayer type name still accurate).

Previous run

Prior review at SHA ecf5175 — see PR comment history.

Previous run (2)

Review

Outcome: Comment — medium-severity findings worth noting but none blocking.

Summary

This PR replaces --vendor-fullsend-binary with a broader --vendor flag that vendors the full workflow/agent asset stack. The implementation is well-structured with good security controls (path traversal protection, size limits, manifest validation). ADR 0047 properly documents the new model. No critical or high-severity issues found.

Findings

Medium

# Category File Description
1 stale-reference docs/architecture.md:41 Stale reference to renamed layer vendor-binary. The PR renames the layer to vendor (Name() returns "vendor") and modifies other parts of this file, but line 41's layer stack ordering still reads vendor-binary.
2 error-messages internal/binary/vendorroot.go New error message uses em-dash character (U+2014 ) in "is a dev build — use --fullsend-source". While em-dashes exist elsewhere in this package (pre-existing pattern), this is a new file — consider standardizing on ASCII.
3 protected-path .github/workflows/reusable-*.yml, .pre-commit-config.yaml 7 protected files modified. Changes add hashFiles('.defaults/action.yml', '.fullsend/.defaults/action.yml') == '' conditional to skip sparse checkout when vendored, and update actionlint config. Authorized by issue #2145.

Low

# Category File Description
4 stale-reference docs/ADRs/0006-ordered-layer-model.md:28 Pre-existing stale reference to vendor-binary (file not modified by this PR).
5 stale-reference docs/plans/vertex-inference-provisioning.md:235 Pre-existing stale reference to vendor-binary (file not modified by this PR).
6 error-messages internal/binary/acquire.go:674 Pre-existing em-dash preserved when adding --fullsend-source to the error message.
7 code-organization internal/cli/vendor.go prepareVendorFiles returns (vendorFileBundle, func(), error) tuple; consider returning a struct with a Cleanup field for consistency with VendorRoot.
8 naming-coherence internal/layers/vendorbinary.go Type name VendorBinaryLayer retained despite Name() returning "vendor". Acknowledged in diff comments — intentional.

Info

  • Security audit: Path traversal protections in extractSourceTree and isSafeVendoredRepoPath are adequate. hashFiles workflow condition is safe. Template rendering uses fixed placeholders with no user input. No secrets exposure.
  • Behavioral change: CommitFiles now uses utf8.Valid to route binary content through the Git Blob API with base64 encoding — correct for vendoring ELF binaries.
  • Authorization: PR properly authorized by issue feat(install): --vendor self-contained workflow and agent assets #2145. Scope matches.

Security Review

No security vulnerabilities found. Key controls verified:

  • Tar extraction (extractSourceTree): Rejects .. paths, absolute paths, validates within destination, enforces size limits, ignores symlinks
  • Manifest validation (isSafeVendoredRepoPath): Allowlist-based path validation with prefix restrictions
  • Workflow conditions: hashFiles reads from workspace after config-repo checkout; cannot be influenced by PR authors (runs on pull_request_target)
  • Binary upload: utf8.Valid check correctly routes binary content to blob API
Previous run

Prior review at SHA 7ecf899 — see PR comment history.

Previous run (3)

Review

Findings

Medium

  • [protected-path] .github/workflows/reusable-*.yml, .pre-commit-config.yaml — This PR modifies 7 protected files: .github/workflows/reusable-code.yml, .github/workflows/reusable-fix.yml, .github/workflows/reusable-prioritize.yml, .github/workflows/reusable-retro.yml, .github/workflows/reusable-review.yml, .github/workflows/reusable-triage.yml, and .pre-commit-config.yaml. These are governance/infrastructure files that require human approval regardless of justification. The changes add hashFiles('.defaults/action.yml') gating for vendored installs and a shellcheck ignore for render placeholders, consistent with the linked issue feat(install): --vendor self-contained workflow and agent assets #2145.

  • [stale-reference] docs/ADRs/0006-ordered-layer-model.md:28 — Stale reference to renamed layer vendor-binary. The layer's Name() method now returns vendor in this PR, but ADR 0006 still references the old name in the stack order: "config-repo → workflows → harness-wrappers → vendor-binary → secrets → inference → dispatch-token → enrollment". This is a minor annotation update permitted by ADR immutability rules.
    Remediation: Update the stack order in ADR 0006 to use vendor instead of vendor-binary.

  • [stale-reference] docs/architecture.md:41 — Stale reference to renamed layer vendor-binary in the installation model description. This file IS modified by the diff (lines 43, 345–350) but line 41 was not updated: "Current stack: config-repo → workflows → harness-wrappers → vendor-binary → secrets → inference → dispatch → enrollment".
    Remediation: Update the stack order string on line 41 to use vendor instead of vendor-binary.

Low

  • [stale-reference] docs/plans/vertex-inference-provisioning.md:235 — Stale reference to renamed layer vendor-binary in the layer stack ordering. This file is not in the diff but references the old layer name.

  • [naming-coherence] internal/layers/vendorbinary.goVendorBinaryLayer type name retained despite scope expansion to the full stack (binary + workflows + actions + agent content). The author explicitly acknowledged this in a code comment. The Name() method now returns vendor, creating a disconnect between Go type name and runtime identity. Follow-up rename recommended.

  • [logic-error] internal/cli/vendor.go — When running a dev CLI outside a checkout without --fullsend-source, the source tree is fetched at the --version value. If the running binary's built-in version doesn't match the fetched source, the manifest records a dev version while the binary may be cross-compiled from a different source. Edge case mitigated by the -vendored build stamp.

  • [logic-error] internal/scaffold/vendormanifest.goPaths in the vendor manifest does not include the binary or manifest paths — these are tracked separately via BinaryPath and added in CleanupPaths. Correct design but subtle: ComparePathPresence during analyze won't flag a missing binary via Paths, only via the separate BinaryPath check.

  • [test-coverage] internal/layers/vendorbinary_test.go — Tests cover the combinedWithScaffold skip path and manifest alignment analysis, but do not exercise the reportSourceAlignment code path with an actual source tree. Source alignment reporting is untested at the unit level.

  • [behavioral-change] internal/forge/github/github.goCommitFiles now uses utf8.Valid to decide between inline UTF-8 content and base64 blob upload. Correctly handles binary content (ELF binaries) via the blob API. Current callers only write text, so this is a defensive improvement.

Info

  • [dead-code] internal/layers/vendor.go:23VendorBinary and VendorCommitMessage become dead code after this PR. Their only production caller in internal/cli/vendor.go is removed by the diff. Test callers in vendor_test.go remain.
Previous run (4)

Review

Findings

Medium

  • [protected-path] .github/workflows/reusable-*.yml, .pre-commit-config.yaml — This PR modifies 7 protected files: .github/workflows/reusable-code.yml, .github/workflows/reusable-fix.yml, .github/workflows/reusable-prioritize.yml, .github/workflows/reusable-retro.yml, .github/workflows/reusable-review.yml, .github/workflows/reusable-triage.yml, and .pre-commit-config.yaml. These are governance/infrastructure files that require human approval regardless of justification. The changes add the vendor_manifest input and update the fullsend binary source logic for vendored installs, consistent with the linked issue feat(install): --vendor self-contained workflow and agent assets #2145.

  • [interface-breaking-change] internal/forge/forge.go — Two new methods added to the forge.Client interface: DeleteFiles and GetWorkflow. Any out-of-tree implementations of forge.Client will fail to compile until they implement these methods. The forge.Client interface is documented as internal, but verify no external consumers exist before merging.

Low

  • [error-handling] internal/layers/workflows.goactivateRepoMaintenance silently discards errors from CreateOrUpdateFile calls when writing maintenance workflow files. If the forge call fails, the function returns nil and the caller has no signal that maintenance workflows were not installed. Consider propagating or logging these errors.

  • [behavioral-change] internal/forge/github/github.goCreateOrUpdateFile switches from base64 blob API to UTF-8 content in the contents API (content field instead of encoding: base64). This works for text files but would silently corrupt binary content. Current callers only write YAML/text, but a future caller passing binary data would hit this silently.

  • [logic-error] internal/scaffold/vendormanifest.goBuildVendorManifest hardcodes the scaffold source path as internal/scaffold/fullsend-repo/ but render.go resolves the scaffold root dynamically via scaffoldFS. If the scaffold directory is ever relocated or the embed path changes, the manifest will silently produce incorrect relative paths.

  • [logic-error] internal/cli/vendor.go — The --version flag defaults to the build-time version, but the vendored binary is the currently-running binary (copied from os.Executable()). If a user specifies a different --version, the manifest records that version while the binary remains the build-time version, creating a version mismatch in the vendored output.

  • [consumer-completeness] internal/layers/enrollment.go — The new errors.As-based retry logic in applyDispatchWorkflow correctly unwraps *forge.ConflictError, but the retry is a single attempt with no backoff. Under contention (multiple PRs triggering enrollment simultaneously), a single retry may not be sufficient.

  • [error-handling] internal/layers/workflows.goactivateRepoWorkflows depends on .github/config.yaml existing (created by activateRepoMaintenance) but does not verify its presence. If maintenance activation is skipped or fails silently (see error-handling finding above), workflow activation may operate against stale or missing config.

  • [naming-coherence] internal/layers/vendorbinary.go — The type is named VendorBinaryLayer but it now handles vendoring the full stack (workflows, scaffold, config), not just the binary. Consider renaming to VendorLayer or VendorStackLayer to match the expanded scope.

  • [scope-documentation] docs/problems/testing-workflows.md — References to --vendor-fullsend-binary should be updated to --vendor to match the CLI flag rename in this PR.

  • [test-coverage] internal/layers/vendorbinary_test.go — Tests cover the binary-copy path but do not exercise the new scaffold rendering, manifest generation, or workflow vendoring paths added in this PR. Consider adding test cases for BuildVendorManifest and CollectVendorContent.

Previous run (5)

Review

Findings

Critical

  • [adr-number-collision] docs/ADRs/0046-vendored-installs-with-vendor-flag.md — ADR number collision: this PR adds 0046-vendored-installs-with-vendor-flag.md, but 0046-host-side-api-server-design.md already exists on main. Two ADRs with the same number violates the numbering convention and will cause confusion. Must be resolved before merge.
    Remediation: Renumber to the next available ADR number (0047+). Update all cross-references in docs/ADRs/0035-layered-content-resolution.md, docs/architecture.md, and docs/guides/dev/testing-workflows.md.

Medium

  • [protected-path] .github/workflows/, .pre-commit-config.yaml — This PR modifies 7 protected files: .github/workflows/reusable-code.yml, .github/workflows/reusable-fix.yml, .github/workflows/reusable-prioritize.yml, .github/workflows/reusable-retro.yml, .github/workflows/reusable-review.yml, .github/workflows/reusable-triage.yml, and .pre-commit-config.yaml. Issue feat(install): --vendor self-contained workflow and agent assets #2145 provides authorization for these changes (vendor install mode requires hashFiles('.defaults/action.yml') gating in reusable workflows, and .pre-commit-config.yaml adds a shellcheck ignore for render placeholders). Human approval is always required for protected-path changes, regardless of context.

  • [interface-breaking-change] internal/forge/forge.go:130 — Two new methods added to forge.Client interface: DeleteFiles() and GetWorkflow(). Both existing implementations (github.go and fake.go) are updated in this PR. Future forge backends (GitLab, Forgejo) will need to implement these methods. The package is internal/, so no external breakage.

  • [logic-error] internal/layers/enrollment.go:191isWorkflowDispatchNotReady uses fragile string matching (strings.Contains(msg, "422") && strings.Contains(msg, "workflow_dispatch")) against error messages. If the GitHub API error format changes, this guard silently fails and the caller treats it as a hard failure instead of retrying. Has test coverage but depends on error message text stability.
    Remediation: Match on the APIError type and status code 422 directly using errors.As, then check for the workflow_dispatch substring in APIError.Message only.

  • [error-handling] internal/layers/workflows.go:108 — When activateRepoMaintenance fails, the error is only logged as StepWarn and Install returns nil (success). If workflow activation fails due to permissions or network error, enrollment never fires but install reports success. The user may not notice the warning.
    Remediation: Surface a distinct message telling the user to manually dispatch repo-maintenance.yml, or return the error.

  • [behavioral-change] internal/forge/github/github.go:602CommitFiles implementation changed to handle binary files via Git Blob API instead of inline content. Method signature unchanged but behavior differs for non-UTF-8 content. Binary files now make additional API calls (blob creation) which may affect rate limiting.

Low

  • [logic-error] internal/cli/vendor.go:100prepareVendorFiles resolves vendor root and binary independently. When ResolveForVendorFromRoot falls back to a release binary download, the binary version and content tree version could diverge. Edge case (only when cross-compilation fails).

  • [stale-identifier-reference] docs/guides/dev/cli-internals.md:261 — Documentation uses "stale vendored binaries" instead of "stale vendored assets". The PR updates the reference at line 249 but missed this occurrence. (Severity anchored from prior review.)
    Remediation: Change "stale vendored binaries" to "stale vendored assets" on line 261.

  • [adr-cross-reference-completeness] docs/ADRs/0046-vendored-installs-with-vendor-flag.md:269 — New ADR references ADRs 0031 and 0033 but not ADR 0035 (layered-content-resolution), even though ADR 0035 is updated to reference this new ADR.
    Remediation: Add ADR 0035 to the References section.

  • [GHA-workflow-command-injection] .github/workflows/reusable-code.yml:77install_mode workflow input interpolated into ::error:: workflow command without sanitizing :: sequences. Risk mitigated: input is workflow_call populated only by trusted thin-caller workflows with hardcoded values. Same pattern in all 6 reusable workflows. Defense-in-depth improvement.

  • [layer-constructor-signature] internal/layers/workflows.go:49NewWorkflowsLayer constructor grew to 6 parameters (added vendor bool). The PR already uses the setter pattern for VendorCollect (WithVendorCollect), creating inconsistency.

  • [parameter-naming] internal/cli/admin.go:1840buildLayerStack has proliferating vendor-prefixed parameters (vendor, vendorFn, vendorCollect, analyzeFullsendSource). Consider grouping into a struct.

  • [function-signature-consistency] internal/cli/admin.go:1289runDryRun grew to 14 parameters. Codebase convention uses config structs for functions with many parameters.

  • [consumer-completeness] internal/layers/enrollment.go:153dispatchRepoMaintenanceWithRetry uses linear-then-capped backoff over 24 attempts (~5 min). Reasonable timeout for workflow registration latency. (Severity anchored from prior review.)

  • [error-handling] internal/layers/workflows.go:96activateRepoMaintenance reads config.yaml and re-writes it unchanged via CreateOrUpdateFile to trigger workflow registration. Deliberate no-op commit. (Severity anchored from prior review.)

  • [error-message-consistency] internal/cli/vendor.go:23 — Error messages use --fullsend-binary requires --vendor phrasing instead of cannot use X without Y.

  • [helper-function-organization] internal/cli/vendor.go:108prepareVendorFiles returns anonymous cleanup closure, inconsistent with structured types using explicit Cleanup fields elsewhere (e.g., binary.VendorRoot).

  • [naming-clarity] internal/cli/vendor.go:73 — Type name vendorFileBundle doesn't follow the descriptive naming pattern used for similar types (VendorOpts, CrossCompileOpts).

Previous run (6)

Review

Findings

Medium

  • [protected-path] .github/workflows/, .pre-commit-config.yaml — This PR modifies 7 protected files: .github/workflows/reusable-code.yml, .github/workflows/reusable-fix.yml, .github/workflows/reusable-prioritize.yml, .github/workflows/reusable-retro.yml, .github/workflows/reusable-review.yml, .github/workflows/reusable-triage.yml, and .pre-commit-config.yaml. Issue feat(install): --vendor self-contained workflow and agent assets #2145 provides authorization for these changes (vendor install mode requires hashFiles('.defaults/action.yml') gating in reusable workflows, and .pre-commit-config.yaml adds a shellcheck ignore for render placeholders). Human approval is always required for protected-path changes, regardless of context.

  • [logic-error] internal/layers/enrollment.go:691isWorkflowDispatchNotReady uses fragile string matching (strings.Contains(msg, "422") && strings.Contains(msg, "workflow_dispatch")) against error messages. This depends on both the GitHub API error message text and the APIError.Error() formatting containing these substrings. If GitHub changes the error wording, the retry logic silently stops retrying and returns the error immediately. (Severity anchored from prior review; code unchanged.)
    Remediation: Match on the APIError type and status code 422 directly using errors.As, then check for the workflow_dispatch substring in APIError.Message only.

Low

  • [consumer-completeness] internal/layers/enrollment.go:165dispatchRepoMaintenanceWithRetry uses linear-then-capped backoff over 24 attempts (~5 min), combined with awaitWorkflowRegistration (5 min) and awaitWorkflowRun (3 min), totaling up to ~13 minutes. When dispatch fails but a push-triggered run succeeds, the dispatch failure is logged via StepWarn and the operation continues. This is intentional fallback behavior. (Severity anchored from prior review.)

  • [error-handling] internal/layers/workflows.go:96activateRepoMaintenance reads config.yaml and re-writes it unchanged to trigger GitHub workflow registration. The optimistic concurrency via SHA is standard, and the caller (StepWarn) makes failures non-fatal. (Severity anchored from prior review.)

  • [stale-identifier-reference] docs/guides/dev/cli-internals.md:250 — Documentation uses "stale vendored binaries" instead of "stale vendored assets". The PR updates similar text elsewhere (e.g., github-setup.md) but missed this occurrence. (Severity anchored from prior review.)
    Remediation: Change "stale vendored binaries" to "stale vendored assets" on line 250.

Previous run (7)

Review

Findings

Medium

  • [protected-path] .github/workflows/, .pre-commit-config.yaml — This PR modifies 7 protected files: .github/workflows/reusable-code.yml, .github/workflows/reusable-fix.yml, .github/workflows/reusable-prioritize.yml, .github/workflows/reusable-retro.yml, .github/workflows/reusable-review.yml, .github/workflows/reusable-triage.yml, and .pre-commit-config.yaml. Issue feat(install): --vendor self-contained workflow and agent assets #2145 provides authorization for these changes (vendor install mode requires hashFiles('.defaults/action.yml') gating in reusable workflows, and .pre-commit-config.yaml adds a shellcheck ignore for render placeholders). Human approval is always required for protected-path changes, regardless of context.

  • [logic-error] internal/layers/enrollment.go:691isWorkflowDispatchNotReady uses fragile string matching (strings.Contains(msg, "422") && strings.Contains(msg, "workflow_dispatch")) against error messages. This depends on both the GitHub API error message text and the APIError.Error() formatting containing these substrings. If GitHub changes the error wording, the retry logic silently stops retrying and returns the error immediately.
    Remediation: Match on the APIError type and status code 422 directly using errors.As, then check for the workflow_dispatch substring in APIError.Message only.

  • [CLI flag removal] internal/cli/admin.go--vendor-fullsend-binary removed and replaced with --vendor. The breaking change is properly signaled in the commit prefix (feat(install)!:) and documented in ADR 0046. The PR body states no known production usage outside this repo. A deprecated alias would provide a smoother migration path.
    Remediation: Consider adding a hidden deprecated alias for --vendor-fullsend-binary that maps to --vendor with a deprecation warning.

Low

  • [consumer-completeness] internal/layers/enrollment.go:165dispatchRepoMaintenanceWithRetry uses linear-then-capped backoff over 24 attempts (~5 min), combined with awaitWorkflowRegistration (5 min) and awaitWorkflowRun (3 min), totaling up to ~13 minutes. When dispatch fails but a push-triggered run succeeds, the dispatch failure is logged via StepWarn and the operation continues. This is intentional fallback behavior.

  • [error-handling] internal/layers/workflows.go:96activateRepoMaintenance reads config.yaml and re-writes it unchanged to trigger GitHub workflow registration. The optimistic concurrency via SHA is standard, and the caller (StepWarn) makes failures non-fatal.

  • [download-integrity] internal/layers/vendorbinary.go — Downloaded vendor binary has no checksum verification beyond HTTPS transport-level integrity. Pre-existing behavior. (Severity anchored from prior review.)

  • [download-integrity] internal/binary/download.go:723FetchSourceTree downloads a source tarball from GitHub without checksum verification. URL is operator-controlled (hardcoded GitHub archive URL), and the archive has size limits and path traversal protections.

  • [stale-identifier-reference] docs/guides/dev/cli-internals.md:250 — Documentation uses "stale vendored binaries" instead of "stale vendored assets". The PR updates similar text elsewhere (e.g., github-setup.md) but missed this occurrence.
    Remediation: Change "stale vendored binaries" to "stale vendored assets" on line 250.

  • [edge-case] internal/cli/vendor.go:110 — When fullsendBinary is empty and cross-compilation fails, the fallback to release download may produce a confusing error message referencing source root resolution despite having a resolved root.

  • [error-wrapping] internal/layers/vendor.go:29 — Error returned without %w wrapping for the directory validation check. Minor style inconsistency with the wrapping pattern used elsewhere in the same file.

  • [Reusable workflow detection] .github/workflows/reusable-code.yml:59 — Workflows now check for .defaults/action.yml presence to skip upstream sparse checkout. The .defaults/ directory is a fullsend-specific convention, so false-positive detection from other uses is negligible.

  • [Workflow contract expansion] .github/workflows/reusable-code.yml — The install_mode input already existed pre-PR with required: false and default: per-org. The diff only adds the hashFiles conditional. Backward compatible.

  • [Template placeholders] internal/scaffold/fullsend-repo/.github/workflows/code.yml — Scaffold templates now use __REUSABLE_WORKFLOW__ and __REUSABLE_DISPATCH__ placeholders. These are internal Go embedded resources processed at install time; rendered output contains valid workflow references.

Previous run (8)

Review

Findings

Medium

  • [protected-path] .github/workflows/, .pre-commit-config.yaml — This PR modifies 7 protected files: .github/workflows/reusable-code.yml, .github/workflows/reusable-fix.yml, .github/workflows/reusable-prioritize.yml, .github/workflows/reusable-retro.yml, .github/workflows/reusable-review.yml, .github/workflows/reusable-triage.yml, and .pre-commit-config.yaml. Issue feat(install): --vendor self-contained workflow and agent assets #2145 provides authorization for these changes (vendor install mode requires workflow template updates and linting config changes). Human approval is always required for protected-path changes, regardless of context.

Low

  • [consumer-completeness] internal/cli/vendor.go:180 — The vendor manifest consumer (cleanup and analyze subcommands) reads the manifest but does not validate that all expected fields (fullsend_binary, defaults_action, reusable_workflows) are present and non-empty before using them. A partially-written or corrupted manifest could cause nil-pointer panics or silent no-ops during cleanup.
    Remediation: Add field-presence validation after unmarshaling the vendor manifest, returning a clear error if required fields are missing.

  • [incomplete-cross-reference] internal/layers/workflows.go:89 — The hashFiles('.defaults/action.yml') runtime detection logic for vendor vs non-vendor mode in reusable workflows is not cross-referenced with the scaffold template rendering that produces these workflows. If the .defaults/action.yml path changes in the scaffold, the runtime detection silently breaks.
    Remediation: Add a comment in both locations cross-referencing the other, or extract the path as a shared constant.

  • [race-condition] internal/layers/vendorbinary.go:45 — The vendor binary download writes to a file path derived from the binary name without file locking. Concurrent invocations targeting the same binary path could produce a corrupted file. While unlikely in typical usage, the fullsend CLI could be invoked in parallel by CI systems.
    Remediation: Use a temporary file with atomic rename, or add file-level locking around the download.

  • [edge-case] internal/cli/admin.go:127 — The source resolution chain (--fullsend-sourceModuleRoot() → GitHub source fetch) does not document what happens when ModuleRoot() returns an error (e.g., not in a Go module). The fallback to GitHub source fetch may silently mask a misconfiguration.
    Remediation: Log a debug-level message when ModuleRoot() fails, clarifying that GitHub source fetch is being used as fallback.

  • [error-handling] internal/layers/vendorbinary.go:78 — Some errors in the vendor binary download flow are returned unwrapped (bare fmt.Errorf without %w), inconsistent with the wrapping pattern used elsewhere in the same file.
    Remediation: Use fmt.Errorf("...: %w", err) consistently for error wrapping.

  • [download-integrity] internal/layers/vendorbinary.go:52 — Downloaded vendor binary has no checksum verification. The binary is fetched over HTTPS (transport-level integrity) but there is no application-level integrity check against a known hash.
    Remediation: Consider adding a checksum field to the vendor manifest and verifying it after download.

  • [binary-integrity] internal/layers/vendorbinary.go:52 — No signature verification of the downloaded fullsend binary. While HTTPS provides transport security, a compromised release artifact would be installed without detection.
    Remediation: Consider adding GPG or cosign signature verification as a future enhancement.

  • [naming-consistency] internal/cli/vendor.go:35 — The --vendor flag replaces --vendor-fullsend-binary but the internal variable name vendorMode doesn't indicate that this encompasses more than just the binary (it also vendors defaults action and reusable workflows).
    Remediation: Consider renaming to selfContainedInstall or similar to reflect the full scope.

  • [stale-identifier-reference] docs/adr/0025-vendor-install-mode.md:42 — The ADR references --vendor-fullsend-binary as the original flag name being replaced, which is correct historical context. However, the "Decision" section could more clearly state the new flag name --vendor for readers who arrive at this document without PR context.
    Remediation: Add explicit mention of --vendor as the replacement flag name in the Decision section.

  • [stale-identifier-reference] docs/problems/applied/fullsend-ai/vendor-install.md:15 — References to the vendor install workflow describe the old --vendor-fullsend-binary flag. The document should be updated to reflect the new --vendor flag and the expanded scope (defaults action, reusable workflows, vendor manifest).
    Remediation: Update the vendor install problem document to describe the current --vendor flag behavior and manifest structure.

Info

  • [test-integrity] internal/layers/vendorbinary_test.go — Tests cover the happy path and basic error cases for vendor binary operations but do not test concurrent access, partial download recovery, or manifest corruption scenarios. Test coverage is adequate for the current implementation but could be strengthened.

  • [scope-alignment] internal/cli/admin.go — The PR scope aligns well with issue feat(install): --vendor self-contained workflow and agent assets #2145's authorization. The --vendor flag consolidation, vendor manifest, and scaffold template rendering are all within the authorized scope.

  • [workflow-security] .github/workflows/reusable-review.yml — The workflow changes add hashFiles('.defaults/action.yml') detection for vendor mode. This is a read-only file existence check with no security implications — the workflow does not execute arbitrary content from the detected file.

  • [template-injection] internal/scaffold/ — Scaffold templates use __REUSABLE_WORKFLOW__ and __REUSABLE_DISPATCH__ placeholder substitution. The placeholders are replaced with hardcoded repository references (not user input), so template injection is not a concern.

  • [path-traversal] internal/layers/vendorbinary.go — File paths for vendor binary operations are constructed from controlled inputs (CLI flags and known constants), not from user-supplied free-form input. No path traversal risk identified.

  • [adr-coherence] docs/adr/0025-vendor-install-mode.md — The ADR is well-structured and provides clear rationale for the vendor install mode design. The decision to use Git Trees API for atomic commits is well-justified.

Previous run (9)

Review

Prior review: d330766 (app-verified) — 1 file changed since prior review (internal/forge/github/github.go); severity anchoring applied to unchanged code.

Medium findings

[consumer-completeness] internal/scaffold/vendormanifest.go:186 — The static list vendoredDefaultsInfraPaths (4 action.yml paths) is used for embed-derived cleanup enumeration when no manifest is present. Meanwhile, isVendoredDefaultsInfra in vendorcontent.go broadly matches strings.HasPrefix(path, ".github/actions/"). If a new composite action is added under .github/actions/, CollectVendoredAssets will vendor those files and include them in the manifest, but the legacy-fallback cleanup via enumerateLegacyFlatVendoredPaths will miss them. Legacy installs (pre-manifest) transitioning to non-vendor mode will leave stale action files behind. The manifest-based cleanup path handles this correctly.
Remediation: Either generate vendoredDefaultsInfraPaths from the same isVendoredDefaultsInfra predicate by walking the embed at init time, or document that legacy installs without a manifest may leave orphaned action files.

[protected-path] .github/workflows/reusable-{code,fix,prioritize,retro,review,triage}.yml, .pre-commit-config.yaml — 7 protected files modified. Issue #2145 authorizes these changes with rationale (vendor mode requires hashFiles('.defaults/action.yml') gating in reusable workflows, and .pre-commit-config.yaml adds a shellcheck ignore for render placeholders). Human approval required via CODEOWNERS.

[cli-flag-breaking-change] internal/cli/admin.go--vendor-fullsend-binary removed and replaced with --vendor. The breaking change is properly signaled in the commit prefix (feat(install)!:) and documented in ADR 0046. The only known caller was the e2e suite (updated in this PR). Scripts or CI referencing the old flag must switch to --vendor.

Low findings

# Category File Description
1 download-integrity internal/binary/download.go:144 FetchSourceTree downloads and extracts a source tarball from GitHub without checksum or signature verification. SourceArchiveBaseURL is an exported var (for tests). HTTPS/TLS provides baseline integrity; defense-in-depth improvement. (severity anchored from prior S3)
2 scope-creep internal/forge/github/github.go:684 CommitFiles() now base64-encodes all content to support binary ELF uploads through the Trees API. Necessary for the feature but a subtle behavioral change to a core forge method — add a comment explaining the encoding choice.
3 stale-identifier-reference docs/guides/dev/cli-internals.md:233 Stack order diagram references VendorBinary layer name; the layer's Name() method now returns "vendor".
4 stale-identifier-reference docs/guides/dev/cli-internals.md:250 Documentation says "stale vendored binaries" but the vendor layer now manages binary + workflows + actions + agent content.
5 race-condition internal/forge/github/github.go:749 DeleteFiles uses the same read-HEAD-then-commit TOCTOU pattern as CommitFiles. Accepted architectural pattern. (severity anchored from prior C4)
6 edge-case internal/scaffold/vendorcontent.go:37 In per-org vendored mode, reusable-dispatch.yml is vendored with upstream @v0 stage refs unrewritten (per-repo dispatch renders correctly).
7 binary-integrity internal/cli/vendor.go:95 Vendored binary committed without checksum verification beyond ELF header validation. Pre-existing behavior. (severity anchored from prior S2)
8 naming-coherence internal/layers/vendorbinary.go VendorBinaryLayer type name retains historical "Binary" despite expanded scope. Code comment acknowledges this.
9 architectural-coherence internal/scaffold/vendorcontent.go vendoredReusableWorkflows is a hardcoded list; adding a new reusable workflow requires updating it. TestVendoredReusableWorkflowsMatchRepo guards against drift.
10 documentation-consistency docs/ADRs/0046-vendored-installs-with-vendor-flag.md ADR 0046 is missing the Date: field in frontmatter (all other ADRs include it).
11 adr-coherence docs/ADRs/0046-vendored-installs-with-vendor-flag.md "What this PR removes" section references distribution.mode / --distribution-mode from earlier branch iterations not visible in this diff.
Previous run (10)

Review

Prior review: 7d71e38 (app-verified) — 1 file changed since prior review (internal/scaffold/vendormanifest.go); severity anchoring applied to unchanged code.

Medium findings

[consumer-completeness] internal/scaffold/vendormanifest.go:186 — The static list vendoredDefaultsInfraPaths (4 action.yml paths) is used for embed-derived cleanup enumeration when no manifest is present. Meanwhile, isVendoredDefaultsInfra in vendorcontent.go broadly matches strings.HasPrefix(path, ".github/actions/"). If a new composite action is added under .github/actions/, CollectVendoredAssets will vendor those files and include them in the manifest, but the legacy-fallback cleanup via enumerateLegacyFlatVendoredPaths will miss them. Legacy installs (pre-manifest) transitioning to non-vendor mode will leave stale action files behind. The manifest-based cleanup path handles this correctly.
Remediation: Either generate vendoredDefaultsInfraPaths from the same isVendoredDefaultsInfra predicate by walking the embed at init time, or document that legacy installs without a manifest may leave orphaned action files.

[protected-path] .github/workflows/reusable-{code,fix,prioritize,retro,review,triage}.yml, .pre-commit-config.yaml — 7 protected files modified. Issue #2145 authorizes these changes with rationale (vendor mode requires hashFiles('.defaults/action.yml') gating in reusable workflows, and .pre-commit-config.yaml adds a shellcheck ignore for render placeholders). Human approval required via CODEOWNERS.

[cli-flag-breaking-change] internal/cli/admin.go--vendor-fullsend-binary removed and replaced with --vendor. The breaking change is properly signaled in the commit prefix (feat(install)!:) and documented in ADR 0046. The only known caller was the e2e suite (updated in this PR). Scripts or CI referencing the old flag must switch to --vendor.

Low findings

# Category File Description
1 download-integrity internal/binary/download.go:144 FetchSourceTree downloads and extracts a source tarball from GitHub without checksum or signature verification. SourceArchiveBaseURL is an exported var (for tests). HTTPS/TLS provides baseline integrity; defense-in-depth improvement. (severity anchored from prior S3)
2 scope-creep internal/forge/github/github.go:684 CommitFiles() now base64-encodes all content to support binary ELF uploads through the Trees API. Necessary for the feature but a subtle behavioral change to a core forge method — add a comment explaining the encoding choice.
3 stale-identifier-reference docs/guides/dev/cli-internals.md:233 Stack order diagram references VendorBinary layer name; the layer's Name() method now returns "vendor".
4 stale-identifier-reference docs/guides/dev/cli-internals.md:250 Documentation says "stale vendored binaries" but the vendor layer now manages binary + workflows + actions + agent content.
5 race-condition internal/forge/github/github.go:749 DeleteFiles uses the same read-HEAD-then-commit TOCTOU pattern as CommitFiles. Accepted architectural pattern. (severity anchored from prior C4)
6 edge-case internal/scaffold/vendorcontent.go:37 In per-org vendored mode, reusable-dispatch.yml is vendored with upstream @v0 stage refs unrewritten (per-repo dispatch renders correctly).
7 binary-integrity internal/cli/vendor.go:95 Vendored binary committed without checksum verification beyond ELF header validation. Pre-existing behavior. (severity anchored from prior S2)
8 naming-coherence internal/layers/vendorbinary.go VendorBinaryLayer type name retains historical "Binary" despite expanded scope. Code comment acknowledges this.
9 architectural-coherence internal/scaffold/vendorcontent.go vendoredReusableWorkflows is a hardcoded list; adding a new reusable workflow requires updating it. TestVendoredReusableWorkflowsMatchRepo guards against drift.
10 documentation-consistency docs/ADRs/0046-vendored-installs-with-vendor-flag.md ADR 0046 is missing the Date: field in frontmatter (all other ADRs include it).
11 adr-coherence docs/ADRs/0046-vendored-installs-with-vendor-flag.md "What this PR removes" section references distribution.mode / --distribution-mode from earlier branch iterations not visible in this diff.
Previous run (11)

Review — comment

PR: #1954 — feat(install)!: add --vendor for self-contained workflow and agent assets
Author: ifireball
Prior review: 52dc9d2 (app-verified) — 2 files changed since prior review (docs only); severity anchoring applied to unchanged code.

Medium findings

[C1] Logic error: isVendoredDefaultsInfra vs vendoredDefaultsInfraPaths mismatchinternal/scaffold/vendorcontent.go:68
The regex isVendoredDefaultsInfra matches the broad prefix .github/actions/ but vendoredDefaultsInfraPaths is a narrow static list of specific action directories. Any action directory not in the static list will be classified as infra by the regex but excluded from vendoredDefaultsInfraPaths, causing TestEnumerateVendoredPathsMatchesCollectInCheckout to fail if such paths exist in the source tree.
Remediation: Either derive vendoredDefaultsInfraPaths from a walk filtered by isVendoredDefaultsInfra, or tighten the regex to match only the listed directories.

[S3] No integrity verification on source archive downloadinternal/binary/download.go
FetchSourceTree downloads and extracts a source tarball from GitHub without checksum or signature verification. The exported SourceArchiveBaseURL allows callers to redirect the download to an arbitrary host.
Remediation: Pin a digest or signature check for the downloaded archive, or restrict SourceArchiveBaseURL to non-exported scope with a compile-time default.

Low findings

# Category File Description
C2 dead-code vendorcontent.go isVendoredDefaultsContent exclusion at L71 may silently drop files that belong in the content set
S1 output-injection internal/layers/vendorbinary.go GHA ::error:: interpolates unsanitized error strings (pre-existing, severity anchored)
S2 integrity internal/layers/vendorbinary.go Binary copied without checksum verification (pre-existing, severity anchored)
I3 naming internal/layers/vendorbinary.go VendorBinaryLayer name no longer reflects expanded scope (binary + content + workflows)
C3 resource-leak internal/binary/download.go Temp dir from FetchSourceTree not cleaned up by caller in error paths
C4 race-condition internal/forge/github/github.go DeleteFiles reads HEAD then commits — TOCTOU window on concurrent pushes
ST1 convention docs/decisions/0046-vendor-install.md ADR missing Date: field in frontmatter

Info findings

# Category File Description
S4 action-scope action.yml contents: write grants broad write scope (pre-existing, not changed in this PR)
S5 action-scope action.yml Default github-token uses github.token (pre-existing, not changed in this PR)
ST2 whitespace multiple Minor trailing whitespace in new files
C5 test-coverage internal/cli/admin_test.go Removed assertion for old --vendor-fullsend-binary flag without replacement

Prior review scope-creep findings resolved

The prior review's two HIGH scope-creep findings (referencing cmd/fullsend/main.go and internal/statuscomment/) are resolved — those files are no longer part of this PR.

Previous run

Prior review SHA: 52dc9d2 | Provenance: app-verified

Previous run (12)

Review

Findings

High

  1. [scope-creep] cmd/fullsend/main.go:22, internal/cli/root.go:38 — Signal handling (signal.NotifyContext for SIGINT/SIGTERM) and context propagation (Execute()Execute(ctx context.Context)) are not authorized by issue feat(install): --vendor self-contained workflow and agent assets #2145. The issue scope covers vendor install mode, protected workflow changes, vendor manifest, and scaffold template rendering. Signal handling is orthogonal to the vendor feature.

  2. [scope-creep] internal/statuscomment/statuscomment.go:96, internal/statuscomment/statuscomment_test.go — Cancellation behavior changed: handleCancelled() removed, cancelled runs now flow through the normal PostCompletion path (updating the start comment with a completion status instead of deleting it). This is a user-facing behavior change not authorized by issue feat(install): --vendor self-contained workflow and agent assets #2145.

Medium

  1. [protected-path] .github/workflows/reusable-{code,fix,prioritize,retro,review,triage}.yml, .pre-commit-config.yaml — 7 protected files modified. Issue feat(install): --vendor self-contained workflow and agent assets #2145 authorizes these changes with rationale (vendor mode requires hashFiles('.defaults/action.yml') gating in reusable workflows, and .pre-commit-config.yaml adds a shellcheck ignore for render placeholders). Human approval required via CODEOWNERS.

  2. [GHA output injection] action.yml:125 — Step outputs (version-url, version-asset, source-ref) are written to $GITHUB_OUTPUT using single-line echo without multiline-safe delimiters. The TAG value resolved from the GitHub API for the latest path is assigned to VERSION without tr -d '[:space:]' sanitization (unlike the direct inputs.version path). A crafted release tag name containing newlines could inject arbitrary step outputs that override install-method and redirect the install path.

    • Remediation: Apply tr -d '[:space:]' sanitization to TAG immediately after API resolution, or switch to heredoc-delimited GITHUB_OUTPUT syntax for all outputs.
  3. [integrity] action.yml:197 — The "Download release binary" step fetches a tarball over HTTPS and extracts it with tar without verifying a checksum or signature. This contrasts with the Go-side download path (internal/binary/download.go) which performs SHA256 checksum verification. The shell-side download path lacks this protection.

    • Remediation: After downloading, verify the tarball against a published SHA256 checksum file from the release (matching the Go-side DownloadRelease pattern).

Low

  1. [GHA workflow command injection] action.yml:132 — The ::error:: workflow command interpolates ${VERSION_URL} and ${HTTP_STATUS}. While tr -d '[:space:]' prevents newline injection (blocking new workflow command injection), the values are not sanitized for :: sequences within the annotation message. Exploitability is limited since ::error:: only sets annotations, not outputs or env vars.

  2. [code-duplication] action.yml:162retry_curl function duplicated in "Detect install method" and "Download release binary" steps with only error message differences. This is an inherent constraint of GitHub Actions composite actions (each run: block is a separate shell process), but worth documenting with a comment.

  3. [docs-currency] docs/guides/dev/testing-workflows.md:13 — The documentation does not explain the new source-build install method introduced in action.yml. When inputs.version does not match a release tag, the action now clones the repo and builds from source — this fallback behavior is undocumented in the testing guide.

  4. [command injection via git argument] action.yml:218SOURCE_REF is passed to git fetch and git checkout without a -- separator. A value starting with -- could be interpreted as a flag. Mitigated by the fact that the caller already has code execution access and the clone targets the same fullsend-ai/fullsend repo.

  5. [error-handling-inconsistency] internal/statuscomment/statuscomment.go:103 — When completion comments are disabled, the start comment deletion error is logged via warnf but not returned. This is intentional fail-open behavior (cosmetic cleanup should not fail the agent run), but the rationale is undocumented.

Previous run (13)

Review

Findings

Medium

  1. [protected-path] .github/workflows/reusable-{code,fix,prioritize,retro,review,triage}.yml, .pre-commit-config.yaml — 7 protected files modified. Issue feat(install): --vendor self-contained workflow and agent assets #2145 authorizes these changes with rationale (vendor mode requires hashFiles('.defaults/action.yml') gating in reusable workflows, and .pre-commit-config.yaml adds a shellcheck ignore for render placeholders). Human approval required via CODEOWNERS.

  2. [integrity] internal/binary/download.goFetchSourceTree downloads and extracts a tarball from GitHub without verifying a checksum or signature. The tag ref provides some integrity (GitHub serves the archive for the resolved commit), but a defense-in-depth checksum — e.g., comparing against a known digest shipped with the release — would guard against CDN or cache-layer corruption.

    • Remediation: Add an optional checksum parameter to FetchSourceTree; when provided, verify the SHA-256 of the downloaded tarball before extraction.

Low

  1. [cli-flag-removal] internal/cli/admin.go — Breaking change: --vendor-fullsend-binary, --distribution-mode, and --upstream-ref flags removed. The !: in the commit prefix correctly signals this, ADR 0046 documents the rationale, and the PR body states no known external consumers exist. E2E tests updated accordingly.

  2. [edge-case] internal/scaffold/vendormanifest.goParseVendorManifest validates that BinaryPath is non-empty but does not validate that Paths is non-empty. A manifest with paths: [] would pass parsing but fail to clean up vendored content paths during uninstall. Unlikely in practice since NewVendorManifest always receives paths from CollectVendoredAssets.

  3. [logic-error] internal/layers/vendorbinary.go — In Analyze, WouldFix entries say "restore vendored path X" suggesting targeted fixes, but Install does a full re-vendor. Minor semantic mismatch between the reported diagnosis and the actual fix mechanism.

  4. [docs-currency] docs/ADRs/0033-per-repo-installation-mode.md:164 — States "it sparse-checkouts upstream defaults" unconditionally, but with --vendor the sparse checkout is skipped when .defaults/action.yml is present. ADR 0035 was updated with an ADR 0046 cross-reference, so discoverability exists via the ADR chain.

  5. [unused-parameter] internal/scaffold/installfiles.go:95ManagedPaths accepts a bool first parameter that is explicitly ignored (_ bool). All call sites pass false.

  6. [data-exposure] internal/scaffold/vendormanifest.go — Vendor manifest embeds SourceRef (the --fullsend-source flag value), which is a local filesystem path committed to the config repo. Low risk (config repos are typically private), but consider recording only the basename.

  7. [tar-extraction-symlink] internal/binary/download.goextractSourceTree silently skips symlinks via continue. Defensively safe but could cause confusing failures if upstream ever ships symlinks. A debug-level log would aid troubleshooting.

Info

  1. [toctou-ref-update] internal/forge/github/github.go — Both CommitFiles and DeleteFiles fetch the latest commit SHA then create a tree/commit against it without compare-and-swap. Inherent limitation of the GitHub Git Data API; consistent with existing patterns.

  2. [api-encoding] internal/forge/github/github.goCommitFiles now sends file content as base64-encoded blobs, correctly enabling binary file (ELF) uploads through the Git Trees API.

  3. [interface-addition] internal/forge/forge.go — New DeleteFiles method added to forge.Client interface. Additive change; both FakeClient and GitHub LiveClient implement it.

@fullsend-ai-review fullsend-ai-review 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.

See the review comment for full details.

Comment thread .github/workflows/reusable-review.yml
Comment thread .github/workflows/reusable-fix.yml
Comment thread .github/workflows/reusable-retro.yml
Comment thread .github/scripts/prepare-agent-workspace.sh Outdated
Comment thread .github/workflows/reusable-code.yml Outdated
@ifireball ifireball self-assigned this Jun 5, 2026
@ifireball
ifireball force-pushed the feat/distribution-mode-layered-standalone branch from a5e80d5 to 7d1c5ec Compare June 7, 2026 05:18
@github-actions

github-actions Bot commented Jun 7, 2026

Copy link
Copy Markdown

Site preview

Preview: https://f825805a-site.fullsend-ai.workers.dev

Commit: 22d710dd7597a9b8cb141235518a33861d6a6802

@fullsend-ai-review fullsend-ai-review 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.

See the review comment for full details.

Comment thread .github/workflows/reusable-review.yml
Comment thread .github/workflows/reusable-fix.yml
Comment thread .github/workflows/reusable-retro.yml
Comment thread .github/workflows/reusable-code.yml

@fullsend-ai-review fullsend-ai-review 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.

See the review comment for full details.

Comment thread .github/workflows/reusable-code.yml Outdated
Comment thread internal/scaffold/scaffold.go Outdated
Comment thread internal/cli/github.go
Comment thread internal/cli/distribution.go Outdated
Comment thread internal/config/config.go

@fullsend-ai-review fullsend-ai-review 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.

See the review comment for full details.

@fullsend-ai-review fullsend-ai-review 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.

See the review comment for full details.

@rh-hemartin

Copy link
Copy Markdown
Member

I don't like that we are introducing yet another flag, all the switch checking, and all the passing around that it includes.

Would it be possible to rename --vendor-fullsend-binary to --vendored (or --vendored-mode) that does everything (files + binary)? This reduces the passing around and switch checking (as there are no options, just a boolean).

Then the workflows detect this or just override it (they downloads to actions/ instead of defaults/ and then paste the vendored files over it). It does not help that our actions and reusable workflows are not centralized in actions/ or something like that.

@rh-hemartin

Copy link
Copy Markdown
Member

Another thing is... I think we can solve e2e tests by changing the fullsend_ai_ref (and related stuff) to the branch being tested, and then push the binary. This is running go run main cmd/.../ --vendor-fullsend-binary and a commit to the repo to change the references.

@ifireball

Copy link
Copy Markdown
Member Author

Another thing is... I think we can solve e2e tests by changing the fullsend_ai_ref (and related stuff) to the branch being tested, and then push the binary. This is running go run main cmd/.../ --vendor-fullsend-binary and a commit to the repo to change the references.

I initially thought so too, and then I remembered that our WIF security model (from both the mint and the inference) depends on the workflow files coming from a particular repo or org - so doing this will block running tests from PRs.

The trade-off I went for is to keep allowing for running workflows from an org's .fullsend repo IFF the mint includes an org allow list and the org is in the list. This matches what we have now, and I will just need to ensure the e2e test orgs have their own mint with n allow list once I deploy a truly public mint without a list. I might want each test org to have its own mint anyway so we can test the mint code as part of the e2e tests.

@ifireball

Copy link
Copy Markdown
Member Author

I don't like that we are introducing yet another flag, all the switch checking, and all the passing around that it includes.

Would it be possible to rename --vendor-fullsend-binary to --vendored (or --vendored-mode) that does everything (files + binary)? This reduces the passing around and switch checking (as there are no options, just a boolean).

Then the workflows detect this or just override it (they downloads to actions/ instead of defaults/ and then paste the vendored files over it). It does not help that our actions and reusable workflows are not centralized in actions/ or something like that.

Actions and workflows are similar but not the same thing so I think they should remain in separate directories.
Its a good idea to use a --vendored flag so I'll go ahead and implement that.

@ifireball

Copy link
Copy Markdown
Member Author

I don't like that we are introducing yet another flag, all the switch checking, and all the passing around that it includes.
Would it be possible to rename --vendor-fullsend-binary to --vendored (or --vendored-mode) that does everything (files + binary)? This reduces the passing around and switch checking (as there are no options, just a boolean).
Then the workflows detect this or just override it (they downloads to actions/ instead of defaults/ and then paste the vendored files over it). It does not help that our actions and reusable workflows are not centralized in actions/ or something like that.

Actions and workflows are similar but not the same thing so I think they should remain in separate directories. Its a good idea to use a --vendored flag so I'll go ahead and implement that.

Actually thinking about this again, --vendored-fullsend-binary requires running out of a local checkout, while I added embeds to not require that for the standalone mode. Then again if we are not vendoring the binary, then we cannot guaranee that we are indeed running everything from a single particular commit.

@ifireball

Copy link
Copy Markdown
Member Author

I don't like that we are introducing yet another flag, all the switch checking, and all the passing around that it includes.
Would it be possible to rename --vendor-fullsend-binary to --vendored (or --vendored-mode) that does everything (files + binary)? This reduces the passing around and switch checking (as there are no options, just a boolean).
Then the workflows detect this or just override it (they downloads to actions/ instead of defaults/ and then paste the vendored files over it). It does not help that our actions and reusable workflows are not centralized in actions/ or something like that.

Actions and workflows are similar but not the same thing so I think they should remain in separate directories. Its a good idea to use a --vendored flag so I'll go ahead and implement that.

Actually thinking about this again, --vendored-fullsend-binary requires running out of a local checkout, while I added embeds to not require that for the standalone mode. Then again if we are not vendoring the binary, then we cannot guaranee that we are indeed running everything from a single particular commit.

Following a chat with @rh-hemartin we are going for:

  1. One --vendored flag to vendor both the binary and the workflows
  2. Support downloading artifacts from GitHub as a fallback to getting from a checkout
  3. Avoid embedding into the binary if we can

PR #2015 implements this for the binary vendoring. After that is merged I will come back to this PR, rebase it and change the behaviour to implement ans use the unified install flag.

@ifireball
ifireball marked this pull request as draft June 8, 2026 13:02

@waynesun09 waynesun09 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Review Squad Report — 5 agents (2× claude-coder, 1× claude-researcher, 1× gemini-code-review, 1× cursor-code-review)

v0.14.3 regression (PR #1971): All 5 agents confirmed the fix is preserved — on-disk reusable-dispatch.yml retains fully-qualified paths, regression test TestReusableDispatchUsesFullyQualifiedPaths is intact, and RenderDispatchPerRepoStagePaths is correctly isolated to the standalone per-repo code path.

Findings posted: 2 HIGH, 5 MEDIUM (inline). 4 false positives removed during verification. 5 LOW and 4 INFO findings omitted from inline comments.

The PR is architecturally sound. No blockers for per-repo install. The highest-consensus finding (3 agents) is the unanchored dispatchStageUses regex.

Comment thread internal/scaffold/render.go Outdated
Comment thread internal/scaffold/render_test.go Outdated
Comment thread internal/config/config.go Outdated
Comment thread internal/config/config.go Outdated
Comment thread internal/scaffold/installfiles.go Outdated
Comment thread upstreamembed.go Outdated
Comment thread .github/workflows/reusable-code.yml Outdated
Raise PR patch coverage above the codecov threshold and address ADR/review
wording for sync-scaffold auto-detection vs --vendor flags.

Signed-off-by: Barak Korren <bkorren@redhat.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@fullsend-ai-review

fullsend-ai-review Bot commented Jun 16, 2026

Copy link
Copy Markdown

🤖 Review · ⚠️ Cancelled · Started 7:07 PM UTC · Ended 7:19 PM UTC
Commit: 4e21a60 · View workflow run →

Exercise runInstall credential validation and the skip-mint-check
install path to raise patch coverage above the 80% gate.

Signed-off-by: Barak Korren <bkorren@redhat.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@fullsend-ai-review

fullsend-ai-review Bot commented Jun 16, 2026

Copy link
Copy Markdown

🤖 Review · ⚠️ Cancelled · Started 7:23 PM UTC · Ended 7:36 PM UTC
Commit: 4e21a60 · View workflow run →

Add runInstall and runPerRepoInstall validation tests, prepareVendorFiles
and FetchSourceTree coverage, VendorBinary error paths, and vendorcontent
scaffold tests to close the codecov/patch gap.

Signed-off-by: Barak Korren <bkorren@redhat.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@fullsend-ai-review

fullsend-ai-review Bot commented Jun 16, 2026

Copy link
Copy Markdown

🤖 Review · ⚠️ Cancelled · Started 7:39 PM UTC · Ended 7:46 PM UTC
Commit: 4e21a60 · View workflow run →

test: gofmt admin_test after coverage additions
Co-authored-by: Cursor <cursoragent@cursor.com>
@fullsend-ai-review

fullsend-ai-review Bot commented Jun 16, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 7:50 PM UTC · Completed 8:08 PM UTC
Commit: 3fb219c · View workflow run →

@fullsend-ai-review fullsend-ai-review 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.

See the review comment for full details.

Comment thread .github/workflows/reusable-code.yml
Comment thread internal/cli/vendor.go
Record that hashFiles gating upstream sparse checkout is an optimization,
not a security control — config-repo write access is equivalent to workflow
authoring.

Signed-off-by: Barak Korren <bkorren@redhat.com>
Co-authored-by: Cursor <cursoragent@cursor.com>
@fullsend-ai-review

fullsend-ai-review Bot commented Jun 16, 2026

Copy link
Copy Markdown

🤖 Finished Review · ✅ Success · Started 8:41 PM UTC · Completed 9:01 PM UTC
Commit: 22d710d · View workflow run →

@fullsend-ai-retro

fullsend-ai-retro Bot commented Jun 16, 2026

Copy link
Copy Markdown

🤖 Finished Retro · ✅ Success · Started 8:59 PM UTC · Completed 9:08 PM UTC
Commit: 22d710d · View workflow run →

@fullsend-ai-review

Copy link
Copy Markdown

Review skipped — this PR is already merged.

The /fs-review command only reviews open pull requests.

Posted by fullsend post-review check

@fullsend-ai-retro

Copy link
Copy Markdown

Retro: PR #1954feat(install)!: add --vendor for self-contained workflow and agent assets

Large human-authored feature PR by @ifireball (33 commits over 11 days, Jun 5–16). The review agent posted 15 CHANGES_REQUESTED reviews with ~65 inline findings across the PR's lifecycle.

Key observations

False positive rate on high-severity findings was significant. Both CRITICAL findings and 3 of 5 HIGH findings were false positives caused by stale diff context — the bot flagged env-var removals in reusable workflow files when those vars had actually been moved to setup-agent-env.sh. Around 20 MEDIUM/LOW findings led to genuine code improvements, so the review agent did provide value on the lower-severity tiers.

Wasted compute on the final day. 5 review dispatches were triggered in the last 2 hours before merge. 3 were cancelled after running 10–16 minutes each (~41 min total wasted compute), all reviewing the same already-superseded commit. The 5th review completed after the PR was already merged.

No new proposals filed — existing issues cover all identified improvements:

Area Relevant open issues
Stale diff false positives #1446, #2116, #1835, #1654
Cancel-in-progress for reviews #1357, #981, #1331
CHANGES_REQUESTED verdict tuning #2029, #2115, #1500
Token cost on large PRs #2096, #1390, #1370
Skip review on merged PRs #1439

This PR is a strong signal that prioritizing #1357 (cancel-in-progress) and #1835/#1654 (verify file contents before asserting facts) would have high ROI — the combination would have eliminated the most visible waste in this workflow.

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

Labels

ok-to-test Allow e2e CI to run after maintainer review (must be re-applied after each push) requires-manual-review Review requires human judgment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

feat(install): --vendor self-contained workflow and agent assets

4 participants