docs(05): create gap closure plans for milestone audit findings

Phase 05: 2 gap closure plans in 1 wave
- 05-06: P0 tenant scoping fix (6 models) + CASL subject correction
- 05-07: E2E test coverage gaps + Phase 2 verification correction
- Both plans are parallel (Wave 1, no dependencies)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
kevin-asprec
2026-03-05 18:15:44 +08:00
parent e70501e8bc
commit 9fdca2ccf7
3 changed files with 348 additions and 9 deletions

View File

@@ -16,7 +16,7 @@ Decimal phases appear between their surrounding integers in numeric order.
- [x] **Phase 2: Subscriber and Billing Core** - Subscriber management, billing engine, payment recording, and the double-entry accounting ledger - [x] **Phase 2: Subscriber and Billing Core** - Subscriber management, billing engine, payment recording, and the double-entry accounting ledger
- [x] **Phase 3: Operational Modules** - Collector workflow, ticketing system, job orders, and technician management - [x] **Phase 3: Operational Modules** - Collector workflow, ticketing system, job orders, and technician management
- [x] **Phase 4: Inventory, Expenses, and Financial Reports** - Inventory event-ledger, asset management, expense tracking, and accounting financial statements - [x] **Phase 4: Inventory, Expenses, and Financial Reports** - Inventory event-ledger, asset management, expense tracking, and accounting financial statements
- [x] **Phase 5: Visibility and Client Portal** - Dashboard metrics, client self-service portal, integration tests, and end-to-end tests - [ ] **Phase 5: Visibility and Client Portal** - Dashboard metrics, client self-service portal, integration tests, and end-to-end tests (gap closure in progress)
## Phase Details ## Phase Details
@@ -113,8 +113,8 @@ Plans:
2. A subscriber can log in to the client portal and view their current bill, outstanding balance, payment history, and current plan details — scoped strictly to their own account at both the API and data layers 2. A subscriber can log in to the client portal and view their current bill, outstanding balance, payment history, and current plan details — scoped strictly to their own account at both the API and data layers
3. A subscriber can submit a support ticket through the client portal and see it reflected in staff's ticket queue 3. A subscriber can submit a support ticket through the client portal and see it reflected in staff's ticket queue
4. All API endpoints have integration tests that assert correct responses for authorized and unauthorized roles, verifying API-layer RBAC is not bypassed 4. All API endpoints have integration tests that assert correct responses for authorized and unauthorized roles, verifying API-layer RBAC is not bypassed
5. Critical user workflows (subscriber registration invoice generation payment recording, collector collection remittance verification, ticket creation job order completion) pass end-to-end tests 5. Critical user workflows (subscriber registration -> invoice generation -> payment recording, collector collection -> remittance verification, ticket creation -> job order completion) pass end-to-end tests
**Plans**: 5 plans **Plans**: 7 plans
Plans: Plans:
- [x] 05-01-PLAN.md — Dashboard service: revenue metrics, overdue counts, subscriber status breakdown, cash flow summary, collector summary (DASH-01, DASH-02, DASH-03, DASH-04) - [x] 05-01-PLAN.md — Dashboard service: revenue metrics, overdue counts, subscriber status breakdown, cash flow summary, collector summary (DASH-01, DASH-02, DASH-03, DASH-04)
@@ -122,18 +122,20 @@ Plans:
- [x] 05-03-PLAN.md — Portal tickets and payment scaffold: ticket submission with conversation threads, online payment "coming soon" page (PORT-03, PORT-05) - [x] 05-03-PLAN.md — Portal tickets and payment scaffold: ticket submission with conversation threads, online payment "coming soon" page (PORT-03, PORT-05)
- [x] 05-04-PLAN.md — Integration tests: API RBAC enforcement for all 5 roles, unauthorized access assertions, two-tenant isolation tests (INFRA-03) - [x] 05-04-PLAN.md — Integration tests: API RBAC enforcement for all 5 roles, unauthorized access assertions, two-tenant isolation tests (INFRA-03)
- [x] 05-05-PLAN.md — End-to-end tests: billing workflow, collection/remittance workflow, ticket-to-job-order workflow (INFRA-04) - [x] 05-05-PLAN.md — End-to-end tests: billing workflow, collection/remittance workflow, ticket-to-job-order workflow (INFRA-04)
- [ ] 05-06-PLAN.md — Gap closure: fix tenant scoping for 6 missing models, add Collection/Remittance CASL subjects (P0 security + tech debt)
- [ ] 05-07-PLAN.md — Gap closure: inventory/expense and portal ticket E2E tests, Phase 2 verification correction (tech debt)
--- ---
## Progress ## Progress
**Execution Order:** **Execution Order:**
Phases execute in numeric order: 1 2 3 4 5 Phases execute in numeric order: 1 -> 2 -> 3 -> 4 -> 5
| Phase | Plans Complete | Status | Completed | | Phase | Plans Complete | Status | Completed |
|-------|----------------|--------|-----------| |-------|----------------|--------|-----------|
| 1. Foundation | 5/5 | Complete | 2026-03-04 | | 1. Foundation | 5/5 | Complete | 2026-03-04 |
| 2. Subscriber and Billing Core | 5/5 | Complete | 2026-03-04 | | 2. Subscriber and Billing Core | 5/5 | Complete | 2026-03-04 |
| 3. Operational Modules | 5/5 | Complete | 2026-03-05 | | 3. Operational Modules | 5/5 | Complete | 2026-03-05 |
| 4. Inventory, Expenses, and Financial Reports | 5/5 | Complete | 2026-03-06 | | 4. Inventory, Expenses, and Financial Reports | 5/5 | Complete | 2026-03-06 |
| 5. Visibility and Client Portal | 5/5 | ✓ Complete | 2026-03-05 | | 5. Visibility and Client Portal | 5/7 | Gap Closure | 2026-03-05 |

View File

@@ -0,0 +1,166 @@
---
phase: 05-visibility-and-client-portal
plan: 06
type: execute
wave: 1
depends_on: []
files_modified:
- src/lib/prisma-tenant.ts
- src/lib/casl/types.ts
- src/lib/casl/permissions.ts
- src/app/api/collections/route.ts
- src/app/api/collections/[id]/route.ts
- src/app/api/collections/[id]/void/route.ts
- src/app/api/remittances/route.ts
- src/app/api/remittances/[id]/verify/route.ts
- src/app/api/reports/collections/route.ts
autonomous: true
gap_closure: true
must_haves:
truths:
- "All 6 missing models (expense, expenseCategory, inventoryItem, stockMovement, vendor, ticketComment) are registered in TENANT_SCOPED_MODELS and have $extends query blocks"
- "Collection and remittance API routes use dedicated CASL subjects instead of borrowing Subscriber"
artifacts:
- path: "src/lib/prisma-tenant.ts"
provides: "Tenant scoping for all 28 tenant-scoped models"
contains: "expense.*expenseCategory.*inventoryItem.*stockMovement.*vendor.*ticketComment"
- path: "src/lib/casl/types.ts"
provides: "Collection and Remittance subjects in AppSubjects"
contains: "Collection.*Remittance"
- path: "src/lib/casl/permissions.ts"
provides: "Collection and Remittance permission rules per role"
contains: "Collection.*Remittance"
key_links:
- from: "src/lib/prisma-tenant.ts"
to: "withTenantContext query extensions"
via: "TENANT_SCOPED_MODELS array + $extends blocks"
pattern: "expense.*expenseCategory.*inventoryItem.*stockMovement.*vendor.*ticketComment"
- from: "src/app/api/collections/route.ts"
to: "src/lib/casl/permissions.ts"
via: "withPermission HOF"
pattern: "withPermission.*Collection"
---
<objective>
Fix the P0 tenant isolation security gap and correct CASL subject naming for collection/remittance routes.
Purpose: Close the critical multi-tenancy vulnerability where 6 models bypass application-layer tenant scoping, and improve permission precision by giving collection/remittance routes their own CASL subjects.
Output: Updated prisma-tenant.ts with all 6 missing models, updated CASL types and permissions, updated collection/remittance route handlers.
</objective>
<execution_context>
@C:\Users\KevinAsprec\.claude/get-shit-done/workflows/execute-plan.md
@C:\Users\KevinAsprec\.claude/get-shit-done/templates/summary.md
</execution_context>
<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/v1-MILESTONE-AUDIT.md
@src/lib/prisma-tenant.ts
@src/lib/casl/types.ts
@src/lib/casl/permissions.ts
</context>
<tasks>
<task type="auto">
<name>Task 1: Add 6 missing models to tenant scoping</name>
<files>src/lib/prisma-tenant.ts</files>
<action>
1. Add the 6 missing model names to the TENANT_SCOPED_MODELS array on line 33:
- "expense"
- "expenseCategory"
- "inventoryItem"
- "stockMovement"
- "vendor"
- "ticketComment"
2. Add $extends query blocks for each of the 6 models inside withTenantContext(), following the EXACT same pattern used by existing models (e.g., the `ticket` or `collection` blocks). Each model needs these query methods:
- findMany: inject tenantId into args.where
- findFirst: inject tenantId into args.where
- findFirstOrThrow: inject tenantId into args.where
- findUnique: route through findFirst with tenant guard (same pattern as existing models — check if "id" in args.where without tenantId, then use prisma.{model}.findFirst with tenantId)
- findUniqueOrThrow: same findFirst routing with throw on null
- create: strip nested tenant relation, inject tenantId into args.data
- createMany: handle array and single data, strip tenant, inject tenantId
- update: inject tenantId into args.where
- updateMany: inject tenantId into args.where
- delete: inject tenantId into args.where
- deleteMany: inject tenantId into args.where
- upsert: inject tenantId into args.where, strip tenant from args.create, inject tenantId
- count: inject tenantId into args.where
- aggregate: inject tenantId into args.where
Place the 6 new model blocks AFTER the existing `jobTypeRate` block (last current model at line ~1804) and BEFORE the closing of the $extends object.
IMPORTANT: Use the exact same eslint-disable comments as existing blocks for the tenant destructuring pattern. Copy the pattern from an existing block like `ticket` or `collection` verbatim — do NOT invent a new pattern.
</action>
<verify>
1. Run: grep -c "expense\|expenseCategory\|inventoryItem\|stockMovement\|vendor\|ticketComment" src/lib/prisma-tenant.ts — should show multiple matches per model
2. Run: node -e "const t = require('./src/lib/prisma-tenant'); console.log(t.TENANT_SCOPED_MODELS)" — verify all 28 models listed (or use TypeScript compilation check)
3. Run: npx tsc --noEmit — no type errors
4. Run: npx jest --testPathPattern="tenant|rbac|e2e" --passWithNoTests — existing tests still pass
</verify>
<done>TENANT_SCOPED_MODELS contains all 28 models. Each of the 6 new models has a complete $extends query block matching the established pattern. TypeScript compiles. Existing tests pass.</done>
</task>
<task type="auto">
<name>Task 2: Add Collection and Remittance CASL subjects and update routes</name>
<files>src/lib/casl/types.ts, src/lib/casl/permissions.ts, src/app/api/collections/route.ts, src/app/api/collections/[id]/route.ts, src/app/api/collections/[id]/void/route.ts, src/app/api/remittances/route.ts, src/app/api/remittances/[id]/verify/route.ts, src/app/api/reports/collections/route.ts</files>
<action>
1. In src/lib/casl/types.ts, add "Collection" and "Remittance" to the AppSubjects union type (after "Payment", before "Zone").
2. In src/lib/casl/permissions.ts, update permission rules:
- OFFICE_STAFF: Add `can("manage", "Collection")` and `can("manage", "Remittance")`
- COLLECTOR: Add `can("create", "Collection")`, `can("read", "Collection")`, `can("create", "Remittance")`, `can("read", "Remittance")`
- Do NOT change ADMIN (already has `can("manage", "all")`)
- Do NOT change TECHNICIAN or CLIENT (they should not access collections/remittances)
3. Update collection API routes to use "Collection" subject instead of "Subscriber":
- src/app/api/collections/route.ts: POST uses withPermission("create", "Collection"), GET uses withPermission("read", "Collection")
- src/app/api/collections/[id]/route.ts: GET uses withPermission("read", "Collection")
- src/app/api/collections/[id]/void/route.ts: uses withPermission("update", "Collection")
4. Update remittance API routes to use "Remittance" subject instead of "Subscriber":
- src/app/api/remittances/route.ts: POST uses withPermission("create", "Remittance"), GET uses withPermission("read", "Remittance")
- src/app/api/remittances/[id]/verify/route.ts: uses withPermission("update", "Remittance")
5. Update collection report route:
- src/app/api/reports/collections/route.ts: GET uses withPermission("read", "Collection") instead of withPermission("read", "Subscriber")
6. Update JSDoc comments in each route file to reflect the new subject name.
IMPORTANT: The RBAC integration tests in api-rbac.test.ts mock withPermission. After changing subjects, verify the RBAC tests still pass. If tests mock specific subjects, update them to match the new subjects.
</action>
<verify>
1. Run: grep -rn "withPermission.*Subscriber" src/app/api/collections/ src/app/api/remittances/ src/app/api/reports/collections/ — should return ZERO matches
2. Run: grep -rn "withPermission.*Collection\|withPermission.*Remittance" src/app/api/ — should show all collection/remittance routes using correct subjects
3. Run: npx tsc --noEmit — no type errors
4. Run: npx jest --testPathPattern="rbac|e2e" — all tests pass
</verify>
<done>Collection and Remittance are proper CASL subjects. All collection/remittance routes use their dedicated subjects. RBAC tests pass. No route still uses "Subscriber" for collection/remittance operations.</done>
</task>
</tasks>
<verification>
1. TypeScript compilation passes: npx tsc --noEmit
2. All existing tests pass: npx jest
3. TENANT_SCOPED_MODELS has 28 entries (22 existing + 6 new)
4. Zero collection/remittance routes use "Subscriber" as CASL subject
5. grep for all 6 model names in prisma-tenant.ts $extends block confirms they exist
</verification>
<success_criteria>
- The P0 tenant isolation gap is closed: all tenant-scoped Prisma models have automatic tenantId injection
- Collection and remittance routes use semantically correct CASL subjects
- All existing tests continue to pass
- TypeScript compiles without errors
</success_criteria>
<output>
After completion, create `.planning/phases/05-visibility-and-client-portal/05-06-SUMMARY.md`
</output>

View File

@@ -0,0 +1,171 @@
---
phase: 05-visibility-and-client-portal
plan: 07
type: execute
wave: 1
depends_on: []
files_modified:
- src/lib/__tests__/integration/e2e-workflows.test.ts
- .planning/phases/02-subscriber-and-billing-core/02-VERIFICATION.md
autonomous: true
gap_closure: true
must_haves:
truths:
- "E2E tests exercise the inventory receiving and expense recording workflows end-to-end with balanced journal entries"
- "E2E tests exercise the portal ticket creation flow from subscriber to staff ticket queue"
- "Phase 2 VERIFICATION.md reflects the actual fixed state (passed, 5/5)"
artifacts:
- path: "src/lib/__tests__/integration/e2e-workflows.test.ts"
provides: "E2E tests for inventory/expense and portal ticket workflows"
contains: "Inventory.*Expense|Portal.*Ticket"
- path: ".planning/phases/02-subscriber-and-billing-core/02-VERIFICATION.md"
provides: "Corrected verification status"
contains: "status: passed"
key_links:
- from: "e2e-workflows.test.ts"
to: "inventory-service.ts"
via: "service import and call"
pattern: "receiveStock|recordMovement"
- from: "e2e-workflows.test.ts"
to: "expense-service.ts"
via: "service import and call"
pattern: "createExpense|recordExpense"
- from: "e2e-workflows.test.ts"
to: "portal-ticket-service.ts"
via: "service import and call"
pattern: "createPortalTicket"
---
<objective>
Add E2E test coverage for Phase 4 inventory/expense workflows and Phase 5 portal ticket flow, and correct the Phase 2 VERIFICATION.md status.
Purpose: Close the E2E test coverage gaps identified in the milestone audit and fix the outdated verification document that still shows a gap that was resolved.
Output: Two new E2E workflow test suites and a corrected Phase 2 VERIFICATION.md.
</objective>
<execution_context>
@C:\Users\KevinAsprec\.claude/get-shit-done/workflows/execute-plan.md
@C:\Users\KevinAsprec\.claude/get-shit-done/templates/summary.md
</execution_context>
<context>
@.planning/PROJECT.md
@.planning/ROADMAP.md
@.planning/STATE.md
@.planning/v1-MILESTONE-AUDIT.md
@src/lib/__tests__/integration/e2e-workflows.test.ts
@src/lib/services/inventory-service.ts
@src/lib/services/asset-service.ts
@src/lib/services/expense-service.ts
@src/lib/services/portal-ticket-service.ts
@.planning/phases/02-subscriber-and-billing-core/02-VERIFICATION.md
</context>
<tasks>
<task type="auto">
<name>Task 1: Add inventory/expense and portal ticket E2E workflows</name>
<files>src/lib/__tests__/integration/e2e-workflows.test.ts</files>
<action>
Add two new describe blocks to the existing e2e-workflows.test.ts file. Follow the exact patterns established by the existing 3 workflows (same test helpers, same tenant setup, same cleanup approach, same trial balance verification pattern).
**Workflow 4: Inventory Receiving -> Expense Recording -> Trial Balance**
Add a describe block "Workflow 4: Inventory Receiving and Expense Recording" with these tests:
1. "receives inventory items and creates balanced journal entries"
- Import and call inventory-service to receive stock (e.g., receiveStock or the appropriate function that creates an INBOUND/RECEIVED stock movement)
- Verify the stock movement was created
- Verify a journal entry was posted (DR Inventory asset account / CR Cash or AP)
- Verify the JE is balanced (debits === credits)
2. "records an expense with automatic journal entry"
- Import and call expense-service to create an expense (with category and vendor)
- Need to create an expense category and vendor first (use the appropriate service functions or direct prisma calls matching existing test patterns)
- Verify the expense was created
- Verify a journal entry was posted (DR Expense account / CR Cash or AP)
- Verify the JE is balanced
3. "trial balance remains balanced after inventory and expense transactions"
- Use JournalEntryService.getTrialBalance (same pattern as existing workflows)
- Assert total debits === total credits > 0
**Workflow 5: Portal Ticket Creation -> Staff Queue**
Add a describe block "Workflow 5: Portal Ticket Submission to Staff Queue" with these tests:
1. "subscriber creates portal ticket and it appears in staff ticket queue"
- Create a subscriber (use existing createSubscriber helper or the pattern from existing tests)
- Import and call portal-ticket-service's createPortalTicket (or equivalent function) to create a ticket as the subscriber
- This should trigger ensurePortalUser internally (creating a shadow User with CLIENT role)
- Verify the ticket was created with source=SUBSCRIBER
- Query tickets via ticket-service's list/search function (staff perspective) and verify the portal ticket appears in the results
2. "portal ticket has correct subscriber association"
- Verify the ticket's createdById links to the shadow portal user
- Verify the shadow user exists with role CLIENT
IMPORTANT:
- Reuse the existing test's tenant, users, and cleanup infrastructure. The existing beforeAll creates a tenant and users — extend it to also create any additional data needed (expense categories, vendors, inventory items).
- Add cleanup for new records in the existing afterAll block (expense categories, expenses, vendors, inventory items, stock movements, plus any portal-created users and tickets).
- Follow the existing import pattern at the top of the file.
- Check the actual function signatures in inventory-service.ts, expense-service.ts, and portal-ticket-service.ts before writing calls — use the exact parameter shapes those functions expect.
</action>
<verify>
1. Run: npx jest --testPathPattern="e2e-workflows" --verbose — all workflows pass (existing 3 + new 2)
2. Run: npx jest --testPathPattern="e2e-workflows" --verbose 2>&1 | grep -c "PASS\|FAIL" — should show PASS
3. Verify new test count: npx jest --testPathPattern="e2e-workflows" --verbose 2>&1 | grep "Tests:" — should be more than the previous 16
</verify>
<done>E2E test file has 5 workflow suites. Inventory receiving and expense recording produce balanced JEs. Portal ticket appears in staff queue. All tests pass.</done>
</task>
<task type="auto">
<name>Task 2: Correct Phase 2 VERIFICATION.md status</name>
<files>.planning/phases/02-subscriber-and-billing-core/02-VERIFICATION.md</files>
<action>
Update the Phase 2 VERIFICATION.md to reflect the actual current state:
1. In the YAML frontmatter:
- Change `status: gaps_found` to `status: passed`
- Change `score: 4/5 must-haves verified` to `score: 5/5 must-haves verified`
- Remove or update the `gaps:` section — change the truth #3 status from `failed` to `fixed` and add a note that the fix was applied in billing-service.ts (status: InvoiceStatus.SENT, issuedAt: new Date()) and validated by Phase 5 E2E tests
2. In the body:
- Update Truth #3 status from "FAILED" to "VERIFIED (fixed post-verification)" in the Observable Truths table
- Add a brief note in the Evidence column: "Fixed: billing-service.ts now sets status=SENT and issuedAt on invoice creation. Validated by Phase 5 E2E billing workflow test."
- Update the Key Link "billing-service.ts -> Invoice (status=SENT)" from "NOT WIRED" to "WIRED" with note "Fixed post-initial-verification"
- Update BILL-03 requirement from "BLOCKED" to "SATISFIED" with note "Fixed: invoices now created as SENT"
- Update the Anti-Patterns section to mark the billing-service.ts issue as resolved
- Update the Gaps Summary section to note that the gap was resolved
- Add a re-verification note: "Re-verified: 2026-03-05 — gap closure confirmed"
3. Update the Score line in the body from "4/5" to "5/5".
Keep all other content intact. This is a documentation correction, not a rewrite.
</action>
<verify>
1. grep "status: passed" .planning/phases/02-subscriber-and-billing-core/02-VERIFICATION.md — should match
2. grep "5/5" .planning/phases/02-subscriber-and-billing-core/02-VERIFICATION.md — should match
3. grep "BLOCKED" .planning/phases/02-subscriber-and-billing-core/02-VERIFICATION.md — should return ZERO matches
</verify>
<done>Phase 2 VERIFICATION.md shows status: passed, score: 5/5, truth #3 marked as verified/fixed, BILL-03 satisfied, key link wired. Document accurately reflects the actual system state.</done>
</task>
</tasks>
<verification>
1. All E2E tests pass: npx jest --testPathPattern="e2e-workflows" --verbose
2. Full test suite passes: npx jest
3. Phase 2 VERIFICATION.md shows passed status with 5/5 score
4. New E2E workflows cover inventory receiving, expense recording, and portal ticket creation
</verification>
<success_criteria>
- E2E test coverage now includes Phase 4 inventory/expense workflows
- E2E test coverage now includes portal ticket creation flow
- Phase 2 VERIFICATION.md accurately reflects the fixed state
- All existing tests continue to pass
</success_criteria>
<output>
After completion, create `.planning/phases/05-visibility-and-client-portal/05-07-SUMMARY.md`
</output>