Goal: retire a tenant from the fleet cleanly — destroy its infrastructure, walk its relationships down in the right order, and keep a permanent audit trail.
Deprovisioning is just another Action. It transitions the tenant entity,
cascades its relationships, and fires a trigger (a destroy run or a PR). As always,
Terrantula never runs terraform apply or destroy itself — the trigger
either fires a run against your runner or opens a PR your CI applies.
DeprovisionTenant is an instance-scope transition-entity Action. A typical
shape (from the SaaS-tenants demo):
onTrigger: deprovisioning → onSuccess: decommissioned → onFailure: active (revert and investigate).runs_on edge moves active → removing on trigger,
then removing → removed on success. The relationship row is kept, never
hard-deleted, so the audit trail is permanent.entity.state == active.gracePeriodSeconds parameter — defers the final removed
transition so you can pause before the relationship is torn down.Instance-scope Actions run against a single entity. Find the tenant's entity ID, then trigger the Action on it:
This creates an ActionRun (who fired what, with which parameters, and how it
ended). The tenant transitions to deprovisioning, the runs_on edge moves to
removing, and the trigger fires — a TFC destroy run, or a PR your CI applies.
On a successful destroy/apply:
runs_on to removed.decommissioned.If the trigger fails, the tenant reverts to active (per onFailure) and the
runs_on edge stays in removing for you to investigate.
Pass gracePeriodSeconds > 0 to defer the final teardown — useful for a
compliance window or a "are you sure?" pause before infrastructure is destroyed:
What changes: on destroy success, instead of advancing the runs_on edge straight
to removed, Terrantula sets the edge to removing and schedules finalization for
now() + gracePeriodSeconds. A deprovision.scheduled audit event is written. The
entity is already decommissioned, so the fleet view shows the tenant as retired
while the relationship waits to be finalized.
Because Terrantula ships no scheduler, you finalize relationships whose grace period has elapsed. Two ways:
Sweep (your scheduled job). Wire a cron / CI schedule / worker task that calls
finalizeElapsedGracePeriods from @terrantula/api. It's idempotent — each row is
finalized exactly once — so it's safe to run on a loop:
Finalized rows emit a deprovision.finalized audit event with method: "sweep".
Force (operator escape hatch). Skip the remaining window for a single
relationship via the env-scoped endpoint. Returns 409 if the relationship has no
scheduled grace period:
This emits deprovision.finalized with method: "force".
The tenant is decommissioned, its runs_on relationship is removed (or held
then finalized), and every transition is in the audit log. The relationship row
survives as a permanent record.
| Event action | When | payload |
|---|---|---|
deprovision.scheduled | Grace period set on trigger | gracePeriodSeconds, scheduledAt |
deprovision.finalized | Relationship finalized | finalState, method: "sweep" | "force" |
Read them from the audit-events surface filtered by
action=deprovision.scheduled or action=deprovision.finalized.
deletion_scheduled_at is a lower bound on when finalization may occur, not a
guarantee. If you never run the sweep, the Terraform-managed infrastructure for the
held relationship is not torn down by your CI. Monitor for relationships in
removing with a deletion_scheduled_atin the past, and make sure a sweep job or
a force-finalize closes them out.
The Action opens a PR or fires a run against your runner; your CI runs
terraform destroy. Terrantula owns the lifecycle transitions, the cascade, and
the audit trail — not the destroy itself.
When a tenant spans several stacks (the golden path), deprovision walks the dependency graph in reverse— the leaf entity tears down before its upstreams, so removing an AWS account doesn't orphan the tenant that ran in it.
transition-entity, cascadeRules, conditions, parameters.