Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 7 additions & 7 deletions docs/repo-cache-auto-discovery.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand All @@ -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

Expand All @@ -38,22 +38,22 @@ 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:

```
/repos/<repository>/current -> <immutable checkout at synced commit SHA>
```

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.

Expand All @@ -65,15 +65,15 @@ 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

1. **Author and validate a controller image:** add source and unit tests in `images`; publish an immutable digest only after its pull-request checks pass. Tests must cover pagination, ETag reuse, archived/private/deny-list filtering, path validation, failed-fetch retention, successful-source-only pruning, and atomic publication.
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

Expand Down
11 changes: 11 additions & 0 deletions workloads/mcp-gateway/kustomization.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand All @@ -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
Expand Down
7 changes: 5 additions & 2 deletions workloads/mcp-gateway/makeitwork-github-remote-proxy.yaml
Original file line number Diff line number Diff line change
@@ -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:
Expand Down
161 changes: 161 additions & 0 deletions workloads/mcp-gateway/repo-cache-sync-private.yaml
Original file line number Diff line number Diff line change
@@ -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
8 changes: 5 additions & 3 deletions workloads/mcp-gateway/repo-cache-sync-twilio.yaml
Original file line number Diff line number Diff line change
@@ -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:
Expand Down
12 changes: 7 additions & 5 deletions workloads/mcp-gateway/repo-cache-sync.yaml
Original file line number Diff line number Diff line change
@@ -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.
Expand Down
Loading