Phase 3 SP1 — Multi-tenancy (OIDC + RBAC + tenant scoping + quota)#15
Merged
Conversation
…ota) Brainstormed design spec for Phase 3 sub-project 1. Decomposes Phase 3 into 4 sub-projects; SP1 is the foundation: OIDC user auth + system-JWT, casbin RBAC, mandatory tenant_id scoping (Invariant 8), per-tenant quota (strong-consistent create check + event-sourced usage + minute snapshots). Closes G1/G2 (user-plane). Companion plan to be written next. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
14 bite-sized TDD tasks across 4 milestones (M1 identity core, M2 RBAC + tenant scoping, M3 quota, M4 isolation e2e + docs + PR). Complete code, no placeholders; self-reviewed for spec coverage / type consistency. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Pre-execution review (2 opus reviewers) found 4-5 BLOCKER + 5 IMPORTANT plan-level defects, all verified against code and fixed before impl: - "completed" -> "succeeded" (real terminal status) - model registration in db/models/__init__.py (not base.py) - no information_schema CI scan exists (real gate: tools/lint_invariants.py) - drop broken project_match -> SP1 tenant-scoped only - NOT-NULL quota/is_active seed values; snapshot-less-tenant race fix - quota.exceeded audit; casbin keyMatch + anchored regex - accurate CI command list (no ruff/mypy/code-vs-yaml CI jobs) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Folds Task 10's make_app_with_state casbin seeding here (Task 9 tests need app.state.casbin). quota.py is a Task-9 stub; Task 11 replaces the body. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Replace DLW_BEARER_TOKEN / _TOKEN bearer auth with DLW_SYSTEM_JWT_SECRET + principal_headers(role="tenant_admin") across all task-route test files. Switch bare create_app() clients to make_app_with_state(ephemeral_ca). Add casbin_rule, quota_snapshots, usage_records to test_alembic expected tables. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…rocess Module-top models import (create_all needs full Base.metadata in isolation); drop_all->create_all clean slate (session-DB Tenant id=1 collision); subprocess sets DLW_AUTH_DEV_MODE so the new SP1 fail-closed startup guard doesn't block this executor-only e2e. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Function-scoped bootstrap (clean DB per test) — the plan's module-scoped fixture leaked the prior test's tenant-A task into the quota test. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…CAL) Final whole-implementation review caught that app.state.casbin was set only in the test helper make_app_with_state, never in production create_app/ lifespan — every user-plane task/quota request would 500 with AttributeError in a real deployment (the whole suite masked it because ASGI tests bypass lifespan). Bootstrap settings + casbin (grants from casbin_rule, empty in SP1) unconditionally in lifespan, like the W3a auth substrate. Adds tests/test_lifespan_state.py: a regression test driving the REAL lifespan (would have caught this). Also commits the principal_headers/service_headers conftest helpers required by the migrated test suite. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This was referenced May 18, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Principal; casbin RBAC (require_perm, tenant-scoped); mandatorytenant_filteredscoping on all task routes (Invariant 8); per-tenant quota (strong-consistent insert-then-lock create check + event-sourcedusage_records+ leader-gated minutequota_snapshots).bearer_token; adds asystem_adminservice token +auth_dev_modefor the IdP-free path; fail-closed startup guard.usage_records/quota_snapshots/casbin_rule), idempotent default-tenant seed. Project-scoped RBAC deferred (SP1 = tenant-scoped only). Closes G1/G2 (user-plane). Phase 3 sub-project 1 of 4.app.state.casbinlifespan bootstrap — fixed + regression test added).Test Plan
uv run pytest -q→ 312 passed, 1 deselected (manual)E2E-MT-*tests/e2e/test_tenant_isolation.py(cross-tenant + quota isolation + service token)tests/test_lifespan_state.pydrives the REAL lifespan (casbin/settings bootstrap regression)python tools/lint_invariants.py+tools/test_lint_invariants.py+tools/lint_no_direct_status_write.pygreenspectral lint api/openapi.yaml --fail-severity=error(0 errors) +swagger-cli validategreenuv.lockupdated forauthlib/casbinSpec:
docs/superpowers/specs/2026-05-18-phase-3-sp1-multi-tenancy-design.mdPlan:
docs/superpowers/plans/2026-05-18-phase-3-sp1-multi-tenancy.md🤖 Generated with Claude Code