13

Change, Redeploy, and Roll Back Safely

Make product change routine by promoting immutable revisions and recovering through the owner of each desired state.

Run it in the public monorepo

This course is built around the public Zheta Kubernetes Lab monorepo. The excerpt below is runnable source, not pseudocode.

Source: services/control_plane/domain.py

def retire(self, project: Project, actor: str) -> None:
        self._owner(project, actor)
        project.status = "retired"
        with self.repository.transaction():
            self.repository.save(project)
            self._audit(project, actor, "project.retired")
        self.runtime.delete(project.organization_id, project.project_id)

    def delete(self, project: Project, actor: str) -> None:
        self._owner(project, actor)
        if project.status != "retired":
            raise ValueError("retire project before deletion")

Code to reality

Declared intent
Enforce the product lifecycle so published runtime state is retired before durable deletion.
Interpreter
The injected Forge model applies ownership and state-machine rules independently of HTTP or storage technology.
Software effect
Retirement removes the hosted runtime and records status before deletion can tombstone control-plane state.
Hardware effect
Runtime containers and storage can be released in order while retained evidence remains physically durable.
Observable evidence
A denied early delete, successful retirement, absent runtime, tombstoned project, and retained audit event prove order.

Start with the people and the result they need

The source tables below remain the detailed contract. Begin with these customer paths:

  • D13-UC-01
    • Person: Application maintainer
    • Job: Deploy an approved change to one generated application
    • Observable result: New Research Brief, Service Desk, or Field Inspection revision serves bounded traffic and meets its acceptance probes
  • D13-UC-02
    • Person: Incident commander
    • Job: Restore service after a defective revision
    • Observable result: Route returns to a known-good compatible artifact and required data repair is explicit

Turn each customer job into a testable story

Now turn each customer job into a story with a result that an engineer can check:

  • D13-US-01
    • Story: As an application maintainer, I want a new immutable revision exposed to bounded traffic, so that failures are detected before all users are affected
    • Observable acceptance: Baseline, candidate digest, traffic percentage, compatibility result, metrics, environment, timestamps, run, and traces are recorded
  • D13-US-02
    • Story: As an incident commander, I want recovery to choose rollback or forward repair explicitly, so that restoring code does not hide unsafe data state
    • Observable acceptance: Decision record names route, artifact, configuration, schema, data side effects, owner, observed recovery, and unaffected control

Add real state and observable proof

Finally trace each story through the system that owns its state and the evidence that proves the outcome:

  • D13-FLOW-01
    • Trigger: Maintainer selects Deploy approved change
    • Responsible systems: Release Orchestrator, compatibility checker, GitOps/runtime deployment controller, Route Controller, telemetry evaluator
    • Authoritative state: Release Store for intent; Git for workload declaration; runtime API for live deployment
    • Owned record: DeploymentRevision
    • Observable evidence: Actor, application, digests, policy, compatibility result, traffic split, expected and observed signals, environment, timestamp, run, and trace IDs
    • Failure signal: Candidate not ready, controller drift, threshold breach, or schema incompatibility
  • D13-FLOW-02
    • Trigger: Canary breaches its error threshold
    • Responsible systems: Incident workflow, Route Controller, GitOps/runtime controller, Migration Service, generated-app data owner
    • Authoritative state: Incident Store for decision; generated-app database for domain state
    • Owned record: MigrationLedger
    • Observable evidence: Rollback decision, restored route digest, schema/data status, affected denial or recovery trace, unaffected positive trace, environment, time, and…
    • Failure signal: Route restored but errors persist, incompatible down-migration, repair incomplete, or unaffected control fails

The enterprise problem and today’s slice

Enterprise problem: Once customers depend on a Zheta Forge application, a prompt, connector, schema, or code change can break requests or corrupt data, and “redeploy the old commit” may not reverse database effects. Whole-course context: The incoming evidence is a local release with a known content-addressed artifact; today selects a recorded release for redeployment. Today’s slice: We implement owner-only artifact rollback in the local runtime and distinguish it from unimplemented configuration rollback, canary routing, schema compatibility, and data repair. End-of-day evidence: A known release restores its exact stored artifact and publishes release.rolled_back; an unknown release is denied and the project remains readable. Still unsolved: Canary and GitOps recovery, schema/data repair, long-term observability, support operations, and retirement remain deferred.

Customer use cases

A redeployment is unsafe when the platform cannot say which changes are reversible and which require repair. These use cases cover controlled change and recovery from a bad release.

Use case IDActorCustomer jobSuccess outcomeDenial or recovery evidence
D13-UC-01Application maintainerDeploy an approved change to one generated applicationNew Research Brief, Service Desk, or Field Inspection revision serves bounded traffic and meets its acceptance probesPromotion stops when the canary—a small traffic slice—breaches error, latency, policy, or compatibility thresholds
D13-UC-02Incident commanderRestore service after a defective revisionRoute returns to a known-good compatible artifact and required data repair is explicitRollback refusal names incompatible schema or irreversible side effect; unaffected tenant control remains healthy

Actor-centred user stories

“Roll back” is ambiguous unless the customer can see whether bytes, configuration, and data all returned to a safe state. These stories require evidence for each dimension.

Story IDUse case IDsUser storyObservable acceptance conditions
D13-US-01D13-UC-01As an application maintainer, I want a new immutable revision exposed to bounded traffic, so that failures are detected before all users are affectedBaseline, candidate digest, traffic percentage, compatibility result, metrics, environment, timestamps, run, and traces are recorded
D13-US-02D13-UC-02As an incident commander, I want recovery to choose rollback or forward repair explicitly, so that restoring code does not hide unsafe data stateDecision record names route, artifact, configuration, schema, data side effects, owner, observed recovery, and unaffected control

End-to-end product flows

Replacing every pod at once converts one bad revision into a full outage, so deployment must expose a bounded comparison before promotion. Recovery begins from customer impact and follows the owner of the divergent state.

Flow IDUse case IDsPathTriggerNumbered stepsTerminal evidence
D13-FLOW-01D13-UC-01HappyMaintainer selects Deploy approved change1. Resolve prior and candidate digests. 2. Verify backward-compatible schema and configuration. 3. Deploy candidate beside prior revision. 4. Route bounded traffic. 5. Compare customer and system signals. 6. Promote only within thresholds.Actor, application, digests, policy, compatibility result, traffic split, expected and observed signals, environment, timestamp, run, and trace IDs
D13-FLOW-02D13-UC-02RecoveryCanary breaches its error threshold1. Freeze promotion. 2. Classify artifact, configuration, schema, and data effects. 3. Route traffic to prior compatible release or issue forward repair. 4. Reconcile owned state. 5. Probe affected and unaffected tenants.Rollback decision, restored route digest, schema/data status, affected denial or recovery trace, unaffected positive trace, environment, time, and run ID

The maintainer changes one application while only a controlled portion of requests reaches the candidate. That bounded path creates room to compare before committing every customer.

System design derived from the flows

Rollback fails when multiple controllers write the same state or when runtime recovery is expected to undo generated-app data. One mutating owner per resource keeps diagnosis and repair directional.

Use case IDEntry pointResponsible servicesAuthoritative storeFailure evidence
D13-UC-01Control-plane deployment actionRelease Orchestrator, compatibility checker, GitOps/runtime deployment controller, Route Controller, telemetry evaluatorRelease Store for intent; Git for workload declaration; runtime API for live deploymentCandidate not ready, controller drift, threshold breach, or schema incompatibility
D13-UC-02Incident recovery actionIncident workflow, Route Controller, GitOps/runtime controller, Migration Service, generated-app data ownerIncident Store for decision; generated-app database for domain stateRoute restored but errors persist, incompatible down-migration, repair incomplete, or unaffected control fails

The Release Orchestrator owns the customer request and policy decision, runtime controllers own pods and routes, and the generated application owns its schema and data. Terraform still owns underlying infrastructure existence; GitOps owns declared workloads; neither should mutate the other’s records.

Data model and ownership

A prior container image is not a recovery plan when schema or business records have changed. The model preserves compatibility and repair decisions alongside release identity.

Generated-application database: Not created in this runnable slice — rollback changes the stored hosted-runtime payload only; app-owned schema, migration ledger, compatibility checks, and business records remain production requirements.

Record or entityStore and ownerPrimary keyForeign key or opaque referenceTenant keyMaterial constraintLifecycle and deletionUse case IDs
DeploymentRevisionControl-plane Release Store, owned by Release Orchestratordeployment_revision_idOpaque artifact_digest and prior_revision_idorganization_idRevision, policy, config, and target environment are immutable after approvalCreate, canary, promote or supersede, retain through rollback window, expire by policyD13-UC-01, D13-UC-02
MigrationLedgerGenerated-application database, owned by Migration Servicemigration_idOpaque deployment_revision_idapp_tenant_idMigration is ordered, idempotent, and declares backward compatibility and repair ownerPlan, apply, verify, compensate or retain, delete only with tenant retirementD13-UC-01, D13-UC-02
RecoveryDecisionIncident Store, owned by Incident Commanderrecovery_decision_idOpaque deployment_revision_id and evidence_bundle_idorganization_idExactly one decision—rollback, forward repair, or contain—has accountable owner and deadlineOpen on breach, update with observations, close after positive and negative proof, retain immutablyD13-UC-02

The same immutable revision reaches a named recovery decision, and the evidence covers runtime route plus generated-app compatibility. This prevents a green rollout status from masking a broken customer or data path.

Reconcile through the owning controller

Imperative pod edits may briefly hide symptoms and then be overwritten by GitOps, so production recovery must change the owning declaration. The current product model implements a smaller local rollback: the owner selects an existing release and the runtime redeploys its stored artifact. It does not route canary traffic, revert Git, or prove Argo CD recovery. Read the exact method in services/control_plane/domain.py.

def rollback(self, project: Project, actor: str, release_id: str) -> dict[str, str]:
    self._owner(project, actor)
    release = next((item for item in project.releases if item["release_id"] == release_id), None)
    if release is None:
        raise ValueError("release not found")
    self.runtime.deploy(project.organization_id, project.project_id, {"mode": "published", **release, "source": self.artifacts.get(project.organization_id, release["artifact_id"])})
    self._audit(project, actor, "release.rolled_back")
    return release
Effect fieldWhat happens
Declared intentRedeploy one previously recorded release and reject an unknown release ID
InterpreterPython executes Forge.rollback; the injected artifact store supplies source and the runtime adapter owns deployment state
Software effectThe tenant/app runtime row is overwritten with the chosen published payload and release.rolled_back is published
Hardware effectLocal control-plane/runtime CPU, network, and SQLite storage are used; no Git, Argo CD, Kubernetes rollout, canary route, or database migration changes
EvidenceReturned release ID, runtime payload/artifact ID, audit action, unknown-release denial, and unaffected project read

Key takeaways

Rollback is a coordinated business recovery decision, not merely an older image tag.

  • Bounded traffic limits blast radius and creates comparative evidence.
  • Artifact, configuration, schema, and data reversibility are separate questions.
  • Repair the desired state through its single owner, then verify the customer path.

Checklist

A change is safe only when promotion and recovery are both rehearsed.

  • [ ] Prior and candidate artifacts are immutable and identifiable
  • [ ] Compatibility is checked before traffic changes
  • [ ] Threshold breach stops promotion automatically
  • [ ] Recovery proves affected and unaffected customer paths