# Professional Management Platform ## Full REST-First System Design Plan > **Revision:** v4.1 — Consistency and Implementation-Contract Cleanup > **Status:** Locked broad architecture baseline with implementation-blocking contradictions resolved. Subsequent detail belongs in ADRs, OpenAPI, migrations, domain specifications, and backlog items. > **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.1 Cleanup Notes v4.1 does not redesign the platform. It resolves contradictions and fills implementation contracts discovered during detailed review. Resolved: - raw UUIDv7 is now the serialized/API identifier format - database columns remain UUID; prefixed strings are not public IDs - all tenant-owned subresources carry direct `organization_id` - design-version document cardinality is explicit and relational - engineering time entries can be attributed to a specific work item - project-level `budget_minor` is removed in favor of the dedicated budget model - duplicate `legal_documents` ownership is removed - batch custom-action paths use one documented convention - membership invitations can pre-assign multiple roles - missing design `revise` command is added - inspection follow-ups now have a table and lifecycle - change requests are moved out of the initial Engineering schema until specified - service accounts and API keys are defined for machine access - API JSON, query parameters, and path parameter names use camelCase; database columns use snake_case - professional profiles support multiple professional credentials - deletion/archival/revocation/unlink behavior is globally defined - invoice-item fields are specified - deferred healthcare placeholder tables receive minimum schemas or explicit deferral notes - document upload and multipart DTOs are defined - webhook subscription fields are defined - `POST /auth/logout` is restored - portal review-request and portal capability schemas are defined - project-role, task-status, priority, and inspection-outcome values are defined - document-category uniqueness has a version-independent fallback - retention policy fields are defined - pagination defaults are explicit - `assigned` authorization scope has resource-specific resolution rules - feature-flag behavior is clarified - audit privacy minimization/anonymization strategy is documented - outbox correlation and causation IDs are added - CORS and web-security configuration is moved into a required ADR - project `stage` duplication is removed; project phases remain authoritative - phase reordering, site listing, appointment locations, and encounter reason fields are clarified - jobs remain tenant-scoped through the standard organization header rather than path nesting --- --- --- --- ## 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: 0193c0a0-7c1e-7b3a-8c4d-6e5f4a3b2c1d X-Request-Id: 0193c0a0-7c1e-7b3a-8c4d-6e5f4a3b2cff Content-Type: application/json ``` ### API Naming Convention Public API representation: ```text JSON properties: camelCase query parameters: camelCase path parameter names in documentation: camelCase HTTP headers: conventional HTTP header casing ``` Database representation: ```text table names: snake_case column names: snake_case constraint/index names: snake_case ``` Example: ```http GET /api/v1/engineering/tasks?assignedToUserId=&createdAfter=2026-08-01T00:00:00Z ``` ```json { "assignedToUserId": "0193c0a0-7c1e-7b3a-8c4d-6e5f4a3b2c3d", "createdAt": "2026-08-26T12:00:00Z" } ``` maps internally to columns such as: ```text assigned_to_user_id created_at ``` ### 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: ```yaml 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_mismatch: status: 404 code: RESOURCE_NOT_FOUND ``` ### Idempotency Use: ```http Idempotency-Key: 8f7d6c5e-4b3a-4b1c-9d8e-7f6a5b4c3d2e ``` Required where duplicate execution can create material side effects. PostgreSQL is authoritative for critical idempotency records. Redis may accelerate lookup. ### Batch Custom-Action Convention For collection-level custom commands use: ```text /{collection}/batch/{action} ``` Examples: ```http POST /api/v1/engineering/tasks/batch/assign POST /api/v1/engineering/tasks/batch/complete POST /api/v1/engineering/time-entries/batch/submit ``` Do not mix `batch-assign`, colon-style custom methods, and `/batch/assign` in the same API. ### Rate-Limit Responses ```http 429 Too Many Requests Retry-After: ``` Additional rate-limit metadata may be exposed according to the selected gateway/standard. Do not freeze legacy `X-RateLimit-*` names here. ### Error Standard Decision The current error envelope remains: ```json { "error": { "code": "RESOURCE_NOT_FOUND", "message": "Resource not found.", "details": {}, "requestId": "0193c0a0-7c1e-7b3a-8c4d-6e5f4a3b2cff" } } ``` ADR-005 decides whether OpenAPI v1 aligns this with RFC 9457 Problem Details. Do not silently change the envelope during implementation. ## 9. Standard Response Format Single resource: ```json { "data": { "id": "0193c0a0-7c1e-7b3a-8c4d-6e5f4a3b2c5d", "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": "0193c0a0-7c1e-7b3a-8c4d-6e5f4a3b2c6d", "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. --- ## 11A. Identifier Convention The serialized identifier standard is **raw UUIDv7**. Example: ```text 0193c0a0-7c1e-7b3a-8c4d-6e5f4a3b2c1d ``` Database: ```sql id UUID PRIMARY KEY ``` API: ```json { "id": "0193c0a0-7c1e-7b3a-8c4d-6e5f4a3b2c1d" } ``` Do not serialize IDs as: ```text org_ user_ project_ ``` unless a future ADR explicitly changes the public identifier contract. Human-friendly resource references use separate fields such as: ```text projectNumber matterNumber patientNumber invoiceNumber ``` This separates machine identity from business/display references. UUID generation is decided by ADR-004: ```text PostgreSQL-native UUIDv7 when supported and selected or application-generated UUIDv7 ``` The API format is identical either way. --- ## 12. Authentication Initial human authentication: ```text Email + Password + Short-Lived Access Token + Opaque Refresh Token + Server-Side Session ``` REST: ```http POST /api/v1/auth/register POST /api/v1/auth/login POST /api/v1/auth/logout 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 ``` `POST /auth/logout` revokes the current session. `DELETE /auth/sessions/{sessionId}` allows a user to revoke a specific session, such as another device. ### Access Token ```yaml format: JWT lifetime: short-lived signed: true encrypted: false claims: - sub - sessionId - issuer - audience - issuedAt - expiresAt ``` Organization context is not trusted from the token as authorization authority. ### Sessions ```text sessions ├── id ├── user_id ├── device metadata ├── created_at ├── last_active_at ├── expires_at ├── revoked_at └── revocation_reason ``` ### Refresh Tokens ```text refresh_tokens ├── id ├── session_id ├── family_id ├── token_hash ├── issued_at ├── expires_at ├── rotated_at ├── replaced_by_token_id ├── revoked_at └── revocation_reason ``` Constraints/indexes: ```text UNIQUE(token_hash) INDEX(family_id) INDEX(session_id) ``` `family_id` is not unique. ### Refresh Reuse Detection Use of a previously rotated token triggers: ```text revoke token family revoke affected session security audit event reauthentication ``` Policy may escalate to all-session revocation. Future human authentication: - MFA - WebAuthn/passkeys - OIDC/SSO - enterprise identity providers ## 12A. Service Accounts and API Keys Machine-to-machine access is separate from human sessions. Use: ```text service_accounts api_keys service_account_roles ``` ### Service Account Suggested fields: ```text id organization_id name description status created_by_user_id created_at updated_at revoked_at ``` ### API Key Suggested fields: ```text id organization_id service_account_id key_prefix secret_hash created_at expires_at last_used_at revoked_at revocation_reason ``` Raw API-key secrets are shown only once. Store only a secure hash of the secret. `key_prefix` is safe display material for identifying a key in administration screens. ### Authorization Service accounts use explicit organization-scoped permissions, preferably through: ```text service_account_roles ``` with the same registered permission vocabulary used by RBAC. They do not become fake human memberships. ### Audit Audit actors support: ```text actor_type = user actor_type = service_account actor_type = system ``` Machine authentication is required when public/integration API access is implemented; it does not block the earliest internal Engineering UI slice. --- ## 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. Tables: ```text membership_invitations membership_invitation_roles ``` `membership_invitations`: ```text id organization_id email invited_by_user_id expires_at accepted_at revoked_at created_at ``` `membership_invitation_roles`: ```text organization_id invitation_id role_id created_at ``` Use tenant-aware foreign keys so invitation roles cannot reference another organization's role. Flow: ```text Invitation + Intended Roles ↓ Accepted ↓ User ↓ Membership ↓ Membership Roles ``` At acceptance: 1. validate invitation token and expiry 2. validate invited email/account policy 3. create membership 4. copy valid intended roles to membership-role assignments 5. mark invitation accepted 6. audit 7. emit outbox event If an intended role was revoked/deleted before acceptance, acceptance fails safely or drops that role according to explicit organization policy. ## 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 ``` Example: ```text Engineer: engineering.projects.read = assigned Principal Engineer: engineering.projects.read = organization ``` `assigned` is not magic. Each resource policy defines how assignment is resolved. ### Engineering Project Assigned when: ```text engineering_project_members.user_id = ctx.userId AND engineering_project_members.left_at IS NULL ``` or when the user is the active project manager, if project-manager assignment is modeled separately. ### Engineering Task Assigned when: ```text engineering_tasks.assigned_to_user_id = ctx.userId ``` For tasks linked to a project, parent-project access may also be required. ### Engineering Design Assigned when an active row exists in: ```text engineering_design_assignments ``` for the user and an allowed assignment role. ### Engineering Inspection Assigned when: ```text engineering_inspections.inspector_user_id = ctx.userId ``` or an explicit inspection assignment exists if the model later supports multiple inspectors. ### Derived Client Access An assigned professional may access a client only through a policy that derives access from authorized projects. Project assignment must not automatically grant access to every project belonging to that client. Future scopes may include: ```text owned team department restricted ``` Do not add them before a real workflow requires them. ## 21. Professional Credentials Professional identity and credentials are separate from RBAC. Use: ```text professional_profiles professional_credentials ``` ### Professional Profile One organization/user/profession relationship. Suggested fields: ```text id organization_id user_id profession title status created_at updated_at ``` ### Professional Credential One profile may hold many credentials. Suggested fields: ```text id organization_id professional_profile_id credential_type credential_number issuing_authority jurisdiction discipline status valid_from expires_at verified_at verified_by_user_id created_at updated_at ``` Examples: ```text professional engineering license in jurisdiction A professional engineering license in jurisdiction B specialty certification medical license controlled-substance prescribing registration where applicable ``` Credential policy evaluates the set of active credentials rather than one `primary_license_number`. High-risk actions such as design approval, record signing, or prescribing use authoritative or revocation-aware credential state. Prescribing remains jurisdiction/scope-of-practice policy, not a hard-coded profession test. ## 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. --- ## 21A. Deletion, Archival, Revocation, and Unlink Policy `DELETE` does not have one universal persistence meaning. Use four lifecycle behaviors. ### Archive / Domain Inactivation For business records whose history matters: ```text engineering clients engineering projects legal matters healthcare patients documents where retention requires history ``` Typical fields: ```text status archived_at archived_by_user_id ``` Restore is permitted only when domain, retention, and organization policy allow it. ### Revoke For access/security resources: ```text sessions refresh tokens API keys membership invitations portal grants webhook credentials ``` Use: ```text revoked_at revoked_by revocation_reason ``` ### Temporal Unlink For relationship records where the historical relationship matters: ```text project documents project members design assignments portal document publications ``` Use: ```text unlinked_at left_at unassigned_at revoked_at ``` rather than deleting historical evidence. ### Hard Delete Reserved for genuinely disposable or never-committed data, such as: ```text expired pending upload artifacts failed temporary staging objects unreferenced draft configuration where audit/retention does not require history ``` Hard deletion of financial, professional, audit, signed clinical, or issued business records is forbidden unless an explicit retention/privacy policy defines the operation. Every resource specification must declare its lifecycle behavior. --- # Engineering Domain ## 26. Engineering Tables Initial Engineering MVP tables: ```text engineering_clients engineering_client_contacts engineering_projects engineering_project_members engineering_project_phases engineering_sites engineering_tasks engineering_designs engineering_design_assignments engineering_design_versions engineering_design_version_documents engineering_design_reviews engineering_inspections engineering_inspection_findings engineering_inspection_followups engineering_specifications engineering_time_entries ``` Later Engineering extensions: ```text engineering_project_budgets engineering_project_budget_items engineering_project_commitments engineering_project_cost_entries engineering_change_requests ``` `engineering_change_requests` is not part of the initial schema until its lifecycle, relationships, and REST contract are specified. ## 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 status project_manager_user_id start_date expected_completion_date completed_date created_at updated_at version ``` `stage` is removed from the project row because project phases are the authoritative workflow decomposition. If the frontend needs a "current stage", derive it from the active/current project phase or maintain an explicitly documented `current_phase_id` pointer. Project `budget_minor` is also removed. Detailed project budgets belong to the dedicated budget model. 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 reads: ```http GET /api/v1/engineering/projects/{projectId}/summary GET /api/v1/engineering/projects/{projectId}/timeline GET /api/v1/engineering/projects/{projectId}/budget ``` The budget endpoint reads from the budget module when that module exists. ## 29. Engineering Project Members Suggested fields: ```text id organization_id project_id user_id project_role joined_at left_at ``` Initial project-role vocabulary: ```text project_manager engineer designer reviewer inspector viewer contractor ``` Project role describes participation in one project. It is not a substitute for RBAC permission. 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} ``` `DELETE` means end participation by setting `left_at`, not erase historical participation. ## 30. Engineering Project Phases Suggested fields: ```text id organization_id project_id name sequence status start_date end_date created_at updated_at version ``` Typical initial statuses: ```text planned active completed cancelled ``` Example 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 POST /api/v1/engineering/projects/{projectId}/phases/reorder ``` Reorder request: ```json { "projectVersion": 12, "orderedPhaseIds": [ "0193c0a0-7c1e-7b3a-8c4d-6e5f4a3b301", "0193c0a0-7c1e-7b3a-8c4d-6e5f4a3b302" ] } ``` Reordering is transactional. Sequences remain unique within a project after commit. ## 31. Engineering Sites Suggested fields: ```text id organization_id project_id name address latitude longitude created_at updated_at ``` REST: ```http GET /api/v1/engineering/sites GET /api/v1/engineering/sites/{siteId} POST /api/v1/engineering/projects/{projectId}/sites GET /api/v1/engineering/projects/{projectId}/sites PATCH /api/v1/engineering/sites/{siteId} ``` Global site listing is still tenant-scoped through `X-Organization-Id`. ## 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 ``` Statuses: ```text todo in_progress completed cancelled ``` Priorities: ```text low medium high urgent ``` 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": [ "0193c0a0-7c1e-7b3a-8c4d-6e5f4a3b2c81", "0193c0a0-7c1e-7b3a-8c4d-6e5f4a3b2c82", "0193c0a0-7c1e-7b3a-8c4d-6e5f4a3b2c83" ], "assigneeUserId": "0193c0a0-7c1e-7b3a-8c4d-6e5f4a3b2c3d", "mode": "partial" } ``` Example response: ```json { "data": { "succeeded": [ "0193c0a0-7c1e-7b3a-8c4d-6e5f4a3b2c81", "0193c0a0-7c1e-7b3a-8c4d-6e5f4a3b2c82" ], "failed": [ { "id": "0193c0a0-7c1e-7b3a-8c4d-6e5f4a3b2c83", "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 ``` 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}/revise 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 ``` 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 ``` Approval remains credential-aware, audited, and idempotent. ## 34. Design Versions and Reviews A design version is a logical professional revision. It may have multiple document files. Use: ```text engineering_design_versions engineering_design_version_documents engineering_design_reviews ``` ### Design Version ```text id organization_id design_id version_number created_by_user_id created_at ``` Unique: ```text (organization_id, design_id, version_number) ``` ### Design Version Documents ```text id organization_id design_version_id document_id document_role linked_by_user_id linked_at unlinked_at ``` Possible `document_role` values: ```text primary_drawing calculation supporting_document specification attachment ``` A design version therefore supports one or many documents without putting `document_id` directly on the version. ### Design Review ```text id organization_id design_id design_version_id reviewer_user_id status comments reviewed_at created_at ``` Statuses: ```text pending approved changes_requested rejected ``` All three tables are tenant-owned and carry direct `organization_id`. ## 35. Engineering Inspections Inspection 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: ```text draft scheduled in_progress completed cancelled ``` Outcome: ```text passed passed_with_observations followup_required failed ``` `inspection_type` is an application/domain registry rather than a PostgreSQL enum. Initial common keys may include: ```text structural mechanical electrical safety final ``` Organizations/modules may add supported types through controlled configuration later. 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 GET /api/v1/engineering/inspections/{inspectionId}/followups POST /api/v1/engineering/inspections/{inspectionId}/followups ``` Inspection completion may create follow-up records. Lifecycle and outcome remain separate. ## 36. Inspection Findings `engineering_inspection_findings`: ```text id organization_id inspection_id severity description status resolved_at resolved_by_user_id created_at updated_at version ``` Severity: ```text observation minor major critical ``` Status: ```text open in_progress resolved accepted_risk ``` REST: ```http POST /api/v1/engineering/inspections/{inspectionId}/findings PATCH /api/v1/engineering/inspection-findings/{findingId} POST /api/v1/engineering/inspection-findings/{findingId}/resolve ``` `organization_id` is direct even though tenant ownership is also derivable through the inspection. ### Follow-Up Resource Use: ```text engineering_inspection_followups ``` Fields: ```text id organization_id inspection_id followup_type linked_task_id nullable linked_inspection_id nullable status created_by_user_id created_at completed_at cancelled_at ``` `followup_type`: ```text corrective_task followup_inspection both ``` `status`: ```text open in_progress completed cancelled ``` Tenant-safe foreign keys apply to the original inspection and any linked task/inspection. ## 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 **Deferred from the initial Engineering schema.** Change requests are a valid future engineering capability, but v4.1 does not create the table until these are specified: ```text relationship to project relationship to design/specification request origin impact analysis cost/schedule effects review workflow approval authority state machine document links REST commands audit requirements ``` Future candidate: ```text engineering_change_requests ``` This belongs in the Engineering extension backlog rather than a half-defined initial migration. ## 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 phase_id nullable task_id nullable design_id nullable inspection_id nullable created_at updated_at version ``` The project is always required. A time entry may also identify one primary work item. Database check: ```text at most one of: phase_id task_id design_id inspection_id ``` Each optional foreign key is tenant-aware: ```text (organization_id, task_id) → engineering_tasks(organization_id, id) ``` and similarly for phase, design, and inspection. This preserves relational integrity instead of using an unconstrained polymorphic `reference_type/reference_id`. REST: ```http POST /api/v1/engineering/time-entries GET /api/v1/engineering/time-entries GET /api/v1/engineering/time-entries/{timeEntryId} PATCH /api/v1/engineering/time-entries/{timeEntryId} POST /api/v1/engineering/time-entries/batch/submit ``` Duration is integer minutes. ## 40. Legal Tables Initial legal-domain tables: ```text legal_clients legal_matters legal_matter_members legal_cases legal_case_parties legal_courts legal_hearings legal_deadlines legal_time_entries legal_retainers legal_conflict_checks legal_conflict_parties legal_conflict_matches ``` There is no separate `legal_documents` ownership table. Documents remain shared infrastructure: ```text documents document_versions ``` Legal relationships use: ```text legal_matter_documents legal_case_documents ``` REST namespace: ```text /api/v1/legal ``` Legal remains a later vertical. ## 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": "0193c0a0-7c1e-7b3a-8c4d-6e5f4a3b2cbd", "status": "pending_review", "potentialConflicts": [ { "type": "possible_direct_adversity", "partyName": "Acme Corporation", "existingMatterId": "0193c0a0-7c1e-7b3a-8c4d-6e5f4a3b2ccd", "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 Healthcare remains a later vertical. Minimum planned tables: ```text healthcare_patients healthcare_patient_contacts healthcare_patient_addresses healthcare_practitioners healthcare_locations healthcare_rooms 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 ``` All tenant-owned tables carry direct `organization_id`. Detailed healthcare interoperability, terminology, and jurisdiction rules require healthcare-specific design before implementation. ## 46. Healthcare Patients Core patient: ```text id organization_id patient_number first_name middle_name last_name date_of_birth administrative_gender nullable sex_at_birth nullable gender_identity nullable status created_at updated_at version ``` Exact demographic terminology and allowed values are finalized in the healthcare-domain specification. Do not make every field mandatory merely because it exists. ### Patient Contact `healthcare_patient_contacts`: ```text id organization_id patient_id contact_type value is_primary created_at updated_at ``` ### Patient Address `healthcare_patient_addresses`: ```text id organization_id patient_id address_type line_1 line_2 city region postal_code country_code is_primary created_at updated_at ``` Sensitive subresources remain permission-controlled. ## 47. Healthcare Practitioners Suggested fields: ```text id organization_id user_id professional_profile_id specialty status created_at updated_at ``` Professional licenses are not duplicated here. Multiple licenses/credentials live in: ```text professional_credentials ``` ## 48. Healthcare Appointments Suggested fields: ```text id organization_id patient_id practitioner_id location_id nullable room_id nullable appointment_type starts_at ends_at status reason created_at updated_at version ``` Planned supporting tables: `healthcare_locations`: ```text id organization_id name address fields timezone status ``` `healthcare_rooms`: ```text id organization_id location_id name status ``` Exact scheduling rules are deferred to the healthcare vertical. ## 49. Healthcare Encounters Suggested fields: ```text id organization_id patient_id practitioner_id appointment_id nullable encounter_type reason_for_visit nullable started_at ended_at status created_at updated_at version ``` Do not add a generic free-form `notes` field as a substitute for clinical records. Clinical narrative belongs in governed clinical-record structures. ## 50. Clinical Records Use: ```text healthcare_clinical_records healthcare_clinical_record_versions healthcare_clinical_record_amendments ``` ### Clinical Record ```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 ``` ### Clinical Record Version ```text id organization_id record_id version_number content_reference or governed content payload created_by_practitioner_id created_at ``` ### Clinical Record Amendment ```text id organization_id record_id source_version_id result_version_id amended_by_practitioner_id amendment_type amendment_reason created_at ``` Possible amendment types: ```text correction addendum clarification ``` Signed/finalized history is preserved. REST: ```http POST /api/v1/healthcare/encounters/{encounterId}/clinical-records GET /api/v1/healthcare/clinical-records/{recordId} PATCH /api/v1/healthcare/clinical-records/{recordId} # Draft/editable only. 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 ``` ## 51. Documents Shared document infrastructure: ```text documents document_versions document_categories retention_policies ``` Binary data lives in S3-compatible object storage. ### Document ```text id organization_id name category_id classification retention_policy_id current_version_id created_by_user_id created_at updated_at ``` Classification: ```text public internal confidential restricted regulated ``` ### Document Version ```text id organization_id document_id version_number storage_key mime_type size_bytes content_hash hash_algorithm uploaded_by_user_id created_at ``` Checksum is version-level authoritative data. ### Document Category ```text id organization_id profession nullable name parent_category_id created_at ``` Uniqueness requirement: ```text shared category: unique organization_id + name where profession IS NULL profession category: unique organization_id + profession + name where profession IS NOT NULL ``` Implementation options: ```text PostgreSQL null-aware unique constraint when supported or two partial unique indexes ``` The partial-index fallback does not depend on selecting PostgreSQL 18. ### Retention Policy ```text id organization_id name profession nullable classification nullable retention_period_days nullable action created_at updated_at ``` Initial actions: ```text review archive delete_when_legally_permitted retain_indefinitely ``` A retention policy describes configured behavior. Actual deletion remains subject to domain, contractual, privacy, and jurisdiction requirements. ### Metadata JSONB is allowed only for genuinely extensible, non-authoritative metadata. Do not put authorization, lifecycle, retention state, or ownership into arbitrary JSON. ## 52. Document Upload Flow ### Standard Upload Request: ```http POST /api/v1/documents/upload-url ``` ```json { "name": "structural-calculations.pdf", "categoryId": null, "classification": "confidential", "mimeType": "application/pdf", "sizeBytes": 2457600, "contentHash": "sha256:..." } ``` Response: ```json { "data": { "documentId": "0193c0a0-7c1e-7b3a-8c4d-6e5f4a3b2d01", "documentVersionId": "0193c0a0-7c1e-7b3a-8c4d-6e5f4a3b2d02", "uploadUrl": "https://object-storage.example/...", "expiresAt": "2026-08-26T13:00:00Z" } } ``` The frontend uploads directly to object storage. Finalize: ```http POST /api/v1/documents/{documentId}/complete-upload ``` ```json { "documentVersionId": "0193c0a0-7c1e-7b3a-8c4d-6e5f4a3b2d02", "contentHash": "sha256:..." } ``` ### Multipart Initialization ```http POST /api/v1/documents/multipart-uploads ``` Request: ```json { "name": "building-model.bin", "categoryId": null, "classification": "confidential", "mimeType": "application/octet-stream", "sizeBytes": 2147483648, "contentHash": null } ``` Response: ```json { "data": { "documentId": "0193c0a0-7c1e-7b3a-8c4d-6e5f4a3b2d10", "documentVersionId": "0193c0a0-7c1e-7b3a-8c4d-6e5f4a3b2d11", "uploadId": "0193c0a0-7c1e-7b3a-8c4d-6e5f4a3b2d12", "recommendedPartSizeBytes": 67108864, "expiresAt": "2026-08-27T12:00:00Z" } } ``` ### Request Signed Part URLs ```http POST /api/v1/documents/{documentId}/multipart-uploads/{uploadId}/parts ``` ```json { "partNumbers": [1, 2, 3, 4] } ``` Response: ```json { "data": [ { "partNumber": 1, "uploadUrl": "https://object-storage.example/..." } ] } ``` Binary parts go directly to object storage. ### Complete Multipart Upload ```http POST /api/v1/documents/{documentId}/multipart-uploads/{uploadId}/complete ``` ```json { "parts": [ { "partNumber": 1, "etag": "..." } ], "contentHash": "sha256:..." } ``` Abort: ```http DELETE /api/v1/documents/{documentId}/multipart-uploads/{uploadId} ``` Upload state: ```text initiated uploading completing completed aborted expired ``` Workers clean up abandoned multipart uploads. Upload policy validates: ```text declared MIME extension content signature size checksum quota classification malware status ``` ## 53. Profession-Specific Document Links Use explicit relationship tables. Engineering: ```text engineering_project_documents engineering_design_version_documents engineering_inspection_documents ``` Legal: ```text legal_matter_documents legal_case_documents ``` Healthcare: ```text healthcare_patient_documents healthcare_encounter_documents ``` `engineering_design_version_documents` is authoritative for files belonging to a specific design revision. Do not also maintain an ambiguous `engineering_design_documents` relation to the unversioned design unless a later requirement introduces a separate clearly named supporting-document relationship. ### Project Documents ```text engineering_project_documents ├── id ├── organization_id ├── project_id ├── document_id ├── category ├── 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} ``` `DELETE` temporally unlinks the relation when history must be preserved. ## 54. Billing Shared financial core: ```text invoices invoice_items payments ``` ### Invoice Core fields include: ```text id organization_id client/reference context invoice_number status currency_code subtotal_minor tax_total_minor total_minor issued_at due_at paid_at created_at updated_at version ``` ### Invoice Item ```text id organization_id invoice_id description quantity unit_price_minor total_amount_minor position created_at updated_at ``` `quantity` uses fixed-precision numeric semantics, not floating point. Money uses integer minor units. The invoice determines currency; invoice items do not independently choose a different currency unless multi-currency invoicing is intentionally designed later. ### Profession-Specific Source Links The shared billing module does not use unconstrained: ```text reference_type reference_id ``` to profession-owned tables. Profession modules create explicit links, for example: ```text engineering_invoice_item_time_entries ├── organization_id ├── invoice_item_id └── time_entry_id ``` This preserves the rule that shared core does not depend on profession-table internals. 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 Use: ```text audit_events ``` Suggested fields: ```text id organization_id actor_type actor_user_id nullable actor_service_account_id nullable action resource_type resource_id request_id correlation_id ip_address user_agent metadata occurred_at ``` Audit records are append-only from normal application workflows. ### Mandatory Examples Engineering: ```text engineering.projects.create engineering.projects.close engineering.designs.approve engineering.inspections.complete ``` Legal: ```text legal.matters.create legal.matters.close legal.conflicts.approve ``` Healthcare: ```text healthcare.records.read healthcare.records.write healthcare.records.sign healthcare.records.amend ``` ### Privacy / Erasure Handling Append-only audit does not mean "store unlimited personal data forever." Audit metadata must be minimized at write time. Where privacy, contractual, or retention obligations require removal of personally identifying material, use a governed privacy process such as: ```text pseudonymize actor references null/remove nonessential PII fields replace identifiers with irreversible privacy references where appropriate retain the security/business event itself when permitted/required ``` The exact action depends on jurisdiction and retention policy and must be reviewed before healthcare/legal production. Do not place passwords, tokens, full clinical content, secret keys, or unnecessary payment data in audit metadata. REST: ```http GET /api/v1/audit-events ``` No public mutation endpoints. ## 57. Domain Events and Transactional Outbox Use: ```text outbox_events ``` Fields: ```text id organization_id nullable for truly global events event_type aggregate_type aggregate_id payload request_id nullable correlation_id causation_id nullable occurred_at available_at processed_at attempt_count last_error dead_lettered_at ``` `correlation_id` groups one logical workflow across requests/jobs/events. `causation_id` identifies the event/command that directly caused this event when applicable. Transaction: ```text BEGIN business change audit event outbox event COMMIT ``` Delivery semantics are at-least-once. Worker claim uses row locking such as: ```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; ``` Every external side-effect consumer must be idempotent. `FOR UPDATE SKIP LOCKED` prevents simultaneous claiming; it does not prevent duplicate side effects after a worker crash. ## 57A. Webhooks and External Integrations Shared tables: ```text webhooks webhook_event_subscriptions webhook_deliveries ``` ### Webhook ```text id organization_id url status secret_ciphertext or signing_key_reference created_by_user_id created_at updated_at ``` ### Subscription ```text id organization_id webhook_id event_type created_at ``` Unique: ```text (organization_id, webhook_id, event_type) ``` Only registered externally publishable event types may be subscribed. ### Delivery ```text id organization_id webhook_id event_id attempt_number request_timestamp response_status response_summary delivered_at failed_at next_attempt_at ``` 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 ``` If HMAC signing is used, signing material is encrypted/recoverable with managed key protection. A one-way secret hash is insufficient for outbound HMAC signing. Webhook consumers deduplicate using stable event IDs. ## 58. Background Jobs Workers handle: ```text notifications reports/PDFs file scanning document processing imports exports bulk operations webhooks search indexing large data operations ``` Use shared tenant-owned: ```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 ``` These endpoints are tenant-scoped through the standard: ```http X-Organization-Id ``` They do not need `/organizations/{id}/jobs` because the platform already chose header-based tenant context. Large import/export operations return: ```http 202 Accepted ``` with a job ID. ## 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. Defaults: ```text default limit = 25 maximum limit = 100 offset pagination = not supported ``` Example: ```http GET /api/v1/engineering/projects?limit=25 ``` Response: ```json { "data": [], "meta": { "pagination": { "nextCursor": null, "hasMore": false } } } ``` Rules: ```text cursor is opaque sort order must be deterministic cursor encodes/represents the selected sort position unsupported limits return validation errors rather than silent huge responses ``` ## 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=0193c0a0-7c1e-7b3a-8c4d-6e5f4a3b2c3d ``` 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": "0193c0a0-7c1e-7b3a-8c4d-6e5f4a3b2c5d", "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 strong password hashing short-lived access tokens refresh-token rotation/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/image scanning request/correlation IDs backup and restore testing ``` ### Web Security / CORS ADR-009 defines environment-specific web security. Baseline requirements: ```text explicit CORS allowlist no wildcard credentialed CORS allowed methods/headers documented preflight behavior tested HSTS at the edge for production HTTPS X-Content-Type-Options: nosniff secure cookie attributes when cookies are used CSP on browser frontends frame-ancestor/clickjacking policy on frontends referrer policy appropriate to the frontend ``` Security headers belong at the appropriate application/CDN/gateway layer. ### Rate Limiting Policies are endpoint-specific and configurable. Return: ```http 429 Too Many Requests Retry-After: ... ``` ### Secrets Production secrets live outside source control, preferably in managed secret/key systems. JWT signing keys support rotation. ## 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 ADR-009 Web Security / CORS / Browser Headers ADR-010 Machine Authentication / API Key Policy ``` 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. Public serialized IDs are raw UUIDv7. 10. Database ID columns use PostgreSQL UUID. 11. Human-readable business references are separate from resource IDs. 12. Every tenant-owned row carries direct `organization_id`. 13. Tenant-scoped requests require explicit `X-Organization-Id`. 14. Tenant boundaries are enforced in queries and database constraints. 15. Cross-tenant resources appear nonexistent. 16. API JSON/query parameter names use camelCase; DB identifiers use snake_case. 17. Authorization is server-side and deny-by-default. 18. `assigned` scope is defined per resource policy, never inferred generically. 19. Roles and professional credentials are separate. 20. A professional profile may own multiple credentials. 21. Sessions and refresh tokens are separate resources. 22. Refresh tokens rotate within families and support reuse detection. 23. Machine identities use service accounts/API keys, not fake human memberships. 24. Important domain transitions use explicit REST command endpoints. 25. High-risk commands use durable idempotency. 26. Batch custom actions use `/{collection}/batch/{action}`. 27. Every batch defines atomic or partial semantics. 28. Every batch item receives independent authorization/domain validation. 29. Large batches become asynchronous jobs. 30. Project phases are authoritative; duplicated project `stage` is not stored. 31. Project budgets use the dedicated budget model; project `budget_minor` is not authoritative. 32. Engineering time entries may attribute time to one explicit primary work item using tenant-safe FKs. 33. Design versions and design-version document links have explicit one-to-many cardinality. 34. All design/review/version/finding/follow-up subresources carry `organization_id`. 35. Inspection lifecycle and inspection outcome are separate. 36. Inspection follow-ups are explicit resources. 37. Engineering change requests remain deferred until fully specified. 38. Shared documents own document records; profession modules own link tables. 39. Legal does not duplicate shared document ownership. 40. Large files use object-storage multipart uploads. 41. Application servers do not proxy multi-gigabyte chunks. 42. Document checksums belong to document versions. 43. Document classification is multi-level. 44. Document retention is explicit policy. 45. Document category uniqueness must work for nullable profession values on the selected PostgreSQL version. 46. Project document linkage does not imply client-portal publication. 47. External publication requires explicit publication records. 48. Client portal accounts are not internal memberships. 49. Client acceptance is not professional engineering approval. 50. Domain events use a transactional outbox. 51. Outbox delivery is at-least-once. 52. Outbox events carry correlation/causation identifiers. 53. External side-effect consumers are idempotent. 54. Webhook subscriptions and deliveries are tenant-owned. 55. HMAC signing secrets are securely recoverable/encrypted, not only hashed. 56. Jobs are tenant-scoped by the standard organization header. 57. PostgreSQL is the authoritative transactional datastore. 58. Redis is acceleration/coordination, not critical source of truth. 59. Search starts with PostgreSQL. 60. Collections use cursor pagination, default 25 and max 100. 61. Important mutable resources use optimistic concurrency. 62. Database entities are not serialized directly. 63. Errors use stable codes. 64. `429` responses use `Retry-After`; exact quota headers are an API decision. 65. Business records use explicit archive/revoke/unlink/hard-delete lifecycle policies. 66. Financial/professional/audit records are not casually hard-deleted. 67. Audit metadata is minimized and supports governed privacy transformation when required. 68. Important/regulated actions are audited. 69. Signed clinical records use sign/amend/version workflows. 70. Prescribing authority remains jurisdiction/scope-of-practice policy. 71. Production migrations use expand/contract. 72. Destructive changes are not assumed trivially reversible. 73. Secrets remain outside source control. 74. CORS and browser security policy are explicit ADR/configuration. 75. Rate limits are calibrated by evidence. 76. CI validates types, tests, OpenAPI, migrations, and security checks. 77. Property-based tests cover high-value state machines. 78. Outbox/job/webhook reliability is tested under failure/concurrency. 79. Critical-path tests matter more than vanity coverage percentages. 80. Framework/ORM/queue/PostgreSQL-minimum choices require ADRs. 81. Engineering is the first vertical. 82. Client portal follows internal Engineering MVP foundations. 83. Legal follows after Engineering validates shared assumptions. 84. Healthcare requires dedicated privacy/security/domain design before implementation. 85. Architecture documentation never equates "designed for" with "certified/compliant". ## 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. ### Feature Flags Feature flags used for deployment safety are operational configuration, not automatically a business database table. Initial implementation may use: ```text environment/config-service flags ``` for global rollout and kill switches. If per-organization feature rollout is later required, introduce an explicit tenant-owned model such as: ```text organization_feature_flags ``` through an ADR/migration. Do not overload `organization_professions` with unrelated product experiments. ### 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.1 Changelog v4.1 resolves implementation-contract issues without changing the core architecture. ```text ✓ raw UUIDv7 API ID contract ✓ camelCase API / snake_case database naming convention ✓ direct organization_id on tenant subresources ✓ invitation role assignments ✓ service accounts and hashed API keys ✓ multiple professional credentials per profile ✓ global archive/revoke/unlink/hard-delete policy ✓ project stage duplication removed ✓ project budget_minor removed ✓ project phase reorder command ✓ global engineering site listing ✓ task status and priority vocabularies ✓ design revise endpoint ✓ design-version many-document cardinality ✓ design review/version tenant keys ✓ inspection finding tenant keys ✓ explicit inspection follow-up table ✓ change requests deferred until fully specified ✓ time-entry work-item attribution ✓ legal_documents duplication removed ✓ healthcare placeholder schemas clarified ✓ invoice-item schema defined ✓ document retention-policy schema ✓ version-independent document-category uniqueness fallback ✓ standard upload DTO ✓ multipart-init/parts/complete DTOs ✓ webhook subscription schema ✓ logout endpoint ✓ assigned-scope resolution rules ✓ audit privacy transformation strategy ✓ outbox correlation and causation IDs ✓ CORS/browser-security ADR ✓ feature-flag strategy clarified ✓ jobs confirmed tenant-scoped via X-Organization-Id ``` The next artifacts should be implementation-specific: ```text ADRs Engineering OpenAPI Engineering database migrations Engineering state-machine spec Engineering MVP backlog ```