Skip to content

Operator components (C4 L3)

Inside the Tend operator — the product core. It is the only writer of authoritative app state (Principle IV): everything an app is gets declared on the App CR, and everything an app gets is rendered from it by the reconcilers below. Source lives in operator/ (Go, Kubebuilder / controller-runtime).

C4Component
    title Tend operator — components

    Container_Boundary(op, "Tend operator (tend-system)") {
        Component(appRec, "AppReconciler", "app_controller.go", "Watches App CRs. Provisions the workload namespace, renders the perimeter and quotas, records build reports, cleans up via the tendtech.io/cleanup finalizer")
        Component(sandbox, "Sandbox provisioning", "sandbox.go", "Namespace labels (tendtech.io/app, owner, env), data-gateway ConfigMap (DATA_URL/DATA_SCOPE), renders default-deny + platform-gateways-allow + edge-ingress-allow CiliumNetworkPolicies; clones the Harbor pull secret")
        Component(quota, "Cap rendering", "quota.go", "ResourceQuota + LimitRange from spec.resourceCaps")
        Component(promo, "Promotion", "promotion.go, promote_api.go", "422 strictness gate on the four governance fields; raises the promotion PR with evidence; approve/reject state transitions")
        Component(costRec, "CostReconciler", "cost.go", "Polls OpenCost allocation; drives normal → warning-75 → warning-90 → suspended; enforces via pauseArgoSync + scaleNamespace; consumes tendtech.io/resume")
        Component(migrate, "Scope migration", "migrate.go", "One-time 001→003 dataScopes migration ([]string → struct)")
    }

    Component_Ext(crd, "App CR", "tendtech.io/v1alpha1", "single source of truth")
    Component_Ext(tekton, "Tekton PipelineRun", "build results", "results: image, attestation digest, prompt hash, model")
    Component_Ext(opencost, "OpenCost", "allocation API")
    Component_Ext(argo, "Argo CD Application", "per-app sync")

    Rel(appRec, crd, "reconciles / writes status")
    Rel(appRec, sandbox, "ensureNamespace, ensureSandbox")
    Rel(appRec, quota, "ensureResourceCaps")
    Rel(appRec, promo, "promotion state machine")
    Rel(appRec, tekton, "reads results of the latest completed run")
    Rel(costRec, opencost, "pollAllocation")
    Rel(costRec, crd, "writes status.costStatus / suspension")
    Rel(costRec, argo, "pause / restore sync on suspend / resume")

    UpdateLayoutConfig($c4ShapeInRow="3", $c4BoundaryInRow="1")

AppReconciler — declared intent becomes a governed namespace

One reconcile pass takes an App CR to a fully governed workspace:

  1. Namespace — creates tend-app-{name} labeled tendtech.io/app, tendtech.io/owner, tendtech.io/env=sandbox, back-referenced to the CR with tendtech.io/app-ref. The tendtech.io/cleanup finalizer guarantees release on App deletion.
  2. Perimeter — renders the three Cilium policies (default-deny floor + gateway-only allow) into the namespace, substituting __APP_NAMESPACE__ in the templates under deploy/policies/cilium/. It also deletes the retired 001 tend-allow-synthetic-data policy — reconciliation removes stale enforcement, not just adds new.
  3. Caps — renders ResourceQuota and LimitRange from spec.resourceCaps; Kyverno's workload-within-caps policy makes sure every pod declares requests/limits so the quota can actually bite.
  4. Data wiring — writes the tend-sandbox-data ConfigMap holding DATA_URL/DATA_SCOPE, so a scope or gateway relocation reconciles through configuration alone — the app template never changes.
  5. Build reports — the pipeline never writes the App CR; it publishes its outcome as PipelineRun results, and the operator reads the latest completed run and stamps the report annotations itself (tendtech.io/sandbox-image, tendtech.io/attestation-digest, tendtech.io/prompt-hash, tendtech.io/model, tendtech.io/built-at), which the reconciler copies into status.sandboxArtifact and attestationRefs.

Everything the operator renders carries the tendtech.io/generated-by=tend-operator label. That label is a checkable claim: hack/policy-provenance.sh (and CI) asserts zero hand-written per-app policy objects exist — with a deliberate hand-written counter-example to prove the check itself works.

Promotion — the 422 gate and the platform-raised PR

Promotion is a small state machine on status.state (see App lifecycle) with one strict entrance: the request is refused with HTTP 422 unless owner, expiry, dataScopes, and purpose are all present. Purpose is deliberately never defaulted — a human must classify the app (including the decisions-about-people flag) before it can reach production. On a valid request the operator raises the PR in Gitea from a fixed sandbox snapshot (status.promotion.snapshotRef), attaches the evidence record, and records the approver group. Merge and rejection flow back into status.promotion.decision.

CostReconciler — a second state machine, deliberately separate

The cost reconciler polls OpenCost's allocation API per app namespace and drives status.costStatus.state through normal → warning-75 → warning-90 → suspended, firing each transition once per crossing (the warning75At/warning90At timestamps make oscillation safe). Enforcement order matters: it pauses the app's Argo CD sync first — otherwise selfHeal would immediately undo the scale-down — then scales workloads to zero and stamps status.suspension.

Two asymmetries are intentional (ADR-0012):

  • Fail closed nowhere here. Missing telemetry sets state unknown and enforces nothing — an app is never punished because OpenCost was down.
  • Resume doesn't hand-scale. The operator restores the sync policy and lets GitOps bring replicas back, keeping git the source of what runs.

What is not in the operator

No webhook server — admission validation and defaulting live in Kyverno (ADR-0006), because the creator's identity only exists in the admission request's userInfo and the platform already has one policy engine. No database — state is the CR. No deploy pipeline — delivery belongs to Argo CD. The operator's envtest suites (reconciliation, defaulting, promotion, cost state machine) are the constitution's required proof for its logic.