diff --git a/docs/repo-cache-auto-discovery.md b/docs/repo-cache-auto-discovery.md index 33b3d9c..b9fb116 100644 --- a/docs/repo-cache-auto-discovery.md +++ b/docs/repo-cache-auto-discovery.md @@ -4,7 +4,7 @@ status: draft owners: - makeitworkcloud created: 2026-09-04 -last_reviewed: 2026-09-04 +last_reviewed: 2026-09-05 source_repositories: - makeitworkcloud/kustomize-cluster - makeitworkcloud/tfroot-github @@ -20,7 +20,7 @@ tags: ## Scope -This design replaces the manually enumerated Make IT Work Cloud source set with automatic discovery of eligible public repositories, while preserving the cache's read-only and eventually consistent contract. It does not authorize implementation, change the existing OpenCode endpoint, or grant cache access to private repositories. +This design replaces the manually enumerated Make IT Work Cloud public source set with automatic discovery of eligible public repositories, while preserving the cache's read-only and eventually consistent contract. It does not authorize implementation or change the existing OpenCode endpoint. Discovery stays public-only and never grants cache access to private repositories; the only private cache sources are the owner-approved explicit credentialed allowlist in `workloads/mcp-gateway/repo-cache-sync-private.yaml` (`agent-knowledge` and `channel-project`). ## Observed baseline @@ -38,14 +38,14 @@ The controller applies this fixed policy before cloning: 1. include only repositories reported public and not archived; 2. reject names that do not safely map to one cache path segment; -3. exclude a reviewed deny list containing at least `agent-knowledge` and `channel-project`, even if their visibility were changed accidentally; and +3. exclude a reviewed deny list containing at least `agent-knowledge` and `channel-project`, even if their visibility were changed accidentally, because both are owned by the explicit private allowlist (`repo-cache-sync-private.yaml`) and must never be duplicated by uncredentialed public discovery; and 4. synchronize each repository's reported default branch only. `tfroot-github` remains the canonical owner of repository creation, visibility, and archival. The controller consumes public GitHub metadata; it does not parse, run, or modify OpenTofu state or `tfroot-github` source. ### Cache writer -The proposed `repo-cache-controller` is a small, pinned image owned by `makeitworkcloud/images` and selected by `kustomize-cluster`. It replaces only the static `repo-cache-sync` writer; the filesystem MCP backend, its ToolHive policy, and the OpenCode `repo-search` endpoint remain unchanged. +The proposed `repo-cache-controller` is a small, pinned image owned by `makeitworkcloud/images` and selected by `kustomize-cluster`. It replaces only the static public `repo-cache-sync` writers; the credentialed private-allowlist containers in `repo-cache-sync-private.yaml` remain static desired state, and the filesystem MCP backend, its ToolHive policy, and the OpenCode `repo-search` endpoint remain unchanged. For every eligible repository, the controller fetches the default branch at the existing 120-second cadence. It stages a complete immutable checkout below that repository's cache root, then atomically publishes the new checkout and flips `current` only after the checkout is complete. The published layout must retain the existing agent-facing form: @@ -53,7 +53,7 @@ For every eligible repository, the controller fetches the default branch at the /repos//current -> ``` -A failed fetch preserves the last known-good `current` checkout and records the repository as stale. A repository may be pruned only after a fully paginated, successful discovery response omits it; discovery errors and partial responses must never trigger pruning. No private-repository credential or token may be added to make a missing repository visible. +A failed fetch preserves the last known-good `current` checkout and records the repository as stale. A repository may be pruned only after a fully paginated, successful discovery response omits it; discovery errors and partial responses must never trigger pruning. No private-repository credential or token may be added to this controller to make a missing repository visible; private repositories reach the cache only through the owner-approved explicit allowlist in `repo-cache-sync-private.yaml`, never through discovery. The controller exposes aggregate readiness only after its first complete discovery and successful initial synchronization of every eligible source. After that point, transient upstream failures should retain readiness and last-good data, while metrics and logs identify stale repositories and the last successful discovery/sync timestamps. The pod keeps `automountServiceAccountToken: false`, a read-only root filesystem, dropped capabilities, non-root execution, and a writable temporary directory only. @@ -65,7 +65,7 @@ Repository-content staleness remains bounded by one 120-second fetch cycle after - eligible, synchronized, stale, and denied repository counts; and - a per-repository current commit SHA and last-success time. -The MCP read path remains intentionally non-authoritative for remote `HEAD`, branch protection, visibility, or freshly pushed source. Agents continue to record the visible cache SHA and use GitHub MCP for writes, private repositories, and freshness-critical reads. +The MCP read path remains intentionally non-authoritative for remote `HEAD`, branch protection, visibility, or freshly pushed source. Agents continue to record the visible cache SHA and use GitHub MCP for writes and freshness-critical reads; the two allowlisted private repositories are additionally readable through the credentialed cache path. ## Delivery plan and acceptance criteria @@ -73,7 +73,7 @@ The MCP read path remains intentionally non-authoritative for remote `HEAD`, bra 2. **Shadow the writer:** deploy the controller against a separate PVC and non-advertised filesystem MCP server. Do not allow static and dynamic writers to share one PVC. Verify every existing eligible root, `current` symlink, and source SHA against the static cache within its documented staleness bound. 3. **Cut over desired state:** switch the existing read-only backend to the validated PVC/controller after cluster CI passes. Verify the `mcp-gateway` Application, cache writer, filesystem backend/proxy, and an MCP listing of `/repos/tfroot-twilio/current` separately. 4. **Exercise lifecycle behavior:** create or use an approved temporary public test repository, observe automatic inclusion without a manifest edit, then archive it and observe pruning only after a successful inventory. This is a confirmation-gated organization mutation and is not part of the current change. -5. **Retire static writers:** remove the per-repository `git-sync` containers only after the cutover and lifecycle checks succeed. Keep the existing static `tfroot-twilio` bridge until then. +5. **Retire static writers:** remove the per-repository public `git-sync` containers only after the cutover and lifecycle checks succeed. Keep the existing static `tfroot-twilio` bridge until then; the credentialed private-allowlist containers in `repo-cache-sync-private.yaml` are outside this design and are not retired by it. ## Open decisions and invalidation conditions diff --git a/workloads/mcp-gateway/kustomization.yaml b/workloads/mcp-gateway/kustomization.yaml index a9057e6..236ab62 100644 --- a/workloads/mcp-gateway/kustomization.yaml +++ b/workloads/mcp-gateway/kustomization.yaml @@ -51,6 +51,13 @@ patches: kind: Deployment name: repo-cache-sync namespace: mcp + - path: repo-cache-sync-private.yaml + target: + group: apps + version: v1 + kind: Deployment + name: repo-cache-sync + namespace: mcp # The single-node workload policy favors spare-capacity bursts over CPU CFS # throttling. Keep this inline so kube-linter does not lint a partial patch as # a standalone Deployment. @@ -64,6 +71,10 @@ patches: template: spec: containers: + - name: sync-agent-knowledge + resources: null + - name: sync-channel-project + resources: null - name: sync-charts resources: null - name: sync-cflan diff --git a/workloads/mcp-gateway/makeitwork-github-remote-proxy.yaml b/workloads/mcp-gateway/makeitwork-github-remote-proxy.yaml index f8d6527..c4eaf9a 100644 --- a/workloads/mcp-gateway/makeitwork-github-remote-proxy.yaml +++ b/workloads/mcp-gateway/makeitwork-github-remote-proxy.yaml @@ -1,6 +1,9 @@ --- -# Keep the GitHub PAT in the mcp namespace and inject it only on outbound -# requests to GitHub's official remote MCP endpoint. +# Keep the GitHub PAT in the mcp namespace. This file injects it on outbound +# requests to GitHub's official remote MCP endpoint; the same +# mcp-makeitwork-github-token Secret is also mounted read-only at +# /etc/git-secret in the two credentialed repo-cache-sync private-allowlist +# containers (repo-cache-sync-private.yaml) and nowhere else. apiVersion: toolhive.stacklok.dev/v1beta1 kind: MCPExternalAuthConfig metadata: diff --git a/workloads/mcp-gateway/repo-cache-sync-private.yaml b/workloads/mcp-gateway/repo-cache-sync-private.yaml new file mode 100644 index 0000000..3635f4b --- /dev/null +++ b/workloads/mcp-gateway/repo-cache-sync-private.yaml @@ -0,0 +1,161 @@ +--- +# Owner-approved explicit private allowlist for the read-only Make IT Work +# Cloud repository cache. agent-knowledge and channel-project are the only +# private, non-archived makeitworkcloud repositories (verified 2026-09-05, +# both default branch main) and the owner approved caching both. +# +# Unlike the public sources, these two containers authenticate with git-sync +# v4's documented --username=x-access-token and --password-file flags against +# the existing SOPS-encrypted mcp-makeitwork-github-token Secret, mounted +# read-only at /etc/git-secret in these two containers only. The encrypted +# Secret source is never decrypted, printed, or edited here, and the +# repo-search backend keeps its uncredentialed read-only view of the same +# mcp-repo-cache PVC; writes still never flow through this cache. +# +# This allowlist is deliberately separate from public automatic discovery +# (docs/repo-cache-auto-discovery.md), which remains public-only and never +# adds private repositories. Extending the private allowlist to another +# repository requires a fresh owner review and its own GitOps change. +# +# Same consumer contract, probe-port scheme, and per-container structure as +# repo-cache-sync.yaml; ports continue sequentially after the previous +# highest (9830). +apiVersion: apps/v1 +kind: Deployment +metadata: + name: repo-cache-sync + namespace: mcp +spec: + selector: + matchLabels: + app: repo-cache-sync + template: + metadata: + labels: + app: repo-cache-sync + spec: + containers: + - name: sync-agent-knowledge + image: registry.k8s.io/git-sync/git-sync:v4.7.1 + args: + - --repo=https://github.com/makeitworkcloud/agent-knowledge + - --ref=HEAD + - --root=/sync/agent-knowledge + - --link=current + - --depth=1 + - --period=120s + - --max-failures=-1 + - --group-write + - --username=x-access-token + - --password-file=/etc/git-secret/github_token + - --http-bind=:9831 + env: + - name: HOME + value: /tmp + ports: + - name: probe9831 + containerPort: 9831 + readinessProbe: + httpGet: + path: / + port: probe9831 + initialDelaySeconds: 5 + periodSeconds: 15 + failureThreshold: 20 + livenessProbe: + httpGet: + path: / + port: probe9831 + initialDelaySeconds: 60 + periodSeconds: 60 + failureThreshold: 6 + resources: + requests: + cpu: 10m + memory: 32Mi + limits: + cpu: 100m + memory: 128Mi + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 65533 + runAsGroup: 65533 + seccompProfile: + type: RuntimeDefault + volumeMounts: + - name: repos + mountPath: /sync + - name: tmp + mountPath: /tmp + - name: git-secret + mountPath: /etc/git-secret + readOnly: true + - name: sync-channel-project + image: registry.k8s.io/git-sync/git-sync:v4.7.1 + args: + - --repo=https://github.com/makeitworkcloud/channel-project + - --ref=HEAD + - --root=/sync/channel-project + - --link=current + - --depth=1 + - --period=120s + - --max-failures=-1 + - --group-write + - --username=x-access-token + - --password-file=/etc/git-secret/github_token + - --http-bind=:9832 + env: + - name: HOME + value: /tmp + ports: + - name: probe9832 + containerPort: 9832 + readinessProbe: + httpGet: + path: / + port: probe9832 + initialDelaySeconds: 5 + periodSeconds: 15 + failureThreshold: 20 + livenessProbe: + httpGet: + path: / + port: probe9832 + initialDelaySeconds: 60 + periodSeconds: 60 + failureThreshold: 6 + resources: + requests: + cpu: 10m + memory: 32Mi + limits: + cpu: 100m + memory: 128Mi + securityContext: + allowPrivilegeEscalation: false + capabilities: + drop: + - ALL + readOnlyRootFilesystem: true + runAsNonRoot: true + runAsUser: 65533 + runAsGroup: 65533 + seccompProfile: + type: RuntimeDefault + volumeMounts: + - name: repos + mountPath: /sync + - name: tmp + mountPath: /tmp + - name: git-secret + mountPath: /etc/git-secret + readOnly: true + volumes: + - name: git-secret + secret: + secretName: mcp-makeitwork-github-token diff --git a/workloads/mcp-gateway/repo-cache-sync-twilio.yaml b/workloads/mcp-gateway/repo-cache-sync-twilio.yaml index a0e9b11..6de8a8a 100644 --- a/workloads/mcp-gateway/repo-cache-sync-twilio.yaml +++ b/workloads/mcp-gateway/repo-cache-sync-twilio.yaml @@ -1,8 +1,10 @@ --- # Adds the active public tfroot-twilio OpenTofu root to the read-only -# Make IT Work Cloud repository cache. The cache never receives private -# repositories, including agent-knowledge; see repo-cache-auto-discovery.md -# for the proposed successor that discovers eligible public repositories. +# Make IT Work Cloud repository cache. Public sources such as this one stay +# credential-free; private repositories are never added by public discovery, +# only through the owner-approved credentialed allowlist in +# repo-cache-sync-private.yaml. See repo-cache-auto-discovery.md for the +# proposed successor that discovers eligible public repositories. apiVersion: apps/v1 kind: Deployment metadata: diff --git a/workloads/mcp-gateway/repo-cache-sync.yaml b/workloads/mcp-gateway/repo-cache-sync.yaml index 99e7974..b7602d4 100644 --- a/workloads/mcp-gateway/repo-cache-sync.yaml +++ b/workloads/mcp-gateway/repo-cache-sync.yaml @@ -1,12 +1,14 @@ --- -# Read-only cache of the public makeitworkcloud repositories, synced with +# Read-only cache of the makeitworkcloud repositories, synced with # the Kubernetes SIG git-sync image. This is the read/discovery surface for # OpenCode agents: it removes per-file GitHub API round trips (private-repo # code search is also unreliable for this org) in exchange for staleness -# bounded by --period plus one sync. agent-knowledge is deliberately -# excluded: it is private and its agent subtrees may hold personal or -# operational data, so it stays GitHub-MCP-only. Adding a private repo here -# later requires credentials and a fresh owner review. +# bounded by --period plus one sync. The containers defined in this file are +# the public, credential-free sources. The only private sources are the two +# owner-approved, credentialed containers in repo-cache-sync-private.yaml +# (agent-knowledge and channel-project); private repositories are never +# added automatically. See docs/repo-cache-auto-discovery.md for the +# public-only automatic discovery design. # # Writes never flow through this cache; agents keep using the GitHub MCP for # mutations and for anything they just pushed.