95 KiB
Professional Management Platform
Full REST-First System Design Plan
Revision: v3 — Implementation Architecture Baseline
Status: Engineering-MVP implementation baseline; production readiness still requires measured evidence, security review, and profession/jurisdiction-specific validation.
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
v3 Integration Notes
v3 incorporates the strongest additions from the second design review while correcting several implementation traps.
Added or strengthened:
- webhook configuration, delivery history, retries, secret rotation, and signing
- asynchronous import/export/report job model
- engineering client contact management
- project document-link APIs
- design assignment, cancellation/withdrawal, and richer lifecycle rules
- engineering project budget and budget-item modeling
- document classification, retention, content hashing, and categories
- refresh-token family lineage with separate session and token records
- rate-limit policy framework without freezing arbitrary limits
- property-based testing for state machines
- chaos/reliability testing for transactional outbox consumers
- operational and security metrics
- explicit pre-production quality gates
- expand/contract migration requirements
- PostgreSQL 18 native UUIDv7 option
- minimum viable file-policy framework
- production checklist and observability requirements
Corrected rather than copied literally:
- refresh-token rotation does not create a schema conflict with
UNIQUE(refresh_token_family_id) - webhook HMAC secrets are not stored as one-way hashes if the server needs them for signing
- async export is modeled as a job resource rather than a side-effecting
GET - inspection follow-up is modeled as an outcome/linked workflow rather than overloading inspection lifecycle state
- document checksum is authoritative on document versions, not the parent document
- document confidentiality is modeled as classification rather than a single boolean
- nullable document-category uniqueness must use explicit PostgreSQL null semantics or partial indexes
- budget spent/committed values must not become uncontrolled duplicate sources of financial truth
- property-based tests distinguish valid and invalid transitions
- outbox processing assumes at-least-once delivery and therefore requires idempotent consumers
- tenant attack signals are distinct from internal tenant-isolation invariant failures
- implementation phases are milestones, not a fictional calendar commitment
- coverage percentage is a diagnostic metric, not a substitute for critical-path tests
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
Legal Frontend
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.
It is deliberately explicit even when a user currently belongs to only one organization. Silent organization selection creates ambiguous clients and becomes dangerous the moment the user later joins a second organization.
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 return another organization's name or membership details in tenant-error responses.
Idempotency
Use:
Idempotency-Key: 8f7d6c5e-4b3a-2b1c-9d8e-7f6a5b4c3d2e
Idempotency is required for commands where duplicate execution can create financial, regulated, external, or otherwise 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
POST /api/v1/legal/retainers
POST /api/v1/legal/conflict-checks/{id}/approve
POST /api/v1/healthcare/prescriptions
POST /api/v1/healthcare/clinical-records/{id}/sign
Do not require idempotency on every ordinary PATCH by default.
Idempotency records must include:
organization_id
actor_id
route/action
idempotency_key
canonical_request_hash
response_status
response_body or resource reference
created_at
expires_at
Rules:
Same key + same operation + same request hash:
return: original result
Same key + different request hash:
status: 409
code: IDEMPOTENCY_KEY_CONFLICT
Durability:
- PostgreSQL is authoritative for critical idempotency records.
- Redis may cache recent records for speed.
- Redis eviction must not make a payment or regulated command executable twice.
- Downstream providers should receive their own idempotency key where supported.
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:
- hash supplied refresh token
- load token and session
- validate token/session status and expiry
- issue replacement token in same family
- mark old token rotated
- link
replaced_by_token_id - 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:
- Controllers never perform ad-hoc role comparisons.
- Tenant resource queries always include
organization_id. - Do not load an arbitrary resource first and then discover it belongs to another tenant.
- High-risk professional actions perform credential checks at command execution time.
- A permission grants the ability to attempt an action, not a guarantee the domain state allows it.
- Cross-tenant resources appear nonexistent.
- 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 Permissions
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.
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
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:
- validate inspector and project access
- validate required fields
- validate findings
- calculate or confirm outcome
- complete inspection
- create corrective work/follow-up records when required
- audit
- write outbox event
- 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
40. Legal Tables
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.
41. Legal Matters
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
42. Legal Cases
Suggested fields:
id
organization_id
matter_id
case_number
court_id
jurisdiction
case_type
status
filed_date
created_at
updated_at
43. Legal Hearings
Suggested fields:
id
organization_id
case_id
hearing_type
scheduled_at
courtroom
judge
status
notes
44. Legal Conflict Checks
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
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 upload
↓
Verify size / checksum / content type
↓
Malware and security scanning
↓
Apply classification / retention
↓
Mark version 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
File Policy
Do not freeze arbitrary product quotas into architecture.
Model a policy:
document_upload_policy
├── max_file_size_bytes
├── allowed_file_classes
├── organization_storage_quota_bytes
├── profession_overrides
└── plan/tier overrides
Engineering may eventually allow file classes such as:
PDF
images
DWG/DXF
spreadsheets
office documents
Healthcare may later have different file policies.
Exact limits are product/configuration decisions validated against storage cost, threat model, and customer needs.
53. Profession-Specific Document Links
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
Mandatory Legal Audit Events
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:
- claim committed event
- process consumer action
- mark processed on success
- increment attempts on failure
- schedule retry with backoff
- dead-letter after policy threshold
- emit metrics
- 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.
63. Search
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_trgmonly 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:
- default patient responses do not contain all available PHI
- clinical record reads may be auditable events
- signed records are immutable except through explicit amendment/version workflows
- prescribing authorization is jurisdiction-specific
- privileged clinical commands revalidate professional authority
- caches must not allow revoked credentials to remain effective for high-risk writes
- healthcare search results themselves are protected data
- access logs may require dedicated permissions
- 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
organization-context rules
session/token model
idempotency strategy
error taxonomy
OpenAPI skeleton
engineering state machines
migration conventions
threat model
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 persistence
rate-limit framework
observability foundation
Phase 2: Engineering CRM
Build:
engineering_clients
engineering_client_contacts
client archive/restore
Phase 3: Engineering Projects
Build:
engineering_projects
engineering_project_members
engineering_project_phases
activation/close/archive
Phase 4: Work and Site Management
Build:
engineering_tasks
engineering_sites
Phase 5: Documents
Build:
documents
document_versions
document categories
classification
retention policy references
signed uploads
content verification
malware scanning
engineering document links
Phase 6: Engineering Designs
Build:
engineering_designs
design assignments
design versions
design reviews
state machine
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:
engineering_time_entries
project budgets when in product scope
invoices
invoice items
payments
financial idempotency
reconciliation
Phase 9: Notifications, Jobs, and Webhooks
Build:
in-app notifications
email
async jobs
imports/exports
webhook configuration
webhook delivery/retry
dead-letter handling
Phase 10: Reporting and Search
Build:
project status
overdue work
inspection status
billable time
revenue
outstanding invoices
engineering dashboard read models
Add advanced indexes, materialized views, read replicas, or external search only if measured need justifies them.
Phase 11: Legal Vertical
Validate the shared core against:
matters
cases
conflicts
legal deadlines
retainers
ethical-wall/restricted access requirements
Phase 12: Healthcare Readiness and Vertical
Before implementation:
healthcare threat model
privacy review
jurisdiction analysis
credential/scope-of-practice policy
record signing/amendment model
retention model
audit requirements
Then implement healthcare.
Estimation Rule
These are dependency-ordered milestones, not calendar promises.
Calendar estimates are produced only after:
team size
frontend scope
UX designs
cloud choices
third-party providers
security requirements
QA capacity
engineering-domain details
are known.
86. Legal Expansion
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.
92. Technology Recommendation
Backend:
TypeScript
NestJS or Fastify-based architecture
Database:
PostgreSQL
ORM/query layer candidates:
Prisma
Drizzle
Kysely
Frontend:
React / Next.js
Queue:
Redis + BullMQ
Storage:
S3-compatible storage
Observability:
OpenTelemetry
Containers:
Docker
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
- REST is the primary frontend and integration API.
- Base path is
/api/v1. - OpenAPI 3.1 is the public API contract.
- GraphQL is not part of v1.
- Begin as one modular monolith backend.
- Each profession has its own frontend.
- Each profession owns its domain tables and state machines.
- Shared modules provide infrastructure rather than forced domain abstractions.
- Every tenant-owned row contains
organization_id. - Tenant-scoped requests require explicit
X-Organization-Id. - The API never silently selects an organization.
- Tenant boundaries are enforced in queries and database constraints.
- Cross-tenant resources appear nonexistent.
- Authorization is server-side and deny-by-default.
- Roles and professional qualifications are separate.
- High-risk professional commands validate authoritative credential state.
- Sessions and refresh-token records are separate concepts.
- Refresh-token rotation uses token families and reuse detection.
- UUIDv7 is the identifier standard.
- PostgreSQL 18 native
uuidv7()may be used when PostgreSQL 18+ is the baseline. - Important domain transitions use explicit REST command endpoints.
- High-risk commands use durable idempotency.
- Redis may accelerate idempotency but is not authoritative for financial/regulated commands.
- Profession-specific state transitions are explicitly modeled and tested.
- Engineering design cancellation and post-review withdrawal are semantically distinct where needed.
- Inspection lifecycle and inspection outcome are separate dimensions.
- Follow-up inspection work is linked work, not an overloaded lifecycle status.
- Shared document binaries live in object storage.
- Document checksum belongs to document versions.
- Document classification is multi-level, not a single confidentiality boolean.
- File upload policy is configurable by organization/profession/product tier.
- Uploads validate declared MIME, extension, content signature, size, quota, and malware status.
- Explicit document-link tables are preferred over generic polymorphic references.
- Domain events use a transactional outbox.
- Outbox semantics are at-least-once.
- Every outbox consumer that can create external side effects is idempotent.
- Webhooks are a shared platform service with delivery history and retry.
- HMAC webhook signing material must be recoverable securely, normally encrypted with managed key protection.
- Async imports, exports, and reports use job resources and return
202 Accepted. GETendpoints do not create export jobs.- Engineering clients support multiple contacts.
- Engineering budget detail uses dedicated tables when budget management enters scope.
- Derived spent/committed budget totals must not become uncontrolled duplicate financial truth.
- PostgreSQL remains the authoritative transactional datastore.
- Redis is an acceleration/coordination layer.
- Search starts with PostgreSQL.
- External search, read replicas, materialized views, and partitioning require measured evidence.
- API collections use cursor pagination.
- Important mutable resources use optimistic concurrency.
- Database entities are not serialized directly as public API contracts.
- Errors use stable codes.
- Important and regulated actions are audited.
- Sensitive healthcare reads are audited when policy requires.
- Signed clinical records use sign/amend/version workflows.
- Prescribing authority is jurisdiction and scope-of-practice policy, not a hard-coded profession.
- Production migrations follow expand/contract.
- Destructive schema changes are not assumed to be trivially reversible.
- Secrets are managed outside source control.
- Rate limits are configurable policies calibrated by security/load evidence.
- CI validates types, tests, OpenAPI, migrations, and security scans.
- Property-based tests are used for high-value state machines.
- Outbox/job/webhook reliability is tested under failure and concurrency.
- Internal tenant-isolation invariant failures and external cross-tenant attempts are separate observability signals.
- Critical-path tests matter more than a vanity coverage percentage.
- Engineering is the first implemented product vertical.
- Legal follows after the engineering product validates shared assumptions.
- Healthcare requires explicit security/privacy/jurisdiction readiness work before implementation.
- Architecture documentation never equates "designed for" with "certified/compliant".
- Milestones define implementation order; calendar estimates require actual delivery context.
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_API_CONVENTIONS.md
08_ENGINEERING_API_SPEC.md
09_FRONTEND_ARCHITECTURE.md
10_DOCUMENT_SECURITY_MODEL.md
11_WEBHOOK_INTEGRATION_MODEL.md
12_ASYNC_JOB_MODEL.md
13_SECURITY_MODEL.md
14_DEPLOYMENT_ARCHITECTURE.md
15_OBSERVABILITY_MODEL.md
16_TESTING_STRATEGY.md
17_MVP_BACKLOG.md
18_OPENAPI.yaml
Supporting state-machine documents should exist for:
engineering projects
engineering designs
engineering inspections
invoices/payments
clinical records when healthcare begins
96. Recommended Implementation Order
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
- every index corresponds to a known query, ordering, or constraint
- column order follows real predicates
- validate with
EXPLAIN (ANALYZE, BUFFERS) - include production-like cardinality in testing
- measure write amplification
- do not index every field
- 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. 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
├── Shared Financial Core
├── Shared Audit / Outbox
├── Shared Jobs / Webhooks / Notifications
│
├── Engineering Product
│ ├── Engineering Frontend
│ ├── Engineering REST APIs
│ ├── Engineering State Machines
│ └── Engineering Tables
│
├── 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 independent domain semantics where professional workflows differ.
v3 is the implementation baseline for the Engineering MVP.
It is not, by itself, evidence of:
production certification
regulatory compliance
security certification
performance at a specific scale
Those claims require implementation evidence, security testing, operational controls, measured load results, restore tests, and profession/jurisdiction-specific review.
v3 Changelog
Compared with v2, v3 adds or changes:
✓ corrected refresh-token family persistence
✓ PostgreSQL 18 UUIDv7 option
✓ engineering client contacts
✓ engineering design assignments
✓ cancelled vs withdrawn design semantics
✓ inspection outcome separate from lifecycle
✓ linked follow-up inspection workflow
✓ project budget domain model
✓ version-level document checksums
✓ document classification and categories
✓ upload policy and content validation
✓ webhook configuration and delivery model
✓ encrypted/recoverable webhook signing material
✓ async job architecture
✓ import/export as POST + 202 job creation
✓ at-least-once outbox semantics made explicit
✓ idempotent outbox consumers required
✓ property-based state-machine testing
✓ chaos tests for outbox/job processing
✓ separate tenant-attack and invariant-failure metrics
✓ milestone-based delivery planning
✓ critical-path production readiness gates
✓ no arbitrary code-coverage production target