# Professional Management Platform ## Full REST-First System Design Plan > **Revision:** v4 — Final Broad Architecture Baseline > **Status:** Final broad architecture baseline for Engineering MVP implementation. Further changes should move into ADRs, OpenAPI, schemas, migrations, and backlog items rather than repeatedly reopening platform architecture. > **Primary vertical:** Engineering > **API style:** REST + JSON + OpenAPI 3.1 > **Backend style:** Modular monolith > **Data:** PostgreSQL + profession-specific tables > **Tenant model:** Organization-scoped, explicit tenant context ### v4 Integration Notes v4 incorporates the remaining high-value refinements from the latest architecture review without reopening settled platform decisions. Added: - dedicated engineering client portal architecture - external-user access grants distinct from internal organization memberships - client review/acceptance separated from professional engineering approval - explicit project-document publication controls for external audiences - synchronous batch-command semantics for small batches - asynchronous job escalation for large batch operations - explicit atomic vs partial-success batch behavior - native object-storage multipart upload orchestration for very large engineering files - rate-limit metadata policy without freezing legacy `X-RateLimit-*` headers - technology ADR requirements instead of treating framework suggestions as settled architecture - provisional performance objectives separated from contractual SLOs - risk register with impact, mitigation, owner, phase, and status - API error-standard ADR to evaluate RFC 9457 Problem Details compatibility - final architecture-change governance to prevent endless review churn Explicitly rejected as permanent architecture: - client contacts becoming ordinary internal organization memberships - external client acceptance being represented as professional design approval - API servers proxying multi-gigabyte file chunks - fixed `X-RateLimit-Limit/Remaining/Reset` as permanent contract - framework/ORM choices being declared "confirmed" without an ADR - arbitrary maturity percentages such as 95% architecture readiness - fixed calendar promises before delivery context exists --- --- --- ## 2. Core Architecture Decision The platform will use: - REST - JSON - OpenAPI - Versioned endpoints - PostgreSQL - Modular monolith backend - Profession-specific frontends - Profession-specific database tables - Shared identity, security, billing, documents, audit, and infrastructure Base API path: ```text /api/v1 ``` GraphQL is not part of v1. --- ## 3. High-Level Architecture ```text FRONTENDS ┌──────────────────┼──────────────────┐ │ │ │ Engineering Web Legal Web Healthcare Web │ │ │ └──────────────────┼──────────────────┘ │ ▼ REST API /api/v1 │ ┌───────────┼───────────┐ │ │ │ Core Engineering Legal │ │ │ │ Healthcare │ │ │ │ └───────────┼───────────┘ │ PostgreSQL │ ┌───────────────┼────────────────┐ │ │ │ Shared Tables Profession Tables Audit/Event Tables ``` Shared infrastructure: ```text PostgreSQL Redis Object Storage Queue / Workers Audit Notifications Billing Observability ``` --- ## 4. System Architecture Strategy Start with a modular monolith. Do not start with microservices. Initial deployment: ```text Frontend Apps │ ▼ Backend API │ ├── PostgreSQL ├── Redis ├── Object Storage └── Worker Queue ``` Benefits: - simpler transactions - easier development - easier deployment - clearer domain boundaries - lower operational burden - easier refactoring - future service extraction remains possible --- ## 5. Repository Structure Recommended monorepo: ```text professional-platform/ │ ├── apps/ │ ├── engineering-web/ │ ├── legal-web/ │ ├── healthcare-web/ │ ├── platform-admin/ │ ├── api/ │ └── workers/ │ ├── packages/ │ ├── ui/ │ ├── api-client/ │ ├── auth-client/ │ ├── validation/ │ ├── types/ │ ├── config/ │ └── testing/ │ ├── database/ │ ├── migrations/ │ ├── seeds/ │ └── scripts/ │ ├── infrastructure/ │ ├── docker/ │ ├── deployment/ │ └── monitoring/ │ └── docs/ ├── architecture/ ├── api/ ├── security/ └── domains/ ``` --- ## 6. Frontend Strategy Every profession receives its own frontend application. Avoid one giant frontend filled with profession checks. ### Engineering Frontend Suggested navigation: ```text Dashboard Clients Projects Project Phases Project Team Sites Designs Design Reviews Inspections Specifications Tasks Documents Timesheets Billing Reports Administration ``` ### Legal Frontend Suggested navigation: ```text Dashboard Clients Matters Cases Hearings Courts Deadlines Documents Conflict Checks Time Tracking Retainers Billing Reports Administration ``` ### Healthcare Frontend Suggested navigation: ```text Dashboard Patients Appointments Practitioners Encounters Clinical Records Diagnoses Prescriptions Insurance Documents Billing Reports Administration ``` ### Platform Admin Frontend Suggested functions: ```text Organizations Users Profession Modules Subscriptions System Health Audit Support Global Configuration ``` Platform administrators and organization administrators are separate concepts. --- ## 7. REST API Structure Shared endpoints: ```text /api/v1/auth /api/v1/me /api/v1/organizations /api/v1/memberships /api/v1/membership-invitations /api/v1/roles /api/v1/permissions /api/v1/documents /api/v1/invoices /api/v1/payments /api/v1/audit-events ``` Engineering: ```text /api/v1/engineering/clients /api/v1/engineering/projects /api/v1/engineering/project-members /api/v1/engineering/phases /api/v1/engineering/sites /api/v1/engineering/tasks /api/v1/engineering/designs /api/v1/engineering/inspections /api/v1/engineering/specifications /api/v1/engineering/time-entries ``` Legal: ```text /api/v1/legal/clients /api/v1/legal/matters /api/v1/legal/cases /api/v1/legal/hearings /api/v1/legal/deadlines /api/v1/legal/conflict-checks /api/v1/legal/retainers /api/v1/legal/time-entries ``` Healthcare: ```text /api/v1/healthcare/patients /api/v1/healthcare/practitioners /api/v1/healthcare/appointments /api/v1/healthcare/encounters /api/v1/healthcare/clinical-records /api/v1/healthcare/diagnoses /api/v1/healthcare/prescriptions /api/v1/healthcare/insurance ``` --- ## 8. REST Conventions All APIs use JSON over HTTPS. Typical tenant-scoped request: ```http Authorization: Bearer X-Organization-Id: org_123 X-Request-Id: req_123 Content-Type: application/json ``` ### Organization Context `X-Organization-Id` is mandatory for every tenant-scoped endpoint. Global endpoints such as these do not require tenant context: ```http POST /api/v1/auth/login POST /api/v1/auth/token/refresh GET /api/v1/me GET /api/v1/me/organizations GET /api/v1/auth/sessions ``` Tenant-context resolution rules: ```yaml Organization Context: header_missing_on_tenant_endpoint: status: 400 code: ORGANIZATION_CONTEXT_REQUIRED organization_not_found: status: 404 code: RESOURCE_NOT_FOUND membership_not_found: status: 404 code: RESOURCE_NOT_FOUND membership_inactive: status: 403 code: AUTHZ_MEMBERSHIP_INACTIVE organization_inactive: status: 403 code: AUTHZ_ORGANIZATION_INACTIVE resource.organization_id_mismatch: status: 404 code: RESOURCE_NOT_FOUND ``` Do not expose another organization's identity in tenant-error responses. ### Idempotency Use: ```http Idempotency-Key: 8f7d6c5e-4b3a-2b1c-9d8e-7f6a5b4c3d2e ``` Idempotency is required where duplicate execution can create material side effects. Examples: ```http POST /api/v1/invoices POST /api/v1/invoices/{id}/payments POST /api/v1/payments/{id}/refund POST /api/v1/engineering/designs/{id}/approve POST /api/v1/engineering/inspections/{id}/complete ``` Idempotency records include: ```text organization_id actor_id route/action idempotency_key canonical_request_hash response_status response_body or result reference created_at expires_at ``` Rules: ```yaml same_key_same_request: return: original result same_key_different_request: status: 409 code: IDEMPOTENCY_KEY_CONFLICT ``` PostgreSQL is authoritative for critical idempotency records. Redis may accelerate lookup. ### Rate-Limit Responses Rate-limited requests return: ```http 429 Too Many Requests Retry-After: ``` Additional quota metadata may be exposed. Do **not** freeze legacy `X-RateLimit-*` header names into the architecture. The exact rate-limit response-header convention is selected and documented in the API ADR/OpenAPI contract based on the gateway and adopted standard at implementation time. ### Error Standard Decision The current platform error envelope remains valid: ```json { "error": { "code": "RESOURCE_NOT_FOUND", "message": "Resource not found.", "details": {}, "requestId": "req_123" } } ``` Before OpenAPI v1 is frozen, create an ADR evaluating compatibility with RFC 9457 Problem Details. Do not silently change the error envelope during implementation. ## 9. Standard Response Format Single resource: ```json { "data": { "id": "project_123", "name": "Central Tower" } } ``` Collection: ```json { "data": [], "meta": { "pagination": { "nextCursor": null, "hasMore": false } } } ``` Standard error: ```json { "error": { "code": "RESOURCE_NOT_FOUND", "message": "Resource not found.", "details": {}, "requestId": "req_123" } } ``` Clients depend on `error.code`, not message text. ### Error Taxonomy Authentication: ```text AUTH_INVALID_CREDENTIALS AUTH_TOKEN_EXPIRED AUTH_TOKEN_INVALID AUTH_MFA_REQUIRED AUTH_SESSION_REVOKED AUTH_REFRESH_TOKEN_REUSED ``` Authorization: ```text AUTHZ_PERMISSION_DENIED AUTHZ_ORGANIZATION_INACTIVE AUTHZ_MEMBERSHIP_INACTIVE AUTHZ_CREDENTIAL_INVALID AUTHZ_SCOPE_MISMATCH ``` Tenant context: ```text ORGANIZATION_CONTEXT_REQUIRED ``` Resource/state: ```text RESOURCE_NOT_FOUND RESOURCE_ALREADY_EXISTS RESOURCE_CONCURRENT_MODIFICATION RESOURCE_INVALID_STATE RESOURCE_ARCHIVED ``` Validation: ```text VALIDATION_ERROR VALIDATION_REQUIRED_FIELD VALIDATION_INVALID_FORMAT VALIDATION_BUSINESS_RULE ``` Idempotency: ```text IDEMPOTENCY_KEY_REQUIRED IDEMPOTENCY_KEY_CONFLICT ``` Rate limiting: ```text RATE_LIMIT_EXCEEDED ``` System/dependency: ```text INTERNAL_ERROR SERVICE_UNAVAILABLE DATABASE_UNAVAILABLE DEPENDENCY_FAILED ``` Validation example: ```json { "error": { "code": "VALIDATION_ERROR", "message": "Request validation failed.", "requestId": "req_123", "details": { "fields": [ { "field": "email", "code": "INVALID_FORMAT", "message": "Must be a valid email address" } ] } } } ``` Business-state example: ```json { "error": { "code": "RESOURCE_INVALID_STATE", "message": "Cannot approve design in current state.", "requestId": "req_123", "details": { "resourceType": "engineering_design", "resourceId": "design_123", "currentState": "draft", "requiredState": "under_review", "allowedActions": [ "submit_review" ] } } } ``` Do not expose internal stack traces, SQL, policy internals, secrets, or cross-tenant information. ## 10. HTTP Status Rules ```text 200 Success 201 Created 202 Accepted 204 No Content 400 Bad Request 401 Unauthorized 403 Forbidden 404 Not Found 409 Conflict 422 Validation Error 429 Too Many Requests 500 Internal Server Error ``` Cross-tenant resource access should return 404. --- ## 11. API Versioning Current API: ```text /api/v1 ``` Breaking changes require: ```text /api/v2 ``` Additive fields generally do not require a new version. --- ## 12. Authentication Initial authentication: ```text Email + Password + Short-Lived Access Token + Opaque Refresh Token + Server-Side Session ``` REST endpoints: ```http POST /api/v1/auth/register POST /api/v1/auth/login POST /api/v1/auth/token/refresh POST /api/v1/auth/token/revoke POST /api/v1/auth/token/revoke-all GET /api/v1/auth/sessions DELETE /api/v1/auth/sessions/{sessionId} GET /api/v1/me ``` ### Access Token ```yaml format: JWT lifetime: 15 minutes by default signed: true encrypted: false preferred signing: asymmetric key or managed signing service claims: - sub / userId - sessionId - issuer - audience - issuedAt - expiresAt organizationId: optional_hint: true authorization_authority: false ``` The organization header and active membership remain authoritative for tenant access. Do not embed the complete permission set in access tokens. ### Session and Refresh-Token Model A login session and a refresh token are different resources. Use: ```text sessions refresh_tokens ``` Suggested `sessions` fields: ```text id user_id device_id device_type device_os app_version ip_address user_agent created_at last_active_at expires_at revoked_at revocation_reason ``` Suggested `refresh_tokens` fields: ```text id session_id family_id token_hash issued_at expires_at rotated_at replaced_by_token_id revoked_at revocation_reason ``` Indexes/constraints: ```text UNIQUE(refresh_tokens.token_hash) INDEX(refresh_tokens.family_id) INDEX(refresh_tokens.session_id) INDEX(sessions.user_id, sessions.revoked_at) ``` Do **not** make `family_id` unique. Every rotated refresh token in the same lineage shares the same family. Conceptually: ```text Session │ └── Refresh Token Family │ ├── Token A [rotated] │ ↓ ├── Token B [rotated] │ ↓ └── Token C [current] ``` ### Refresh Rotation On successful refresh: 1. hash supplied refresh token 2. load token and session 3. validate token/session status and expiry 4. issue replacement token in same family 5. mark old token rotated 6. link `replaced_by_token_id` 7. return new access + refresh tokens ### Reuse Detection If a previously rotated token is used again: ```text possible token theft ↓ revoke token family ↓ revoke affected session ↓ security audit event ↓ reauthentication required ``` Policy may escalate to revoking all user sessions for higher-risk environments. Audit event: ```text auth.refresh_token.reuse_detected ``` ### Device Metadata Device metadata is useful for: ```text session display security alerts audit context user-initiated revocation ``` It is not identity proof. Future authentication: - MFA - passkeys / WebAuthn - OIDC / SSO - enterprise identity providers - risk-based authentication ## 13. Shared Core Backend Recommended modules: ```text core/ ├── auth/ ├── users/ ├── organizations/ ├── memberships/ ├── roles/ ├── permissions/ ├── authorization/ ├── documents/ ├── billing/ ├── notifications/ ├── audit/ └── events/ ``` Dependency rule: ```text Profession module → Core ``` Never: ```text Core → Profession module ``` --- ## 14. Organizations Organizations are tenants. Examples: ```text Atlas Structural Engineering Smith & Associates Law North Shore Medical Practice ``` Suggested fields: ```text id name slug status country_code timezone currency_code created_at updated_at ``` --- ## 15. Profession Enablement Use: ```text organization_professions ``` Suggested fields: ```text organization_id profession enabled_at configuration ``` Possible professions: ```text engineering legal healthcare ``` An organization may eventually enable more than one profession module. --- ## 16. Users and Memberships Users are global identities. A user gains tenant access through membership. ```text User │ ▼ Membership │ ▼ Organization ``` Suggested `users` fields: ```text id email first_name last_name phone avatar_url status created_at updated_at ``` Suggested `memberships` fields: ```text id organization_id user_id status joined_at created_at updated_at ``` --- ## 17. Membership Invitations Keep invitations separate from memberships. Suggested table: ```text membership_invitations ``` Fields: ```text id organization_id email invited_by_user_id expires_at accepted_at revoked_at created_at ``` Flow: ```text Invitation ↓ Accepted ↓ User ↓ Membership ``` --- ## 18. Authorization Use: ```text RBAC + Permission Scope + Resource Policies + Professional Qualification Policies + Domain State Rules ``` Decision flow: ```text Authenticated User ↓ Explicit Organization Context ↓ Active Membership ↓ Enabled Profession Module ↓ Roles ↓ Permissions ↓ Permission Scope ↓ Tenant-scoped Resource Query ↓ Resource Policy ↓ Credential/Jurisdiction Policy ↓ Domain State Rule ↓ ALLOW / DENY ``` Default decision: ```text DENY ``` Authorization rules: 1. Controllers never perform ad-hoc role comparisons. 2. Tenant resource queries always include `organization_id`. 3. Do not load an arbitrary resource first and then discover it belongs to another tenant. 4. High-risk professional actions perform credential checks at command execution time. 5. A permission grants the ability to attempt an action, not a guarantee the domain state allows it. 6. Cross-tenant resources appear nonexistent. 7. Profession module enablement is checked before profession-specific authorization. ## 19. Roles and Permissions Roles are organization-scoped collections of permissions. Example roles: ```text Owner Administrator Project Manager Engineer Reviewer Inspector Lawyer Paralegal Doctor Nurse Billing Manager Viewer ``` Roles are not professional credentials. ### Engineering Permissions ```text engineering.clients.read engineering.clients.create engineering.clients.update engineering.clients.archive engineering.projects.read engineering.projects.create engineering.projects.update engineering.projects.activate engineering.projects.close engineering.projects.archive engineering.project_members.manage engineering.phases.manage engineering.tasks.manage engineering.sites.manage engineering.documents.read engineering.documents.upload engineering.documents.delete engineering.designs.read engineering.designs.create engineering.designs.update engineering.designs.review engineering.designs.approve engineering.designs.reject engineering.designs.supersede engineering.inspections.read engineering.inspections.manage engineering.inspections.complete engineering.time_entries.manage engineering.reports.read ``` ### Legal Permissions ```text legal.clients.read legal.clients.create legal.clients.update legal.matters.read legal.matters.create legal.matters.update legal.matters.close legal.matters.reopen legal.cases.read legal.cases.manage legal.hearings.manage legal.deadlines.manage legal.documents.read legal.documents.upload legal.conflicts.manage legal.conflicts.approve legal.retainers.manage legal.time_entries.manage ``` ### Healthcare Permissions ```text healthcare.patients.read healthcare.patients.create healthcare.patients.update healthcare.appointments.read healthcare.appointments.manage healthcare.encounters.read healthcare.encounters.manage healthcare.records.read healthcare.records.write healthcare.records.sign healthcare.records.amend healthcare.records.access_log.read healthcare.prescriptions.read healthcare.prescriptions.write healthcare.prescriptions.sign healthcare.insurance.read healthcare.insurance.manage ``` ### Shared Permissions ```text documents.read documents.upload billing.read invoices.create invoices.issue invoices.void payments.record payments.refund members.read members.invite members.update members.remove roles.read roles.manage audit.read ``` Avoid vague permissions such as `admin_everything` in normal tenant RBAC. ## 20. Permission Scopes Initial scopes: ```text assigned organization ``` Examples: ```text Engineer: engineering.projects.read = assigned Principal Engineer: engineering.projects.read = organization ``` Potential future scopes: ```text owned team department restricted ``` Do not implement until required. --- ## 21. Professional Credentials Professional qualification is separate from RBAC. Suggested shared profile: ```text professional_profiles ``` Fields: ```text id organization_id user_id profession title credential_status primary_license_number primary_license_jurisdiction valid_from expires_at created_at updated_at ``` Profession modules may add dedicated credential tables when one generic profile is insufficient. ### Credential Policy Examples Engineering design approval may require: ```yaml permission: engineering.designs.approve credential: profession_family: engineering status: verified active_license: true jurisdiction_match: when required discipline_match: when required ``` Healthcare record signing may require: ```yaml permission: healthcare.records.sign credential: profession_allowed_by_policy: true status: verified active_license: true scope_of_practice_allows_action: true jurisdiction_match: true ``` Prescribing must **not** be hard-coded to `profession = doctor` or to a single U.S. credential such as a DEA number. Prescribing authority varies by: - jurisdiction - profession - drug class - supervising relationship - organization policy - credential status Therefore use a policy concept such as: ```text PrescribingAuthorityPolicy ``` rather than a permanent global rule. ### Cache Safety Credential status may be cached briefly for ordinary reads, but high-risk writes such as: ```text engineering.designs.approve healthcare.records.sign healthcare.prescriptions.sign ``` must use authoritative or revocation-aware credential validation. A five-minute stale cache is unacceptable if a license was just suspended. ## 22. Database Architecture Use PostgreSQL. Start with: ```text One database + Shared schema + Profession-specific tables ``` Do not begin with database-per-profession or database-per-customer unless compliance or residency requirements force that choice. --- ## 23. Shared Tables Recommended shared tables: ```text organizations organization_professions users user_credentials sessions memberships membership_invitations roles permissions role_permissions membership_roles professional_profiles documents document_versions invoices invoice_items payments notifications notification_deliveries audit_events outbox_events ``` --- ## 24. Multi-Tenancy Rule Every tenant-owned row must contain: ```text organization_id ``` Examples: ```text engineering_projects.organization_id legal_matters.organization_id healthcare_patients.organization_id ``` Enforce tenant boundaries at: - API layer - authorization layer - repository/query layer - database constraints --- ## 25. Tenant-Safe Foreign Keys Use composite tenant-aware foreign keys when possible. Example: ```text engineering_projects organization_id client_id ``` references: ```text engineering_clients organization_id id ``` This prevents linking a resource from one organization to another organization's data. --- # Engineering Domain ## 26. Engineering Tables Initial tables: ```text engineering_clients engineering_projects engineering_project_members engineering_project_phases engineering_sites engineering_tasks engineering_designs engineering_design_versions engineering_design_reviews engineering_inspections engineering_inspection_findings engineering_specifications engineering_change_requests engineering_time_entries ``` --- ## 27. Engineering Clients Suggested core client fields: ```text id organization_id client_type display_name legal_name status created_at updated_at version ``` Do not permanently squeeze all contacts into one `email`, one `phone`, and one `contact_name`. Engineering customers commonly have multiple: ```text technical contacts billing contacts executive contacts site contacts contract contacts ``` Use: ```text engineering_client_contacts ``` Suggested contact fields: ```text id organization_id client_id name title department email phone contact_type is_primary created_at updated_at ``` Client REST: ```http GET /api/v1/engineering/clients POST /api/v1/engineering/clients GET /api/v1/engineering/clients/{clientId} PATCH /api/v1/engineering/clients/{clientId} POST /api/v1/engineering/clients/{clientId}/archive POST /api/v1/engineering/clients/{clientId}/restore GET /api/v1/engineering/clients/{clientId}/projects GET /api/v1/engineering/clients/{clientId}/invoices ``` Contact REST: ```http GET /api/v1/engineering/clients/{clientId}/contacts POST /api/v1/engineering/clients/{clientId}/contacts PATCH /api/v1/engineering/clients/{clientId}/contacts/{contactId} DELETE /api/v1/engineering/clients/{clientId}/contacts/{contactId} ``` Delete may be implemented as archival when contact history matters. Client restore is allowed only when organization policy and retention rules permit it. ## 27A. Engineering Client Portal External clients are not internal organization members. Use shared authentication identities where practical, but create a separate authorization boundary. ```text User │ ├── Internal Membership │ ↓ │ Organization Staff Access │ └── Client Portal Account ↓ Engineering Client Contact ↓ Project Access Grants ``` Suggested tables: ```text engineering_client_portal_accounts engineering_client_portal_project_grants engineering_project_document_publications engineering_client_review_requests ``` ### Portal Account Suggested fields: ```text id organization_id user_id engineering_client_contact_id status invited_by_user_id invited_at accepted_at revoked_at revoked_by_user_id ``` Portal accounts are not placed in `memberships`. ### Project Grant Suggested fields: ```text id organization_id portal_account_id project_id access_profile granted_by_user_id granted_at expires_at revoked_at ``` Initial access capabilities may include: ```text project.status.read project.documents.read_published project.comments.create project.files.submit client_review.respond ``` The access model may later normalize capabilities into a grant table if simple profiles become insufficient. ### Separate Frontend Recommended: ```text apps/ ├── engineering-web/ └── engineering-client-portal/ ``` The internal engineering frontend and external portal do not share authorization assumptions. ### Client Acceptance Is Not Engineering Approval Never represent client acceptance with: ```text engineering.designs.approve ``` Professional engineering approval is reserved for qualified internal/authorized professionals. Client-facing review should use separate concepts such as: ```text engineering.client_reviews.request engineering.client_reviews.respond engineering.client_reviews.accept engineering.client_reviews.request_changes ``` Example: ```http POST /api/v1/engineering/client-review-requests/{reviewId}/accept POST /api/v1/engineering/client-review-requests/{reviewId}/request-changes ``` A client acceptance may be commercially meaningful without being a professional engineering approval. ### Portal Security Rules 1. portal access is deny-by-default 2. every portal request remains organization-scoped 3. portal users only access explicitly granted projects 4. project membership does not apply to portal users 5. internal RBAC roles do not automatically apply to portal users 6. portal account revocation is immediate 7. portal grants may expire 8. sensitive document access requires explicit publication 9. portal activity is audited according to organization policy 10. professional approval endpoints are never exposed through portal grants --- ## 27B. External Document Publication A document being linked to an engineering project does **not** make it externally visible. Use: ```text engineering_project_document_publications ``` Suggested fields: ```text id organization_id project_document_link_id audience_type portal_account_id nullable client_id nullable published_by_user_id published_at expires_at revoked_at revoked_by_user_id ``` Possible audiences: ```text all_active_client_portal_accounts_for_project specific_portal_account specific_client_contact ``` External download checks: ```text authenticated portal user + active portal account + active project grant + active document publication + publication not expired/revoked + document classification allows publication + download permission ``` This prevents an internal project document from appearing in the client portal merely because it is linked to the project. ## 28. Engineering Projects Suggested fields: ```text id organization_id client_id project_number name description discipline stage status project_manager_user_id start_date expected_completion_date completed_date budget_minor currency_code created_at updated_at version ``` REST: ```http GET /api/v1/engineering/projects POST /api/v1/engineering/projects GET /api/v1/engineering/projects/{projectId} PATCH /api/v1/engineering/projects/{projectId} POST /api/v1/engineering/projects/{projectId}/activate POST /api/v1/engineering/projects/{projectId}/close POST /api/v1/engineering/projects/{projectId}/archive ``` Purpose-built read models may be added when the frontend requires them: ```http GET /api/v1/engineering/projects/{projectId}/summary GET /api/v1/engineering/projects/{projectId}/timeline GET /api/v1/engineering/projects/{projectId}/budget ``` These are read-model endpoints, not necessarily separate aggregate tables. Do not put arbitrary budget-breakdown JSON into the core project row merely because the response can display it. Model detailed budget data in dedicated tables when that feature is implemented. ## 29. Engineering Project Members Suggested fields: ```text id organization_id project_id user_id project_role joined_at left_at ``` REST: ```http GET /api/v1/engineering/projects/{projectId}/members POST /api/v1/engineering/projects/{projectId}/members PATCH /api/v1/engineering/projects/{projectId}/members/{memberId} DELETE /api/v1/engineering/projects/{projectId}/members/{memberId} ``` --- ## 30. Engineering Project Phases Suggested fields: ```text id organization_id project_id name sequence status start_date end_date created_at updated_at ``` Typical phases: ```text Concept Preliminary Design Detailed Design Construction Inspection Closeout ``` REST: ```http GET /api/v1/engineering/projects/{projectId}/phases POST /api/v1/engineering/projects/{projectId}/phases PATCH /api/v1/engineering/projects/{projectId}/phases/{phaseId} POST /api/v1/engineering/projects/{projectId}/phases/{phaseId}/complete ``` --- ## 31. Engineering Sites Suggested fields: ```text id organization_id project_id name address latitude longitude created_at updated_at ``` REST: ```http POST /api/v1/engineering/projects/{projectId}/sites GET /api/v1/engineering/projects/{projectId}/sites GET /api/v1/engineering/sites/{siteId} PATCH /api/v1/engineering/sites/{siteId} ``` --- ## 32. Engineering Tasks Suggested fields: ```text id organization_id project_id title description status priority created_by_user_id assigned_to_user_id due_at completed_at created_at updated_at version ``` REST: ```http POST /api/v1/engineering/tasks GET /api/v1/engineering/tasks GET /api/v1/engineering/tasks/{taskId} PATCH /api/v1/engineering/tasks/{taskId} POST /api/v1/engineering/tasks/{taskId}/complete POST /api/v1/engineering/tasks/{taskId}/reopen POST /api/v1/engineering/tasks/{taskId}/cancel ``` --- ## 32A. Engineering Batch Operations Batch operations are useful for repetitive engineering workflows, but they must not bypass per-resource authorization or domain rules. Examples: ```http POST /api/v1/engineering/tasks/batch-assign POST /api/v1/engineering/tasks/batch-complete POST /api/v1/engineering/time-entries/batch-submit ``` ### Batch Execution Modes Every batch command explicitly defines one of: ```text atomic partial ``` Atomic: ```text all resources succeed or entire operation fails ``` Partial: ```text each resource is evaluated independently successful items commit failed items return individual errors ``` Do not leave this behavior implicit. Example request: ```json { "taskIds": [ "task_1", "task_2", "task_3" ], "assigneeUserId": "user_123", "mode": "partial" } ``` Example response: ```json { "data": { "succeeded": [ "task_1", "task_2" ], "failed": [ { "id": "task_3", "code": "RESOURCE_INVALID_STATE" } ] } } ``` ### Authorization Each resource is evaluated for: ```text tenant permission scope resource access state validity credential policy where applicable ``` Never authorize the first item and assume the remaining batch is equivalent. ### Synchronous vs Asynchronous Small batches may execute synchronously. Large batches become jobs: ```http 202 Accepted ``` with: ```text jobId ``` The synchronous/asynchronous threshold is configuration based on: ```text batch size operation cost database load side effects product tier ``` Financial or regulated batch actions require stricter idempotency and audit rules than ordinary task updates. --- ## 33. Engineering Designs Suggested fields: ```text id organization_id project_id design_number title description discipline status owner_user_id prepared_by_user_id approved_by_user_id approved_at created_at updated_at version ``` Suggested states: ```text draft under_review changes_requested approved rejected cancelled withdrawn superseded ``` REST: ```http GET /api/v1/engineering/projects/{projectId}/designs POST /api/v1/engineering/projects/{projectId}/designs GET /api/v1/engineering/designs/{designId} PATCH /api/v1/engineering/designs/{designId} POST /api/v1/engineering/designs/{designId}/submit-review POST /api/v1/engineering/designs/{designId}/request-changes POST /api/v1/engineering/designs/{designId}/approve POST /api/v1/engineering/designs/{designId}/reject POST /api/v1/engineering/designs/{designId}/cancel POST /api/v1/engineering/designs/{designId}/withdraw POST /api/v1/engineering/designs/{designId}/supersede POST /api/v1/engineering/designs/{designId}/assign POST /api/v1/engineering/designs/{designId}/unassign GET /api/v1/engineering/designs/{designId}/versions POST /api/v1/engineering/designs/{designId}/versions GET /api/v1/engineering/designs/{designId}/reviews POST /api/v1/engineering/designs/{designId}/reviews ``` ### Assignment Model Use: ```text engineering_design_assignments ``` Possible assignment roles: ```text owner designer reviewer approver checker ``` Suggested fields: ```text id organization_id design_id user_id assignment_role notes assigned_by_user_id assigned_at unassigned_at ``` Assignment does not automatically grant platform permission. Both RBAC and resource policy still apply. ### Design State Machine ```text draft ├── submit-review ───────────────► under_review └── cancel ──────────────────────► cancelled under_review ├── request-changes ─────────────► changes_requested ├── approve ─────────────────────► approved ├── reject ──────────────────────► rejected └── withdraw ────────────────────► withdrawn changes_requested ├── submit-review ───────────────► under_review └── withdraw ────────────────────► withdrawn rejected └── revise ──────────────────────► draft approved └── supersede ───────────────────► superseded ``` Use `cancelled` for work stopped before formal review. Use `withdrawn` for work intentionally removed after review workflow has started. Approval requires: ```text permission + project access + appropriate assignment/policy + valid professional qualification + valid design state + organization approval policy ``` Approval, rejection, withdrawal, and supersession are audited. Approval is idempotent. Do not approve by PATCHing `status`. ## 34. Design Versions and Reviews `engineering_design_versions`: ```text id design_id version_number document_id created_by_user_id created_at ``` `engineering_design_reviews`: ```text id organization_id design_id reviewer_user_id status comments reviewed_at ``` Possible review statuses: ```text pending approved changes_requested rejected ``` --- ## 35. Engineering Inspections Suggested fields: ```text id organization_id project_id site_id inspection_type inspector_user_id status outcome scheduled_at started_at performed_at cancelled_at summary created_at updated_at version ``` Lifecycle status: ```text draft scheduled in_progress completed cancelled ``` Outcome is separate: ```text passed passed_with_observations followup_required failed ``` This distinction matters. An inspection can be fully completed and still require corrective work. REST: ```http GET /api/v1/engineering/projects/{projectId}/inspections POST /api/v1/engineering/projects/{projectId}/inspections GET /api/v1/engineering/inspections/{inspectionId} PATCH /api/v1/engineering/inspections/{inspectionId} POST /api/v1/engineering/inspections/{inspectionId}/schedule POST /api/v1/engineering/inspections/{inspectionId}/start POST /api/v1/engineering/inspections/{inspectionId}/complete POST /api/v1/engineering/inspections/{inspectionId}/cancel GET /api/v1/engineering/inspections/{inspectionId}/findings POST /api/v1/engineering/inspections/{inspectionId}/findings POST /api/v1/engineering/inspections/{inspectionId}/followups GET /api/v1/engineering/inspections/{inspectionId}/followups ``` A follow-up may be: ```text corrective task new inspection or both ``` Do not encode all follow-up workflow into the original inspection's lifecycle state. Inspection completion: 1. validate inspector and project access 2. validate required fields 3. validate findings 4. calculate or confirm outcome 5. complete inspection 6. create corrective work/follow-up records when required 7. audit 8. write outbox event 9. notify appropriate participants Completion is idempotent. ## 36. Inspection Findings Suggested fields: ```text id inspection_id severity description status resolved_at ``` Possible severities: ```text observation minor major critical ``` REST: ```http POST /api/v1/engineering/inspections/{inspectionId}/findings PATCH /api/v1/engineering/inspection-findings/{findingId} POST /api/v1/engineering/inspection-findings/{findingId}/resolve ``` --- ## 37. Engineering Specifications Suggested fields: ```text id organization_id project_id specification_number title version status document_id created_at updated_at ``` --- ## 38. Engineering Change Requests Suggested fields: ```text id organization_id project_id request_number title description status requested_by_user_id approved_by_user_id estimated_cost_minor created_at updated_at ``` --- ## 38A. Engineering Project Budgets A single `budget_minor` column is sufficient only for a very early project total. When budget management enters scope, introduce: ```text engineering_project_budgets engineering_project_budget_items engineering_project_commitments engineering_project_cost_entries ``` ### Budget Suggested fields: ```text id organization_id project_id name currency_code status approved_by_user_id approved_at created_at updated_at version ``` ### Budget Item Suggested fields: ```text id organization_id budget_id category description allocated_amount_minor created_at updated_at ``` Do not casually store mutable: ```text spent_amount_minor committed_amount_minor ``` as independent sources of truth if those values are derived from time entries, expenses, purchase commitments, or invoices. Prefer: ```text authoritative cost/commitment records ↓ derived budget projections ``` If denormalized totals are needed for performance, update them transactionally and reconcile them. Potential REST: ```http GET /api/v1/engineering/projects/{projectId}/budgets POST /api/v1/engineering/projects/{projectId}/budgets GET /api/v1/engineering/budgets/{budgetId} PATCH /api/v1/engineering/budgets/{budgetId} POST /api/v1/engineering/budgets/{budgetId}/approve GET /api/v1/engineering/budgets/{budgetId}/items POST /api/v1/engineering/budgets/{budgetId}/items ``` Budget approval is an explicit command. --- ## 39. Engineering Time Entries Suggested fields: ```text id organization_id project_id user_id work_date duration_minutes description billable billing_rate_minor currency_code created_at updated_at ``` REST: ```http POST /api/v1/engineering/time-entries GET /api/v1/engineering/time-entries GET /api/v1/engineering/time-entries/{id} PATCH /api/v1/engineering/time-entries/{id} ``` Store duration as integer minutes. --- # Legal Domain ## 40. Legal Tables Initial tables: ```text legal_clients legal_matters legal_matter_members legal_cases legal_case_parties legal_courts legal_hearings legal_deadlines legal_documents legal_time_entries legal_retainers legal_conflict_checks legal_conflict_parties legal_conflict_matches ``` REST namespace: ```text /api/v1/legal ``` Core examples: ```http GET /api/v1/legal/matters POST /api/v1/legal/matters GET /api/v1/legal/matters/{matterId} PATCH /api/v1/legal/matters/{matterId} POST /api/v1/legal/matters/{matterId}/close POST /api/v1/legal/matters/{matterId}/reopen GET /api/v1/legal/matters/{matterId}/cases GET /api/v1/legal/matters/{matterId}/documents GET /api/v1/legal/matters/{matterId}/time-entries GET /api/v1/legal/matters/{matterId}/invoices POST /api/v1/legal/conflict-checks GET /api/v1/legal/conflict-checks/{conflictCheckId} POST /api/v1/legal/conflict-checks/{conflictCheckId}/approve POST /api/v1/legal/conflict-checks/{conflictCheckId}/decline ``` Legal remains a later vertical. These endpoints define intended boundaries, not a P0 build commitment. ## 41. Legal Matters Suggested fields: ```text id organization_id client_id matter_number title practice_area responsible_lawyer_user_id status opened_date closed_date created_at updated_at ``` --- ## 42. Legal Cases Suggested fields: ```text id organization_id matter_id case_number court_id jurisdiction case_type status filed_date created_at updated_at ``` --- ## 43. Legal Hearings Suggested fields: ```text id organization_id case_id hearing_type scheduled_at courtroom judge status notes ``` --- ## 44. Legal Conflict Checks Suggested tables: ```text legal_conflict_checks legal_conflict_parties legal_conflict_matches ``` Conflict-check fields: ```text id organization_id potential_client_name matter_description requested_by_user_id reviewed_by_user_id status decision decision_reason created_at reviewed_at version ``` Request example: ```json { "potentialClientName": "Acme Corporation", "relatedParties": [ { "name": "John Smith", "relationship": "CEO" }, { "name": "Acme Subsidiary LLC", "relationship": "Subsidiary" } ], "matterDescription": "Corporate acquisition" } ``` Response may contain possible matches: ```json { "data": { "id": "conflict_123", "status": "pending_review", "potentialConflicts": [ { "type": "possible_direct_adversity", "partyName": "Acme Corporation", "existingMatterId": "matter_456", "existingMatterNumber": "MAT-2026-089" } ] } } ``` The system should distinguish: ```text automated possible match ``` from: ```text lawyer-approved conflict determination ``` The software may assist discovery; it should not silently make the professional judgment. Approvals and declines are auditable commands. ## 45. Healthcare Tables Initial tables: ```text healthcare_patients healthcare_patient_contacts healthcare_patient_addresses healthcare_practitioners healthcare_appointments healthcare_encounters healthcare_clinical_records healthcare_clinical_record_versions healthcare_clinical_record_amendments healthcare_diagnoses healthcare_prescriptions healthcare_insurance_policies healthcare_allergies healthcare_medications ``` REST namespace: ```text /api/v1/healthcare ``` Examples: ```http GET /api/v1/healthcare/patients POST /api/v1/healthcare/patients GET /api/v1/healthcare/patients/{patientId} PATCH /api/v1/healthcare/patients/{patientId} POST /api/v1/healthcare/patients/{patientId}/archive GET /api/v1/healthcare/patients/{patientId}/appointments GET /api/v1/healthcare/patients/{patientId}/encounters GET /api/v1/healthcare/patients/{patientId}/clinical-records GET /api/v1/healthcare/patients/{patientId}/prescriptions GET /api/v1/healthcare/patients/{patientId}/allergies POST /api/v1/healthcare/encounters POST /api/v1/healthcare/encounters/{encounterId}/clinical-records GET /api/v1/healthcare/clinical-records/{recordId} GET /api/v1/healthcare/clinical-records/{recordId}/history GET /api/v1/healthcare/clinical-records/{recordId}/access-log POST /api/v1/healthcare/clinical-records/{recordId}/sign POST /api/v1/healthcare/clinical-records/{recordId}/amend ``` Healthcare is intentionally not treated as ordinary CRM plus extra columns. ## 46. Healthcare Patients Core patient fields: ```text id organization_id patient_number first_name middle_name last_name date_of_birth sex_or_administrative_gender_as_required status created_at updated_at version ``` Do not make a single default patient DTO return every available PHI field. Use minimum-necessary response shapes. Example general patient response: ```json { "data": { "id": "patient_123", "patientNumber": "PAT-2026-001", "name": { "firstName": "Alice", "middleName": "Marie", "lastName": "Johnson" }, "dateOfBirth": "1985-03-15", "status": "active", "version": 2 } } ``` More sensitive subresources should have separate permissions and endpoints where useful: ```text contact information addresses emergency contacts insurance policies clinical records prescriptions ``` Do not return insurance member IDs or emergency contact details on every patient read merely because the database has them. ## 47. Healthcare Practitioners Suggested fields: ```text id organization_id user_id specialty license_number license_jurisdiction credential_status created_at updated_at ``` --- ## 48. Healthcare Appointments Suggested fields: ```text id organization_id patient_id practitioner_id appointment_type starts_at ends_at status reason created_at updated_at ``` --- ## 49. Healthcare Encounters Suggested fields: ```text id organization_id patient_id practitioner_id appointment_id encounter_type started_at ended_at status ``` --- ## 50. Clinical Records Suggested tables: ```text healthcare_clinical_records healthcare_clinical_record_versions healthcare_clinical_record_amendments ``` Core record fields: ```text id organization_id patient_id encounter_id author_practitioner_id record_type sensitivity_level status signed_by_practitioner_id signed_at created_at updated_at version ``` Draft content may be editable according to workflow. Once signed/finalized: - do not overwrite history - create amendments or new versions - preserve previous signed content - audit reads when policy requires - audit all writes/signatures/amendments REST: ```http POST /api/v1/healthcare/encounters/{encounterId}/clinical-records GET /api/v1/healthcare/clinical-records/{recordId} PATCH /api/v1/healthcare/clinical-records/{recordId} # Only when editable/draft according to policy. POST /api/v1/healthcare/clinical-records/{recordId}/sign POST /api/v1/healthcare/clinical-records/{recordId}/amend GET /api/v1/healthcare/clinical-records/{recordId}/history GET /api/v1/healthcare/clinical-records/{recordId}/access-log ``` Clinical content representation should be designed around actual healthcare requirements and interoperability needs rather than permanently committing to one ad-hoc JSON SOAP-note structure. Sensitive record access should support an `accessReason` when organization or regulatory policy requires it. ## 51. Documents Use shared object storage. Database: ```text documents document_versions document_categories retention_policies ``` Binary data: ```text S3-compatible object storage ``` ### Document Suggested fields: ```text id organization_id name category_id classification retention_policy_id current_version_id created_by_user_id created_at updated_at ``` Classification examples: ```text public internal confidential restricted regulated ``` Avoid a single `is_confidential` boolean as the long-term security model. ### Document Version Suggested fields: ```text id organization_id document_id version_number storage_key mime_type size_bytes content_hash hash_algorithm uploaded_by_user_id created_at ``` The authoritative checksum belongs on the version because each binary revision has different content. Optional document-level metadata may include: ```text current_version_id current_version_number ``` but should not replace version-level integrity data. ### Metadata Use JSONB only for genuinely extensible metadata that does not deserve stable relational columns. Examples: ```text CAD-specific extraction results scanner metadata non-authoritative document properties ``` Do not place access control, retention state, ownership, or lifecycle rules inside arbitrary metadata JSON. ### Document Categories Suggested fields: ```text id organization_id profession nullable name parent_category_id created_at ``` If `profession` is nullable and shared categories must remain unique, PostgreSQL uniqueness must explicitly handle nulls. Options include: ```text UNIQUE NULLS NOT DISTINCT ``` where supported, or separate partial unique indexes for: ```text profession IS NULL profession IS NOT NULL ``` Do not rely on a plain nullable composite unique constraint and assume NULL behaves like a normal value. ### Upload Security Validate: ```text declared MIME extension magic bytes/content signature file size malware scan organization quota classification policy ``` A renamed executable is not a PDF merely because the filename developed ambition. ## 52. Document Upload Flow Small and ordinary file uploads: ```text Frontend ↓ Request upload authorization ↓ Backend validates tenant + permission + upload policy ↓ Create pending document/version ↓ Return signed upload URL ↓ Frontend uploads directly to object storage ↓ Backend finalizes ↓ verify checksum/type/size ↓ malware/security scan ↓ classification + retention ↓ available ``` REST: ```http POST /api/v1/documents/upload-url POST /api/v1/documents/{documentId}/complete-upload GET /api/v1/documents/{documentId} GET /api/v1/documents/{documentId}/download-url POST /api/v1/documents/{documentId}/versions ``` ### Very Large Engineering Files Large CAD/BIM/model files use native object-storage multipart upload. The API orchestrates authorization and signed part URLs. It does **not** proxy gigabytes of file content through application servers. Flow: ```text Frontend ↓ POST /documents/multipart-uploads ↓ Backend authorizes and initializes object-storage multipart upload ↓ Frontend requests signed part URLs ↓ Frontend uploads parts directly to object storage ↓ Frontend reports completed parts ↓ POST /documents/{id}/multipart-upload/complete ↓ Backend finalizes object ↓ verify object metadata/checksum ↓ malware/security scan ↓ mark document version available ``` Possible REST: ```http POST /api/v1/documents/multipart-uploads POST /api/v1/documents/{documentId}/multipart-upload/parts POST /api/v1/documents/{documentId}/multipart-upload/complete DELETE /api/v1/documents/{documentId}/multipart-upload ``` The `/parts` endpoint returns signed upload URLs and part metadata. It does not carry binary chunks. ### Multipart Upload State Track: ```text upload_id organization_id document_id document_version_id object_storage_upload_id status created_at expires_at completed_at aborted_at ``` States: ```text initiated uploading completing completed aborted expired ``` Cleanup workers abort abandoned multipart uploads. ### File Policy Use configurable policy: ```text max_file_size_bytes allowed_file_classes organization_storage_quota_bytes profession_overrides plan/tier overrides multipart_threshold_bytes ``` Exact file-size and quota values are product/configuration decisions. Validation includes: ```text declared MIME extension content signature / magic bytes file size checksum quota classification malware scan ``` ## 53. Profession-Specific Document Links Use explicit relationship tables. Engineering: ```text engineering_project_documents engineering_design_documents engineering_inspection_documents ``` Legal: ```text legal_matter_documents legal_case_documents ``` Healthcare: ```text healthcare_patient_documents healthcare_encounter_documents ``` ### Engineering Project Documents Suggested link fields: ```text id organization_id project_id document_id category classification_override nullable linked_by_user_id linked_at unlinked_at ``` REST: ```http GET /api/v1/engineering/projects/{projectId}/documents POST /api/v1/engineering/projects/{projectId}/documents DELETE /api/v1/engineering/project-documents/{documentLinkId} ``` Link deletion may preserve historical linkage through `unlinked_at` when required. Example response: ```json { "data": [ { "documentLinkId": "projdoc_123", "category": "calculations", "document": { "id": "doc_456", "name": "structural_calculations.pdf", "classification": "confidential", "currentVersion": 2, "mimeType": "application/pdf", "sizeBytes": 2457600 } } ] } ``` Explicit link resources give stronger referential integrity than generic polymorphic foreign keys. ## 54. Billing Shared financial core: ```text invoices invoice_items payments ``` Profession-specific modules may extend billing workflows. Engineering examples: ```text project billing hourly billing milestone billing ``` Legal examples: ```text matter billing time billing retainers trust accounting ``` Healthcare examples: ```text insurance claims patient billing ``` REST: ```http POST /api/v1/invoices GET /api/v1/invoices GET /api/v1/invoices/{invoiceId} PATCH /api/v1/invoices/{invoiceId} POST /api/v1/invoices/{invoiceId}/issue POST /api/v1/invoices/{invoiceId}/void POST /api/v1/invoices/{invoiceId}/payments POST /api/v1/payments/{paymentId}/refund ``` --- ## 55. Money Representation Use integer minor units: ```json { "amountMinor": 12550, "currency": "USD" } ``` Meaning: ```text $125.50 ``` Never use floating point for money. --- ## 56. Audit Logging Table: ```text audit_events ``` Suggested fields: ```text id organization_id actor_type actor_user_id actor_service_account_id action resource_type resource_id request_id correlation_id ip_address user_agent metadata occurred_at ``` Audit events are append-only. ### Mandatory Engineering Audit Events ```text engineering.projects.create engineering.projects.close engineering.designs.approve engineering.designs.reject engineering.designs.supersede engineering.inspections.complete ``` ### Mandatory Legal Audit Events ```text legal.matters.create legal.matters.close legal.matters.reopen legal.conflicts.approve legal.conflicts.decline legal.retainers.manage ``` ### Mandatory Healthcare Audit Events ```text healthcare.records.read healthcare.records.write healthcare.records.sign healthcare.records.amend healthcare.prescriptions.write healthcare.prescriptions.sign ``` Example: ```json { "id": "audit_123", "organizationId": "org_456", "actorUserId": "user_789", "action": "healthcare.records.read", "resourceType": "healthcare_clinical_record", "resourceId": "record_456", "requestId": "req_abc", "ipAddress": "192.0.2.10", "userAgent": "Mozilla/5.0", "metadata": { "patientId": "patient_123", "recordType": "progress_note", "accessReason": "clinical_review" }, "occurredAt": "2026-08-26T01:30:00Z" } ``` Audit metadata must never contain: - passwords - access or refresh tokens - full clinical note content - secret keys - unnecessary payment data REST: ```http GET /api/v1/audit-events ``` No public create/update/delete endpoints. ## 57. Domain Events and Transactional Outbox Profession modules produce internal domain events. Examples: ```text engineering.project.created engineering.design.approved engineering.inspection.completed legal.matter.closed legal.conflict_check.approved healthcare.appointment.created healthcare.clinical_record.signed invoice.issued payment.recorded ``` Consumers: ```text notifications webhooks analytics search indexing integrations background workflows ``` Use: ```text outbox_events ``` Suggested fields: ```text id organization_id event_type aggregate_type aggregate_id payload occurred_at available_at processed_at attempt_count last_error dead_lettered_at ``` Transaction: ```text BEGIN business change audit event outbox event COMMIT ``` The outbox is **at-least-once delivery**, not magically exactly-once. Worker claim example: ```sql SELECT id FROM outbox_events WHERE processed_at IS NULL AND dead_lettered_at IS NULL AND available_at <= now() ORDER BY occurred_at FOR UPDATE SKIP LOCKED LIMIT 100; ``` Worker responsibilities: 1. claim committed event 2. process consumer action 3. mark processed on success 4. increment attempts on failure 5. schedule retry with backoff 6. dead-letter after policy threshold 7. emit metrics 8. preserve replay/debug metadata ### Critical Failure Case A worker may: ```text perform external side effect ↓ crash ↓ fail to mark event processed ↓ event is retried ``` Therefore every external consumer must support idempotency. Examples: ```text payment provider command → provider idempotency key webhook delivery → delivery/event ID email notification → dedupe key if duplicate mail is unacceptable search indexing → upsert by entity/version ``` `FOR UPDATE SKIP LOCKED` prevents concurrent claims. It does **not** prevent duplicate side effects after a crash. Workers may be awakened by queue notifications, but must still poll durable outbox state so lost wake-ups do not strand events. ## 57A. Webhooks and External Integrations Webhooks are a shared platform capability, not profession-specific transport code. Configuration REST: ```http GET /api/v1/webhooks POST /api/v1/webhooks GET /api/v1/webhooks/{webhookId} PATCH /api/v1/webhooks/{webhookId} DELETE /api/v1/webhooks/{webhookId} POST /api/v1/webhooks/{webhookId}/test POST /api/v1/webhooks/{webhookId}/rotate-secret ``` Delivery REST: ```http GET /api/v1/webhook-deliveries GET /api/v1/webhook-deliveries/{deliveryId} POST /api/v1/webhook-deliveries/{deliveryId}/retry ``` Suggested tables: ```text webhooks webhook_event_subscriptions webhook_deliveries ``` Webhook fields: ```text id organization_id url status secret_ciphertext or signing_key_reference created_by_user_id created_at updated_at ``` Do not return a secret hash to the client. ### Secret Handling If using symmetric HMAC signing: ```text generate secret ↓ show plaintext once ↓ encrypt using KMS/key-management system ↓ store ciphertext ↓ decrypt only for signing ``` A one-way hash alone is insufficient because the server must possess the signing material. Alternative: ```text asymmetric signing + published verification key ``` ### Delivery Model Each delivery records: ```text id organization_id webhook_id event_id attempt_number request_timestamp response_status response_summary delivered_at failed_at next_attempt_at ``` Webhook workers require: ```text timeouts retry with backoff dead-letter/failure state request signing event IDs idempotency guidance for consumers delivery history manual replay ``` Events should include stable identifiers so consumers can deduplicate. Example: ```json { "id": "evt_123", "type": "engineering.design.approved", "organizationId": "org_456", "occurredAt": "2026-08-26T12:00:00Z", "data": { "designId": "design_789" } } ``` --- ## 58. Background Jobs Workers handle: ```text Email SMS Notifications PDF/report generation File security scanning Document processing Imports Exports Bulk updates Webhook delivery Search indexing Large data operations ``` Architecture: ```text API ↓ Queue ↓ Worker ``` ### Async Job Resource Use a shared job model for long-running user-requested operations. Suggested table: ```text jobs ``` Fields: ```text id organization_id requested_by_user_id job_type status input_reference result_reference progress_percent created_at started_at completed_at failed_at error_code error_summary ``` States: ```text queued running completed failed cancelled ``` REST: ```http GET /api/v1/jobs/{jobId} GET /api/v1/jobs/{jobId}/result POST /api/v1/jobs/{jobId}/cancel ``` ### Import / Export Do not create asynchronous side effects with `GET`. Engineering examples: ```http POST /api/v1/engineering/project-imports POST /api/v1/engineering/project-exports POST /api/v1/engineering/time-entry-imports POST /api/v1/engineering/time-entry-exports ``` Response: ```http 202 Accepted ``` ```json { "data": { "jobId": "job_123", "status": "queued" } } ``` Initial formats may include: ```text CSV JSON ``` Import requirements: ```text validation report row-level errors all-or-partial mode explicitly defined idempotency strategy audit event job result artifact ``` Export requirements: ```text authorization applied before generation signed result URL expiration audit where data sensitivity requires it ``` ## 59. Redis Use Redis as an acceleration and coordination layer, not the authoritative system of record. Appropriate uses: ```text job queue rate-limit counters short-lived authorization caches organization configuration cache session lookup acceleration idempotency lookup acceleration distributed locks when justified ``` ### Cache Layers L1 optional application-memory cache: ```text static permission definitions non-sensitive configuration ``` L2 Redis shared cache: ```text organization settings membership snapshots role permission snapshots rate-limit counters session lookup cache recent idempotency lookups ``` CDN: ```text frontend static assets explicitly public assets only ``` Do not cache private professional API responses at a CDN by default. ### Cache Invalidation Invalidate or version caches when: ```text membership changes role permissions change organization settings change professional credentials change session is revoked profession module enablement changes ``` High-risk authorization decisions must not depend solely on stale cached credential state. ### Idempotency Durability Redis may improve idempotency lookup latency, but PostgreSQL remains authoritative for high-risk commands. ## 60. Pagination Use cursor pagination. Example: ```http GET /api/v1/engineering/projects?limit=25 ``` Response: ```json { "data": [], "meta": { "pagination": { "nextCursor": "...", "hasMore": true } } } ``` Maximum page size: ```text 100 ``` --- ## 61. Filtering Use explicit resource-specific filters. Examples: ```http GET /api/v1/engineering/projects?status=active&discipline=structural GET /api/v1/engineering/tasks?status=todo&assignedToUserId=user_123 ``` Do not build a generic query DSL in v1. --- ## 62. Sorting Examples: ```http GET /api/v1/engineering/projects?sort=createdAt GET /api/v1/engineering/projects?sort=-createdAt ``` Only explicitly supported fields may be sorted. --- ## 63. Search Start with PostgreSQL search. Engineering search may cover: ```text project number project name client name ``` Legal: ```text matter number client case number ``` Healthcare: ```text patient number patient identity ``` Healthcare search requires stricter privacy and authorization controls. Potential PostgreSQL capabilities: - B-tree indexes for exact/filter queries - PostgreSQL full-text search where appropriate - `pg_trgm` only when fuzzy search requirements justify it Do not introduce Elasticsearch/OpenSearch until real query volume, relevance requirements, or indexing features justify another distributed system. Do not create every conceivable search index on day one. Indexes cost memory, storage, and write performance. ## 64. Optimistic Concurrency Important mutable resources should use a version field. Example: ```json { "id": "project_123", "version": 6 } ``` Update: ```json { "version": 6, "name": "Central Tower Phase II" } ``` If the current database version differs: ```text 409 CONCURRENT_MODIFICATION ``` --- ## 65. Domain-Oriented REST Important state transitions use explicit command endpoints. Good: ```http POST /engineering/projects/{id}/close POST /engineering/designs/{id}/approve POST /engineering/tasks/{id}/complete POST /engineering/inspections/{id}/complete POST /invoices/{id}/issue ``` Avoid: ```http PATCH /resource/{id} { "status": "approved" } ``` when the change has significant rules or side effects. --- ## 66. Transaction Boundaries Create project: ```text BEGIN create project assign project manager write audit event write outbox event COMMIT ``` Approve design: ```text BEGIN validate permission validate project access validate credentials validate design state create review result mark approved write audit event write outbox event COMMIT ``` --- ## 67. Request Context Every authenticated request should resolve: ```text RequestContext { requestId userId sessionId organizationId membershipId permissions } ``` Profession modules consume this context. --- ## 68. Request IDs Every request has: ```http X-Request-Id ``` If missing, the server generates one. Use it in: - logs - audit context - error diagnostics - asynchronous correlation --- ## 69. OpenAPI Maintain: ```text openapi.yaml ``` Use OpenAPI 3.1. Production server example: ```yaml servers: - url: https://api.example.com/api/v1 ``` The server URL and path definitions must remain consistent with the platform base path. OpenAPI defines: - routes - request DTOs - response DTOs - security schemes - organization header - request IDs - idempotency header - pagination - filters - error schemas - examples - profession tags Security scheme: ```yaml components: securitySchemes: bearerAuth: type: http scheme: bearer bearerFormat: JWT ``` Reusable headers/parameters: ```text X-Organization-Id X-Request-Id Idempotency-Key limit cursor ``` CI must validate the OpenAPI document. Contract tests should detect drift between implementation and specification. Generated clients may be used by the separate frontends, but generated transport code should not dictate frontend domain architecture. ## 70. DTO Rule Database models are not public API contracts. Use: ```text Request DTO Response DTO ``` A database migration should not accidentally change the public API. --- ## 71. Backend Module Structure Recommended: ```text src/ ├── core/ │ ├── auth/ │ ├── organizations/ │ ├── memberships/ │ ├── authorization/ │ ├── documents/ │ ├── billing/ │ ├── audit/ │ └── events/ │ ├── engineering/ │ ├── clients/ │ ├── projects/ │ ├── project-members/ │ ├── phases/ │ ├── sites/ │ ├── tasks/ │ ├── designs/ │ ├── inspections/ │ └── specifications/ │ ├── legal/ │ ├── clients/ │ ├── matters/ │ ├── cases/ │ ├── hearings/ │ ├── conflicts/ │ └── retainers/ │ └── healthcare/ ├── patients/ ├── practitioners/ ├── appointments/ ├── encounters/ ├── records/ └── prescriptions/ ``` --- ## 72. Internal Module Structure Example: ```text projects/ ├── domain/ │ ├── project.entity.ts │ ├── project-status.ts │ └── project.errors.ts │ ├── application/ │ ├── commands/ │ │ ├── create-project.ts │ │ ├── update-project.ts │ │ └── close-project.ts │ │ │ └── queries/ │ ├── get-project.ts │ └── list-projects.ts │ ├── infrastructure/ │ └── project.repository.ts │ └── api/ ├── project.controller.ts ├── project.request.ts └── project.response.ts ``` --- ## 73. Controllers Controllers should handle: ```text HTTP authentication context input DTO parsing application command/query invocation response mapping ``` Controllers should not contain: ```text business rules raw SQL role logic transaction orchestration email sending audit implementation ``` --- ## 74. Commands and Queries Mutations use commands. Examples: ```text CreateEngineeringProjectCommand ApproveEngineeringDesignCommand CloseLegalMatterCommand CompleteHealthcareEncounterCommand ``` Reads use queries. Examples: ```text GetEngineeringProjectQuery ListLegalMattersQuery GetHealthcarePatientQuery ``` --- ## 75. Repositories Use domain-specific repositories. Examples: ```text EngineeringProjectRepository LegalMatterRepository HealthcarePatientRepository ``` Avoid one massive generic repository abstraction that eventually needs dozens of flags. --- ## 76. Security Baseline Minimum controls: ```text TLS everywhere Argon2id or equivalent strong password hashing short-lived access tokens refresh-token rotation refresh-token reuse detection server-side session revocation rate limiting anti-automation controls RBAC resource policies credential-aware authorization tenant isolation input validation SQL injection protection signed object-storage URLs file-content validation malware scanning audit trails secret management encryption at rest dependency scanning container/image scanning security headers request/correlation IDs backup and restore testing ``` ### Rate Limiting Model policy rather than baking arbitrary numbers into architecture. Example: ```typescript interface RateLimitRule { routePattern: string; method: string; windowSeconds: number; maxRequests: number; scope: 'user' | 'organization' | 'ip' | 'email' | 'session'; } ``` Policy classes: ```text authentication password recovery general API search upload authorization report generation webhooks/integrations clinical record reads ``` Rate-limit values are configuration derived from: ```text security testing load testing observed traffic customer tier endpoint cost abuse risk ``` Do not grant normal tenant roles blanket rate-limit bypass. Administrative exceptions, if any, require explicit trusted-system policy. Return: ```http 429 Too Many Requests Retry-After: ... ``` Error: ```text RATE_LIMIT_EXCEEDED ``` ### Secrets Use managed secrets/key management where possible. Never put real secrets in source-controlled examples. Prefer JWT asymmetric signing or managed signing keys with rotation capability. ## 77. Data Classification Suggested classes: ### Public ```text marketing configuration ``` ### Internal ```text organization settings tasks ``` ### Confidential ```text engineering documents legal matters billing ``` ### Highly Sensitive ```text clinical records professional credentials authentication secrets ``` --- ## 78. Healthcare Security Before healthcare production use, define: ```text privacy model minimum-necessary access model clinical access policies break-glass/emergency access policy if required audit policy record-signing policy amendment policy retention policy credential policy scope-of-practice policy jurisdiction requirements encryption strategy consent requirements data residency requirements backup/restore handling export/portability requirements breach-response requirements ``` Healthcare is a stricter security tier. Key rules: 1. default patient responses do not contain all available PHI 2. clinical record reads may be auditable events 3. signed records are immutable except through explicit amendment/version workflows 4. prescribing authorization is jurisdiction-specific 5. privileged clinical commands revalidate professional authority 6. caches must not allow revoked credentials to remain effective for high-risk writes 7. healthcare search results themselves are protected data 8. access logs may require dedicated permissions 9. do not claim regulatory compliance from architecture alone ## 79. Observability Use: ```text structured logs metrics distributed tracing request IDs correlation IDs ``` Recommended: ```text OpenTelemetry ``` ### Core Metrics API: ```text api_requests_total api_errors_total api_request_duration_seconds ``` Authentication: ```text auth_login_attempts_total auth_token_refresh_total auth_refresh_reuse_detections_total auth_sessions_revoked_total ``` Authorization/security: ```text cross_tenant_access_attempts_total tenant_isolation_invariant_failures_total authorization_denials_total credential_policy_denials_total rate_limit_events_total ``` Important distinction: ```text cross_tenant_access_attempt = request attempted another tenant's resource ``` This may be a stale link, mistake, or attack. ```text tenant_isolation_invariant_failure = our system nearly or actually created/returned cross-tenant data ``` That is a high-severity internal correctness/security incident. Outbox/jobs/webhooks: ```text outbox_events_pending outbox_events_failed_total outbox_processing_duration_seconds jobs_queued jobs_failed_total job_duration_seconds webhook_delivery_attempts_total webhook_delivery_failures_total webhook_delivery_latency_seconds ``` Database: ```text db_pool_active db_pool_waiting db_query_duration_seconds db_transaction_duration_seconds ``` Business metrics may include: ```text engineering_projects_created_total engineering_designs_approved_total engineering_inspections_completed_total invoices_issued_total ``` Avoid patient-specific or sensitive identifiers in metric labels. ### Alerts Examples: ```text refresh token reuse detected tenant isolation invariant failure outbox backlog exceeds SLO webhook failure spike database pool saturation error-rate spike latency regression backup failure malware scanner unavailable ``` Thresholds are calibrated from real environments rather than copied from a review document. ### SLOs Define by endpoint class. Interactive CRUD, reports, file orchestration, and background jobs should not share one arbitrary latency target. ## 80. Logging Useful fields: ```text request_id route method status duration user_id when appropriate organization_id when appropriate ``` Never log: ```text passwords tokens clinical record text full sensitive documents payment secrets ``` --- ## 81. Testing Strategy ### Unit Tests Test: ```text domain rules state transitions authorization policies credential policies money calculations idempotency request hashing ``` ### Property-Based Tests Use property-based testing for high-value domain state machines. Candidates: ```text engineering design lifecycle engineering inspection lifecycle invoice lifecycle payment state transitions membership/role invariants ``` Correct properties: ```text every successful transition ends in a valid state every forbidden transition is rejected terminal states reject prohibited actions required invariants survive every valid transition transition sequences never bypass required approval/credential rules ``` Do not assert that every random state/action pair succeeds. Many are supposed to fail. ### Integration Tests Test: ```text repositories tenant-aware foreign keys PostgreSQL constraints transactions outbox persistence idempotency persistence cache invalidation job persistence webhook delivery persistence ``` ### API Tests Every important endpoint covers: ```text happy path request validation authentication organization context permission denial scope denial credential denial where relevant cross-tenant access concurrent modification invalid state transition idempotent replay idempotency conflict audit creation outbox creation ``` ### Outbox Reliability / Chaos Tests Test: ```text worker crash before side effect worker crash after side effect but before marking processed two workers competing for same row temporary dependency outage retry/backoff behavior dead-letter behavior consumer idempotency lost worker wake-up replay ``` The dangerous scenario is: ```text external side effect succeeds worker dies event retries ``` Tests must prove the consumer does not create an unacceptable duplicate. ### Tenant Security Tests Test both: ```text external cross-tenant access attempts ``` and: ```text internal cross-tenant data invariant failures ``` These are different classes of failure. ### Performance Tests Create realistic profiles: ```text interactive reads interactive writes search dashboard read models reporting file upload orchestration outbox processing webhook bursts notification bursts ``` Measure: ```text p50 p95 p99 throughput error rate database saturation queue backlog ``` Set production SLO gates only after a realistic baseline exists. ### Coverage Track code coverage. Do not treat a single percentage such as `90%` as proof of quality. Critical-path expectations are stronger: ```text all tenant-isolation paths tested all financial commands tested all regulated commands tested all state transitions tested all critical authorization policies tested ``` ## 82. Tenant Security Tests For every major resource, attempt: ```text Organization A resource using Organization B context ``` Test: ```text read update delete/action list filtering search documents ``` Expected result: ```text 404 / denied ``` --- ## 83. Engineering MVP Engineering is the first vertical. Initial features: ```text Authentication Organization management Users / memberships / roles Engineering clients Projects Project members Project phases Tasks Sites Documents Basic design records Inspections Time entries Basic billing Audit history ``` Do not initially build: ```text advanced CAD integration BIM integration full document markup advanced resource planning procurement complex accounting AI design analysis IoT integrations ``` --- ## 84. Engineering MVP Workflow ```text User registers ↓ Creates engineering organization ↓ Invites engineer ↓ Assigns role ↓ Creates client ↓ Creates project ↓ Assigns project team ↓ Creates project phases ↓ Creates tasks ↓ Uploads documents ↓ Creates design ↓ Reviews / approves design ↓ Schedules inspection ↓ Records inspection findings ↓ Records engineering time ↓ Creates invoice ↓ Records payment ↓ Closes project ↓ Audit history contains lifecycle ``` --- ## 85. Development Phases ### Phase 0: Architecture Foundation Deliver: ```text domain boundaries database conventions REST conventions authorization model session/token model idempotency strategy error taxonomy OpenAPI skeleton engineering state machines migration conventions threat model initial ADRs risk register ``` ### Phase 1: Shared Platform Core Build: ```text auth sessions refresh-token families token rotation/revocation users organizations organization professions membership invitations memberships roles permissions authorization audit outbox request context idempotency rate limiting observability ``` ### Phase 2: Engineering CRM Build: ```text engineering clients engineering client contacts client archive/restore ``` ### Phase 3: Engineering Projects Build: ```text projects project members project phases activation/close/archive ``` ### Phase 4: Work and Site Management Build: ```text tasks task batch operations sites ``` ### Phase 5: Documents Build: ```text documents versions categories classification retention references signed uploads multipart uploads content verification malware scanning engineering document links ``` ### Phase 6: Engineering Designs Build: ```text designs assignments versions reviews cancel/withdraw semantics credential-aware approval audit outbox idempotency ``` ### Phase 7: Engineering Inspections Build: ```text inspection lifecycle inspection outcome findings corrective work follow-up inspections attachments audit outbox idempotency ``` ### Phase 8: Time, Budgets, and Billing Build: ```text time entries batch timesheet submission project budgets when required invoices payments financial idempotency reconciliation ``` ### Phase 9: Notifications, Jobs, and Webhooks Build: ```text notifications email async jobs imports/exports webhooks delivery/retry dead-letter handling ``` ### Phase 10: Reporting and Search Build: ```text project status overdue work inspection status billable time revenue outstanding invoices dashboard read models ``` ### Phase 11: Engineering Client Portal Build: ```text portal account invitations external project grants published project documents client review/acceptance workflow portal audit portal-specific frontend ``` Do not expose professional approval actions to client portal accounts. ### Phase 12: Legal Vertical Validate shared core against: ```text matters cases conflicts deadlines retainers restricted access / ethical walls ``` ### Phase 13: Healthcare Readiness and Vertical Before implementation: ```text healthcare threat model privacy review jurisdiction analysis scope-of-practice policy record signing/amendment model retention model audit requirements ``` ### Estimation Rule These are dependency-ordered milestones. They are not calendar promises. Calendar estimates require: ```text team size frontend/UX scope cloud decisions third-party providers security requirements QA capacity domain-expert availability ``` ## 86. Legal Expansion Only after engineering proves the shared platform assumptions. Build: ```text Legal Client ↓ Matter ↓ Case ↓ Hearings / Deadlines / Documents ``` Do not redesign engineering around legal terminology. Extract only genuinely reusable infrastructure. --- ## 87. Healthcare Expansion Healthcare comes after: - core platform is stable - audit model is proven - permission model is proven - tenant isolation is tested - retention and encryption strategies are defined Healthcare should be treated as its own security and compliance workstream. --- ## 88. Deployment Environments Use: ```text development testing staging production ``` Each environment has independent: ```text database object storage secrets queues API keys ``` --- ## 89. Initial Deployment Architecture ```text CDN │ ├── Engineering Web ├── Legal Web └── Healthcare Web Load Balancer │ Backend API │ ├── PostgreSQL ├── Redis ├── Object Storage └── Queue │ Workers ``` Prefer managed infrastructure where practical. --- ## 90. Backup Strategy Database: ```text automated backups point-in-time recovery tested restores ``` Object storage: ```text versioning retention policies backup or replication where required ``` A backup strategy is incomplete until restoration is tested. --- ## 91. Migration Strategy Use explicit immutable migration files. Recommended naming: ```text YYYYMMDDHHMMSS_description.sql ``` Example: ```text 20260826010000_create_organizations.sql 20260826011000_create_users.sql 20260826012000_create_memberships.sql 20260826013000_create_rbac.sql 20260826014000_create_audit_outbox.sql 20260826015000_create_engineering_clients.sql ``` ### UUID Standard The platform uses UUIDv7. Supported implementation choices: ```text PostgreSQL 18+: use native uuidv7() if database-generated identifiers are desired Earlier PostgreSQL: generate UUIDv7 in the application or use a controlled extension ``` Database columns remain PostgreSQL `UUID`. The rule is consistency, not ideological loyalty to one generation layer. Do not silently fall back to UUIDv4 while documenting UUIDv7. ### Production Migration Rules Use expand/contract: ```text 1. add backward-compatible schema 2. deploy code supporting old + new schema 3. backfill/migrate 4. switch reads/writes 5. observe 6. remove obsolete schema later ``` For destructive changes: ```text backup/restore plan compatibility window production-like dry run explicit approval post-migration verification ``` Do not assume a destructive database migration can always be reversed by a simple down migration. Never use automatic ORM schema synchronization in production. ## 91A. Architecture Decision Records v4 stops treating technology suggestions as automatically settled architecture. Create ADRs before implementation locks in: ```text ADR-001 Backend Framework ADR-002 SQL / ORM / Query Layer ADR-003 Queue Implementation ADR-004 PostgreSQL Minimum Version ADR-005 Error Format / RFC 9457 Compatibility ADR-006 Rate-Limit Header Convention ADR-007 Webhook Signing Strategy ADR-008 Object Storage Provider / Multipart Strategy ``` Each ADR should include: ```text context decision alternatives considered tradeoffs security impact operational impact migration/exit path date status ``` The architecture currently fixes capabilities and boundaries. It does not require a framework merely because a review document described it positively. --- ## 92. Technology Recommendation The following are preferred candidates, not all final decisions. ### Fixed Platform Choices ```text API style: REST Contract: OpenAPI 3.1 Primary language: TypeScript Primary database: PostgreSQL Architecture: Modular Monolith Observability standard: OpenTelemetry Object storage model: S3-compatible Container model: Docker/OCI ``` ### ADR-Gated Choices Backend framework candidates: ```text NestJS Fastify-centered custom application structure ``` SQL / persistence candidates: ```text Drizzle Kysely Prisma direct SQL for specialized queries ``` Queue candidates: ```text BullMQ / Redis managed cloud queue ``` PostgreSQL baseline: ```text PostgreSQL 18+ ``` is attractive because of native UUIDv7 and current capabilities, but the minimum supported version must be confirmed against: ```text hosting provider availability operations policy extension requirements upgrade policy support lifecycle ``` Do not claim one ORM is categorically "faster" or "better" without workload-specific evidence. The selected stack should preserve: ```text transaction control explicit SQL visibility tenant-safe query design migration control observability testability ``` ## 93. REST API Milestones ### Milestone 1: Platform Access and Security ```http POST /auth/register POST /auth/login POST /auth/token/refresh POST /auth/token/revoke POST /auth/token/revoke-all GET /auth/sessions DELETE /auth/sessions/{sessionId} GET /me POST /organizations GET /me/organizations POST /membership-invitations GET /memberships GET /roles POST /roles GET /permissions ``` Includes: ```text explicit organization context session revocation refresh-token reuse detection audit foundation outbox foundation idempotency foundation rate limiting ``` ### Milestone 2: Engineering Clients ```http GET /engineering/clients POST /engineering/clients GET /engineering/clients/{id} PATCH /engineering/clients/{id} POST /engineering/clients/{id}/archive POST /engineering/clients/{id}/restore GET /engineering/clients/{id}/projects ``` ### Milestone 3: Engineering Projects ```http GET /engineering/projects POST /engineering/projects GET /engineering/projects/{id} PATCH /engineering/projects/{id} POST /engineering/projects/{id}/activate POST /engineering/projects/{id}/close POST /engineering/projects/{id}/archive GET /engineering/projects/{id}/summary ``` Timeline and budget read models follow when the frontend requires them. ### Milestone 4: Collaboration ```http POST /engineering/projects/{id}/members GET /engineering/projects/{id}/members POST /engineering/tasks GET /engineering/tasks POST /engineering/tasks/{id}/complete ``` ### Milestone 5: Sites and Documents Build: ```text engineering sites signed file uploads document versions malware scanning project document links ``` ### Milestone 6: Designs Build: ```text design lifecycle versions reviews submit-review request-changes approve reject supersede credential validation audit + outbox + idempotency ``` ### Milestone 7: Inspections Build: ```text schedule start complete cancel findings finding resolution audit + outbox + idempotency ``` ### Milestone 8: Commercial Workflows Build: ```text time entries invoices payments refunds financial idempotency reports ``` ## 94. Architecture Rules to Freeze 1. REST is the primary frontend and integration API. 2. Base path is `/api/v1`. 3. OpenAPI 3.1 is the public API contract. 4. GraphQL is not part of v1. 5. Start as one modular monolith backend. 6. Each profession has its own frontend. 7. Each profession owns its domain tables and state machines. 8. Shared modules provide infrastructure, not forced domain abstractions. 9. Every tenant-owned row contains `organization_id`. 10. Tenant-scoped requests require explicit `X-Organization-Id`. 11. The API never silently selects an organization. 12. Tenant boundaries are enforced in queries and database constraints. 13. Cross-tenant resources appear nonexistent. 14. Authorization is server-side and deny-by-default. 15. Roles and professional qualifications are separate. 16. High-risk professional commands validate authoritative credential state. 17. Sessions and refresh tokens are separate resources. 18. Refresh tokens rotate within families and support reuse detection. 19. UUIDv7 is the identifier standard. 20. PostgreSQL 18 native UUIDv7 may be used when PostgreSQL 18+ is selected. 21. Important domain transitions use explicit REST command endpoints. 22. High-risk commands use durable idempotency. 23. Redis may accelerate idempotency but is not authoritative for financial/regulated commands. 24. Profession-specific state transitions are explicitly modeled and tested. 25. Design cancellation and post-review withdrawal are distinct when needed. 26. Inspection lifecycle and outcome are separate dimensions. 27. Follow-up inspection work is linked work, not overloaded lifecycle state. 28. Files live in object storage. 29. Large files use native object-storage multipart uploads. 30. Application servers do not proxy multi-gigabyte file chunks. 31. Document checksum is version-level authoritative data. 32. Document classification is multi-level. 33. File upload policy is configurable. 34. Upload validation includes content signature, size, quota, checksum, and malware scanning. 35. Explicit document-link tables are preferred over generic polymorphic links. 36. Project document linkage does not imply client-portal publication. 37. External publication requires an explicit publication record. 38. Client portal identities are not ordinary internal memberships. 39. Client portal permissions are separate from internal RBAC assumptions. 40. Client acceptance/review is not professional engineering approval. 41. Professional design approval is never granted through a client portal role. 42. Small batch commands define atomic or partial semantics explicitly. 43. Large batch operations become asynchronous jobs. 44. Every batch item receives tenant/authorization/domain validation. 45. Domain events use a transactional outbox. 46. Outbox semantics are at-least-once. 47. External side-effect consumers are idempotent. 48. Webhooks are shared platform infrastructure with retry, replay, delivery history, and signing. 49. HMAC webhook signing material is securely recoverable, normally encrypted with managed keys. 50. Async imports/exports/reports use job resources and return `202 Accepted`. 51. `GET` endpoints do not create export jobs. 52. Engineering clients support multiple contacts. 53. Engineering budgets use dedicated tables when budget management enters scope. 54. Derived financial totals do not become uncontrolled duplicate truth. 55. PostgreSQL is the authoritative transactional datastore. 56. Redis is an acceleration and coordination layer. 57. Search starts in PostgreSQL. 58. External search/read replicas/materialized views require measured need. 59. API collections use cursor pagination. 60. Important mutable resources use optimistic concurrency. 61. Database entities are not serialized directly as public API contracts. 62. Errors use stable codes. 63. Rate-limited responses use 429 and Retry-After. 64. Additional rate-limit headers are implementation/API-contract decisions, not frozen legacy header names. 65. Important and regulated actions are audited. 66. Sensitive healthcare reads are audited where policy requires. 67. Signed clinical records use sign/amend/version workflows. 68. Prescribing authority is jurisdiction and scope-of-practice driven. 69. Production migrations follow expand/contract. 70. Destructive schema changes are not assumed trivially reversible. 71. Secrets are managed outside source control. 72. Rate limits are policy/configuration calibrated by evidence. 73. CI validates types, tests, OpenAPI, migrations, and security scans. 74. Property-based tests are used for high-value state machines. 75. Outbox/job/webhook reliability is tested under failure and concurrency. 76. Internal tenant-isolation invariant failures and external cross-tenant attempts are separate signals. 77. Critical-path tests matter more than vanity coverage percentages. 78. Performance objectives are provisional until measured. 79. Architecture risks are maintained in a living register. 80. Framework/ORM/queue choices require ADRs. 81. Engineering is the first implemented product vertical. 82. The engineering client portal follows internal Engineering MVP foundations. 83. Legal follows after engineering validates shared assumptions. 84. Healthcare requires explicit security/privacy/jurisdiction readiness work. 85. Architecture documentation never equates "designed for" with "certified/compliant". 86. v4 is the final broad architecture baseline unless a foundational assumption is invalidated. ## 95. Required Design Artifacts Maintain: ```text 01_PROJECT_ARCHITECTURE.md 02_DATABASE_CONVENTIONS.md 03_AUTHORIZATION_MODEL.md 04_AUTH_SESSION_MODEL.md 05_ENGINEERING_DOMAIN.md 06_ENGINEERING_DATABASE_SCHEMA.md 07_ENGINEERING_STATE_MACHINES.md 08_API_CONVENTIONS.md 09_ENGINEERING_API_SPEC.md 10_OPENAPI.yaml 11_FRONTEND_ARCHITECTURE.md 12_CLIENT_PORTAL_SECURITY_MODEL.md 13_DOCUMENT_SECURITY_MODEL.md 14_LARGE_FILE_UPLOAD_MODEL.md 15_WEBHOOK_INTEGRATION_MODEL.md 16_ASYNC_JOB_MODEL.md 17_SECURITY_MODEL.md 18_DEPLOYMENT_ARCHITECTURE.md 19_OBSERVABILITY_MODEL.md 20_TESTING_STRATEGY.md 21_ARCHITECTURE_DECISION_RECORDS/ 22_RISK_REGISTER.md 23_MVP_BACKLOG.md ``` Important ADRs: ```text backend framework persistence/query layer queue implementation PostgreSQL minimum version error format rate-limit headers webhook signing object-storage provider ``` ## 96. Recommended Implementation Order ```text Foundation ↓ Authentication ↓ Organizations ↓ Memberships ↓ RBAC ↓ Engineering Clients ↓ Engineering Projects ↓ Project Team ↓ Tasks ↓ Sites ↓ Documents ↓ Designs ↓ Inspections ↓ Time Tracking ↓ Billing ↓ Notifications ↓ Reports ↓ Legal Vertical ↓ Healthcare Vertical ``` --- ## 97A. Database Indexing Strategy All tenant-owned tables need efficient tenant scoping. Baseline: ```text (organization_id, id) ``` Common list access often benefits from: ```text (organization_id, created_at) ``` Query-specific examples: ```text (organization_id, status) (organization_id, client_id) (organization_id, project_id) (organization_id, assigned_to_user_id) ``` ### Rules 1. every index corresponds to a known query, ordering, or constraint 2. column order follows real predicates 3. validate with `EXPLAIN (ANALYZE, BUFFERS)` 4. include production-like cardinality in testing 5. measure write amplification 6. do not index every field 7. introduce trigram/full-text indexes only for actual search requirements Potential later tools: ```text covering indexes materialized views read replicas table partitioning external search ``` These are evidence-driven scaling mechanisms, not baseline dependencies. ### Document Category Uniqueness If a nullable field such as profession participates in uniqueness: ```text organization_id profession nullable name ``` do not assume plain uniqueness treats NULL as one shared value. Use PostgreSQL-supported null-aware uniqueness or partial unique indexes according to the selected PostgreSQL version. --- ## 97B.## 97B. CI/CD and Deployment Gates Pipeline stages: ```text lint/typecheck ↓ unit tests ↓ integration tests ↓ OpenAPI validation + contract tests ↓ security/dependency scan ↓ container build + image scan ↓ migration compatibility check ↓ deploy development ↓ smoke tests ↓ deploy staging ↓ E2E + performance/security baseline ↓ manual production approval ↓ production deployment ↓ post-deploy verification ``` Production deployment should support: ```text rolling or blue/green application deployment backward-compatible database migrations health checks fast application rollback feature flags for incomplete features observability gates ``` Database schema rollback is not treated as equivalent to application rollback. ### Configuration and Secrets Non-secret configuration may use environment variables. Secrets should use a managed secret store where possible: ```text database credentials Redis credentials JWT/private signing keys object storage credentials SMTP/API provider credentials monitoring credentials ``` Do not publish real secrets in sample configuration. Organization profession enablement remains primarily data-driven through `organization_professions`. Global feature flags may be used for staged rollout, kill switches, or incomplete features. --- ## 97C. Review-Driven Deferred Decisions The following ideas are valid possibilities but are explicitly **not frozen into v1**: ```text read replicas materialized views Elasticsearch/OpenSearch universal 100 MB file limit fixed 100 req/min user limit fixed 1000 req/hour organization limit specific cache-hit-ratio target specific p95 latency promise database-per-tenant microservices GraphQL ``` These require evidence from: ```text load tests security analysis customer requirements compliance requirements real production workloads ``` This prevents benchmark-shaped guesses from becoming architecture law. --- ## 97D. Provisional Performance Objectives Performance numbers in architecture are starting hypotheses, not guarantees. Initial engineering objectives may begin with: ```text Interactive read: target p95 <= 500 ms Interactive mutation: target p95 <= 750 ms Simple list/search: target p95 <= 800 ms Upload authorization: target p95 <= 300 ms Background outbox pickup: target <= 5 seconds under normal operating conditions ``` These are revised after realistic testing. Track: ```text p50 p95 p99 throughput error rate database saturation queue backlog outbox lag ``` Different endpoint classes receive different SLOs. Do not use file-transfer completion time as an API SLO when bytes travel directly between client and object storage. --- ## 97E. Risk Register Maintain a living risk register. Suggested structure: | Risk | Impact | Mitigation | Owner | Phase | Status | |---|---|---|---|---|---| | Cross-tenant data exposure | Critical | Tenant-aware FKs, scoped queries, security tests | Backend/Security | P0 | Open | | Non-idempotent outbox side effect | Critical | Consumer dedupe, provider idempotency, chaos tests | Backend | P0 | Open | | Migration failure | High | Expand/contract, dry runs, backups | Backend/Platform | P0 | Open | | Engineering workflow mismatch | High | Domain expert validation | Product/Engineering SME | MVP | Open | | Portal authorization leak | Critical | Separate external access model, publication grants | Backend/Security | Portal | Open | | Webhook delivery instability | Medium | Retry, dead-letter, replay, metrics | Backend | Integrations | Open | | Large upload abandonment | Medium | Multipart expiry and cleanup | Backend/Platform | Documents | Open | | Documentation drift | Medium | OpenAPI validation, ADRs, CI | Engineering | Continuous | Open | Do not pretend likelihood labels are quantitative unless the team defines and uses a scoring method. --- ## 97F. Architecture Change Governance v4 is the last broad platform-architecture revision before Engineering MVP implementation. New discoveries should normally become: ```text ADR OpenAPI change database migration domain-state-machine update security decision backlog item runbook ``` rather than a new full architecture rewrite. Reopen the broad architecture only when a discovery invalidates one of these foundational assumptions: ```text tenant model profession separation shared-core boundary REST API model data ownership security trust boundary deployment topology database architecture ``` This prevents design review from becoming an infinite recursion problem. --- ## 97G. Production Readiness Gates Architecture being coherent does not mean production is safe. Before production, require evidence in these categories. ### Security ```text TLS configured password hashing configured refresh rotation/reuse detection tested session revocation tested tenant isolation tests passing authorization/credential policies tested rate limiting active secrets managed outside source control file security scanning active security review completed ``` ### Reliability ```text database backups automated restore tested object storage recovery strategy tested outbox monitoring active job queue monitoring active webhook retry/dead-letter behavior tested health checks configured dependency failures tested ``` ### Data Integrity ```text tenant-aware foreign keys present where required financial invariants tested migration tested on production-like data idempotency tested for high-risk commands optimistic concurrency tested audit integrity tested ``` ### Contract / API ```text OpenAPI validates contract tests pass error schema consistent versioning rules documented client SDK generation validated if used ``` ### Performance ```text load test executed realistic SLOs defined database pool configured key queries analyzed outbox/job backlogs remain within SLO ``` ### Critical Domain Coverage Rather than a magic overall coverage number, require explicit test coverage for: ```text tenant boundaries design approval inspection completion invoice issue payment/refund membership privilege changes clinical record signing/amendment when healthcare exists prescribing authorization when healthcare exists ``` ### Release Gate Principle No single metric such as: ```text 90% test coverage ``` is sufficient evidence of production readiness. Quality gates are based on critical behavior, not vanity percentages. --- ## 97. Final Design Position The platform is: ```text One Shared Platform │ ├── Shared Identity / Sessions ├── Shared Security / Authorization ├── Shared Documents / Multipart Uploads ├── Shared Financial Core ├── Shared Audit / Outbox ├── Shared Jobs / Webhooks / Notifications │ ├── Engineering Internal Product │ ├── Engineering Frontend │ ├── Engineering REST APIs │ ├── Engineering State Machines │ └── Engineering Tables │ ├── Engineering Client Portal │ ├── External Portal Frontend │ ├── Portal Accounts │ ├── Project Grants │ ├── Published Documents │ └── Client Review / Acceptance │ ├── Legal Product │ ├── Legal Frontend │ ├── Legal REST APIs │ └── Legal Tables │ └── Healthcare Product ├── Healthcare Frontend ├── Healthcare REST APIs ├── Healthcare Security Policies └── Healthcare Tables ``` The system shares infrastructure where reuse is valuable while preserving profession-specific domain semantics and trust boundaries. v4 is the final broad architecture baseline for Engineering MVP implementation. From this point forward, architecture detail should primarily move into: ```text ADRs OpenAPI database schema/migrations state-machine specifications security policies implementation backlog runbooks ``` rather than repeatedly rewriting the entire architecture plan. This document does not itself prove: ```text regulatory compliance production certification security certification performance at a specific scale ``` Those require implementation evidence, security review, domain validation, operational testing, restore testing, and measured production-like workloads. --- # v4 Changelog Compared with v3, v4 adds or changes: ```text ✓ dedicated engineering client portal trust boundary ✓ portal accounts separated from organization memberships ✓ explicit project-level external access grants ✓ professional design approval separated from client acceptance ✓ explicit external document-publication model ✓ batch operations with atomic/partial semantics ✓ asynchronous escalation for large batches ✓ object-storage multipart upload orchestration ✓ no API proxying of multi-gigabyte chunks ✓ rate-limit response policy with Retry-After ✓ legacy X-RateLimit headers not frozen into the architecture ✓ framework/ORM/queue decisions moved into ADRs ✓ PostgreSQL minimum version moved into an ADR ✓ RFC 9457 error-format compatibility added as an ADR ✓ provisional performance objectives separated from production SLOs ✓ living risk register introduced ✓ architecture-change governance introduced ✓ v4 designated final broad architecture baseline ```