Design Patterns · Principal Track
Git is the only write path, the control plane splits on blast radius rather than on cluster count, and read access is treated as a security decision. Verified against Argo CD v3.5.1 · August 2026.
| # | Resolution | Decision | Recommendation | Ref |
|---|
Conceptual and Requirements deliberately break the shared topology, because a bar and a capability model have no deployment shape. That is what this table owes the reader — and the mapping is many-to-many.
| Capability | What implements it | Where it is configured |
|---|---|---|
| Declared intent | Git repository, Application CR | spec.source.repoURL · path · targetRevision |
| Gated change | Protected branch + policy check — outside Argo CD | SCM branch protection |
| Detect divergence | application controller + its cluster cache | --status-processors · timeout.reconciliation |
| Converge or report | sync policy, with a reviewed exception list | syncPolicy.automated{prune,selfHeal} · ignoreDifferences |
| Prove what is live | operation state and the resource tree | status.operationState.syncResult.resources |
| Which cluster, which ring | cluster Secrets + ApplicationSet generators | argocd.argoproj.io/secret-type: cluster |
| Generated, not copied | ApplicationSet matrix / merge generators | ApplicationSet.spec.generators |
| Who may read and change | Argo CD RBAC + AppProject roles | argocd-rbac-cm · AppProject.spec.destinations |
Follow the hardest one — how a change actually reaches a cluster — and watch it resolve from a constraint into a command an SRE could run.
| Resolution | The same concern at this level | The decision captured |
|---|---|---|
| Environment | Clusters live in accounts this team does not own, and no network path to their API servers is guaranteed. | Reach is a dated dependency with an owning team, not an assumption |
| Requirements | Not a component — a bar: deploy only from git, and prove the change landed. Verified continuously, not asserted. | Where the functional bar sits, and how each line is tested |
| Conceptual | Two capabilities: detect divergence, then converge or report. Neither names a mechanism. | Converge by default, with a short reviewed exception list — a runtime-mutated resource fights the loop forever |
| Logical | The controller pushes to the cluster API; nothing calls in. An agent is the alternative for clusters that cannot be reached. | Push, keeping the agent model in view — one-way door |
| Physical | An EKS access entry in each workload account naming the control plane’s role, cluster-wide read and namespace-scoped write. | No cross-account IAM role or trust policy is needed; cluster-wide read is not negotiable |
| Phase | What ships | Gate before moving on |
|---|---|---|
| 1 | One HA control plane in the platform account, on three nodes. No cluster registered yet. | SSO wired and role:admin restricted to a named group — everything after this is auditable |
| 2 | AppProjects with specific cluster and namespace destinations, before a single Application exists. | No project has a wildcard destination |
| 3 | One non-production cluster registered declaratively, as a secret in git. Manual sync only. | An access entry with cluster-wide read and namespace-scoped write, not AmazonEKSClusterAdminPolicy |
| 4 | Auto-sync on non-production. Self-heal only once drift is understood rather than surprising. | A controller kill rehearsed, and running workloads observed to be unaffected |
| 5 | The production control plane — a separate instance, not a namespace. | The repository allow-list differs between the two instances |
| 6 | The platform baseline via a cluster generator, so a new cluster is self-onboarding. | Cluster registration is a reviewed commit, because it is now a mass-deployment trigger |
| Team | Owns | The thing they get paged for |
|---|---|---|
| Platform | The control planes, projects, RBAC, generators, upgrades | A control plane down, or an upgrade that broke SSO |
| Application teams | Their config repository and what it declares | Their app OutOfSync or Degraded |
| Security | Read RBAC scope, the secret store, webhook exposure | An advisory that makes read equivalent to secret access |
| Network | Reach from the control plane to each cluster API | A registered cluster stuck in Unknown for a real reason |
| SCM owners | Availability of git and the identity provider | An outage that stops deploys — but not running workloads |
| Failure | Why it happens | What blunts it |
|---|---|---|
| Read access leaks secrets | A critical advisory in May 2026 let a user with read privileges retrieve raw manifests of any resource in the destination cluster, and exposed Secret data for apps using server-side diff. | Stay inside the support window; scope read like write; keep secrets out of manifest generation entirely |
| An upgrade breaks login | 3.0 changed the Dex subject from sub to federated_claims.user_id, so every existing RBAC policy stopped matching at once. | Read the notes for every intermediate minor; rehearse on the non-prod instance first |
| Helm renders differently after an upgrade | 3.5 forces Helm 4 and ignores spec.source.helm.version: v3. You cannot pin your way out. | Test OCI-over-plain-HTTP repositories before upgrading, not after |
| Health checks look broken | The control plane needs cluster-wide read on all resource types even when it writes to two namespaces. A read-restricted install produces errors that look like product bugs. | Grant cluster-wide read deliberately and say why in the access request |
| A cluster registration becomes a mass deployment | A matrix generator over the cluster list creates one Application per app the moment a cluster appears. | Narrow project destinations, and treat registration as a reviewed commit |
| Scaling stops working | Sharding is assigned per cluster. One very large cluster cannot be split across controller shards. | Size clusters with the shard boundary in mind, or plan the second instance |
| The webhook is a DoS vector | The endpoint is unauthenticated by design; three separate high-severity advisories in September 2025 were malformed payloads crashing the parser. | Do not expose it without a WAF or an SCM egress allowlist |