Skip to content

feat(infrastructure,app-studio): enforce retirement of removed platform templates#416

Merged
mjudeikis merged 1 commit into
mainfrom
feat/retire-sandbox-runner
Jul 11, 2026
Merged

feat(infrastructure,app-studio): enforce retirement of removed platform templates#416
mjudeikis merged 1 commit into
mainfrom
feat/retire-sandbox-runner

Conversation

@mjudeikis

Copy link
Copy Markdown
Contributor

Why

Seeding only upserts the current catalog, so a workspace initialized before a template was retired keeps serving it forever — visible in the portal, selectable by agents, backed by code paths that no longer exist. sandbox-runner (replaced by template-native development mode in #394) is exactly that ghost on any long-lived deployment.

What

Retirement reconciler (controller/template/retired.go): Templates whose names are on the retired list (sandbox-runner, sandbox-preview-httproute) are deleted on sight by the Template controller, and the existing finalize chain (backend teardown → APIExport entry removal → per-template CRD deletion) dismantles everything they authored — exactly as if an operator had deleted them by hand. Because it runs in the watch loop, a re-applied retired template is swept again: retirement is enforced, not a one-shot migration.

Placement details:

  • After the finalizer add, so the deletion always runs the full teardown.
  • Before the backend lookup, so a retired template whose backend is no longer registered still gets swept instead of parking on BackendNotFound.
  • Retired names are documented as reserved — never reuse one for something new.

Pinned by TestReconcileRetiredTemplateIsSwept, including the re-apply case.

Stale-prose sweep (follow-through on the retirement):

  • Project CRD spec.template description claimed empty "keeps the legacy SandboxRunner development binding" — empty now means no development environment. Fixed in types_project.go + regenerated schemas (make codegen-app-studio-provider).
  • Factually wrong comments fixed: infra chart claimed the node dev-image default is "the kedge sandbox-runner image" (it's docker.io/library/node:22-bookworm); the kro backend documented a ${kedge.sandboxPreviewBaseDomain} token no code mints; dataplane route examples used the retired kind; template-conventions.md called the database template postgres-database.
  • Genuinely historical notes ("the proven sandbox-runner pattern" in the dev overlay) are kept — they're accurate history.

Verification

go build, go vet (incl. -tags e2e), and full go test ./... pass for both providers; app-studio schema regen is included and clean.

🤖 Generated with Claude Code

…rm templates

Seeding only upserts the CURRENT catalog, so a workspace initialized
before a template was retired keeps serving it forever — visible in the
portal, selectable by agents, backed by code paths that no longer
exist. sandbox-runner (replaced by template-native development mode in
#394) is exactly that ghost on any long-lived deployment.

Add a retirement mechanism to the Template controller: templates whose
names are on the retired list (retired.go — sandbox-runner,
sandbox-preview-httproute) are deleted on sight, and the existing
finalize chain (backend teardown → APIExport entry removal →
per-template CRD deletion) dismantles everything they authored. Because
it runs in the watch loop, a re-applied retired template is swept
again — retirement is enforced, not a one-shot migration. The check
sits after the finalizer add (teardown guaranteed) and before the
backend lookup (a retired template with an unregistered backend still
gets swept). Pinned by TestReconcileRetiredTemplateIsSwept.

Also sweep the stale prose the retirement leaves behind:

- Project CRD: spec.template's description claimed an empty value
  "keeps the legacy SandboxRunner development binding" — empty now
  means no development environment. Fixed in types_project.go and
  regenerated (config/crds, apiresourceschema, chart schema).
- Factually wrong comments: the infra chart claimed the node dev-image
  default is "the kedge sandbox-runner image" (it is
  docker.io/library/node:22-bookworm); the kro backend documented a
  ${kedge.sandboxPreviewBaseDomain} token that no code mints; dataplane
  route examples and app-studio runtime comments referenced the
  SandboxRunner kind; template-conventions.md named the database
  template "postgres-database". All updated to current names —
  genuinely historical notes (e.g. "the proven sandbox-runner pattern")
  are kept.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings July 11, 2026 09:27

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR enforces retirement of removed infrastructure “platform templates” by having the infrastructure Template controller automatically delete any Template whose name is on a retired list, ensuring long-lived workspaces don’t keep serving ghost templates. It also sweeps stale references to the retired sandbox-runner era across infrastructure/app-studio docs, charts, and API/CRD prose.

Changes:

  • Add an infrastructure Template “retirement” mechanism that deletes retired templates on sight and relies on the existing finalizer cleanup path.
  • Add a unit test pinning the retirement behavior, including re-apply enforcement.
  • Remove/replace stale sandbox-runner references in charts, comments, and Project CRD/API descriptions; regenerate app-studio schemas.

Reviewed changes

Copilot reviewed 18 out of 18 changed files in this pull request and generated 5 comments.

Show a summary per file
File Description
providers/infrastructure/docs/template-conventions.md Updates examples to reflect current template names/conventions.
providers/infrastructure/deploy/chart/values.yaml Fixes chart comments about dev image defaults and schema-field conventions.
providers/infrastructure/dataplane/handler.go Updates dataplane URL examples/comments to non-retired resources.
providers/infrastructure/dataplane/contract.go Updates contract doc comment example resource.
providers/infrastructure/controller/template/retired.go Introduces the retired template name list and rationale.
providers/infrastructure/controller/template/controller.go Enforces retirement early in reconcile by deleting retired templates on sight.
providers/infrastructure/controller/template/controller_test.go Adds unit coverage for retirement + re-apply enforcement.
providers/infrastructure/backend/kro/e2e_test.go Removes outdated sandbox-runner-specific naming commentary.
providers/infrastructure/backend/kro/backend.go Updates token/env-var documentation comment to match current behavior.
providers/app-studio/deploy/chart/values.yaml Updates chart comments to reflect template-instance-based development dataplane.
providers/app-studio/deploy/chart/files/schemas/projects.ai.kedge.faros.sh.yaml Regenerated schema with updated Project.template description.
providers/app-studio/config/kcp/apiresourceschema-projects.ai.kedge.faros.sh.yaml Regenerated APIResourceSchema with updated Project.template description.
providers/app-studio/config/kcp/apiexport-ai.kedge.faros.sh.yaml Updates APIExport schema reference to the regenerated schema name.
providers/app-studio/config/crds/ai.kedge.faros.sh_projects.yaml Regenerated CRD with updated Project.template description.
providers/app-studio/apis/ai/v1alpha1/types_project.go Fixes Project.template field description to reflect current semantics.
providers/app-studio/api/development_sync.go Updates comments to remove sandboxrunner-specific wording.
providers/app-studio/api/assistant_workflow.go Updates comments to reflect generic “development runtime” terminology.
providers/app-studio/api/assistant_runtime_tools.go Updates comments to reflect generic dev-agent/development dataplane terminology.

Comment on lines +18 to +22
// reconciler deletes any Template on this list on sight; the normal finalize
// chain (backend teardown → APIExport entry removal → per-template CRD
// deletion) then dismantles what it authored, exactly as if an operator had
// deleted it by hand. Because this runs in the watch loop, a re-applied
// retired template is removed again — retirement is enforced, not one-shot.
Comment on lines +121 to +125
// Retired platform templates are deleted on sight (see retired.go).
// AFTER the finalizer add so the deletion runs the full finalize chain,
// BEFORE the backend lookup so a retired template whose backend is no
// longer registered still gets swept instead of parking on a
// BackendNotFound condition.
Comment on lines +268 to +272
var post infrav1alpha1.Template
if err := r.Client.Get(context.Background(), types.NamespacedName{Name: "sandbox-runner"}, &post); err == nil {
t.Fatalf("retired template still present after reconcile (deletionTimestamp=%v, finalizers=%v)",
post.DeletionTimestamp, post.Finalizers)
}
Comment on lines +281 to +283
if _, err := dyn.Resource(crdGVR).Get(context.Background(), perTemplateCRDName(tmpl), metav1.GetOptions{}); err == nil {
t.Fatal("per-template CRD exists for a retired template")
}
Comment on lines +303 to +305
if err := r.Client.Get(context.Background(), types.NamespacedName{Name: "sandbox-runner"}, &post); err == nil {
t.Fatal("re-applied retired template survived reconciliation")
}
@mjudeikis mjudeikis merged commit b7324c2 into main Jul 11, 2026
23 checks passed
mjudeikis added a commit that referenced this pull request Jul 11, 2026
…ider suites (#417)

* test(e2e): infrastructure provider subprocess suite + CI job for provider suites

The infrastructure provider had e2e coverage only for its kro side (the
kind-based template e2e). Its kcp side — provisioning, init bootstrap,
the Template controller, the tenant catalog — ran only in unit tests or
against a manually-started tilt stack. And the existing quickstart
subprocess suite (make e2e-provider) was wired into no CI workflow at
all, so neither suite ever ran automatically.

Add suites/infraprovider, modeled on suites/provider: kedge-hub with
embedded kcp + the infrastructure provider (init, then serve) as host
subprocesses. No kind/Helm/Docker/kro — with KRO_KUBECONFIG unset only
the stub backend registers, which is exactly what lets the suite drive
the Template controller end-to-end without a runtime cluster:

- TestABootstrapSeedsCatalog — init seeded every current template; the
  platform APIExport exists.
- TestBStubTemplateFullReconcile — a backend:stub Template reaches
  Ready=True with its per-template CRD established and the APIExport
  resources entry added, and deletion runs the finalize chain back down.
- TestCRetiredTemplateIsSwept — a retired platform template applied
  into the live workspace (the pre-retirement deployment state) is
  deleted by the controller without operator action (#416's reconciler,
  now covered against real kcp).
- TestDProvidersDTO — the hub lists the provider for a logged-in user.
- TestETenantSeesTemplatesCatalog — APIBinding in the static user's
  workspace; the seeded templates are listable through the binding
  (the read App Studio's picker and MCP list_templates perform).

Wire both subprocess suites into CI as one sequential job (they share
the embedded kcp's fixed etcd port 2380) in e2e.yaml, and add the
make e2e-infra-provider target with the same port guard the quickstart
suite uses.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test(e2e): serve with the init-minted SA kubeconfig, not admin

Review feedback: running serve with the kcp admin kubeconfig bypasses
the init/serve split's RBAC — the suite would mask permission
regressions in the controller chain. init already mints the
workspace-scoped ServiceAccount kubeconfig; serve now uses it, exactly
like the chart's serve container.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test(e2e): harden infraprovider suite per review

- Handle os.Create errors for hub/init/provider logs (a failed create
  nil-panicked on .Name() and hid the root cause).
- Surface SetNestedField errors when overriding the CatalogEntry URLs.
- Per-attempt HTTP timeouts inside waitForCondition loops (an unbounded
  DefaultClient request could hang the whole suite).
- Bounded contexts in t.Cleanup deletes so teardown can't hang the run.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test(e2e): retry manifest apply until catalog APIs are servable

First CI run of the provider suites flushed out a startup race the
suites' own login helper already documents: the hub reports /readyz
before the catalog APIs in the providers workspaces are fully servable,
so on a slow runner the first CatalogEntry/Provider Create fails with
"the server could not find the requested resource" and cascades through
the whole quickstart suite. Retry the applies (90s bound) in both the
quickstart and infraprovider suites instead of failing on the first
attempt.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* test(e2e): descope the stale quickstart suite from the CI job

Its first-ever CI run revealed the quickstart suite predates the
provider bootstrap refactor: it applies only the CatalogEntry, into
root:kedge:providers, while the current flow applies Provider +
CatalogEntry into root:kedge:system:providers and delegates the
APIExport/schemas/bind-grant to `quickstart-provider init` — so every
test from catalog provisioning to tenant enable fails against today's
hub. Modernizing it onto the same bootstrap the infraprovider suite
uses is queued as a follow-up; until then the CI job runs the
infraprovider suite only.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

* fix(infrastructure): grant the runtime SA delete on templates + CRDs

The infraprovider e2e suite's first run (serving with the init-minted
SA kubeconfig instead of the hub-minted cluster-admin one) exposed a
real RBAC gap: the runtime ClusterRole granted no delete verb anywhere,
so the retired-template sweep (controller deletes Templates on sight)
and the finalize chain's per-template CRD deletion both 403 — the
features work only under the cluster-admin hub-minted kubeconfig.
Grant delete on templates and customresourcedefinitions; the role is
idempotently overwritten on init, so existing deployments pick this up
on their next init run.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

---------

Co-authored-by: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants