Skip to main content

App State Machine & Lifecycle Enforcement

The application state machine governs transitions between lifecycle states, ensuring consistency and enforcing business rules across the platform.

Overview

Each application progresses through a fixed set of server-side states managed by pkg/v2/domain/app.State. Marketplace services build an aggregate.AppAggregate around that stateful object and call the lifecycle state machine to validate mutations. That coordination keeps lifecycle hooks in sync with the persisted state while preventing invalid transitions.

State Definitions

Applications can only exist in four states today—the additional Installing, Running, Unhealthy, or Failed concepts mentioned in earlier design docs were never implemented in the Go domain model.

Code Reference: pkg/v2/domain/app/state.go

Implemented States

StateUnregistered → StateRegistered → StateInstalled → StateUninstalled
↑ ↓
└─────────────── re-registration ─────┘
  • StateUnregistered – No persisted app data exists.
  • StateRegistered – Manifest/certificate were validated and stored, but the app has not been installed yet.
  • StateInstalled – The app was installed successfully and can receive traffic. Marketplace services treat this as the “running” state.
  • StateUninstalled – The app was installed previously but has been removed. From here it can be deregistered back to StateUnregistered or reinstalled.

State.CanTransitionTo and the lifecycle validators enforce the diagram above.

Lifecycle Validators

lifecycle.NewStateMachine() wires a validator for each target state and keeps those validators deliberately lightweight:

  • registerValidator verifies a certificate exists and is currently valid and UI apps provide at least one asset. It stamps RegisteredAt/UpdatedAt.
  • installValidator only checks that the current state is StateRegistered before stamping InstalledAt. Dependency, settings, and route validation are handled by the marketplace service beforehand.
  • uninstallValidator requires StateInstalled before setting UninstalledAt.
  • deregisterValidator ensures the app is registered (but not installed) before allowing cleanup.

Validators never call external systems. Permission checks, dependency analysis, route registration, and Docker orchestration are all performed by the marketplace service before or after the state transition runs.

Post-Transition Actions

There is no catch-all app.state_changed event. The marketplace service publishes the concrete events defined in pkg/v2/domain/event/app_events.go (app.registered, app.installed, app.uninstalled, app.deregistered), and downstream components subscribe to those specific topics. Cache invalidation or OpenFGA tuple updates are also performed explicitly by the marketplace service rather than by the state machine itself.

SDK Coordination

The Node.js SDK still models richer client-side phases (e.g. “installing”) so it can orchestrate hooks, but when it communicates with the host it only sees StateRegistered, StateInstalled, StateUninstalled, and StateUnregistered. Marketplace.Subscribe sends AppStateResponse messages with the persisted state—there is no STATE_CHANGED control message on the gRPC stream—so SDKs should map their local states onto those four host states and treat StateInstalled as “running”.

Allowed Transitions

app.State.CanTransitionTo confines transitions to the following set:

StateUnregistered → StateRegistered
StateRegistered → StateInstalled
StateRegistered → StateUnregistered
StateInstalled → StateUninstalled
StateUninstalled → StateRegistered
StateUninstalled → StateUnregistered

Self-transitions are also permitted for idempotency. Any other combination (for example StateInstalled → StateRegistered) returns an InvalidStateTransitionError from stateMachine.ValidateTransition.

Failure Handling

If installation or uninstallation fails inside the marketplace service, it surfaces the error and the aggregate stays in its previous state. There are no persisted “installing”, “failed”, or “unhealthy” markers—operational tooling relies on the emitted lifecycle events and Docker orchestration health checks.