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 are designed so that destruction is irreversible exactly when it needs to be, and reversible everywhere else.
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:
- The Forgejo
org_member.removedevent fires. - fremforge recomputes
seats_in_usefrom Forgejo’s live member list and emits atenant.seat.releasedaudit event. The freed slot becomes immediately available to your next invite or SSO-JIT sign-in. - Any org-scoped PATs the leaving user holds for this tenant are revoked.
- 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:
- Sets
prohibit_login=trueon the Forgejo user (no more sign-in via SSO or local credentials). - Revokes every Forgejo PAT the user owns, anywhere.
- 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.
- Records the deactivation in the
orphaned_usersbook withcleanup_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.
| State | Trigger | What it means |
|---|---|---|
trial | Signup | First 30 days, no card required. Trial-suspension job moves to suspended if not converted by trial_expires_at. |
active | Paid plan starts | Normal operation. |
suspended | Dunning past T+14, or trial expired | Write access is paused; read + export + reactivate still work. |
cancellation_scheduled | Customer initiated cancel | Service continues until cancel_effective_at. Undo lifts back to active any time before that date. |
cancelled | cancel_effective_at reached | Forgejo org goes read-only. 60-day export + reactivate window. |
deleted | cancel_effective_at + 60d | Forgejo org and PII permanently destroyed. Certificate of destruction issued. |
Cancellation initiated
When you click “Cancel” on the Billing page:
- The status moves to
cancellation_scheduledandcancel_effective_atis 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 = trueon 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, orauthorized).
When all gates clear, fremforge:
- Emits the
tenant.physically_deletedaudit event (this happens BEFORE the row mutation so the audit chain captures the action under the still-valid tenant scope). - Revokes the per-tenant Renovate bot account and revokes its PAT.
- Purges any queued webhook-delivery jobs for the tenant from the Redis queue.
- Calls Forgejo
DELETE /api/v1/orgs/{slug}— the org goes; repositories, LFS objects, and the org’s full state cascade. - Deletes the Mollie customer record.
- Nulls the tenant’s PII columns (display name, billing contact email, billing emails, Mollie/Dinero references, VAT number), sets
status = 'deleted', setsdeleted_at = now. - 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_deletedaudit 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 cancellation | What happens |
|---|---|
| +0 days (cancel_effective_at) | Status → cancelled. Forgejo org archived, read-only. Export + reactivate available. |
| +60 days | Status → deleted. Forgejo org physically deleted. PII nulled. Mollie customer removed. Certificate of destruction issued. |
| +90 days | Audit-event PII pseudonymisation runs against rows where deleted_at is at least 90 days old. |
| +120 days | Backups 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.