Files
professional_management/professional_management_platform_rest_plan_v4.md
2026-08-27 21:52:21 -04:00

106 KiB

Professional Management Platform

Full REST-First System Design Plan

Revision: v4 — Final Broad Architecture Baseline
Status: Final broad architecture baseline for Engineering MVP implementation. Further changes should move into ADRs, OpenAPI, schemas, migrations, and backlog items rather than repeatedly reopening platform architecture.
Primary vertical: Engineering
API style: REST + JSON + OpenAPI 3.1
Backend style: Modular monolith
Data: PostgreSQL + profession-specific tables
Tenant model: Organization-scoped, explicit tenant context

v4 Integration Notes

v4 incorporates the remaining high-value refinements from the latest architecture review without reopening settled platform decisions.

Added:

  • dedicated engineering client portal architecture
  • external-user access grants distinct from internal organization memberships
  • client review/acceptance separated from professional engineering approval
  • explicit project-document publication controls for external audiences
  • synchronous batch-command semantics for small batches
  • asynchronous job escalation for large batch operations
  • explicit atomic vs partial-success batch behavior
  • native object-storage multipart upload orchestration for very large engineering files
  • rate-limit metadata policy without freezing legacy X-RateLimit-* headers
  • technology ADR requirements instead of treating framework suggestions as settled architecture
  • provisional performance objectives separated from contractual SLOs
  • risk register with impact, mitigation, owner, phase, and status
  • API error-standard ADR to evaluate RFC 9457 Problem Details compatibility
  • final architecture-change governance to prevent endless review churn

Explicitly rejected as permanent architecture:

  • client contacts becoming ordinary internal organization memberships
  • external client acceptance being represented as professional design approval
  • API servers proxying multi-gigabyte file chunks
  • fixed X-RateLimit-Limit/Remaining/Reset as permanent contract
  • framework/ORM choices being declared "confirmed" without an ADR
  • arbitrary maturity percentages such as 95% architecture readiness
  • fixed calendar promises before delivery context exists



2. Core Architecture Decision

The platform will use:

  • REST
  • JSON
  • OpenAPI
  • Versioned endpoints
  • PostgreSQL
  • Modular monolith backend
  • Profession-specific frontends
  • Profession-specific database tables
  • Shared identity, security, billing, documents, audit, and infrastructure

Base API path:

/api/v1

GraphQL is not part of v1.


3. High-Level Architecture

                        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:

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:

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:

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:

Dashboard
Clients
Projects
Project Phases
Project Team
Sites
Designs
Design Reviews
Inspections
Specifications
Tasks
Documents
Timesheets
Billing
Reports
Administration

Suggested navigation:

Dashboard
Clients
Matters
Cases
Hearings
Courts
Deadlines
Documents
Conflict Checks
Time Tracking
Retainers
Billing
Reports
Administration

Healthcare Frontend

Suggested navigation:

Dashboard
Patients
Appointments
Practitioners
Encounters
Clinical Records
Diagnoses
Prescriptions
Insurance
Documents
Billing
Reports
Administration

Platform Admin Frontend

Suggested functions:

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:

/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:

/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:

/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:

/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:

Authorization: Bearer <access-token>
X-Organization-Id: org_123
X-Request-Id: req_123
Content-Type: application/json

Organization Context

X-Organization-Id is mandatory for every tenant-scoped endpoint.

Global endpoints such as these do not require tenant context:

POST /api/v1/auth/login
POST /api/v1/auth/token/refresh
GET  /api/v1/me
GET  /api/v1/me/organizations
GET  /api/v1/auth/sessions

Tenant-context resolution rules:

Organization Context:
  header_missing_on_tenant_endpoint:
    status: 400
    code: ORGANIZATION_CONTEXT_REQUIRED

  organization_not_found:
    status: 404
    code: RESOURCE_NOT_FOUND

  membership_not_found:
    status: 404
    code: RESOURCE_NOT_FOUND

  membership_inactive:
    status: 403
    code: AUTHZ_MEMBERSHIP_INACTIVE

  organization_inactive:
    status: 403
    code: AUTHZ_ORGANIZATION_INACTIVE

  resource.organization_id_mismatch:
    status: 404
    code: RESOURCE_NOT_FOUND

Do not expose another organization's identity in tenant-error responses.

Idempotency

Use:

Idempotency-Key: 8f7d6c5e-4b3a-2b1c-9d8e-7f6a5b4c3d2e

Idempotency is required where duplicate execution can create material side effects.

Examples:

POST /api/v1/invoices
POST /api/v1/invoices/{id}/payments
POST /api/v1/payments/{id}/refund

POST /api/v1/engineering/designs/{id}/approve
POST /api/v1/engineering/inspections/{id}/complete

Idempotency records include:

organization_id
actor_id
route/action
idempotency_key
canonical_request_hash
response_status
response_body or result reference
created_at
expires_at

Rules:

same_key_same_request:
  return: original result

same_key_different_request:
  status: 409
  code: IDEMPOTENCY_KEY_CONFLICT

PostgreSQL is authoritative for critical idempotency records.

Redis may accelerate lookup.

Rate-Limit Responses

Rate-limited requests return:

429 Too Many Requests
Retry-After: <seconds-or-http-date>

Additional quota metadata may be exposed.

Do not freeze legacy X-RateLimit-* header names into the architecture.

The exact rate-limit response-header convention is selected and documented in the API ADR/OpenAPI contract based on the gateway and adopted standard at implementation time.

Error Standard Decision

The current platform error envelope remains valid:

{
  "error": {
    "code": "RESOURCE_NOT_FOUND",
    "message": "Resource not found.",
    "details": {},
    "requestId": "req_123"
  }
}

Before OpenAPI v1 is frozen, create an ADR evaluating compatibility with RFC 9457 Problem Details.

Do not silently change the error envelope during implementation.

9. Standard Response Format

Single resource:

{
  "data": {
    "id": "project_123",
    "name": "Central Tower"
  }
}

Collection:

{
  "data": [],
  "meta": {
    "pagination": {
      "nextCursor": null,
      "hasMore": false
    }
  }
}

Standard error:

{
  "error": {
    "code": "RESOURCE_NOT_FOUND",
    "message": "Resource not found.",
    "details": {},
    "requestId": "req_123"
  }
}

Clients depend on error.code, not message text.

Error Taxonomy

Authentication:

AUTH_INVALID_CREDENTIALS
AUTH_TOKEN_EXPIRED
AUTH_TOKEN_INVALID
AUTH_MFA_REQUIRED
AUTH_SESSION_REVOKED
AUTH_REFRESH_TOKEN_REUSED

Authorization:

AUTHZ_PERMISSION_DENIED
AUTHZ_ORGANIZATION_INACTIVE
AUTHZ_MEMBERSHIP_INACTIVE
AUTHZ_CREDENTIAL_INVALID
AUTHZ_SCOPE_MISMATCH

Tenant context:

ORGANIZATION_CONTEXT_REQUIRED

Resource/state:

RESOURCE_NOT_FOUND
RESOURCE_ALREADY_EXISTS
RESOURCE_CONCURRENT_MODIFICATION
RESOURCE_INVALID_STATE
RESOURCE_ARCHIVED

Validation:

VALIDATION_ERROR
VALIDATION_REQUIRED_FIELD
VALIDATION_INVALID_FORMAT
VALIDATION_BUSINESS_RULE

Idempotency:

IDEMPOTENCY_KEY_REQUIRED
IDEMPOTENCY_KEY_CONFLICT

Rate limiting:

RATE_LIMIT_EXCEEDED

System/dependency:

INTERNAL_ERROR
SERVICE_UNAVAILABLE
DATABASE_UNAVAILABLE
DEPENDENCY_FAILED

Validation example:

{
  "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:

{
  "error": {
    "code": "RESOURCE_INVALID_STATE",
    "message": "Cannot approve design in current state.",
    "requestId": "req_123",
    "details": {
      "resourceType": "engineering_design",
      "resourceId": "design_123",
      "currentState": "draft",
      "requiredState": "under_review",
      "allowedActions": [
        "submit_review"
      ]
    }
  }
}

Do not expose internal stack traces, SQL, policy internals, secrets, or cross-tenant information.

10. HTTP Status Rules

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:

/api/v1

Breaking changes require:

/api/v2

Additive fields generally do not require a new version.


12. Authentication

Initial authentication:

Email
+
Password
+
Short-Lived Access Token
+
Opaque Refresh Token
+
Server-Side Session

REST endpoints:

POST   /api/v1/auth/register
POST   /api/v1/auth/login

POST   /api/v1/auth/token/refresh
POST   /api/v1/auth/token/revoke
POST   /api/v1/auth/token/revoke-all

GET    /api/v1/auth/sessions
DELETE /api/v1/auth/sessions/{sessionId}

GET    /api/v1/me

Access Token

format: JWT
lifetime: 15 minutes by default
signed: true
encrypted: false
preferred signing: asymmetric key or managed signing service
claims:
  - sub / userId
  - sessionId
  - issuer
  - audience
  - issuedAt
  - expiresAt
organizationId:
  optional_hint: true
  authorization_authority: false

The organization header and active membership remain authoritative for tenant access.

Do not embed the complete permission set in access tokens.

Session and Refresh-Token Model

A login session and a refresh token are different resources.

Use:

sessions
refresh_tokens

Suggested sessions fields:

id
user_id

device_id
device_type
device_os
app_version

ip_address
user_agent

created_at
last_active_at
expires_at

revoked_at
revocation_reason

Suggested refresh_tokens fields:

id
session_id
family_id

token_hash

issued_at
expires_at

rotated_at
replaced_by_token_id

revoked_at
revocation_reason

Indexes/constraints:

UNIQUE(refresh_tokens.token_hash)

INDEX(refresh_tokens.family_id)
INDEX(refresh_tokens.session_id)
INDEX(sessions.user_id, sessions.revoked_at)

Do not make family_id unique. Every rotated refresh token in the same lineage shares the same family.

Conceptually:

Session
  │
  └── Refresh Token Family
         │
         ├── Token A [rotated]
         │      ↓
         ├── Token B [rotated]
         │      ↓
         └── Token C [current]

Refresh Rotation

On successful refresh:

  1. hash supplied refresh token
  2. load token and session
  3. validate token/session status and expiry
  4. issue replacement token in same family
  5. mark old token rotated
  6. link replaced_by_token_id
  7. return new access + refresh tokens

Reuse Detection

If a previously rotated token is used again:

possible token theft
      ↓
revoke token family
      ↓
revoke affected session
      ↓
security audit event
      ↓
reauthentication required

Policy may escalate to revoking all user sessions for higher-risk environments.

Audit event:

auth.refresh_token.reuse_detected

Device Metadata

Device metadata is useful for:

session display
security alerts
audit context
user-initiated revocation

It is not identity proof.

Future authentication:

  • MFA
  • passkeys / WebAuthn
  • OIDC / SSO
  • enterprise identity providers
  • risk-based authentication

13. Shared Core Backend

Recommended modules:

core/
├── auth/
├── users/
├── organizations/
├── memberships/
├── roles/
├── permissions/
├── authorization/
├── documents/
├── billing/
├── notifications/
├── audit/
└── events/

Dependency rule:

Profession module → Core

Never:

Core → Profession module

14. Organizations

Organizations are tenants.

Examples:

Atlas Structural Engineering
Smith & Associates Law
North Shore Medical Practice

Suggested fields:

id
name
slug
status
country_code
timezone
currency_code
created_at
updated_at

15. Profession Enablement

Use:

organization_professions

Suggested fields:

organization_id
profession
enabled_at
configuration

Possible professions:

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.

User
  │
  ▼
Membership
  │
  ▼
Organization

Suggested users fields:

id
email
first_name
last_name
phone
avatar_url
status
created_at
updated_at

Suggested memberships fields:

id
organization_id
user_id
status
joined_at
created_at
updated_at

17. Membership Invitations

Keep invitations separate from memberships.

Suggested table:

membership_invitations

Fields:

id
organization_id
email
invited_by_user_id
expires_at
accepted_at
revoked_at
created_at

Flow:

Invitation
   ↓
Accepted
   ↓
User
   ↓
Membership

18. Authorization

Use:

RBAC
+
Permission Scope
+
Resource Policies
+
Professional Qualification Policies
+
Domain State Rules

Decision flow:

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:

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:

Owner
Administrator
Project Manager
Engineer
Reviewer
Inspector
Lawyer
Paralegal
Doctor
Nurse
Billing Manager
Viewer

Roles are not professional credentials.

Engineering Permissions

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

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

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:

assigned
organization

Examples:

Engineer:
engineering.projects.read = assigned

Principal Engineer:
engineering.projects.read = organization

Potential future scopes:

owned
team
department
restricted

Do not implement until required.


21. Professional Credentials

Professional qualification is separate from RBAC.

Suggested shared profile:

professional_profiles

Fields:

id
organization_id
user_id
profession
title
credential_status
primary_license_number
primary_license_jurisdiction
valid_from
expires_at
created_at
updated_at

Profession modules may add dedicated credential tables when one generic profile is insufficient.

Credential Policy Examples

Engineering design approval may require:

permission: engineering.designs.approve
credential:
  profession_family: engineering
  status: verified
  active_license: true
  jurisdiction_match: when required
  discipline_match: when required

Healthcare record signing may require:

permission: healthcare.records.sign
credential:
  profession_allowed_by_policy: true
  status: verified
  active_license: true
  scope_of_practice_allows_action: true
  jurisdiction_match: true

Prescribing must not be hard-coded to profession = doctor or to a single U.S. credential such as a DEA number.

Prescribing authority varies by:

  • jurisdiction
  • profession
  • drug class
  • supervising relationship
  • organization policy
  • credential status

Therefore use a policy concept such as:

PrescribingAuthorityPolicy

rather than a permanent global rule.

Cache Safety

Credential status may be cached briefly for ordinary reads, but high-risk writes such as:

engineering.designs.approve
healthcare.records.sign
healthcare.prescriptions.sign

must use authoritative or revocation-aware credential validation. A five-minute stale cache is unacceptable if a license was just suspended.

22. Database Architecture

Use PostgreSQL.

Start with:

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:

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:

organization_id

Examples:

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:

engineering_projects
organization_id
client_id

references:

engineering_clients
organization_id
id

This prevents linking a resource from one organization to another organization's data.


Engineering Domain

26. Engineering Tables

Initial tables:

engineering_clients
engineering_projects
engineering_project_members
engineering_project_phases
engineering_sites
engineering_tasks
engineering_designs
engineering_design_versions
engineering_design_reviews
engineering_inspections
engineering_inspection_findings
engineering_specifications
engineering_change_requests
engineering_time_entries

27. Engineering Clients

Suggested core client fields:

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:

technical contacts
billing contacts
executive contacts
site contacts
contract contacts

Use:

engineering_client_contacts

Suggested contact fields:

id
organization_id
client_id

name
title
department

email
phone

contact_type
is_primary

created_at
updated_at

Client REST:

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:

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.

User
 │
 ├── Internal Membership
 │       ↓
 │   Organization Staff Access
 │
 └── Client Portal Account
         ↓
     Engineering Client Contact
         ↓
     Project Access Grants

Suggested tables:

engineering_client_portal_accounts
engineering_client_portal_project_grants
engineering_project_document_publications
engineering_client_review_requests

Portal Account

Suggested fields:

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:

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:

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:

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:

engineering.designs.approve

Professional engineering approval is reserved for qualified internal/authorized professionals.

Client-facing review should use separate concepts such as:

engineering.client_reviews.request
engineering.client_reviews.respond
engineering.client_reviews.accept
engineering.client_reviews.request_changes

Example:

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:

engineering_project_document_publications

Suggested fields:

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:

all_active_client_portal_accounts_for_project
specific_portal_account
specific_client_contact

External download checks:

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:

id
organization_id
client_id
project_number
name
description
discipline
stage
status
project_manager_user_id
start_date
expected_completion_date
completed_date
budget_minor
currency_code
created_at
updated_at
version

REST:

GET    /api/v1/engineering/projects
POST   /api/v1/engineering/projects
GET    /api/v1/engineering/projects/{projectId}
PATCH  /api/v1/engineering/projects/{projectId}

POST   /api/v1/engineering/projects/{projectId}/activate
POST   /api/v1/engineering/projects/{projectId}/close
POST   /api/v1/engineering/projects/{projectId}/archive

Purpose-built read models may be added when the frontend requires them:

GET /api/v1/engineering/projects/{projectId}/summary
GET /api/v1/engineering/projects/{projectId}/timeline
GET /api/v1/engineering/projects/{projectId}/budget

These are read-model endpoints, not necessarily separate aggregate tables.

Do not put arbitrary budget-breakdown JSON into the core project row merely because the response can display it. Model detailed budget data in dedicated tables when that feature is implemented.

29. Engineering Project Members

Suggested fields:

id
organization_id
project_id
user_id
project_role
joined_at
left_at

REST:

GET    /api/v1/engineering/projects/{projectId}/members
POST   /api/v1/engineering/projects/{projectId}/members
PATCH  /api/v1/engineering/projects/{projectId}/members/{memberId}
DELETE /api/v1/engineering/projects/{projectId}/members/{memberId}

30. Engineering Project Phases

Suggested fields:

id
organization_id
project_id
name
sequence
status
start_date
end_date
created_at
updated_at

Typical phases:

Concept
Preliminary Design
Detailed Design
Construction
Inspection
Closeout

REST:

GET  /api/v1/engineering/projects/{projectId}/phases
POST /api/v1/engineering/projects/{projectId}/phases
PATCH /api/v1/engineering/projects/{projectId}/phases/{phaseId}
POST /api/v1/engineering/projects/{projectId}/phases/{phaseId}/complete

31. Engineering Sites

Suggested fields:

id
organization_id
project_id
name
address
latitude
longitude
created_at
updated_at

REST:

POST  /api/v1/engineering/projects/{projectId}/sites
GET   /api/v1/engineering/projects/{projectId}/sites
GET   /api/v1/engineering/sites/{siteId}
PATCH /api/v1/engineering/sites/{siteId}

32. Engineering Tasks

Suggested fields:

id
organization_id
project_id
title
description
status
priority
created_by_user_id
assigned_to_user_id
due_at
completed_at
created_at
updated_at
version

REST:

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:

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:

atomic
partial

Atomic:

all resources succeed
or
entire operation fails

Partial:

each resource is evaluated independently
successful items commit
failed items return individual errors

Do not leave this behavior implicit.

Example request:

{
  "taskIds": [
    "task_1",
    "task_2",
    "task_3"
  ],
  "assigneeUserId": "user_123",
  "mode": "partial"
}

Example response:

{
  "data": {
    "succeeded": [
      "task_1",
      "task_2"
    ],
    "failed": [
      {
        "id": "task_3",
        "code": "RESOURCE_INVALID_STATE"
      }
    ]
  }
}

Authorization

Each resource is evaluated for:

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:

202 Accepted

with:

jobId

The synchronous/asynchronous threshold is configuration based on:

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:

id
organization_id
project_id
design_number
title
description
discipline
status
owner_user_id
prepared_by_user_id
approved_by_user_id
approved_at
created_at
updated_at
version

Suggested states:

draft
under_review
changes_requested
approved
rejected
cancelled
withdrawn
superseded

REST:

GET    /api/v1/engineering/projects/{projectId}/designs
POST   /api/v1/engineering/projects/{projectId}/designs

GET    /api/v1/engineering/designs/{designId}
PATCH  /api/v1/engineering/designs/{designId}

POST   /api/v1/engineering/designs/{designId}/submit-review
POST   /api/v1/engineering/designs/{designId}/request-changes
POST   /api/v1/engineering/designs/{designId}/approve
POST   /api/v1/engineering/designs/{designId}/reject
POST   /api/v1/engineering/designs/{designId}/cancel
POST   /api/v1/engineering/designs/{designId}/withdraw
POST   /api/v1/engineering/designs/{designId}/supersede

POST   /api/v1/engineering/designs/{designId}/assign
POST   /api/v1/engineering/designs/{designId}/unassign

GET    /api/v1/engineering/designs/{designId}/versions
POST   /api/v1/engineering/designs/{designId}/versions

GET    /api/v1/engineering/designs/{designId}/reviews
POST   /api/v1/engineering/designs/{designId}/reviews

Assignment Model

Use:

engineering_design_assignments

Possible assignment roles:

owner
designer
reviewer
approver
checker

Suggested fields:

id
organization_id
design_id
user_id
assignment_role
notes
assigned_by_user_id
assigned_at
unassigned_at

Assignment does not automatically grant platform permission. Both RBAC and resource policy still apply.

Design State Machine

draft
  ├── submit-review ───────────────► under_review
  └── cancel ──────────────────────► cancelled

under_review
  ├── request-changes ─────────────► changes_requested
  ├── approve ─────────────────────► approved
  ├── reject ──────────────────────► rejected
  └── withdraw ────────────────────► withdrawn

changes_requested
  ├── submit-review ───────────────► under_review
  └── withdraw ────────────────────► withdrawn

rejected
  └── revise ──────────────────────► draft

approved
  └── supersede ───────────────────► superseded

Use cancelled for work stopped before formal review.

Use withdrawn for work intentionally removed after review workflow has started.

Approval requires:

permission
+
project access
+
appropriate assignment/policy
+
valid professional qualification
+
valid design state
+
organization approval policy

Approval, rejection, withdrawal, and supersession are audited.

Approval is idempotent.

Do not approve by PATCHing status.

34. Design Versions and Reviews

engineering_design_versions:

id
design_id
version_number
document_id
created_by_user_id
created_at

engineering_design_reviews:

id
organization_id
design_id
reviewer_user_id
status
comments
reviewed_at

Possible review statuses:

pending
approved
changes_requested
rejected

35. Engineering Inspections

Suggested fields:

id
organization_id
project_id
site_id

inspection_type
inspector_user_id

status
outcome

scheduled_at
started_at
performed_at
cancelled_at

summary

created_at
updated_at
version

Lifecycle status:

draft
scheduled
in_progress
completed
cancelled

Outcome is separate:

passed
passed_with_observations
followup_required
failed

This distinction matters.

An inspection can be fully completed and still require corrective work.

REST:

GET    /api/v1/engineering/projects/{projectId}/inspections
POST   /api/v1/engineering/projects/{projectId}/inspections

GET    /api/v1/engineering/inspections/{inspectionId}
PATCH  /api/v1/engineering/inspections/{inspectionId}

POST   /api/v1/engineering/inspections/{inspectionId}/schedule
POST   /api/v1/engineering/inspections/{inspectionId}/start
POST   /api/v1/engineering/inspections/{inspectionId}/complete
POST   /api/v1/engineering/inspections/{inspectionId}/cancel

GET    /api/v1/engineering/inspections/{inspectionId}/findings
POST   /api/v1/engineering/inspections/{inspectionId}/findings

POST   /api/v1/engineering/inspections/{inspectionId}/followups
GET    /api/v1/engineering/inspections/{inspectionId}/followups

A follow-up may be:

corrective task
new inspection
or both

Do not encode all follow-up workflow into the original inspection's lifecycle state.

Inspection completion:

  1. validate inspector and project access
  2. validate required fields
  3. validate findings
  4. calculate or confirm outcome
  5. complete inspection
  6. create corrective work/follow-up records when required
  7. audit
  8. write outbox event
  9. notify appropriate participants

Completion is idempotent.

36. Inspection Findings

Suggested fields:

id
inspection_id
severity
description
status
resolved_at

Possible severities:

observation
minor
major
critical

REST:

POST  /api/v1/engineering/inspections/{inspectionId}/findings
PATCH /api/v1/engineering/inspection-findings/{findingId}
POST  /api/v1/engineering/inspection-findings/{findingId}/resolve

37. Engineering Specifications

Suggested fields:

id
organization_id
project_id
specification_number
title
version
status
document_id
created_at
updated_at

38. Engineering Change Requests

Suggested fields:

id
organization_id
project_id
request_number
title
description
status
requested_by_user_id
approved_by_user_id
estimated_cost_minor
created_at
updated_at

38A. Engineering Project Budgets

A single budget_minor column is sufficient only for a very early project total.

When budget management enters scope, introduce:

engineering_project_budgets
engineering_project_budget_items
engineering_project_commitments
engineering_project_cost_entries

Budget

Suggested fields:

id
organization_id
project_id

name
currency_code
status

approved_by_user_id
approved_at

created_at
updated_at
version

Budget Item

Suggested fields:

id
organization_id
budget_id

category
description

allocated_amount_minor

created_at
updated_at

Do not casually store mutable:

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:

authoritative cost/commitment records
        ↓
derived budget projections

If denormalized totals are needed for performance, update them transactionally and reconcile them.

Potential REST:

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:

id
organization_id
project_id
user_id
work_date
duration_minutes
description
billable
billing_rate_minor
currency_code
created_at
updated_at

REST:

POST  /api/v1/engineering/time-entries
GET   /api/v1/engineering/time-entries
GET   /api/v1/engineering/time-entries/{id}
PATCH /api/v1/engineering/time-entries/{id}

Store duration as integer minutes.


Legal Domain

Initial tables:

legal_clients
legal_matters
legal_matter_members
legal_cases
legal_case_parties
legal_courts
legal_hearings
legal_deadlines
legal_documents
legal_time_entries
legal_retainers
legal_conflict_checks
legal_conflict_parties
legal_conflict_matches

REST namespace:

/api/v1/legal

Core examples:

GET    /api/v1/legal/matters
POST   /api/v1/legal/matters
GET    /api/v1/legal/matters/{matterId}
PATCH  /api/v1/legal/matters/{matterId}
POST   /api/v1/legal/matters/{matterId}/close
POST   /api/v1/legal/matters/{matterId}/reopen

GET    /api/v1/legal/matters/{matterId}/cases
GET    /api/v1/legal/matters/{matterId}/documents
GET    /api/v1/legal/matters/{matterId}/time-entries
GET    /api/v1/legal/matters/{matterId}/invoices

POST   /api/v1/legal/conflict-checks
GET    /api/v1/legal/conflict-checks/{conflictCheckId}
POST   /api/v1/legal/conflict-checks/{conflictCheckId}/approve
POST   /api/v1/legal/conflict-checks/{conflictCheckId}/decline

Legal remains a later vertical. These endpoints define intended boundaries, not a P0 build commitment.

Suggested fields:

id
organization_id
client_id
matter_number
title
practice_area
responsible_lawyer_user_id
status
opened_date
closed_date
created_at
updated_at

Suggested fields:

id
organization_id
matter_id
case_number
court_id
jurisdiction
case_type
status
filed_date
created_at
updated_at

Suggested fields:

id
organization_id
case_id
hearing_type
scheduled_at
courtroom
judge
status
notes

Suggested tables:

legal_conflict_checks
legal_conflict_parties
legal_conflict_matches

Conflict-check fields:

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:

{
  "potentialClientName": "Acme Corporation",
  "relatedParties": [
    {
      "name": "John Smith",
      "relationship": "CEO"
    },
    {
      "name": "Acme Subsidiary LLC",
      "relationship": "Subsidiary"
    }
  ],
  "matterDescription": "Corporate acquisition"
}

Response may contain possible matches:

{
  "data": {
    "id": "conflict_123",
    "status": "pending_review",
    "potentialConflicts": [
      {
        "type": "possible_direct_adversity",
        "partyName": "Acme Corporation",
        "existingMatterId": "matter_456",
        "existingMatterNumber": "MAT-2026-089"
      }
    ]
  }
}

The system should distinguish:

automated possible match

from:

lawyer-approved conflict determination

The software may assist discovery; it should not silently make the professional judgment.

Approvals and declines are auditable commands.

45. Healthcare Tables

Initial tables:

healthcare_patients
healthcare_patient_contacts
healthcare_patient_addresses
healthcare_practitioners
healthcare_appointments
healthcare_encounters
healthcare_clinical_records
healthcare_clinical_record_versions
healthcare_clinical_record_amendments
healthcare_diagnoses
healthcare_prescriptions
healthcare_insurance_policies
healthcare_allergies
healthcare_medications

REST namespace:

/api/v1/healthcare

Examples:

GET    /api/v1/healthcare/patients
POST   /api/v1/healthcare/patients
GET    /api/v1/healthcare/patients/{patientId}
PATCH  /api/v1/healthcare/patients/{patientId}
POST   /api/v1/healthcare/patients/{patientId}/archive

GET /api/v1/healthcare/patients/{patientId}/appointments
GET /api/v1/healthcare/patients/{patientId}/encounters
GET /api/v1/healthcare/patients/{patientId}/clinical-records
GET /api/v1/healthcare/patients/{patientId}/prescriptions
GET /api/v1/healthcare/patients/{patientId}/allergies

POST /api/v1/healthcare/encounters
POST /api/v1/healthcare/encounters/{encounterId}/clinical-records

GET  /api/v1/healthcare/clinical-records/{recordId}
GET  /api/v1/healthcare/clinical-records/{recordId}/history
GET  /api/v1/healthcare/clinical-records/{recordId}/access-log

POST /api/v1/healthcare/clinical-records/{recordId}/sign
POST /api/v1/healthcare/clinical-records/{recordId}/amend

Healthcare is intentionally not treated as ordinary CRM plus extra columns.

46. Healthcare Patients

Core patient fields:

id
organization_id
patient_number
first_name
middle_name
last_name
date_of_birth
sex_or_administrative_gender_as_required
status
created_at
updated_at
version

Do not make a single default patient DTO return every available PHI field.

Use minimum-necessary response shapes.

Example general patient response:

{
  "data": {
    "id": "patient_123",
    "patientNumber": "PAT-2026-001",
    "name": {
      "firstName": "Alice",
      "middleName": "Marie",
      "lastName": "Johnson"
    },
    "dateOfBirth": "1985-03-15",
    "status": "active",
    "version": 2
  }
}

More sensitive subresources should have separate permissions and endpoints where useful:

contact information
addresses
emergency contacts
insurance policies
clinical records
prescriptions

Do not return insurance member IDs or emergency contact details on every patient read merely because the database has them.

47. Healthcare Practitioners

Suggested fields:

id
organization_id
user_id
specialty
license_number
license_jurisdiction
credential_status
created_at
updated_at

48. Healthcare Appointments

Suggested fields:

id
organization_id
patient_id
practitioner_id
appointment_type
starts_at
ends_at
status
reason
created_at
updated_at

49. Healthcare Encounters

Suggested fields:

id
organization_id
patient_id
practitioner_id
appointment_id
encounter_type
started_at
ended_at
status

50. Clinical Records

Suggested tables:

healthcare_clinical_records
healthcare_clinical_record_versions
healthcare_clinical_record_amendments

Core record fields:

id
organization_id
patient_id
encounter_id
author_practitioner_id
record_type
sensitivity_level
status
signed_by_practitioner_id
signed_at
created_at
updated_at
version

Draft content may be editable according to workflow.

Once signed/finalized:

  • do not overwrite history
  • create amendments or new versions
  • preserve previous signed content
  • audit reads when policy requires
  • audit all writes/signatures/amendments

REST:

POST /api/v1/healthcare/encounters/{encounterId}/clinical-records

GET  /api/v1/healthcare/clinical-records/{recordId}

PATCH /api/v1/healthcare/clinical-records/{recordId}
# Only when editable/draft according to policy.

POST /api/v1/healthcare/clinical-records/{recordId}/sign
POST /api/v1/healthcare/clinical-records/{recordId}/amend

GET /api/v1/healthcare/clinical-records/{recordId}/history
GET /api/v1/healthcare/clinical-records/{recordId}/access-log

Clinical content representation should be designed around actual healthcare requirements and interoperability needs rather than permanently committing to one ad-hoc JSON SOAP-note structure.

Sensitive record access should support an accessReason when organization or regulatory policy requires it.

51. Documents

Use shared object storage.

Database:

documents
document_versions
document_categories
retention_policies

Binary data:

S3-compatible object storage

Document

Suggested fields:

id
organization_id

name
category_id

classification

retention_policy_id

current_version_id

created_by_user_id
created_at
updated_at

Classification examples:

public
internal
confidential
restricted
regulated

Avoid a single is_confidential boolean as the long-term security model.

Document Version

Suggested fields:

id
organization_id
document_id

version_number

storage_key

mime_type
size_bytes

content_hash
hash_algorithm

uploaded_by_user_id
created_at

The authoritative checksum belongs on the version because each binary revision has different content.

Optional document-level metadata may include:

current_version_id
current_version_number

but should not replace version-level integrity data.

Metadata

Use JSONB only for genuinely extensible metadata that does not deserve stable relational columns.

Examples:

CAD-specific extraction results
scanner metadata
non-authoritative document properties

Do not place access control, retention state, ownership, or lifecycle rules inside arbitrary metadata JSON.

Document Categories

Suggested fields:

id
organization_id
profession nullable
name
parent_category_id
created_at

If profession is nullable and shared categories must remain unique, PostgreSQL uniqueness must explicitly handle nulls.

Options include:

UNIQUE NULLS NOT DISTINCT

where supported, or separate partial unique indexes for:

profession IS NULL
profession IS NOT NULL

Do not rely on a plain nullable composite unique constraint and assume NULL behaves like a normal value.

Upload Security

Validate:

declared MIME
extension
magic bytes/content signature
file size
malware scan
organization quota
classification policy

A renamed executable is not a PDF merely because the filename developed ambition.

52. Document Upload Flow

Small and ordinary file uploads:

Frontend
   ↓
Request upload authorization
   ↓
Backend validates tenant + permission + upload policy
   ↓
Create pending document/version
   ↓
Return signed upload URL
   ↓
Frontend uploads directly to object storage
   ↓
Backend finalizes
   ↓
verify checksum/type/size
   ↓
malware/security scan
   ↓
classification + retention
   ↓
available

REST:

POST /api/v1/documents/upload-url
POST /api/v1/documents/{documentId}/complete-upload

GET  /api/v1/documents/{documentId}
GET  /api/v1/documents/{documentId}/download-url

POST /api/v1/documents/{documentId}/versions

Very Large Engineering Files

Large CAD/BIM/model files use native object-storage multipart upload.

The API orchestrates authorization and signed part URLs.

It does not proxy gigabytes of file content through application servers.

Flow:

Frontend
   ↓
POST /documents/multipart-uploads
   ↓
Backend authorizes
and initializes object-storage multipart upload
   ↓
Frontend requests signed part URLs
   ↓
Frontend uploads parts directly to object storage
   ↓
Frontend reports completed parts
   ↓
POST /documents/{id}/multipart-upload/complete
   ↓
Backend finalizes object
   ↓
verify object metadata/checksum
   ↓
malware/security scan
   ↓
mark document version available

Possible REST:

POST   /api/v1/documents/multipart-uploads

POST   /api/v1/documents/{documentId}/multipart-upload/parts
POST   /api/v1/documents/{documentId}/multipart-upload/complete

DELETE /api/v1/documents/{documentId}/multipart-upload

The /parts endpoint returns signed upload URLs and part metadata.

It does not carry binary chunks.

Multipart Upload State

Track:

upload_id
organization_id
document_id
document_version_id
object_storage_upload_id
status
created_at
expires_at
completed_at
aborted_at

States:

initiated
uploading
completing
completed
aborted
expired

Cleanup workers abort abandoned multipart uploads.

File Policy

Use configurable policy:

max_file_size_bytes
allowed_file_classes
organization_storage_quota_bytes
profession_overrides
plan/tier overrides
multipart_threshold_bytes

Exact file-size and quota values are product/configuration decisions.

Validation includes:

declared MIME
extension
content signature / magic bytes
file size
checksum
quota
classification
malware scan

Use explicit relationship tables.

Engineering:

engineering_project_documents
engineering_design_documents
engineering_inspection_documents

Legal:

legal_matter_documents
legal_case_documents

Healthcare:

healthcare_patient_documents
healthcare_encounter_documents

Engineering Project Documents

Suggested link fields:

id
organization_id
project_id
document_id

category
classification_override nullable

linked_by_user_id
linked_at
unlinked_at

REST:

GET    /api/v1/engineering/projects/{projectId}/documents
POST   /api/v1/engineering/projects/{projectId}/documents

DELETE /api/v1/engineering/project-documents/{documentLinkId}

Link deletion may preserve historical linkage through unlinked_at when required.

Example response:

{
  "data": [
    {
      "documentLinkId": "projdoc_123",
      "category": "calculations",
      "document": {
        "id": "doc_456",
        "name": "structural_calculations.pdf",
        "classification": "confidential",
        "currentVersion": 2,
        "mimeType": "application/pdf",
        "sizeBytes": 2457600
      }
    }
  ]
}

Explicit link resources give stronger referential integrity than generic polymorphic foreign keys.

54. Billing

Shared financial core:

invoices
invoice_items
payments

Profession-specific modules may extend billing workflows.

Engineering examples:

project billing
hourly billing
milestone billing

Legal examples:

matter billing
time billing
retainers
trust accounting

Healthcare examples:

insurance
claims
patient billing

REST:

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:

{
  "amountMinor": 12550,
  "currency": "USD"
}

Meaning:

$125.50

Never use floating point for money.


56. Audit Logging

Table:

audit_events

Suggested fields:

id
organization_id
actor_type
actor_user_id
actor_service_account_id
action
resource_type
resource_id
request_id
correlation_id
ip_address
user_agent
metadata
occurred_at

Audit events are append-only.

Mandatory Engineering Audit Events

engineering.projects.create
engineering.projects.close
engineering.designs.approve
engineering.designs.reject
engineering.designs.supersede
engineering.inspections.complete
legal.matters.create
legal.matters.close
legal.matters.reopen
legal.conflicts.approve
legal.conflicts.decline
legal.retainers.manage

Mandatory Healthcare Audit Events

healthcare.records.read
healthcare.records.write
healthcare.records.sign
healthcare.records.amend
healthcare.prescriptions.write
healthcare.prescriptions.sign

Example:

{
  "id": "audit_123",
  "organizationId": "org_456",
  "actorUserId": "user_789",
  "action": "healthcare.records.read",
  "resourceType": "healthcare_clinical_record",
  "resourceId": "record_456",
  "requestId": "req_abc",
  "ipAddress": "192.0.2.10",
  "userAgent": "Mozilla/5.0",
  "metadata": {
    "patientId": "patient_123",
    "recordType": "progress_note",
    "accessReason": "clinical_review"
  },
  "occurredAt": "2026-08-26T01:30:00Z"
}

Audit metadata must never contain:

  • passwords
  • access or refresh tokens
  • full clinical note content
  • secret keys
  • unnecessary payment data

REST:

GET /api/v1/audit-events

No public create/update/delete endpoints.

57. Domain Events and Transactional Outbox

Profession modules produce internal domain events.

Examples:

engineering.project.created
engineering.design.approved
engineering.inspection.completed

legal.matter.closed
legal.conflict_check.approved

healthcare.appointment.created
healthcare.clinical_record.signed

invoice.issued
payment.recorded

Consumers:

notifications
webhooks
analytics
search indexing
integrations
background workflows

Use:

outbox_events

Suggested fields:

id
organization_id
event_type
aggregate_type
aggregate_id
payload
occurred_at
available_at
processed_at
attempt_count
last_error
dead_lettered_at

Transaction:

BEGIN

business change
audit event
outbox event

COMMIT

The outbox is at-least-once delivery, not magically exactly-once.

Worker claim example:

SELECT id
FROM outbox_events
WHERE processed_at IS NULL
  AND dead_lettered_at IS NULL
  AND available_at <= now()
ORDER BY occurred_at
FOR UPDATE SKIP LOCKED
LIMIT 100;

Worker responsibilities:

  1. claim committed event
  2. process consumer action
  3. mark processed on success
  4. increment attempts on failure
  5. schedule retry with backoff
  6. dead-letter after policy threshold
  7. emit metrics
  8. preserve replay/debug metadata

Critical Failure Case

A worker may:

perform external side effect
      ↓
crash
      ↓
fail to mark event processed
      ↓
event is retried

Therefore every external consumer must support idempotency.

Examples:

payment provider command → provider idempotency key
webhook delivery → delivery/event ID
email notification → dedupe key if duplicate mail is unacceptable
search indexing → upsert by entity/version

FOR UPDATE SKIP LOCKED prevents concurrent claims. It does not prevent duplicate side effects after a crash.

Workers may be awakened by queue notifications, but must still poll durable outbox state so lost wake-ups do not strand events.

57A. Webhooks and External Integrations

Webhooks are a shared platform capability, not profession-specific transport code.

Configuration REST:

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:

GET  /api/v1/webhook-deliveries
GET  /api/v1/webhook-deliveries/{deliveryId}
POST /api/v1/webhook-deliveries/{deliveryId}/retry

Suggested tables:

webhooks
webhook_event_subscriptions
webhook_deliveries

Webhook fields:

id
organization_id
url
status
secret_ciphertext or signing_key_reference
created_by_user_id
created_at
updated_at

Do not return a secret hash to the client.

Secret Handling

If using symmetric HMAC signing:

generate secret
    ↓
show plaintext once
    ↓
encrypt using KMS/key-management system
    ↓
store ciphertext
    ↓
decrypt only for signing

A one-way hash alone is insufficient because the server must possess the signing material.

Alternative:

asymmetric signing
+
published verification key

Delivery Model

Each delivery records:

id
organization_id
webhook_id
event_id

attempt_number
request_timestamp
response_status
response_summary

delivered_at
failed_at
next_attempt_at

Webhook workers require:

timeouts
retry with backoff
dead-letter/failure state
request signing
event IDs
idempotency guidance for consumers
delivery history
manual replay

Events should include stable identifiers so consumers can deduplicate.

Example:

{
  "id": "evt_123",
  "type": "engineering.design.approved",
  "organizationId": "org_456",
  "occurredAt": "2026-08-26T12:00:00Z",
  "data": {
    "designId": "design_789"
  }
}

58. Background Jobs

Workers handle:

Email
SMS
Notifications

PDF/report generation

File security scanning
Document processing

Imports
Exports
Bulk updates

Webhook delivery
Search indexing

Large data operations

Architecture:

API
 ↓
Queue
 ↓
Worker

Async Job Resource

Use a shared job model for long-running user-requested operations.

Suggested table:

jobs

Fields:

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:

queued
running
completed
failed
cancelled

REST:

GET  /api/v1/jobs/{jobId}
GET  /api/v1/jobs/{jobId}/result
POST /api/v1/jobs/{jobId}/cancel

Import / Export

Do not create asynchronous side effects with GET.

Engineering examples:

POST /api/v1/engineering/project-imports
POST /api/v1/engineering/project-exports

POST /api/v1/engineering/time-entry-imports
POST /api/v1/engineering/time-entry-exports

Response:

202 Accepted
{
  "data": {
    "jobId": "job_123",
    "status": "queued"
  }
}

Initial formats may include:

CSV
JSON

Import requirements:

validation report
row-level errors
all-or-partial mode explicitly defined
idempotency strategy
audit event
job result artifact

Export requirements:

authorization applied before generation
signed result URL
expiration
audit where data sensitivity requires it

59. Redis

Use Redis as an acceleration and coordination layer, not the authoritative system of record.

Appropriate uses:

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:

static permission definitions
non-sensitive configuration

L2 Redis shared cache:

organization settings
membership snapshots
role permission snapshots
rate-limit counters
session lookup cache
recent idempotency lookups

CDN:

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:

membership changes
role permissions change
organization settings change
professional credentials change
session is revoked
profession module enablement changes

High-risk authorization decisions must not depend solely on stale cached credential state.

Idempotency Durability

Redis may improve idempotency lookup latency, but PostgreSQL remains authoritative for high-risk commands.

60. Pagination

Use cursor pagination.

Example:

GET /api/v1/engineering/projects?limit=25

Response:

{
  "data": [],
  "meta": {
    "pagination": {
      "nextCursor": "...",
      "hasMore": true
    }
  }
}

Maximum page size:

100

61. Filtering

Use explicit resource-specific filters.

Examples:

GET /api/v1/engineering/projects?status=active&discipline=structural
GET /api/v1/engineering/tasks?status=todo&assignedToUserId=user_123

Do not build a generic query DSL in v1.


62. Sorting

Examples:

GET /api/v1/engineering/projects?sort=createdAt
GET /api/v1/engineering/projects?sort=-createdAt

Only explicitly supported fields may be sorted.


Start with PostgreSQL search.

Engineering search may cover:

project number
project name
client name

Legal:

matter number
client
case number

Healthcare:

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:

{
  "id": "project_123",
  "version": 6
}

Update:

{
  "version": 6,
  "name": "Central Tower Phase II"
}

If the current database version differs:

409 CONCURRENT_MODIFICATION

65. Domain-Oriented REST

Important state transitions use explicit command endpoints.

Good:

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:

PATCH /resource/{id}
{
  "status": "approved"
}

when the change has significant rules or side effects.


66. Transaction Boundaries

Create project:

BEGIN

create project
assign project manager
write audit event
write outbox event

COMMIT

Approve design:

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:

RequestContext
{
    requestId
    userId
    sessionId
    organizationId
    membershipId
    permissions
}

Profession modules consume this context.


68. Request IDs

Every request has:

X-Request-Id

If missing, the server generates one.

Use it in:

  • logs
  • audit context
  • error diagnostics
  • asynchronous correlation

69. OpenAPI

Maintain:

openapi.yaml

Use OpenAPI 3.1.

Production server example:

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:

components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT

Reusable headers/parameters:

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:

Request DTO
Response DTO

A database migration should not accidentally change the public API.


71. Backend Module Structure

Recommended:

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:

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:

HTTP
authentication context
input DTO parsing
application command/query invocation
response mapping

Controllers should not contain:

business rules
raw SQL
role logic
transaction orchestration
email sending
audit implementation

74. Commands and Queries

Mutations use commands.

Examples:

CreateEngineeringProjectCommand
ApproveEngineeringDesignCommand
CloseLegalMatterCommand
CompleteHealthcareEncounterCommand

Reads use queries.

Examples:

GetEngineeringProjectQuery
ListLegalMattersQuery
GetHealthcarePatientQuery

75. Repositories

Use domain-specific repositories.

Examples:

EngineeringProjectRepository
LegalMatterRepository
HealthcarePatientRepository

Avoid one massive generic repository abstraction that eventually needs dozens of flags.


76. Security Baseline

Minimum controls:

TLS everywhere
Argon2id or equivalent strong password hashing
short-lived access tokens
refresh-token rotation
refresh-token reuse detection
server-side session revocation

rate limiting
anti-automation controls

RBAC
resource policies
credential-aware authorization
tenant isolation

input validation
SQL injection protection

signed object-storage URLs
file-content validation
malware scanning

audit trails
secret management
encryption at rest

dependency scanning
container/image scanning
security headers

request/correlation IDs
backup and restore testing

Rate Limiting

Model policy rather than baking arbitrary numbers into architecture.

Example:

interface RateLimitRule {
  routePattern: string;
  method: string;
  windowSeconds: number;
  maxRequests: number;
  scope: 'user' | 'organization' | 'ip' | 'email' | 'session';
}

Policy classes:

authentication
password recovery
general API
search
upload authorization
report generation
webhooks/integrations
clinical record reads

Rate-limit values are configuration derived from:

security testing
load testing
observed traffic
customer tier
endpoint cost
abuse risk

Do not grant normal tenant roles blanket rate-limit bypass.

Administrative exceptions, if any, require explicit trusted-system policy.

Return:

429 Too Many Requests
Retry-After: ...

Error:

RATE_LIMIT_EXCEEDED

Secrets

Use managed secrets/key management where possible.

Never put real secrets in source-controlled examples.

Prefer JWT asymmetric signing or managed signing keys with rotation capability.

77. Data Classification

Suggested classes:

Public

marketing configuration

Internal

organization settings
tasks

Confidential

engineering documents
legal matters
billing

Highly Sensitive

clinical records
professional credentials
authentication secrets

78. Healthcare Security

Before healthcare production use, define:

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:

structured logs
metrics
distributed tracing
request IDs
correlation IDs

Recommended:

OpenTelemetry

Core Metrics

API:

api_requests_total
api_errors_total
api_request_duration_seconds

Authentication:

auth_login_attempts_total
auth_token_refresh_total
auth_refresh_reuse_detections_total
auth_sessions_revoked_total

Authorization/security:

cross_tenant_access_attempts_total
tenant_isolation_invariant_failures_total
authorization_denials_total
credential_policy_denials_total
rate_limit_events_total

Important distinction:

cross_tenant_access_attempt
=
request attempted another tenant's resource

This may be a stale link, mistake, or attack.

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:

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:

db_pool_active
db_pool_waiting
db_query_duration_seconds
db_transaction_duration_seconds

Business metrics may include:

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:

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:

request_id
route
method
status
duration
user_id when appropriate
organization_id when appropriate

Never log:

passwords
tokens
clinical record text
full sensitive documents
payment secrets

81. Testing Strategy

Unit Tests

Test:

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:

engineering design lifecycle
engineering inspection lifecycle
invoice lifecycle
payment state transitions
membership/role invariants

Correct properties:

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:

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:

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:

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:

external side effect succeeds
worker dies
event retries

Tests must prove the consumer does not create an unacceptable duplicate.

Tenant Security Tests

Test both:

external cross-tenant access attempts

and:

internal cross-tenant data invariant failures

These are different classes of failure.

Performance Tests

Create realistic profiles:

interactive reads
interactive writes
search
dashboard read models
reporting
file upload orchestration
outbox processing
webhook bursts
notification bursts

Measure:

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:

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:

Organization A resource
using Organization B context

Test:

read
update
delete/action
list filtering
search
documents

Expected result:

404 / denied

83. Engineering MVP

Engineering is the first vertical.

Initial features:

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:

advanced CAD integration
BIM integration
full document markup
advanced resource planning
procurement
complex accounting
AI design analysis
IoT integrations

84. Engineering MVP Workflow

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:

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:

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:

engineering clients
engineering client contacts
client archive/restore

Phase 3: Engineering Projects

Build:

projects
project members
project phases
activation/close/archive

Phase 4: Work and Site Management

Build:

tasks
task batch operations
sites

Phase 5: Documents

Build:

documents
versions
categories
classification
retention references
signed uploads
multipart uploads
content verification
malware scanning
engineering document links

Phase 6: Engineering Designs

Build:

designs
assignments
versions
reviews
cancel/withdraw semantics
credential-aware approval
audit
outbox
idempotency

Phase 7: Engineering Inspections

Build:

inspection lifecycle
inspection outcome
findings
corrective work
follow-up inspections
attachments
audit
outbox
idempotency

Phase 8: Time, Budgets, and Billing

Build:

time entries
batch timesheet submission
project budgets when required
invoices
payments
financial idempotency
reconciliation

Phase 9: Notifications, Jobs, and Webhooks

Build:

notifications
email
async jobs
imports/exports
webhooks
delivery/retry
dead-letter handling

Build:

project status
overdue work
inspection status
billable time
revenue
outstanding invoices
dashboard read models

Phase 11: Engineering Client Portal

Build:

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.

Validate shared core against:

matters
cases
conflicts
deadlines
retainers
restricted access / ethical walls

Phase 13: Healthcare Readiness and Vertical

Before implementation:

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:

team size
frontend/UX scope
cloud decisions
third-party providers
security requirements
QA capacity
domain-expert availability

Only after engineering proves the shared platform assumptions.

Build:

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:

development
testing
staging
production

Each environment has independent:

database
object storage
secrets
queues
API keys

89. Initial Deployment Architecture

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:

automated backups
point-in-time recovery
tested restores

Object storage:

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:

YYYYMMDDHHMMSS_description.sql

Example:

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:

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:

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:

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:

ADR-001 Backend Framework
ADR-002 SQL / ORM / Query Layer
ADR-003 Queue Implementation
ADR-004 PostgreSQL Minimum Version
ADR-005 Error Format / RFC 9457 Compatibility
ADR-006 Rate-Limit Header Convention
ADR-007 Webhook Signing Strategy
ADR-008 Object Storage Provider / Multipart Strategy

Each ADR should include:

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

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:

NestJS
Fastify-centered custom application structure

SQL / persistence candidates:

Drizzle
Kysely
Prisma
direct SQL for specialized queries

Queue candidates:

BullMQ / Redis
managed cloud queue

PostgreSQL baseline:

PostgreSQL 18+

is attractive because of native UUIDv7 and current capabilities, but the minimum supported version must be confirmed against:

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:

transaction control
explicit SQL visibility
tenant-safe query design
migration control
observability
testability

93. REST API Milestones

Milestone 1: Platform Access and Security

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:

explicit organization context
session revocation
refresh-token reuse detection
audit foundation
outbox foundation
idempotency foundation
rate limiting

Milestone 2: Engineering Clients

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

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

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:

engineering sites
signed file uploads
document versions
malware scanning
project document links

Milestone 6: Designs

Build:

design lifecycle
versions
reviews
submit-review
request-changes
approve
reject
supersede
credential validation
audit + outbox + idempotency

Milestone 7: Inspections

Build:

schedule
start
complete
cancel
findings
finding resolution
audit + outbox + idempotency

Milestone 8: Commercial Workflows

Build:

time entries
invoices
payments
refunds
financial idempotency
reports

94. Architecture Rules to Freeze

  1. REST is the primary frontend and integration API.
  2. Base path is /api/v1.
  3. OpenAPI 3.1 is the public API contract.
  4. GraphQL is not part of v1.
  5. Start as one modular monolith backend.
  6. Each profession has its own frontend.
  7. Each profession owns its domain tables and state machines.
  8. Shared modules provide infrastructure, not forced domain abstractions.
  9. Every tenant-owned row contains organization_id.
  10. Tenant-scoped requests require explicit X-Organization-Id.
  11. The API never silently selects an organization.
  12. Tenant boundaries are enforced in queries and database constraints.
  13. Cross-tenant resources appear nonexistent.
  14. Authorization is server-side and deny-by-default.
  15. Roles and professional qualifications are separate.
  16. High-risk professional commands validate authoritative credential state.
  17. Sessions and refresh tokens are separate resources.
  18. Refresh tokens rotate within families and support reuse detection.
  19. UUIDv7 is the identifier standard.
  20. PostgreSQL 18 native UUIDv7 may be used when PostgreSQL 18+ is selected.
  21. Important domain transitions use explicit REST command endpoints.
  22. High-risk commands use durable idempotency.
  23. Redis may accelerate idempotency but is not authoritative for financial/regulated commands.
  24. Profession-specific state transitions are explicitly modeled and tested.
  25. Design cancellation and post-review withdrawal are distinct when needed.
  26. Inspection lifecycle and outcome are separate dimensions.
  27. Follow-up inspection work is linked work, not overloaded lifecycle state.
  28. Files live in object storage.
  29. Large files use native object-storage multipart uploads.
  30. Application servers do not proxy multi-gigabyte file chunks.
  31. Document checksum is version-level authoritative data.
  32. Document classification is multi-level.
  33. File upload policy is configurable.
  34. Upload validation includes content signature, size, quota, checksum, and malware scanning.
  35. Explicit document-link tables are preferred over generic polymorphic links.
  36. Project document linkage does not imply client-portal publication.
  37. External publication requires an explicit publication record.
  38. Client portal identities are not ordinary internal memberships.
  39. Client portal permissions are separate from internal RBAC assumptions.
  40. Client acceptance/review is not professional engineering approval.
  41. Professional design approval is never granted through a client portal role.
  42. Small batch commands define atomic or partial semantics explicitly.
  43. Large batch operations become asynchronous jobs.
  44. Every batch item receives tenant/authorization/domain validation.
  45. Domain events use a transactional outbox.
  46. Outbox semantics are at-least-once.
  47. External side-effect consumers are idempotent.
  48. Webhooks are shared platform infrastructure with retry, replay, delivery history, and signing.
  49. HMAC webhook signing material is securely recoverable, normally encrypted with managed keys.
  50. Async imports/exports/reports use job resources and return 202 Accepted.
  51. GET endpoints do not create export jobs.
  52. Engineering clients support multiple contacts.
  53. Engineering budgets use dedicated tables when budget management enters scope.
  54. Derived financial totals do not become uncontrolled duplicate truth.
  55. PostgreSQL is the authoritative transactional datastore.
  56. Redis is an acceleration and coordination layer.
  57. Search starts in PostgreSQL.
  58. External search/read replicas/materialized views require measured need.
  59. API collections use cursor pagination.
  60. Important mutable resources use optimistic concurrency.
  61. Database entities are not serialized directly as public API contracts.
  62. Errors use stable codes.
  63. Rate-limited responses use 429 and Retry-After.
  64. Additional rate-limit headers are implementation/API-contract decisions, not frozen legacy header names.
  65. Important and regulated actions are audited.
  66. Sensitive healthcare reads are audited where policy requires.
  67. Signed clinical records use sign/amend/version workflows.
  68. Prescribing authority is jurisdiction and scope-of-practice driven.
  69. Production migrations follow expand/contract.
  70. Destructive schema changes are not assumed trivially reversible.
  71. Secrets are managed outside source control.
  72. Rate limits are policy/configuration calibrated by evidence.
  73. CI validates types, tests, OpenAPI, migrations, and security scans.
  74. Property-based tests are used for high-value state machines.
  75. Outbox/job/webhook reliability is tested under failure and concurrency.
  76. Internal tenant-isolation invariant failures and external cross-tenant attempts are separate signals.
  77. Critical-path tests matter more than vanity coverage percentages.
  78. Performance objectives are provisional until measured.
  79. Architecture risks are maintained in a living register.
  80. Framework/ORM/queue choices require ADRs.
  81. Engineering is the first implemented product vertical.
  82. The engineering client portal follows internal Engineering MVP foundations.
  83. Legal follows after engineering validates shared assumptions.
  84. Healthcare requires explicit security/privacy/jurisdiction readiness work.
  85. Architecture documentation never equates "designed for" with "certified/compliant".
  86. v4 is the final broad architecture baseline unless a foundational assumption is invalidated.

95. Required Design Artifacts

Maintain:

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:

backend framework
persistence/query layer
queue implementation
PostgreSQL minimum version
error format
rate-limit headers
webhook signing
object-storage provider
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:

(organization_id, id)

Common list access often benefits from:

(organization_id, created_at)

Query-specific examples:

(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:

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:

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:

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:

rolling or blue/green application deployment
backward-compatible database migrations
health checks
fast application rollback
feature flags for incomplete features
observability gates

Database schema rollback is not treated as equivalent to application rollback.

Configuration and Secrets

Non-secret configuration may use environment variables.

Secrets should use a managed secret store where possible:

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:

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:

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:

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:

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:

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:

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

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

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

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

OpenAPI validates
contract tests pass
error schema consistent
versioning rules documented
client SDK generation validated if used

Performance

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:

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:

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:

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:

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:

regulatory compliance
production certification
security certification
performance at a specific scale

Those require implementation evidence, security review, domain validation, operational testing, restore testing, and measured production-like workloads.


v4 Changelog

Compared with v3, v4 adds or changes:

✓ dedicated engineering client portal trust boundary
✓ portal accounts separated from organization memberships
✓ explicit project-level external access grants
✓ professional design approval separated from client acceptance
✓ explicit external document-publication model
✓ batch operations with atomic/partial semantics
✓ asynchronous escalation for large batches
✓ object-storage multipart upload orchestration
✓ no API proxying of multi-gigabyte chunks
✓ rate-limit response policy with Retry-After
✓ legacy X-RateLimit headers not frozen into the architecture
✓ framework/ORM/queue decisions moved into ADRs
✓ PostgreSQL minimum version moved into an ADR
✓ RFC 9457 error-format compatibility added as an ADR
✓ provisional performance objectives separated from production SLOs
✓ living risk register introduced
✓ architecture-change governance introduced
✓ v4 designated final broad architecture baseline