diff --git a/docs/ADRs/0003-org-config-repo-convention.md b/docs/ADRs/0003-org-config-repo-convention.md index a2ecec3b33..41e9e60fdb 100644 --- a/docs/ADRs/0003-org-config-repo-convention.md +++ b/docs/ADRs/0003-org-config-repo-convention.md @@ -22,6 +22,12 @@ Date: 2026-03-25 Accepted +> **Note (2026-09-03):** Per-org installation is deprecated +> ([ADR 0044](0044-deprecate-per-org-installation-mode.md)). A dedicated +> `/.fullsend` repo is no longer required: shared configuration is a +> preset hosted in any repo and bumped per consuming repo by Renovate +> ([ADR 0103](0103-shared-config-presets-converged-by-fullsend-update.md)). + ## Context An organization adopting fullsend must configure it: the intent repo, the diff --git a/docs/ADRs/0044-deprecate-per-org-installation-mode.md b/docs/ADRs/0044-deprecate-per-org-installation-mode.md index efbaf15ca1..f9cab61f04 100644 --- a/docs/ADRs/0044-deprecate-per-org-installation-mode.md +++ b/docs/ADRs/0044-deprecate-per-org-installation-mode.md @@ -38,6 +38,10 @@ Deprecates the per-org installation mode established in Per-repo installation mode ([ADR 0033](0033-per-repo-installation-mode.md)) becomes the sole supported installation model. +> **Note (2026-09-03):** The "no centralized policy" and "per-repo setup +> overhead" costs listed below are decided in recorded presets and +> `fullsend update` ([ADR 0103](0103-shared-config-presets-converged-by-fullsend-update.md)). + ## Context Fullsend's original installation model is per-org: `fullsend admin install ` diff --git a/docs/ADRs/0069-ready-made-configuration-presets.md b/docs/ADRs/0069-ready-made-configuration-presets.md index e0087f2bdb..0be85e07cd 100644 --- a/docs/ADRs/0069-ready-made-configuration-presets.md +++ b/docs/ADRs/0069-ready-made-configuration-presets.md @@ -19,6 +19,11 @@ Date: 2026-06-29 Accepted +> **Note (2026-09-03):** Preset provenance (a bot-owned +> `.fullsend/preset.lock.yaml`) and the refresh path (Renovate running +> `fullsend update` as a post-upgrade task) are decided in +> [ADR 0103](0103-shared-config-presets-converged-by-fullsend-update.md). + ## Context `fullsend github setup` today spreads installation decisions across many CLI diff --git a/docs/ADRs/0103-shared-config-presets-converged-by-fullsend-update.md b/docs/ADRs/0103-shared-config-presets-converged-by-fullsend-update.md new file mode 100644 index 0000000000..755807413b --- /dev/null +++ b/docs/ADRs/0103-shared-config-presets-converged-by-fullsend-update.md @@ -0,0 +1,105 @@ +--- +title: "103. Shared configuration as recorded presets bumped by Renovate" +status: Accepted +relates_to: + - governance + - agent-infrastructure +topics: + - configuration + - installation + - distribution + - presets +--- + +# 103. Shared configuration as recorded presets bumped by Renovate + +Date: 2026-09-03 + +## Status + +Accepted + + + +## Context + +An organization wants one shared agent configuration for many repos, and +wants each repo to pick up updates automatically, the way Renovate already +bumps its packages and GitHub Actions pins. Per-repo installation +([ADR 0044](0044-deprecate-per-org-installation-mode.md)) removed the org +config repo that used to do this and left nothing in its place. + +The building blocks exist but do not connect. A shared +`config.base.yaml` can be installed with `github setup --config` +([ADR 0069](0069-ready-made-configuration-presets.md)), but nothing records +where it came from, so nothing can refresh it. Agent pins carry a commit +SHA and a `#sha256=` hash, but `agent update` does not re-pin `base:` in +local harness files and does not regenerate `lock.yaml` (#5433, #5802). +No command checks pins in CI (#6191). `repos install` +([ADR 0074](0074-repos-command-consolidation.md)) does not know presets. + +Today the fullsend-ai org fills the gap with a workflow that `sed`-rewrites +`config.yaml` in a hard-coded list of repos and pushes to `main` through a +ruleset-bypass App. It always tracks `main`, offers no review, and leaves +stale hashes on any path outside `harness/`. It replaced a Renovate custom +manager that did the bump correctly but ran in only one repo +(fullsend-ai/.fullsend#174). Two readers also ignore the base layer and +are fixed as prerequisites, not decided here: the dispatch Route job reads +only `config.yaml` (#6422's class), and `agent add/update/remove` copy +preset entries into `config.yaml`, where they shadow the preset. + +## Decision + +Shared configuration is a **preset**: a versioned `config.base.yaml` in +any repo, recorded in each consuming repo by provenance, and bumped by that +repo's own Renovate job. + +1. **Preset.** The preset is a `config.base.yaml` plus the harness bases, + skills and policies it references by URL. It lives in whichever repo + hosts the org's shared Renovate config. There is no dedicated org repo, + no org-level workflow and no enrollment list. +2. **Two kinds of files.** Humans own `config.yaml` and local harness + files. Tooling owns `config.base.yaml` (a byte-identical copy of the + preset), `lock.yaml`, the shim, and a new `.fullsend/preset.lock.yaml` + whose `source` URL names the preset and its tracked ref; the resolved + SHA and `sha256` are derived from it. Tooling never writes the human + files. That split is why no three-way merge and no compile step are + needed. +3. **Renovate bumps the pins.** fullsend ships a Renovate preset with + `custom.regex` managers for the preset record, `agents[].source` URLs + and harness `base:` URLs (never `config.base.yaml`). A repo's + `renovate.json` extends it. Renovate's built-in `github-actions` manager + already bumps the shim. +4. **`fullsend update` does the rest.** Renovate runs it as the + `postUpgradeTasks` command. It re-fetches the preset into + `config.base.yaml`, recomputes every `#sha256=`, and regenerates + `lock.yaml` without re-resolving unchanged dependencies. Run by hand it + also bumps the shim ref. `--check` writes nothing and exits non-zero on + drift from a tag or SHA. `repos install` applies the preset on a fresh + install. +5. **Policy is Renovate's.** Which ref to track, how long to wait + (`minimumReleaseAge`) and how often to run (`schedule`) are ordinary + Renovate `packageRules`, shared through the org's Renovate preset. + +Repos with no explicit `agents:` entry follow the fullsend build tag for +first-party agents, so the shim bump is their agent bump. Agents generated +by `fullsend agent new` (#6966) are local files and are left alone. + +## Consequences + +- Agent and preset bumps arrive as ordinary Renovate pull requests, and the + fullsend-ai sync workflows are retired. +- #5433, #5802 and #6191 close on the verb; #6597 and #6607 become + Renovate policy. +- `postUpgradeTasks` needs self-hosted Renovate with an anchored + `allowedCommands` entry, so a repo without Renovate runs `fullsend + update` by hand. +- The preset URL is a supply-chain trust surface covered by + `allowed_remote_resources` and `--config-hash`; signing is a follow-on. +- Refusing to run when a repo drifts from its preset is a separate + decision; this ADR only makes drift visible. diff --git a/docs/architecture.md b/docs/architecture.md index c7128f52dc..19af2441a5 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -58,6 +58,7 @@ the dedicated org-level `/.fullsend` config repo is deprecated - Multi-repo management: a `fullsend repos` subcommand group with a declarative `repos.yaml` manifest for managing per-repo installations at scale — install, convergence (provision, sync, upgrade), status, and uninstall across repos and orgs ([ADR 0057](ADRs/0057-repos-management.md), [ADR 0074](ADRs/0074-repos-command-consolidation.md)). - Dispatch version-skew resolution: per-repo `reusable-dispatch.yml` inlines stage workflow jobs directly, eliminating `@v0` references to `reusable-{stage}.yml` ([ADR 0062](ADRs/0062-dispatch-version-skew.md)). - Ready-made configuration presets: `fullsend github setup --config ` installs a vendor preset as `.fullsend/config.base.yaml` and a stub `.fullsend/config.yaml` overlay in the target repository; mint URL, inference backend, and related settings live in configuration files resolved through accessor methods, not CLI flags. Shared-infrastructure presets will reduce per-adopter enrollment (target state): mint via `job_workflow_ref` trust per [ADR 0059](ADRs/0059-public-mint-mode-with-wildcard-allowlists.md); inference authorization model undecided ([ADR 0069](ADRs/0069-ready-made-configuration-presets.md)); enrollment remains required until follow-on ADRs land. +- Shared presets bumped by Renovate: a bot-owned `.fullsend/preset.lock.yaml` records the preset's source and tracked ref; Renovate custom managers bump the preset record, `agents[].source` and harness `base:` pins, and run `fullsend update` as a post-upgrade task to refresh `config.base.yaml`, recompute hashes and regenerate `lock.yaml` ([ADR 0103](ADRs/0103-shared-config-presets-converged-by-fullsend-update.md)). - GitLab event dispatch: two-path model — native CI triggers (`merge_request_event`) for MR events, cron-based polling for issues/comments/labels. No external infrastructure (no webhook bridge). Bot PAT stored as a protected CI/CD variable. Per-repo only ([ADR 0067](ADRs/0067-gitlab-cron-polling-event-dispatch.md)). **Open questions:** @@ -533,6 +534,10 @@ Fullsend uses a three-tier configuration inheritance model for all configuration In per-repo installation the middle tier is replaced by files inside the target repo: `.fullsend/config.base.yaml` (vendor preset or baseline) and `.fullsend/config.yaml` (repo overlay), with code defaults below both. The +preset may live in any repo; Renovate bumps its pins and `fullsend update` +refreshes the tooling-owned files (`config.base.yaml`, `preset.lock.yaml`, +`lock.yaml`), never `config.yaml` or local harness files +([ADR 0103](ADRs/0103-shared-config-presets-converged-by-fullsend-update.md)). The org-tier box above describes the historical per-org model, now deprecated ([ADR 0044](ADRs/0044-deprecate-per-org-installation-mode.md), [ADR 0069](ADRs/0069-ready-made-configuration-presets.md)). diff --git a/docs/problems/agent-infrastructure.md b/docs/problems/agent-infrastructure.md index 04859dac0a..8b9b28e590 100644 --- a/docs/problems/agent-infrastructure.md +++ b/docs/problems/agent-infrastructure.md @@ -113,7 +113,7 @@ Many fullsend scenarios skew toward **ephemeral, task-scoped** execution (triage - **Agent architecture** — Instance topology (per-repo vs shared) and “local vs remote” for pre-PR review depend on what infrastructure we have. Infrastructure enables or constrains those choices. - **Security threat model** — Isolation and “separate execution environments” are implemented by this layer. Supply chain (what base images and dependencies the runtime uses) also lives here. -- **Governance** — Policy may be applied at runtime by agents reading from a policy repo; infrastructure determines where that runtime runs and how it accesses policy. +- **Governance** — Policy may be applied at runtime by agents reading from a policy repo; infrastructure determines where that runtime runs and how it accesses policy. Shared configuration reaches per-repo installs as a recorded preset bumped by Renovate ([ADR 0103](../ADRs/0103-shared-config-presets-converged-by-fullsend-update.md)); the same pinned-remote-reference model maps onto Tekton remote resolution if stages later run there. - **Repo readiness** — Repos need reliable CI and signals; agent infrastructure may consume or depend on the same CI (e.g. for “run tests” or “run linters”) and should not conflict with it. Headless runtimes amplify **feedback latency** and **workspace handoff** costs when CI is the only execution path. ## Open questions diff --git a/docs/problems/governance.md b/docs/problems/governance.md index 8deb43e78e..77a690da08 100644 --- a/docs/problems/governance.md +++ b/docs/problems/governance.md @@ -29,7 +29,7 @@ Agent configuration is itself a security-critical attack surface. If someone can **Open design questions:** - Where does agent policy live? In the repos it governs (as CLAUDE.md, agent config files)? In a separate policy repo? In a central configuration system? -- If policy lives in a separate repo, how does it get applied to target repos? Push-based (policy repo pushes to targets) or pull-based (agents read from policy repo at runtime)? +- If policy lives in a separate repo, how does it get applied to target repos? Push-based (policy repo pushes to targets) or pull-based (agents read from policy repo at runtime)? (Push-based as Renovate pull requests, from a preset hosted in any repo — decided in [ADR 0103](../ADRs/0103-shared-config-presets-converged-by-fullsend-update.md); agents still read the merged layers at runtime, and enforcing a policy floor remains open.) - How do we audit changes to agent configuration? Git history helps if policy is in git, but we also need to detect unauthorized runtime changes. - How do we handle the bootstrap problem — who sets up the initial agent configuration for a new repo, and how is that initial setup secured? (Preset-based install and `config.base.yaml` / `config.yaml` layering decided in [ADR 0069](../ADRs/0069-ready-made-configuration-presets.md); workflow pinning and backend policy remain open.)