# Track A Status

| Task | Status | Branch | Notes / blockers |
|---|---|---|---|
| T-A-01 | ✅ merged | track-a/T-A-01-base-domain-model | BaseDomainModel + 5-DB scaffolding. |
| T-A-02 | ✅ merged | track-a/T-A-02-audit-events | audit_events + AuditService + Auditable hooks + GET /api/audit (immutable). |
| T-A-03 | ✅ merged | track-a/T-A-03-verification-tokens | verification_tokens + TokenService (issue/verify/consume, TTL, cooldown, lockout) + phone column. Full suite green. |
| T-A-04 | ✅ merged | track-a/T-A-04-rbac-engine | RBAC engine: roles/permissions/role_permission/user_role on identity_billing, RbacService (union, deny-by-default, institute hard boundary), `Can` middleware (`rbac.can:`), RolesController CRUD, RbacDefaultsSeeder. Audit endpoint gated by `rbac.can:audit.view`. |
| T-A-05 | 🚀 re-pushed (ownership-fixed; on new main 0d54b58) | track-a/T-A-05-response-envelope | ApiResponse trait + EnvelopeExceptionHandler wired in bootstrap/app.php (subsumes T-A-03 TokenStatusException render). **Ownership fix**: the orchestrator rejected the first push (`ownership_rejected`) because `app/Http/Concerns/` and `docs/api-conventions.md` are outside Track A's owned paths. Trait moved to `app/Exceptions/ApiResponse` (A-owned); the conventions doc is now the README "API Response & Error Conventions" section. Rebased onto main 0d54b58 (post T-A-06 merge); 74 passing, 0 failing. |
| T-A-06 | ✅ merged | track-a/T-A-06-domain-events | 11 owned contract events in app/Events/{Identity,Billing} (all ShouldQueue); 10 B/C/D same-shape test doubles; QUEUE_MAX_TRIES=3. Ownership fix (queue doc folded into README "Queue & domain-event dispatch policy" + new README "Contract Change Log") landed the merge. Merged to main as 0d54b58. |
| T-A-24 | ✅ merged | track-a/T-A-24-api-keys-rates-webhooks | API keys (issue/rotate/revoke, full key shown once, scope-gated), rate-limit policies (key>endpoint>global floor, burst+sustained, 429+Retry-After), webhook endpoints (admin CRUD + signing-secret rotation, 24h retry budget, IP allowlist). Models/services on identity_billing; `apikeys.manage` RBAC permission seeded. Merged to main as 5f33d23. |
| T-A-24f | ✅ merged | track-a/T-A-24-followup-fix-validation-500 | Bug fix: `RateLimitController` threw `ValidationException::withMessages()` without importing `Illuminate\Validation\ValidationException` → duplicate-subject / over-floor policy writes 500'd instead of 422 (merged 13-test suite never hit these paths). Added the import; removed redundant explicit `AuditService` logging (Auditable model hook already records create/update/delete with before/after snapshots, so double-logging was noise); set deterministic `APP_KEY` in the A-owned `BootsIdentityBilling` trait so the webhook `encrypted` cast works in a fresh checkout (no `.env`); broadened the test suite 13 → 28 covering over-scope 422, hash-only-at-rest, lookup-by-plaintext, sliding-window sustained + burst, global-floor clamp, retry-budget 422, and the two bug-triggering paths. 61 passing, 0 failing (full suite). Merged to main as 604496d. |
| T-A-07 | ✅ merged | track-a/T-A-07-user-model-v2 | User model v2: 11 type enum, status (active/suspended/deactivated), provenance (self/admin), pending_identity fields. AuthService: register per type (type-specific onboarding), login (timing-safe dummy-hash, 423 lockout), suspended 403, deactivated 403, one-phone-one-active 409, email/phone change (pending identity + re-verification, old stays active until new verified). Identity\AuthController + routes on api_identity.php. PAT moved to identity_billing/ (A-owned path). 14 new tests; rebased onto main with T-B-04/T-C-03 merged (173 passing, 0 failing). Merged to main as 24f1792. |
| T-A-08 | ✅ merged | track-a/T-A-08-security-policy | Security policy on identity_billing: (1) 2FA — TOTP enrollment (secret + otpauth URI + 10 single-use hashed backup codes, plaintext shown once), SMS phone fallback (6-digit single-use code, TTL, provider stub), per-role mandatory 2FA via `roles.requires_2fa` (enrollment-forced login flow); (2) progressive lockout — per-account AND per-IP + separate 2FA counter, 5 failures → 423, doubling duration to a cap; (3) `ip_rules` CIDR allow/deny, deny-beats-allow, checked BEFORE credentials (403); (4) `geo_restrictions` platform-level. Admin surface `/api/security/*` gated by `rbac.can:security.manage`. Merged to main as adaf591. |
| T-A-09 | 🚀 pushed (rebased onto main 7e09d0b) | track-a/T-A-09-account-lifecycle | User account management: directory (GET /api/users filters type/status/org/plan/q + pagination + org-hierarchy view, GET /api/users/{id} with subscriptions), admin create (provenance=admin) + profile update (email/phone locked to the re-verification flow → 422), lifecycle (suspend = IMMEDIATE: EnsureActiveAccount guard 403s the existing token on the next request on ANY endpoint + subscriptions paused → recurring-charge gate off; activate restores account + unpauses; deactivate = irreversible: subscriptions cancelled, seats released, tokens revoked, UserDeactivated → T-A-25 privacy flow; re-activation ONLY via POST /users/{id}/restore retention-restore path with recorded reason). Bulk ops are preview-then-commit (POST /users/bulk/{register,status,enroll}/preview → /{bulkId}/commit with confirm=true + reason): row-level validation with per-item reasons + original line numbers, duplicate emails MAPPED (not created), MAX_ROWS=5000, one audit event per op (`bulk.*_committed`), bulk status never touches deactivated accounts, bulk enroll only active accounts. `EnsureActiveAccount` registered on the api group (resolves the bearer user itself — guard group runs before route-level auth:sanctum); `auth:sanctum`+`rbac.can:users.manage` on /api/users*. New: migration 20260926_000001 (subscriptions, seat_assignments, bulk_operations), models Subscription/SeatAssignment/BulkOperation, services DirectoryService/AccountLifecycleService/BulkService, UserController, EnsureActiveAccount middleware. 13 new tests (UserLifecycleTest): 520 passing, 0 failing (full suite after rebase). **Ownership fix (ac8dd39):** the 3 services were first placed under `app/Services/Identity/`, which is NOT in Track A's ownership matrix (docs §2 lists A's service dirs as Auth/Rbac/Audit/Token/Subscription/Billing/Seats/DataProtection/Settings/ApiKey; the orchestrator `OWNED` map agrees) — the merge gate would have rejected the branch (`ownership_rejected`). Relocated DirectoryService/AccountLifecycleService/BulkService to `app/Services/Auth/` (same home as T-A-07/08's user-domain services), updated namespace + controller use-imports, no behavior change; re-verified the full branch diff is 100% A-owned. 520 passing, 0 failing. |
| T-A-10…T-A-23, T-A-25 | ⏭ next | — | Proceed in task-list order per skip-ahead rule. |

## Gate 1 (Phase 0 complete)

**GATE 1 passed** — 2026-09-27: T-A-05 merged to main (4ad28ae) after T-A-06
(0d54b58). All six Phase 0 tasks (T-A-01…T-A-06) + T-A-24 are on main; tracks
B/C/D are unblocked to start. Their pre-flight blockers
(`track-b/status-pre-flight-blocked` / `track-c/pre-flight-status`) can be
closed: the "missing" artifacts — BaseDomainModel, envelope, RBAC middleware,
events bus — all exist on main now. Track A continues with T-A-07 (User
model v2 + AuthService), the first Phase-1 task.


