# 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 | ✅ merged | 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 | ✅ merged | 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-12 | ✅ local integration | track-a/T-A-12-tenant-accounts | Tenant accounts, membership, invitations, verification and transactability gate integrated. A-owned adapters bind D corporate, CSR, organization and sponsor gate contracts to identity_billing tenant records. |
| T-A-10 | ✅ local integration | codex/guardian-consent-integration | Account links require confirmed, verified parent relationships; institute seat transfers are scoped and transactional. |
| T-A-11 | ✅ local integration | codex/guardian-consent-integration | Consent history records actors; minors cannot self-grant guardian consent; linked verified guardians can grant or withdraw. |
| T-A-13 | ✅ local integration | codex/T-A-14-pricing | Membership plans and public self-service trials; trials create a scoped seven-day subscription. |
| T-A-14 | ✅ local integration | codex/T-A-14-pricing | Effective-dated prices, FX, tax, proration, bulk tiers, and enterprise quotes with guarded arithmetic and immutable snapshots. |
| T-A-15 | ✅ local integration | codex/T-A-15-discounts | Server-priced discounts, bounded affiliate grants, stable A/B assignments, and usage reports grouped by currency. |
| T-A-16 | ✅ local integration | codex/T-A-16-affiliate-config | Versioned affiliate configuration on identity_billing, application review/reopen, referral state history, A15 grant visibility, and payout decisions bridged to D. A binds D's affiliate eligibility contract to identity user type/status/referral flags; D still needs consumers for multi-level rewards and payout scheduling. |
| T-A-17 | ✅ local integration | codex/T-A-17-subscriptions | Payment-gated subscription lifecycle, owner-scoped APIs, recurring billing, grace/lapse, prorated plan changes, immutable pricing snapshots, and scheduled processing. Gateway transaction handling is now in T-A-18; invoice/refund integration is in T-A-19. |
| T-A-18 | ✅ local integration | codex/T-A-18-payments | Encrypted gateway configuration, Stripe adapter, idempotent signed webhook inbox, transaction ledger, failure/chargeback/refund processing, and additive method-aware checkout; other payment-method adapters remain future work. Initial checkout retry regression now verifies configured delays, no early retry, attempt-specific idempotency keys, and retry-state reset after success. Subscription lifecycle + gateway tests passed 13/13 (76 assertions); prior branch full suite was 803/803. |
| T-A-19 | ✅ local integration | track-a/T-A-19-invoices-refunds | Sequential immutable invoices and tax/proration snapshots; bounded credit notes; approved, idempotent gateway refunds; adjustments; suspension pause/reactivation resume. Added a full-refund lifecycle regression proving the source transaction is refunded, a credit note is issued, the subscription lapses, auto-renew stops, and entitlement is revoked (InvoiceRefundTest now 8 tests). Opt-in `InvoiceSequenceConcurrencyTest` verifies a competing issue call waits on the sequence lock and receives consecutive numbering against isolated socket-only MariaDB (1 test, 7 assertions); default SQLite run skips cleanly. |
| T-A-20 | ✅ local integration | codex/T-A-20-revenue-reconciliation | Identity-billing revenue/tax, FX snapshots, transaction reconciliation, A16-rate commission/payout ledger, production subscription-health feed, and finance report APIs. FinanceLedgerTest 8/8 (59 assertions); related A billing/event tests 22/22 (152 assertions). Integrated suite 854/854 (5,063 assertions), including the D dashboard bound to both live A20/A21 feeds. Reconciliation reads A18 transaction references/amount/currency; reports snapshot A19 invoice lines and credit notes; FX rows retain rate + A14 exchange-rate ID. D must call `CommissionLedgerService::createPending` for A16-approved attributed conversions/renewals to write commissions in identity_billing; no D engagement DB joins. |
| T-A-21 | ✅ local integration | codex/T-A-21-seats-license-pools | Identity_billing seat tiers, gateway-paid pools/renewals, transactional cap-checked assignments, history, expiry processing, entitlement and D contract adapters. Expiry clears learner and corporate active keys; D's finance test now asserts the production `SeatLicenseService` feed. Opt-in `SeatPoolConcurrencyTest` races a second learner against a locked one-seat pool and verifies a 409 with assigned capacity remaining at 1 (1 test, 7 assertions) against isolated socket-only MariaDB; the default SQLite run skips cleanly. Corporate employee IDs remain opaque D IDs, and D's org-seat state remains D-owned and separate from A's department/group pool scope. |
| T-A-22 | ✅ local integration | codex/T-A-22-compliance-fresh | DSAR access/export requires password verification and exports owner-scoped profile/consent/subscription data with third-party participants excluded. T-A-25 adds B/C/D producer adapters for records with direct owner relations; ownerless catalog records and media remain excluded. Deletion is blocked by deactivation/retention requirements, legal holds, active subscription/grace balance, issued/overdue invoice balance, chargeback transaction, assigned seat, or open support ticket. Policies, child safety, age verification, creator screening, DMCA takedowns and content-protection/license APIs are implemented. A dispatches `InappropriateContentReported` after persisting and auditing a report; D consumes it through a queued, idempotent moderation listener. DMCA remains without an acknowledgement channel. Deletion anonymizes identity while retaining numeric financial references and uses a single-use 90-day token; tests cover invoice and chargeback guards. |
| T-A-23 | ✅ local integration | codex/T-A-23-system-settings | Audited, versioned identity-billing settings with rollback; branding and protected localized templates; locale/currency/time-zone defaults; trial/freemium policy; content/download policy; notification defaults; maintenance gate; banners/popups; and scheduled-notification storage. Trial policy is wired to T-A-13; feature toggles default off and hide disabled routes. B consumes `DownloadPolicyService::effectivePolicy` and `consumeSuccessfulDownload` plus A22 content-protection/license checks after its entitlement gate. C has no catalog-file download endpoint; future file-download paths must call policy after `AccessGate`. D reads notification defaults through `SystemSettingsService` and delivers scheduled notices through its preference-aware service and one-minute scheduler command. Focused settings + membership tests 20/20; branch full suite 827/827 (4,866 assertions). |
| T-A-25 | ✅ local integration; DR provider blocked on infra/security contract | codex/T-A-25-backup-export-dr | Audited identity_billing backup schedules/jobs, retention pruning, restore requests with measured RPO/RTO, and per-scope DR readiness. `BackupArtifactProvider` defines snapshot/restore/delete operations, but no infrastructure provider is bound, so backup runs fail explicitly and readiness remains not-ready. A local encrypted-file implementation is not production-safe with current configuration: the only private disk is `storage/app/private` on the application host; there is no dedicated/off-host backup target or capacity/availability policy, no independent backup key/KMS and rotation/recovery policy (Laravel `APP_KEY` is not an appropriate sole recovery key), and no configured DB snapshot utility/principal/consistency strategy for the five MySQL domain connections. More critically, `restoreSnapshot(scope, artifactReference)` has no isolated target, verification/promotion, maintenance-lock, or explicit destructive-restore approval contract, so implementing it against the live named DB risks overwriting production data; the provider interface also has no readiness/health operation. Before binding a production provider, infrastructure must supply an off-host private target, backup encryption key management, least-privilege consistent-snapshot credentials/tooling and a verified isolated restore target/promotion procedure; the contract should represent restore target and readiness explicitly. Keep backup execution failing closed and DR not-ready until these are configured. Owner-scoped JSON/CSV exports cover identity/financial data, institute records selected through active `student_institute` links, A-owned content license/protection metadata, and B/C/D rows through the tagged `ExportDataSource` contract. Adapters query their own domain database and filter by requester; catalog records without explicit ownership, media bytes/keys, transcripts, and secrets are excluded. Export jobs split output at 100,000 rows and issue hashed opaque download links that expire after 24h with cleanup. Verified T-A-22 portability requests link to export jobs; export remains read-only during legal holds. Local readiness now requires a successful restore of the latest completed snapshot and measured RPO/RTO within configured targets. Focused `BackupExportTest` passed 14/14 (90 assertions); the prior branch full suite was 859/859 (5,086 assertions). |

### Local security regression follow-up — 2026-10-03

Additional endpoint-level T-A-08 coverage verifies progressive per-account and
per-IP lockout isolation, independent 2FA OTP lockout counters, overlapping allow
and deny CIDR evaluation before credential checks, known-country geo denial before
credentials (while unknown IP locations remain unblocked), mandatory TOTP
enrollment, and single-use backup codes. `SecurityPolicyTest` plus
`UserAuthServiceTest` cover these security acceptance paths.

An audit sweep found that T-A-07's current-session logout and named-session
revocation deleted Sanctum tokens without recording the actor in the immutable
audit stream. Both endpoints now emit explicit `auth.logout` / `auth.session_revoked`
events with token identifiers and minimal before/after revocation snapshots;
regressions assert actor and target. `UserAuthServiceTest`: 15 passed, 222
assertions.

The data-protection audit sweep also found that password-verified deletion requests
blocked by a pending retention/financial prerequisite were moved to `granted` and
issued a single-use completion token without an audit event. They now record
`privacy.deletion_granted` with the actor, prior status, blocker reason, and token
issuance flag (never the token or its hash). `DataProtectionComplianceTest` asserts
the actor and snapshots.

The billing sweep found that full-refund processing lapsed its linked subscription
through a query-builder update, bypassing the subscription audit hook. Refund
processing now locks and saves the subscription model so the status, auto-renew,
and refunded grace-state transition is snapshotted. `RefundSubscriptionAuditTest`:
1 passed, 8 assertions.

The final billing write-path sweep checked learner and corporate license-pool
occupancy counters. Laravel's Eloquent model `increment`/`decrement` fires model
update events, so these writes already use `LicensePool`'s audit hook; no service
rewrite was needed. `LicenseManagementTest` now verifies before/after counter
snapshots for learner and corporate assign/release flows: 6 passed, 45 assertions.

Invoice download remains open. `routes/api_billing.php` exposes owner-scoped JSON
history/detail and a seven-day tokenized JSON share view, but has no file-download
or PDF route. `InvoiceController` has no renderer contract, and `composer.json` /
`composer.lock` contain no PDF renderer dependency or document-storage provider.
Student billing requires invoice download plus view/download event logging; the
Super Admin spec calls for a selected format without defining the supported
format. Institute-branded invoices additionally require logo, colors, and name,
which are not defined as a complete renderer input contract in current invoice
snapshots. Detail retrieval now emits `billing.invoice_retrieved` with actor,
invoice ID/number, and timestamp. Invoice history now supports owner-scoped status
and issued-date range filters, with pagination metadata and an audit event for each
successful list request; `InvoiceRefundTest` covers filtering, paging, invalid ranges,
and actor attribution. Invoice exports and download events remain unimplemented.
Confirm the supported format, branding source/snapshot, and full
retrieval/download event semantics before adding a renderer dependency or download
route. T-A-19's current immutable invoice record and expiring JSON share surface
remain implemented.

The Training Institute payment-history surface now reads `billing_transactions`
through explicit owner-scoped projections: paginated date-range history, owner-scoped
detail, and CSV export. Responses and exports contain only transaction ID, date,
minor-unit amount, currency, method, and status; provider references, idempotency
keys, metadata, payment tokens, and gateway credentials are excluded. List, detail,
and export operations are actor-audited; reversed dates and page sizes above 100
are rejected. `PaymentHistoryTest`: 4 passed, 49 assertions.

The related Super Admin invoicing spec also calls for generated receipts. Current
billing stores payment facts in `billing_transactions`, but A has no receipt model,
generation endpoint, or receipt artifact contract; the existing engagement receipt
references are separate workflows. Decide whether the receipt is a structured API
projection or a downloadable document, and define its fields/format before closing
that product requirement.

The token write-path sweep found that resend flows invalidated live prior tokens
with a bulk update but emitted no audit event. They now log each invalidated token
ID and kind without recording token/code/hash material. The resend cooldown also
used a reversed signed time difference, so valid resends remained rate-limited after
the 60-second interval; the comparison now measures elapsed time from token issue.
`VerificationTokensTest`: 11 passed, 48 assertions.

## 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.
