Stratify
Legal and compliance

Joiner / Mover / Leaver Procedure

docs/compliance/isms/procedures/PRO-01-joiner-mover-leaver.md

Source updated 03. Aug. 2026

Joiner / Mover / Leaver Procedure

Status: written ahead of the event it describes

Stratify is a single-engineer team today (register/facts.md, "Team / access" — one human identity across all commits in git log). Nobody has ever joined, moved, or left under a formal process, because nobody besides Toby has held technical access to begin with. This procedure is written for the first hire or contractor, not as a retrofit of something that has already happened. Treat every step below as the target process, not a description of past practice.

The two ISMS roles that exist today — ISMS Owner (Toby) and DPO/Privacy Owner (Antonios) — were assigned by direct confirmation with Toby on 2026-08-01 (register/facts.md), not through this procedure. This procedure governs everyone who comes after.

Scope

Applies to anyone who is granted, changes, or loses access to Stratify systems: engineers, contractors, and any future non-engineering hire who needs elevated application roles (e.g. a compliance-role account). It does not cover subscriber or partner account lifecycle — see PRO-03 for partner onboarding/offboarding and PRIV-09 for subscriber data-subject rights.

Systems in scope for any access grant or revocation:

  • GitHub (thoughtful-toby/stratify repo — read, write, or admin)
  • Supabase / Postgres: application-level subscriber_roles (admin, founder, compliance

roles per apps/web/lib/auth/roles.ts:4), and the Supabase service-role API key, which structurally bypasses RLS (register/facts.md, "Database & access control")

  • Hetzner production host: Tailscale network membership + SSH access

(docs/runbooks/deploy.md:3-19)

  • Secrets: macOS Keychain entries and any production env vars the role needs

(docs/secrets.md)

  • Any third-party vendor console the role needs (Sentry, PostHog, AWS SES, Stripe)

1. Joiner

Least-privilege by default: grant only the access a role needs to do its actual work, not a copy of Toby's own access.

  1. Role definition. Before access is granted, write down in one sentence what the person

does and which application role(s) they need (subscriber, pilot, compliance, founder, adminapps/web/lib/auth/roles.ts:4), whether they need repository write access, and whether they need any production system access at all. Most contractor roles (e.g. a frontend contractor) need none of the latter two.

  1. GitHub access. Add as a collaborator with the minimum role (read for review-only,

write for a contributor who merges their own PRs under the CI gate). Do not grant admin unless the person is taking over ISMS Owner or repository-administration responsibilities.

  1. Application role grant. If the role needs an elevated subscriber_roles entry

(admin, founder, compliance), the ISMS Owner inserts the row via the admin action path or a direct, audited service-role write — never by sharing the service-role key itself with the new person. Elevated writes already call recordAudit() (register/facts.md), so the grant itself becomes an audit_log entry.

  1. Secrets / Keychain access, if applicable. Only for engineering roles that need local

development access. Follow docs/secrets.md's existing pattern: the person adds their own Keychain entries for secrets they're handed, sourced via their own ~/.keychain-secrets.sh. No secret is ever transmitted in plaintext over chat or email — hand it over via a channel that isn't persisted (verbal, or a password manager share), then have the person confirm the Keychain entry exists.

  1. Production/Tailscale access, if applicable. Only for a second engineer who needs to

operate the Hetzner host directly. Add them to the Tailscale network as a separate identity (not a shared key), and confirm ufw's Tailscale-only policy still applies (docs/runbooks/deploy.md:3-19).

  1. Security-awareness briefing. Before the person's first production-affecting change, walk

through the onboarding checklist in GOV-09: CI gate and secure-coding basics, secrets handling, the incident-reporting path (PRO-08 once written), and access-provisioning/removal expectations (this document). Record a dated, named acknowledgement that the briefing happened — this is the evidence record for this step.

  1. Evidence produced. A dated access-change log entry (see "Evidence" below) plus the

audit_log row(s) from step 3, plus the signed GOV-09 acknowledgement from step 6.

2. Mover

A role change (e.g. a contractor becomes a full engineer, or someone moves off the compliance role) is treated as a joiner-for-the-new-access and a leaver-for-the-old-access, not a silent edit.

  1. Determine the new role definition, same one-sentence exercise as Joiner step 1.
  2. Provision the new access following the relevant Joiner steps above for whatever is newly

needed.

  1. Revoke the access that no longer matches the new role — do not leave old subscriber_roles

entries, GitHub permissions, or Keychain-shared secrets in place "just in case." If the person is dropping the compliance role, remove that subscriber_roles row explicitly; don't rely on it becoming stale.

  1. Evidence produced. A dated access-change log entry recording both the grant and the

revocation, with the audit_log row IDs for each.

3. Leaver

Leaver revocation is the highest-priority case here — an unrevoked credential after departure is a live risk, not a documentation gap. docs/secrets.md's rotation table already states the governing rule: "Engineer leaves → rotate every secret they could have read. Service-role first." This procedure operationalizes that line into a checklist.

Leaver checklist (ISMS Owner executes, or delegates and verifies):

  1. GitHub — remove the person as a collaborator; if they held admin, transfer or revoke

any personal access tokens tied to their account that touch the repo.

  1. Supabase / application roles — remove their subscriber_roles row(s) entirely (don't

just downgrade). If they ever held or could have read the service-role key, treat the service-role key itself as compromised — see step 4.

  1. Supabase / DB access — if they had direct Postgres access (e.g. via

scripts/with-stratify-hetzner-supabase-db.sh or a personal STRATIFY_DATABASE_URL), revoke that connection string / credential.

  1. Secrets rotation — rotate every secret the person could have read, service-role key

first, per docs/secrets.md's rotation table. In practice today that means, at minimum: STRATIFY_SUPABASE_SERVICE_ROLE, STRATIFY_DATABASE_URL, and any vendor key (STRATIFY_STRIPE_SECRET_KEY, AWS SES keys, STRATIFY_VAPID_PRIVATE_KEY, STRATIFY_CRON_SECRET, STRATIFY_PARTNER_WEBHOOK_SECRET) they had Keychain access to or could reach on the production host. Use the manual deploy path (infra/hetzner/deploy-web-hetzner.sh) to push rotated production secrets — the pull-based deploy timer never writes secrets (docs/runbooks/deploy.md, "Deliberately not automated").

  1. Tailscale / host access — remove their Tailscale node/identity from the tailnet; confirm

ssh [email protected] no longer accepts their key.

  1. Vendor consoles — remove their access from Sentry, PostHog, AWS SES, Stripe, and any

other third-party console they had a seat on.

  1. Local Keychain entries — this is the departing person's own responsibility to destroy on

their own machine, but the ISMS Owner should not treat that as sufficient — rotation (step 4) is what actually neutralizes the risk, regardless of what happens to their local copy.

  1. Confirm and log. Once all of the above is done, write the dated access-change log entry

listing exactly what was revoked/rotated and when. This is the evidence artifact for the leaver event.

Evidence

Every joiner/mover/leaver event produces a dated markdown entry under docs/compliance/isms/evidence/, one file per event (e.g. evidence/2026-MM-DD-leaver-<name-or-role>.md), listing:

  • Event type (joiner/mover/leaver), person, role, date
  • Each access system touched and the action taken (granted/changed/revoked)
  • Relevant audit_log row ID(s), where the action went through an audited code path
  • Who executed the checklist and who verified it (today, the same person — see PRO-02 for why

that's an honestly-stated limitation, not a hidden one)

Review

This procedure has never been exercised. It should be reviewed and, if necessary, corrected the first time it actually runs — a written-ahead-of-practice procedure that survives its first real use unchanged is the exception, not the rule. Otherwise reviewed annually alongside the other Tier 2 procedures.