Skip to main content

Provider-aware repository rollout

Owner: coordinating project-manager and DevOps. Contract: #3373 Revision 2; implementation #3377/#3378. This runbook does not authorize deployment or enablement. Registration records declared GitHub/GitLab metadata, not upstream existence, credentials, repository access, checkout, redirect following, or a verified head.

Application default and explicit opt-out​

Starting with #3429, provider metadata registration defaults on in every environment running the upgraded application and deployment configuration. cmd/context-engine/main.go passes the tested repoProviderRegistrationEnabled helper into reporegistry.WithProviderRegistration. The helper uses os.LookupEnv:

Environment valueNew explicit-provider registration
AbsentEnabled
Exactly trueEnabled
Exactly falseDisabled
Present but emptyDisabled
Any other value (TRUE, 1, whitespace, invalid text)Disabled

This is not a change to old running binaries. Existing explicit false values continue to disable registration after upgrade until the operator removes or changes them. Compose uses ${REPO_PROVIDER_REGISTRATION_ENABLED-true} (without :), so an absent input becomes true while an explicitly empty input stays empty. Do not use a permissive boolean parser or :-true interpolation.

Every registration still goes through ConnectAdapter.RegisterRepo → Service.RegisterRepo → Store.RegisterRepo. The store interprets identity and applies the existing capability, clearance and tenant/team checks. A bare reporegistry.NewStore() retains its library default off; cmd/upsquad-knowledge-mcp/main.go uses this constructor for reads and exposes no registration writer.

Disabling the flag blocks new explicit-provider registrations, with FailedPrecondition REPO_PROVIDER_REGISTRATION_DISABLED. It does not disable list/read/map/removal, remove registry history, delete upstream content, revoke credentials or relax server authority. Omitted-provider, unknown-host legacy registration keeps its previous nonblank path/ID bytes. A host or URL without a provider is InvalidArgument. Historical 007, group//project, and URI-looking paths remain legacy data; unknown host does not mean gitlab.com.

Compatibility before upgrading or enabling​

Default-on makes readiness a pre-upgrade requirement, including automatic reconcilers watching main. If an installation is not ready, persist an explicit REPO_PROVIDER_REGISTRATION_ENABLED=false in its supported operator configuration before upgrading code/manifests. Keep it false throughout a mixed-version rollout. Do not add false defaults to the shipped manifests: the hold is a specific installation's explicit opt-out.

  1. Land reviewed schema/backend changes and verify the actual merged revisions, migration blobs, independent exact-head approvals and executed CI. Apply migration 251 or a higher clean version with the authorized migration role; record version/dirty state and role posture with migration-role-rls-posture.md.
  2. Inventory and upgrade every reader of the installation's registry dataset to compatible pinned code before allowing new provider-aware writes. Include RepoService replicas, gateway processes running the context-engine binary, standalone knowledge MCP, and scale-out templates. An independently isolated tenant dataset has its own readiness record; one healthy beta endpoint proves nothing about another tenant's inventory.
  3. Record each actual instance/container/pod UID, role, digest/revision, desired template version, selected flag value, health and probe results. Inspect only these fields, never entire environments or secret contents. Resolve missing revision labels against the release digest manifest. Unknown or older readers block activation; a mutable restart must not resurrect an older reader.
  4. Use the deployment's established identity flow and authorized tenant/team scope for per-reader probes. Read retained odd legacy paths by path and their printed {"repo_id":"..."} selectors; preserve unknown GitLab host and exact legacy bytes, and verify no-map/UNKNOWN freshness where applicable. Exercise qualified selectors and provider-host presence using existing legacy rows; absent retained rows are an explicit evidence limitation, not permission to fabricate history. Use scoped staging fixtures for mixed-provider reads. During an explicit hold, confirm the named gate-off denial and no new row.
  5. After the scoped readiness evidence passes and deployment is authorized, remove the explicit false override or set literal true; recreate/restart the affected writers using compatible images. Then perform authorized disposable GitHub/GitLab registration/list/map/removal acceptance, retaining receipts and a cleanup manifest. These are metadata operations, not evidence of upstream access, checkout or a verified head.

For beta's existing dev-bootstrap identity flow, record the founder-authorized Vaisakh or Ashik persona and actual Engineering-team mapping. This is live beta identity acceptance, not Clerk-authentication evidence. Do not request a nonexistent Clerk session for that flow, invent caller IDs, or bypass server authority. Production identity requirements remain unchanged. This runbook does not itself authorize a fleet deployment or changes to auth/network/secrets.

Supported operator configuration and restart​

DeploymentWriters / configuration
docker-compose.dev.ymlcontext-engine; retained box-local .env
docker-compose.tenant.ymlcontext-engine; tenant operator interpolation
docker-compose.ics.ymlBoth context-engine and gateway; ICS operator interpolation
Dev registry and local-embedding overlaysInherit the dev flag; render with their dev base
docker-compose.embedding-egress-smoke.ymlIsolated CI context-engine writer; same opt-out, no deployment claim
test/load/docker/docker-compose.drills.ymlSource-built fault-drill context-engine; same operator opt-out
Kubernetes base and dev/staging/prod/mnet overlayscontext-engine-config ConfigMap; all three context-engine containers explicitly bind its key

The inventory guard discovers every docker-compose*.yml outside generated/cache directories and requires actual render coverage for each writer. Root-Dockerfile non-writers are explicitly excluded only while their declared SERVICE remains: mg-baseline-report, preview-router, upsquad-memory-mcp, agent-orchestrator, approval-scheduler, model-gateway, and the smoke bench seed/reembed/score commands (tools/recall-bench, tools/rag-reembed). They build other binaries and expose no RepoService writer. The registry overlay inherits the guarded dev flag and is tested together with that base; it is the sole mapping-inheritance exclusion. Missing or stale exclusions fail the sweep.

The sandbox worker overlay has no context-engine writer. Standalone knowledge MCP is read-only and keeps NewStore()'s unchanged library default. Kubernetes's compaction/embedding containers use the same binary; they receive the same flag rather than relying on a service-name assumption. The base ConfigMap ships REPO_PROVIDER_REGISTRATION_ENABLED: "true". Every explicit env binding uses configMapKeyRef, so an envFrom Secret cannot shadow this flag. No secret is read by the render tests.

Compose​

Set the following in the installation's retained operator .env to disable new registrations, or set literal true to re-enable after readiness checks:

REPO_PROVIDER_REGISTRATION_ENABLED=false

The calling shell takes precedence over .env; unset a stale exported flag before running Compose so the durable operator value wins. Explicitly empty is also off. A one-off command-line override is not durable across reconciliation. Select the installation's actual Compose file(s) and recreate all its writers:

# Tenant example; images must already be present and verified compatible.
unset GH_TOKEN REPO_PROVIDER_REGISTRATION_ENABLED
docker compose -f docker-compose.tenant.yml up -d --no-deps --pull never context-engine
# ICS has TWO writers.
docker compose -f docker-compose.ics.yml up -d --no-deps --pull never context-engine gateway

For beta, the existing reconciler reads docker-compose.dev.yml and the retained, gitignored /opt/upsquad/upsquad-core/.env. Preserve all unrelated entries and retain a verified compatible digest pin across flag changes:

CONTEXT_ENGINE_IMAGE_VERSION=latest@sha256:<verified-compatible-digest>
REPO_PROVIDER_REGISTRATION_ENABLED=false

The dev image retains the literal ghcr.io/upsquad-ai/upsquad-context-engine namespace required by publication coverage. Its default version remains latest; readiness-sensitive operations pin the actual compatible digest to prevent a mutable-tag restart surprise. Registry overlays have their own image override; verify/pin that effective image through their established release mechanism too.

Inspect only selected rendered values, not unrelated environment or credentials:

unset GH_TOKEN REPO_PROVIDER_REGISTRATION_ENABLED
cd /opt/upsquad/upsquad-core
docker compose -f docker-compose.dev.yml config --format json | python3 -c '
import json, sys
s = json.load(sys.stdin)["services"]["context-engine"]
print(json.dumps({"image": s["image"], "provider_registration":
s["environment"]["REPO_PROVIDER_REGISTRATION_ENABLED"]}))'
flock -n /tmp/dev-reconcile.lock docker compose -f docker-compose.dev.yml \
up -d --no-deps --pull never context-engine

An occupied reconciler lock is a wait condition. Record before/after selected flag, container ID, image digest/revision and health; require the verified image to remain in use. Beta health is http://127.0.0.1:9095/healthz (use /readyz if you want dependencies checked rather than liveness). It was 8084 until #3585 — that was the deleted WS listener's /healthz, and the host port is no longer published, so the old URL is connection-refused. /healthz and /readyz are served on the metrics mux (container :9091, host 9095). Then run scoped acceptance through the deployed UI. Removing/changing the explicit false is a controlled activation; merging a default-on config can activate an unheld beta reconciler, so establish readiness or the explicit hold before landing.

Kubernetes​

Persist REPO_PROVIDER_REGISTRATION_ENABLED: "false" in the installation's ConfigMap overlay/managed configuration, render it with the existing Kustomize workflow, and apply through that installation's approved deployment process. The same value can be set to "true" after compatibility checks. Set this key; never delete it: the non-optional binding deliberately makes a missing key a CreateContainerConfigError, not the bare binary's absent-environment default. Managed overlay "false" holds are supported by the render guard, including an overlay explicitly set to "true"; only overlays without an override inherit base changes. EnvFrom and configMapKeyRef values are captured at container start: changing the ConfigMap alone does not change running processes. Restart and observe all three Deployments (or use their established GitOps rollout mechanism):

kubectl -n platform rollout restart deployment/context-engine \
deployment/context-engine-compaction-worker deployment/context-engine-embedding-worker
kubectl -n platform rollout status deployment/context-engine
kubectl -n platform rollout status deployment/context-engine-compaction-worker
kubectl -n platform rollout status deployment/context-engine-embedding-worker

Do not rely on an ad-hoc ConfigMap patch that GitOps will overwrite. Keep the explicit hold in the managed overlay through mixed-version upgrades, and retain compatible image pins. The checked-in dev Argo Application watches main with automated sync; production is an explicit promotion path, not authorization to deploy a production fleet from this change.

Verification​

bash scripts/test-repo-provider-compose.sh and its Bash-CI discovery wrapper render real profiles with clean synthetic interpolation and no operator secrets. They test absent/default, false, empty, true and invalid values, every writer, complete unrelated-field isolation, image pin/publication coverage and exact negative-control predicates. bash scripts/test-repo-provider-kubernetes.sh and its Bash-CI wrapper use existing Kustomize/PyYAML to render every maintained overlay and prove all three explicit ConfigMap bindings and opt-outs. Malformed renders or failures at a different predicate cannot count as semantic controls. Neither harness starts containers, pulls images, contacts a cluster or changes data.

The focused TestRepoProviderRegistrationEnabled Go table covers absent, empty, literal true/false, uppercase, numeric, invalid and whitespace inputs; the Compose harness guards the production constructor's actual helper linkage. The registry package's existing tests retain library defaults and server-owned behavior.

Identity and operator guidance​

GitHub owner/repo defaults to github.com; an HTTPS URL supplies its actual host. GitLab explicit registration requires a visible known host and canonical decimal project ID [1-9][0-9]*, retained as text without an integer-width limit. Never normalize legacy IDs or infer their hosts. DNS case and HTTPS port 443 normalize; nondefault ports remain visible. DNS/strict IPv4 are supported; IPv6, trailing-dot DNS, Unicode host spellings and leading-zero ports are rejected. URLs and supplied structured fields must agree. .git is never stripped and self-hosted provider is an explicit declaration, not an inference from DNS.

Same paths can legitimately coexist across provider/host namespaces. Plain path reads return REPO_LOCATION_AMBIGUOUS when more than one visible active row matches. Use the exact registry UUID selector printed by list_repos or a qualified path; explicit empty provider_host selects the unknown-host legacy namespace, while omission does not filter host. Team comes from validated agent identity on MCP and is not a tool argument.

GitHub rename/transfer is remove plus add. Re-add receives a new registry UUID and inherits no map, head, section curator or divergence provenance. Removal does not delete upstream content or revoke credentials. No redirect/upstream-ID continuity is claimed.

Rollback after provider-aware data​

The committed old_first_match_reader_is_unsafe DB control demonstrates why an old binary is unsafe: four visible same-path registrations exist and its unqualified QueryRow returns an arbitrary row, whereas the compatible resolver refuses ambiguity. Turning new writes off cannot repair old readers of existing data.

Once any GitHub or known-host identity exists, including removed history, retain migration 251 and compatible readers on rollback. Disable new writes if needed, then fix forward or deploy a previously verified compatible build. Do not roll back to an old reader merely because the flag is off. DOWN must refuse unrepresentable all-history metadata and old-schema active collisions, with a write-exclusive lock covering preflight through schema mutation. A refusal can leave golang-migrate metadata dirty while transactional schema/data remains unchanged; investigate using the schema runbook instead of forcing the version. Do not delete, rename, normalize, or fabricate real registration data to force a rollback. Old-binary/DOWN eligibility needs a separately verified representable, unambiguous entire population and coordinator authorization.