Prove deprovisioning reaches application authority
Python 3.11+; standard library and temporary SQLite database only.

Reference solution: not included here, so the exercise does not hand you the answer.
Attempt the repair first. Then download identity-lifecycle-solution.zip from the project card
on https://softwaresecurity.com/labs/#project-identity-lifecycle and extract it into this folder;
it adds reference.py without changing your files, and the reference commands below
need it.

This is a normalized provisioning lifecycle model, not a SCIM HTTP server,
PATCH parser or identity-provider connector. Input event_id/version fields belong
to this fixture's trusted adapter contract; SCIM does not define those event fields
or a globally ordered event stream. See the accompanying SCIM guide for protocol.

Objective: demonstrate that a directory disable must affect current credentials,
group-derived permission and queued work, including after retries and restarts.

1. Run python3 -m unittest -v test_lifecycle.py. Inspect the successful session and
   job tests, then the deliberate failures after disable. The same ID "42" and
   email exist in Acme and Beta; connector identity, not email, chooses a tenant.
2. Read model.py. Credentials record an authorization epoch; jobs also record the
   epoch and permission they needed when queued. A user has active/version/epoch.
   The normalized snapshot specifies desired active status and application grants.
3. Repair apply in exercise.py. Use BEGIN IMMEDIATE. Deduplicate by authenticated
   connector plus event_id and reject changed content under that ID. Ignore older
   or equal source versions without re-enabling a user. The ordered version is a
   fixture assumption; a real connector needs an actual source consistency plan.
4. For a newer snapshot, update active/version and increment epoch. Replace local
   grants, revoke credentials and cancel pending jobs in the same transaction as
   the event record. Disabled snapshots yield no local grants. A new enable grants
   only the explicit new list and must never revive old credentials or old jobs.
5. Repair authorize and dispatch. Check current user status, epoch and permission
   at use time. Tests intentionally miss a revocation flag or job-cancellation
   update to show why a current-authority check is a separate defense.
6. Run the crash test: interruption after the user update must roll back the entire
   transition, including the event receipt. Retrying must complete normally.
   Run all 18 tests. Compare reference.py after you can explain each invariant.
   Reference: EXERCISE_MODULE=reference python3 -m unittest -v test_lifecycle.py
   PowerShell: $env:EXERCISE_MODULE='reference'; python -m unittest -v test_lifecycle.py
   Remove the variable before evaluating your changes.

Keep: the before/after authority inventory, a duplicate/stale-event timeline and
the measured result for both tenants, both credential types and pending jobs.
Complete when: all 18 tests pass, old tokens remain dead after re-enable, group
removal prevents export, and Beta's identity remains usable after Acme is disabled.

Scope and transfer to a real service
The model stores opaque credential IDs, not real signed tokens or browser cookies.
The application owns one local transactional database. Its effect table is an
observable local job effect; no external action occurs. An already completed
effect is retained as history, not undone by disable. Enqueue/effect races and
cross-service propagation require additional protocols; a real SCIM success
response must not falsely promise that external systems completed revocation.
This exercise aggressively rotates all credentials for every changed snapshot;
a production product may scope invalidation more narrowly with explicit policy.
Treat connector authentication, least-privilege credential storage, mapping and
reconciliation as separate implementation work. Never use a mutable email as an
authoritative cross-tenant join key. Rotate/revoke connector credentials too.

Sources (checked 9 October 2026):
https://www.rfc-editor.org/rfc/rfc7643.html
https://www.rfc-editor.org/rfc/rfc7644.html
https://sqlite.org/lang_transaction.html

Dev container
.devcontainer/devcontainer.json opens this folder in VS Code Dev Containers or
GitHub Codespaces using the Dev Containers Python 3.13 image (Debian 13, Expat
2.8), pinned by digest. Run the commands above in its terminal.
