Task-Centric Cutover Runbook¶
This is a one-time migration, executed once, driven by three tatara-helmfile PRs named PR-A, PR-B, and PR-C. Only PR-A and the cleanup/verify steps between it and PR-B are reversible.
PR-B is the point of no return. Recovery is forward only.
########################################################################
# PR-B IS THE POINT OF NO RETURN. RECOVERY IS FORWARD ONLY. #
# #
# PR-B lands the new operator CHART, and the chart carries the CRDs. #
# After it applies: #
# - the OLD operator cannot read the NEW CRs (different schema); #
# - reverting ONLY the operator is WORSE than going forward: the #
# new cli would then be talking to an old REST surface. #
# #
# AND HELM WILL TRY TO ROLL YOU BACK AUTOMATICALLY. #
# syncArgs carries --rollback-on-failure GLOBALLY, so ANY failure in #
# this apply triggers a rollback you did not ask for. And a rollback #
# DOES revert the CRDs: resource-policy: keep skips DELETION only, #
# and a CRD present in both revisions is UPDATED - PATCHED BACK TO #
# THE OLD SCHEMA, over the new Issue/MergeRequest CRs. #
# #
# => PR-B MUST REMOVE --rollback-on-failure FROM helmDefaults.syncArgs #
# GLOBALLY. PR-C restores it. There is NO per-release syncArgs key #
# to scope it with instead. Verify with `helmfile build` first. #
# #
# A failed PR-B must STOP, loudly, in a known-bad state that a human #
# fixes forward - not silently restore a schema that the already- #
# released cli/wrapper/agent-skills cannot talk to. #
# #
# There is no rollback. There is only fixing forward. #
########################################################################
Release train order¶
The redesign's own umbrella Task declares this order in spec.mergeOrder. Every repo above tatara-helmfile merges and releases its artifacts before anything is applied to the cluster; the cutover below is what happens once all six are sitting in Harbor, unapplied.
| Order | Repo | Significance | Why it goes here |
|---|---|---|---|
| 1 | tatara-operator | major | The operator chart and the tatara-project chart both live here - it carries every new CRD. |
| 2 | tatara-cli | major | Consumes the operator's new REST surface. |
| 3 | tatara-agent-skills | major | Names the cli's new MCP tools; a skill naming a tool the cli does not yet serve leaves the agent with no terminal tool. |
| 4 | tatara-claude-code-wrapper | major | Reports the contract version the operator now injects. |
| 5 | tatara-observability | alerts + dashboards | Same train, not after it. The alerts and dashboards this release replaces key on fields the operator removes in this same release, including both CD-cascade alerts. Shipping the operator without them leaves the merge/deploy path to a cluster-admin-scoped runner with zero alert coverage while every dashboard still reads green. |
| 6 | tatara-helmfile | five pins; replicaCount 0; values migration; tatara-chat uninstall | The cutover itself - everything below this table. |
| 7 | tatara-documentation | hard rule #7; new architecture | This page. It lands last because it describes the platform the other six just became, not the one they are leaving. |
Five pins, and no operator image pin at all - the operator's image rides its chart's appVersion:
| # | Location | What it pins |
|---|---|---|
| 1 | helmfile.yaml.gotmpl:85 | tatara-operator chart version - lands the new CRDs |
| 2 | helmfile.yaml.gotmpl:104 | project-tatara release, tatara-project chart version |
| 3 | helmfile.yaml.gotmpl:116 | project-infrastructure release, tatara-project chart version |
| 4 | values/project-tatara/common.yaml:66 | agent image |
| 5 | values/project-infrastructure/common.yaml:63 | agent image |
Pins 2 and 3 are what render the Project and Repository CRs. Miss either one and that project's CRs stay on the old vocabulary - silently, because a map[string]string ignores an unmatched key rather than rejecting it.
Pre-flight (reversible)¶
Nothing below is applied to the cluster yet.
- Forensic backup. It will not restore cleanly into the new CRDs - it is for post-mortems, not rollback. Dump every
Task,Subtask,Project,RepositoryandQueuedEventobject:kubectl get tasks,subtasks,projects,repositories,queuedevents -n tatara -o yaml > cutover-backup-$(date +%F).yaml. - Back up
tatara-chat's Postgres, or explicitly accept the loss, in writing. Its CNPG cluster is a subchart of the chat chart with no retain annotation and no backup configured. Rooms are TTL'd and disposable;chat_handoffsis not - dump it withkubectl exec -n tatara <chat-pg-primary> -- pg_dump -t chat_handoffs ..., or lose it. The loss is triggered by the uninstall in PR-B (installed: false->helm uninstall-> theClusterCR is deleted -> CNPG garbage-collects the PVCs), not by deleting the helmfile block - see PR-B below. - Merge and release the code, in the order of the table above (operator, cli, agent-skills, wrapper, observability). Images and charts publish. Nothing is applied to the cluster yet.
PR-A: pause, declaratively, and only that (reversible)¶
One tatara-helmfile PR, exactly one line:
Nothing else. No image pins, no chart-version bumps, no chat change.
kubectl scale is undone by the release - PR-A must be alone
The operator chart carries the CRDs (charts/tatara-operator/templates/crds.yaml). Any helmfile apply is a helm upgrade: if PR-A bundled the pause with the new chart version, that single apply would land the new CRD schemas over the live Task population before the wipe - the point of no return, one PR too early. kubectl scale --replicas=0 is not used at all: it is an imperative override that the very next helmfile apply resets back to 3, mid-cutover, against a half-wiped world. PR-A commits the pause instead, so it survives every apply until PR-C explicitly reverses it.
Verify: 0/0 operator pods.
Step 2: cleanup (reversible - nothing is ever deleted from SCM)¶
Run the tatara-platform-reset skill. Dry-run first; it shows the close-list before it acts. Across both projects' 15 repos it:
- closes every bot-authored open issue (human-authored open issues survive - the sweep re-mints them as backlog Tasks after cutover)
- closes every open agent PR/MR and deletes its head branch
- strips the operator's
tatara-*labels - leaves closed history untouched
This trips GitHub's secondary rate limit if it is not paced
15 repos times (close bot issues + close PRs + delete branches + strip labels) is 300-600 content-creating mutations against a limit of 80/minute and 500/hour. Run unpaced, it 403s partway through and leaves the cleanup half-applied - the worst possible state to carry into a window that has no rollback. tatara-platform-reset is paced (at most one mutation/second, with jitter), idempotent, and resumable from a progress ledger: a 403 pauses it, re-running it picks up where it left off and never double-acts.
Step 3: verify (the last reversible moment)¶
Confirm all of the following before touching PR-B:
- operator at 0 replicas
- the reset ledger is complete
- all five component releases (operator, cli, agent-skills, wrapper, observability) are green in
main - the
tatara-chatPostgres dump exists, or the loss is signed off in writing - both agent-image pins are ready to land:
values/project-tatara/common.yamlandvalues/project-infrastructure/common.yaml - the fifth pin is ready: the
tatara-projectchart, at bothhelmfile.yaml.gotmpl:104and:116 - both
values/project-*/common.yamlare migrated to the new vocabulary (see PR-B item (e) below), and ahelmfile templatediff confirms the renderedProjectCR carriesmaxConcurrentAgents: 5- every one of these migrations fails silently if skipped - PR-B removes
--rollback-on-failurefromhelmDefaults.syncArgsglobally, andhelmfile buildpasses - PR-B edits the CRD-adopt pre-hook: adds
issues, addsmergerequests, dropssubtasks
Once every box above is checked, the next step is irreversible.
Step 4: the wipe (irreversible)¶
The wipe precedes PR-B, so the new CRDs land on a clean world instead of over the live Task population. Delete every Task, Subtask, and QueuedEvent object, then delete the Subtask CRD itself: run kubectl delete tasks,subtasks,queuedevents --all -n tatara, then kubectl delete crd subtasks.tatara.dev.
!!! warning "The Subtask CRD survives a plain upgrade" charts/tatara-operator/templates/crds.yaml carries helm.sh/resource-policy: keep, so helm will not remove it on its own. The kubectl delete crd above is an explicit, required step - it does not happen by itself.
PR-B: the train (irreversible)¶
One tatara-helmfile PR containing all of the following, together:
(a) The new operator chart version - it carries the new CRDs, Issue and MergeRequest.
(b) Rollback disabled, globally. Every helmfile apply already runs with --rollback-on-failure set in helmDefaults.syncArgs (helmfile.yaml.gotmpl:9-10). There is no per-release syncArgs key to scope it with instead, and atomic: false cannot countermand a raw arg string - so the only fix is a global one:
Left in place, any failure during PR-B's own apply would trigger an automatic rollback nobody asked for - and a rollback of a CRD present in both revisions is an update, not a skipped deletion. resource-policy: keep only skips deletion. That update would patch the new Issue/MergeRequest schema back to the old one, over the CRs the new operator just wrote. PR-C restores this line once the operator is back at replicaCount: 3. Verify the removal with helmfile build before merging either PR - it strict-decodes the state file, so a stray or missing key errors loudly instead of being silently ignored.
© The agent image pin, in both files - values/project-tatara/common.yaml:66 and values/project-infrastructure/common.yaml:63. project-infrastructure is the GitLab project, and its repos include tatara-helmfile itself: miss this pin and every infrastructure Task starts failing on a contract mismatch.
(d) The tatara-project chart pins, at both helmfile.yaml.gotmpl:104 and :116. The Project/Repository CRs are rendered by a separate chart from the operator's own. Miss this pin and both Project CRs stay on the old vocabulary, silently.
(d2) The CRD-adopt pre-hook (values/tatara-operator/hooks/crd-adopt.tatara-operator.pre.sh:20-24) - a hardcoded CRD list the hook uses to adopt pre-existing CRDs into the helm release. projects.tatara.dev, repositories.tatara.dev, tasks.tatara.dev and queuedevents.tatara.dev are unchanged; PR-B adds issues.tatara.dev and mergerequests.tatara.dev, and drops subtasks.tatara.dev. A CRD missing from this list is never adopted; a deleted CRD still listed makes the hook operate on something that no longer exists.
(e) Migrate both values/project-*/common.yaml to the new vocabulary. Every line below fails silently, not loudly, if skipped:
| Old key | New key | Failure mode if missed |
|---|---|---|
maxConcurrentTasks: 5 | maxConcurrentAgents: 5 | structural pruning drops the old key; the kubebuilder default of 3 applies - concurrency silently cut from 5 to 3 |
modelByKind/effortByKind keyed on triageIssue | rekeyed on the surviving agent kinds | a map[string]string ignores a stale key rather than rejecting it - every kind falls back to the Opus/high default, the exact cost regression this migration exists to prevent |
approvalLabel: tatara/awaiting-approval | deleted | the field no longer exists on the CRD |
maxOpenTasks: 6 | unchanged | kept as-is; verify it survives the migration as a neighbour of the renamed fields |
(f) tatara-chat: installed: false. Keep the release block - do not delete it. helmfile apply does not prune a release whose block is removed from the state file; deleting the block orphans the release, and it keeps running as a zombie while the operator de-references it. installed: false is the only declarative way to trigger helm uninstall, and that uninstall is also what deletes the CNPG Cluster CR - see the pre-flight backup above.
(g) The observability alerts and dashboards.
Merge and apply. The operator is still at replicaCount: 0 when this PR merges - nothing runs yet.
Verify: helm list -n tatara no longer shows tatara-chat; its pods and its CNPG Cluster are gone. Verify both Project CRs show maxConcurrentAgents: 5, not 3.
Step 6: de-reference chat¶
Verify the operator release carries no remaining reference to tatara-chat anywhere: the pod env, the per-Project Ingress path, the NetworkPolicy egress rule, the /readyz tool-surface probe, and deploy_supervision.go's release-artifact map. If any reference survives, the operator reconciles against a Service that no longer exists and the probe alerts forever.
PR-C: start (the moment of truth)¶
Third tatara-helmfile PR, two changes:
Merge. The operator comes up against a clean world, and every future apply is atomic again. This is a real PR with a real review - not an imperative kubectl command the next apply would silently erase.
Step 8: observe¶
Three things to watch after PR-C lands:
operator_agent_contract_mismatch_total. Any non-zero value means the release train is skewed - check the infrastructure project specifically, since its agent-image pin lives in a second file.- The sweep minting
parked(backlog-sweep)Tasks at 5 per pass, with zero pods. This is the surviving human-authored issue backlog being mirrored in, not a malfunction. - A post-deploy check that the eight rewritten alerts fire on synthetic no-data, rather than reporting silently OK.
Also delete¶
PR-C's own diff should also remove the now-dead tatara-chat helmfile files entirely, once the release block has done its installed: false job and the uninstall has been verified.