Skip to main content
Lifecycle

User and tenant lifecycle

This page covers what happens — and when — across the two lifecycles that matter most: the people you put on your org, and the org itself. Both lifecycles guarantee destruction is irreversible only when it must be (post-retention-window); every step before that is reversible.

User lifecycle

A user only exists in fremforge as a member of one or more orgs. Removing them from an org is a per-tenant action; deactivating their entire account only happens when they’re removed from their last org.

Removal from a single org

You remove a member from your org either manually (the Seat-management section in Billing lists every member with last-activity, supports bulk-remove, and offers an opt-in auto-cleanup policy) or via SCIM deprovisioning from your IdP. The behaviour is the same either way:

  1. The Forgejo org_member.removed event fires.
  2. fremforge recomputes seats_in_use from Forgejo’s live member list and emits a tenant.seat.released audit event. The freed slot becomes immediately available to your next invite or SSO-JIT sign-in.
  3. Any org-scoped PATs the leaving user holds for this tenant are revoked.
  4. If this was their last org, the orphan-deactivation flow below kicks in.

Your contracted seat cap is never reduced by cleanup. seats_in_use drops; the cap stays whole. To reduce the cap, use the Billing page (seat reductions take effect at the next renewal — annual prepayment is non-refundable per terms §4.7).

Auto-cleanup of stale accounts

The Members page lets an Owner set an inactivity threshold (default: disabled). Once set, a daily job removes members whose Forgejo last_login is older than the threshold. Owners, Operator accounts, and Bot accounts are never auto-removed; a per-tenant floor (default 30 days) prevents accidentally kicking members on long absences. The daily job is capped at 50 removals per tenant per day — a safety brake so a misconfigured policy can’t wipe a whole org in one tick.

Orphan deactivation (last org)

When the per-tenant removal above leaves a user with zero org memberships across all tenants, fremforge automatically:

  1. Sets prohibit_login=true on the Forgejo user (no more sign-in via SSO or local credentials).
  2. Revokes every Forgejo PAT the user owns, anywhere.
  3. Archives — does NOT delete — any personal-namespace repositories the user owns. Archive preserves history; the customer can restore by re-inviting before the 90-day hard-delete window closes.
  4. Records the deactivation in the orphaned_users book with cleanup_due_at = today + 90 days.

Re-inviting the user to any org within the 90-day window restores them: prohibit_login is lifted, PATs can be re-minted, and the orphan tombstone gets a restored_at stamp so the hard-delete job skips it.

Hard delete (orphan +90 days)

A daily job hard-deletes the Forgejo accounts that have been orphan-deactivated for more than 90 days and were not re-invited in that window. The Forgejo DELETE /api/v1/admin/users/{u} call removes the user account; the user’s archived personal repositories are removed transitively. The audit log retains a forgejo.user.orphan_hard_deleted event tied to the originally-triggering tenant for traceability.

After hard-delete, the username + account history are gone. There is no recovery beyond the 90-day window — provision a fresh user from your IdP if needed.

Tenant lifecycle

A tenant transitions through five states. The state machine is monotonic except for the cancellation_scheduled → active undo path and suspended → active reactivation path.

StateTriggerWhat it means
trialSignupFirst 30 days, no card required. Trial-suspension job moves to suspended if not converted by trial_expires_at.
activePaid plan startsNormal operation.
suspendedDunning past T+14, or trial expiredWrite access is paused; read + export + reactivate still work.
cancellation_scheduledCustomer initiated cancelService continues until cancel_effective_at. Undo lifts back to active any time before that date.
cancelledcancel_effective_at reachedForgejo org goes read-only. 60-day export + reactivate window.
deletedcancel_effective_at + 60dForgejo org and PII permanently destroyed. Certificate of destruction issued.

Cancellation initiated

When you click “Cancel” on the Billing page:

  • The status moves to cancellation_scheduled and cancel_effective_at is set (monthly term: +30 days; annual term: the prepaid commitment end-date — annual is non-refundable per terms §4.7).
  • Any in-flight overage is settled to the active payment method.
  • The Mollie SEPA mandate is revoked so no further automatic charges fire.
  • A confirmation email lands at every recipient in billing_emails. The Billing page now shows a countdown banner with the read-only-ends date, an export-data CTA, and an undo button.

The undo button is available right up until cancel_effective_at — and again during the 60-day read-only window via the reactivate button (a fresh payment method is set up through the standard checkout).

Cancellation effective (+0 days)

At cancel_effective_at the daily cancellation-effect job flips the status to cancelled and archives every repository in the Forgejo org (read-only). The org itself stays, the data stays — write access is paused everywhere except Billing (for reactivation) and Data export (for portability).

Permanent deletion (+60 days)

Sixty days after the cancellation took effect, the same daily job performs the physical erasure. Gates the deletion will respect — and any one of them blocks the purge for that day:

  • legal_hold = true on the tenant (an operator-set preservation flag for legal-hold preservation orders).
  • Any export job still queued or running.
  • Any in-flight payment (Mollie webhook status open, pending, or authorized).

When all gates clear, fremforge:

  1. Emits the tenant.physically_deleted audit event (recorded while the tenant scope is still valid so the audit chain captures the action).
  2. Revokes the per-tenant Renovate bot account and revokes its PAT.
  3. Purges any queued webhook-delivery jobs for the tenant.
  4. Deletes the Forgejo org — repositories, LFS objects, and the org’s full state cascade.
  5. Deletes the Mollie customer record.
  6. Erases the tenant’s PII (display name, billing contact email, billing emails, Mollie/Dinero references, VAT number) and marks the tenant deleted.
  7. Generates the certificate of destruction PDF (see below) and archives it in OBS under the legal-hold-protected bucket. Emails the certificate to the captured billing-contact address.

Certificate of destruction

A one-page PDF generated at physical-delete time and emailed to the billing contact. It records:

  • The org slug, display name, VAT number, and tenant identifier.
  • The cancellation-effective date and the permanent-deletion date.
  • The categories of data destroyed (Forgejo content, tenant configuration, scoped member accounts, audit payloads, billing PII).
  • The residual retention carve-outs that still apply (see below).
  • A SHA-256 anchor hash that pairs the certificate with the tenant.physically_deleted audit event for cross-verification.

If you need a re-issued copy later, the archive lives in a WORM-locked OBS bucket — email compliance@frem.sh and we’ll re-issue from the archive.

Retention windows (DPA §9)

Time from cancellationWhat happens
+0 days (cancel_effective_at)Status → cancelled. Forgejo org archived, read-only. Export + reactivate available.
+60 daysStatus → deleted. Forgejo org physically deleted. PII nulled. Mollie customer removed. Certificate of destruction issued.
+90 daysAudit-event PII pseudonymisation runs against rows where deleted_at is at least 90 days old.
+120 daysBackups containing the tenant are purged. Total termination-to-backup-clear: 120 days.

Residual carve-outs

Some categories of data have statutory retention that survives the +60d primary deletion. These are documented in DPA §9 and reflected on the certificate of destruction:

  • Accounting records (invoices, voucher attachments): retained 5 years per Bogføringsloven §10.
  • Audit log metadata: retained 3 years, pseudonymised at +90d; no repository content, no PII.
  • WORM-locked compliance objects (audit-chain anchors, the certificate-of-destruction PDF itself): retained until the lock period expires. Documented carve-out in DPA Annex §9.

Legal hold

Operators can set legal_hold = true on a tenant in response to a preservation order (subpoena, GDPR investigation, dispute). While set, the cancellation-effect job skips the tenant entirely — no physical delete, no PII null-out, no backup purge. The flag is operator-only; customers cannot toggle it.

Common questions

My seat shows used even though the member left. Forgejo’s member list filters out users with prohibit_login=true, so a deactivated last-org user doesn’t count against your seats_in_use. If your number looks stale, the nightly seat-cap-drift-check job recomputes it.

Will downgrading the seat cap refund me? No — seat reductions take effect at the next billing cycle (monthly) or annual renewal (annual). Annual prepayment is non-refundable per terms §4.7. See Billing for the seat-cap form.

Can I get my data after the 60-day window? No. By the time you read this, it’s already gone (Forgejo org, repos, LFS, members, PII). Backups containing it are purged at +120 days. Plan exports during the read-only window — the Billing page surfaces an export CTA exactly for this reason.

Does GDPR Article 17 erasure happen at cancellation or at +60 days? The +60-day mark is when primary storage is wiped. Backups follow within another 30 days (+90 total from deleted_at, or +120 from cancellation). The certificate of destruction is the artifact you can show your regulator.

What if I cancel by mistake? Click the undo button on the Billing page — it works the full grace window (until cancel_effective_at). After that, the read-only window gives you 60 more days to reactivate via the standard checkout. After +60d there is no recovery.