CONSTELLATION — System Architecture Specification
Document Version: 1.1 — Architecture Concept Date: 26 February 2026 (revised April 2026) Classification: Internal — For Technical Review Status: Approved — Open questions resolved March 2026. Implementation may proceed. Section 12 updated April 2026 (Stella merger). Companion to: Constellation General Specification v0.3
Table of Contents
- Purpose and Scope
- Architectural Drivers
- Module Rationalisation: From 13 to 7
- Monorepo Structure
- Shared Platform Packages
- Database Architecture
- Identity, Tenancy and Access Control
- Event-Driven Integration
- Deployment Strategy and Enterprise Readiness
- AI-Assisted Development Model
- Existing Project-Tracker: Migration Strategy
- Relationship to Stella Catalog
- Patterns Adopted from Open Mercato
- Execution Roadmap
- Risk Register
- Resolved Architecture Decisions
1. Purpose and Scope
This document defines the system architecture for the Constellation platform. It translates the functional specification (Constellation General Specification v0.3) into a concrete technical design that accounts for:
- A small team (1-2 humans + AI coding agents) building the entire platform
- The need for each module to deliver standalone value while sharing cross-cutting infrastructure
- A migration path from SaaS prototype to enterprise-grade sovereign deployment
- 100% AI-assisted development, which imposes specific constraints on code organisation, context management, and testing strategy
This is a concept document for review. No implementation should begin until the architecture has been discussed, challenged, and approved.
What This Document Decides
- Repository structure and build system
- Module boundaries and what gets merged or deferred
- Shared vs. module-specific code boundaries
- Database isolation strategy
- Identity and access control architecture
- Inter-module communication pattern
- Build order and execution sequence
- AI agent development rules
What This Document Does NOT Decide
- Detailed data models for individual modules (each module will have its own design phase)
- UI/UX design system details (beyond establishing that
packages/uiexists) - Specific API endpoint designs
- Pricing, licensing, or commercialisation strategy
2. Architectural Drivers
These constraints, derived from the specification and practical reality, drive every architectural decision in this document.
D1: Modular Independence with Shared Infrastructure
"Organisations will be able to adopt Constellation module by module." — Spec Section 2
Each module must be deployable and usable independently. However, duplicating authentication, tenancy, event handling, and UI components across 7 modules is unsustainable for a small team. The architecture must provide shared infrastructure without creating tight coupling.
D2: Multi-Tenancy as a First-Class Concept
"Multi-tenancy... is a first-class architectural concept, not an afterthought." — Spec Section 5
Every data row, every API request, and every UI view operates within a tenant context. The database enforces isolation via Row-Level Security. The application layer cannot bypass it.
D3: Enterprise-Grade Security (Progressive)
"Security is not a layer applied on top — it is embedded in every component." — Spec Section 5
The specification requires hybrid RBAC/ABAC, data classification enforcement, per-tenant encryption keys, HSM support, FIPS 140-2 compliance, and air-gapped deployment support. These cannot all be built on day one, but the architecture must not make them impossible.
D4: Deployment Flexibility
"The platform's architecture must support [on-premises] deployment without modification to the application logic." — Spec Section 7
The current prototype uses Supabase (hosted auth + DB), Vercel (hosting), and S3-compatible storage. The production platform must support self-hosted PostgreSQL, Keycloak (or equivalent), MinIO, and containerised deployment. Every infrastructure dependency must be behind a provider abstraction.
D5: AI-Optimised Development
The entire codebase will be developed and maintained by AI coding agents supervised by 1-2 humans. This imposes specific constraints:
- Context window limits: Each module must fit within an AI agent's context window (~150 files)
- Isolation: An AI agent working on one module must not be able to accidentally break another
- Discoverability: Conventions, schemas, and contracts must be machine-readable
- Reproducibility: Module scaffolding must be templated so new modules start consistently
D6: Small Team Reality
With 1-2 humans and AI agents, the architecture must minimise operational overhead:
- Single repository (not 7+ repos to manage)
- Single database instance (not 7 databases to operate)
- Shared CI/CD pipeline (not per-module deployment infrastructure)
- Minimal runtime dependencies (not a Kubernetes cluster with 30 services)
3. Module Rationalisation: From 13 to 7
The specification defines 13 modules (8 core + 5 extension). Analysis by domain boundaries, team capacity, and dependency structure leads to a rationalisation to 7 buildable modules.
Modules Retained as Independent Apps
| # | Module | Spec Section | Rationale |
|---|---|---|---|
| 1 | Directory | 4.1 | Identity foundation — every module depends on it |
| 2 | Catalog | 4.2 + 4.11 | Product/service listings + inventory availability (absorbed) |
| 3 | Procurement | 4.3 | Core value proposition — RFQ, CPQ, scoring, orders |
| 4 | Project Coordination | 4.4 | EXISTS as project-tracker — programme/stage-gate/task management. Also owns quality, helpdesk, and audit-tracker functionality (see below). |
| 5 | Documentation | 4.5 | Secure document repository with classification |
| 6 | Supply Chain | 4.10 | Multi-tier visibility, dependency tracing, risk indicators |
| 7 | Compliance | 4.12 | Export control, sanctions screening, licence matching |
Modules Absorbed or Deferred
| Original Module | Spec Section | Disposition | Rationale |
|---|---|---|---|
| E-Learning | 4.7 | Deferred — COTS integration | Building an LMS (SCORM, xAPI, proctoring) is a product in itself. Integrate a commercial LMS and expose completion data via API. |
| Communication & Notifications | 4.8 | Absorbed — cross-cutting package | Notifications, in-app messaging, and email are infrastructure, not a module. Implemented in packages/platform/notifications. |
| Self-Service Portal | 4.9 | Absorbed — UI layer | The portal is a supplier-facing view of Directory + Catalog + Procurement. It is a frontend route group, not a separate module. |
| Inventory Availability | 4.11 | Absorbed into Catalog | Stock levels and capacity data are attributes of catalog entries, not a separate bounded context. |
| Quality (Helpdesk + Quality + Audit Tracker) | 4.6 + 4.13 | Absorbed into Project Coordination | Quality/helpdesk shares the same entity model as project issues — separate modules would mean duplicate UIs, notification pipelines, and search indexes for the same concept. Tickets, findings, corrective actions, and certifications are modelled as issue types within the Project Coordination module. |
Spec Section Cross-Reference Preserved
Every requirement from the original 13 modules is accounted for. No functionality is dropped — it is either assigned to one of the 7 modules, placed in a shared package, or explicitly deferred to COTS integration.
4. Monorepo Structure
The platform uses a Turborepo monorepo. All modules and shared packages live in a single repository, with strict boundaries enforced by ESLint and TypeScript project references.
4.1 Architectural Shape: Three Explicit Layers
Constellation adopts the same architectural discipline that proved strongest in the later Stella work: every module is designed around three explicit layers.
- Domain Core — deterministic business rules, repositories, policies, validation, and state transitions
- Tool Layer — high-level business capabilities that expose intent-oriented actions to UI flows, automation, and future AI assistants
- Automation Layer — workflows, jobs, event handlers, notifications, and bounded AI-assisted decisions that orchestrate the system without owning business truth
This is not a microservice split. It is an internal module design rule.
Rules:
- The Domain Core is the source of truth for module state.
- The Tool Layer exposes business capabilities such as
publish_catalog_entry,build_rfq_shortlist, orschedule_audit, not thin CRUD wrappers. - The Automation Layer may orchestrate multi-step flows, but it never bypasses domain rules or writes directly to persistence without going through the Domain Core.
- Any future agentic capability in Constellation must be introduced through the Tool Layer and Automation Layer, not by granting an LLM direct repository access.
4.2 Monorepo Topology
constellation/
├── apps/ # Target topology — entries marked ✓ exist today
│ ├── auth/ # Auth app — Login, registration, MFA, session management (Phase 3a, not yet built)
│ ├── directory/ # Module 1 — Identity, orgs, roles, tenancy ✓
│ ├── catalog/ # Module 2 — Products, services, taxonomy (Phase 2 skeleton, Phase 3 full)
│ ├── procurement/ # Module 3 — RFQ, CPQ, scoring, orders (Phase 3)
│ ├── project-tracker/ # Module 4 — Existing app, migrated into monorepo ✓
│ ├── documents/ # Module 5 — Document repo, classification (future)
│ ├── supply-chain/ # Module 6 — Multi-tier visibility (future)
│ └── compliance/ # Module 7 — Export control, sanctions (future)
│
├── packages/
│ ├── platform/ # Core shared infrastructure
│ │ ├── auth-core/ # Auth provider abstraction + permission primitives
│ │ ├── auth-next/ # Next.js auth adapter
│ │ ├── db/ # Prisma client factory, RLS, audit helpers
│ │ ├── events/ # Typed event bus + outbox
│ │ ├── jobs/ # Postgres-backed jobs, BullMQ adapter
│ │ ├── errors/ # Shared error hierarchy
│ │ └── testing/ # Fixtures, contract tests, integration harness
│ ├── ui/ # Design system: shadcn, Tailwind, shared components
│ ├── config/ # Shared tsconfig, ESLint, Vitest config
│ └── contracts/ # Shared Zod schemas, event contracts, classification types
│
├── tools/
│ └── create-module/ # Module scaffold generator (CLI tool)
│
├── docker/ # Docker Compose for local dev + self-hosted
│ ├── docker-compose.yml # PostgreSQL, (optional) Keycloak, MinIO
│ └── keycloak/ # Keycloak realm export for dev
│
├── docs/ # Architecture docs, ADRs, runbooks
│ └── adr/ # Architecture Decision Records
│
├── turbo.json # Turborepo pipeline config
├── package.json # Root workspace config
├── CLAUDE.md # Root AI agent context (conventions, rules)
└── .github/
└── workflows/ # Shared CI pipeline
4.3 Canonical Module Template
Each application follows a predictable internal shape so AI coding agents can discover the right place for changes without relying on framework magic.
apps/<module>/
├── src/
│ ├── app/ # Next.js routes, layouts, route handlers
│ ├── server/
│ │ ├── services/ # Domain Core business logic
│ │ ├── repositories/ # Data access only
│ │ ├── policies/ # Permission and classification rules
│ │ ├── tools/ # High-level business capabilities
│ │ ├── workflows/ # Automation Layer orchestration
│ │ └── events/ # Event publishers and handlers
│ ├── lib/ # Module-local utilities
│ └── components/ # Module-specific UI
├── prisma/
│ └── schema.prisma
├── tests/
│ ├── unit/
│ ├── integration/
│ └── property/
├── evals/ # Tool/workflow evaluations when automation is present
├── CLAUDE.md
└── README.md
4.4 Composition Model
Constellation avoids deep framework magic. Route handlers, server actions, jobs, and event handlers compose module behavior through explicit factory functions and shared platform helpers.
Rules:
- Next.js is the delivery framework, not the owner of business composition.
- Services, tools, and workflows are wired explicitly in module code.
- No hidden DI container is required for platform primitives.
- Any shared runtime bootstrap must remain plain TypeScript and framework-independent where possible.
4.5 Key Structural Rules
| Rule | Enforcement |
|---|---|
| Apps never import from other apps | ESLint boundaries plugin |
Apps may only import from packages/ | TypeScript paths + ESLint |
Each app has its own CLAUDE.md | Convention (checked in CI) |
| Each app has its own Prisma schema | Prisma multi-schema support |
| Shared packages are read-only for module AI agents | Process discipline + branch protection |
| Module stays under 150 files | CI check (warn at 120, fail at 200) |
| Repositories never contain business decisions | Code review + module template |
| Tools and workflows call services, not raw Prisma | Code review + module template |
4.6 Technology Stack Per Module
Each module (app) is a Next.js application with:
| Layer | Technology | Notes |
|---|---|---|
| Framework | Next.js 16+ (App Router) | Server components by default |
| Language | TypeScript 5 (strict) | Shared tsconfig from packages/tsconfig |
| Database | PostgreSQL via Prisma 6 | Module-specific schema, shared DB instance |
| UI | Tailwind CSS 4 + shadcn/ui | Shared design tokens from packages/ui |
| Auth | @constellation-platform/auth-core + auth-next | Provider abstraction (Supabase / Keycloak / Mock) |
| Validation | Zod 3.23+ (import { z } from 'zod') | Shared base schemas where applicable |
| Testing | Vitest + fast-check + integration tests | Playwright optional for UI-heavy modules |
5. Shared Platform Packages
Shared packages are the mechanism by which modules avoid code duplication while remaining independently deployable. Each package is versioned within the monorepo and consumed as a workspace dependency.
5.1 @constellation-platform/auth-core and @constellation-platform/auth-next
Purpose: Authentication and authorisation abstraction layer with a thin Next.js adapter.
Provider interface (extending the pattern already established in project-tracker):
AuthProvider
├── signIn(email, password?) → AuthResult
├── signOut() → void
├── getSession() → Session | null
├── signUp(email, password, metadata) → AuthResult
├── verifyMfa(code, challengeId) → AuthResult
├── enrollMfa(userId) → MfaEnrollment
├── resetPassword(email) → void
├── updatePassword(newPassword) → void
└── readonly name: string
Implementations:
MockAuthProvider— Local dev, no passwords, cookie-basedSupabaseAuthProvider— Current production (Supabase hosted auth)KeycloakAuthProvider— Self-hosted / enterprise on-premises deployments (future)
Access control primitives (new, built for Directory module):
checkPermission(user, action, resource, context) → booleangetTenantContext(request) → TenantContextrequireRole(role) → middlewarerequirePermission(permission) → middleware
Key design decision: The auth packages provide primitives. They do NOT define the permission model — that is the Directory module's responsibility. The platform packages provide the evaluation engine and request-context wiring; the Directory module provides the policies, roles, and permissions data.
5.2 @constellation-platform/db
Purpose: Database connection management, Prisma client factory, RLS enforcement, and base model types.
Responsibilities:
- Prisma client singleton with tenant-aware connection
- RLS helper:
SET LOCAL app.tenant_id = $1on every transaction (viawithTenantContext()) - Connection pooling configuration (PgBouncer for serverless, direct for self-hosted)
- Base model types (every table inherits
id,tenantId,createdAt,updatedAt) - Shared audit capture helpers
- Migration coordination across module schemas
What it does NOT do:
- Define module-specific models (each module owns its own Prisma schema)
- Enforce business rules (that's the module's job)
5.3 @constellation-platform/events
Purpose: Typed, append-only event bus for inter-module communication.
Canonical implementation strategy:
- PostgreSQL outbox +
LISTEN/NOTIFYis the default and canonical runtime model - durable subscriber state lives in PostgreSQL
- BullMQ or another broker-backed path is allowed only for a queue that proves PostgreSQL throughput is insufficient
This is a deliberate simplification for a small team and an AI-assisted codebase. The architecture does not treat NATS, RabbitMQ, or Kafka as part of the baseline plan.
Event contract ownership:
Event definitions live in the module that publishes them (e.g., apps/directory/src/server/events/directory.events.ts). When another module needs to consume these events, the Zod payload schemas are exported to packages/contracts/ so consumers can validate payloads without importing from the publishing module.
# Each module owns its event definitions:
apps/directory/src/server/events/directory.events.ts # User.created, Org.verified
apps/project-tracker/src/server/events/projects.events.ts # Task.completed, Gate.reviewed
# Cross-module schemas (populated when first consumer is built):
packages/contracts/
├── directory.schemas.ts # Zod schemas for directory events consumed by other modules
├── projects.schemas.ts # Zod schemas for projects events consumed by other modules
└── ...
Note: Earlier drafts placed event contracts in packages/platform/events/contracts/. That location is superseded — packages/platform/events/ provides infrastructure only (outbox, dispatcher, subscriber), not domain-specific schemas.
Rules:
- Event contracts are append-only — existing events are never modified
- New fields may be added as optional — breaking changes require a new event version
- Each event carries:
id,eventType,tenantId,actorId,correlationId,payload, andcreatedAt(transport metadata materialised by the outbox ascreated_at, not a field modules must supply). Modules publisheventType,payload, andmeta: { tenantId, actorId, correlationId }— the platform addsidandcreatedAtautomatically - Consumers must be idempotent (events may be delivered more than once)
5.4 @constellation-platform/jobs
Purpose: Shared background execution abstraction for jobs and workflow wake-ups.
Canonical implementation strategy:
- PostgreSQL-backed job queue by default
- BullMQ adapter only for proven high-throughput cases
- in-memory test adapter for unit tests
Rules:
- Job payloads must carry
tenantId,actorId?, andcorrelationId - job handlers are idempotent
- retries and dead-letter behavior are platform-owned, not reimplemented per module
5.5 @constellation-platform/errors
Purpose: Shared error hierarchy and standard API error envelope.
Rules:
- modules throw typed application errors
- API routes serialize errors through one shared formatter
- no module invents its own error envelope
5.6 @constellation-platform/testing
Purpose: Shared fixtures, auth token builders, contract tests, and integration harnesses.
Contents:
- tenant-aware test fixtures
- auth token builders
- real-Postgres integration helpers
- contract test helpers for platform packages and event schemas
- scaffolding support for property tests and workflow/tool evals
5.7 packages/contracts, packages/config, and packages/ui
Purpose: Shared contracts, configuration, and UI primitives used by all modules.
Contracts
- Zod schemas
- event payload definitions
- classification label types
- shared API envelopes
Config
- shared tsconfig
- ESLint rules
- Vitest + fast-check presets
UI
- Tailwind CSS 4 configuration and design tokens
- shadcn/ui primitives
- Constellation-specific components
- layout shells and form primitives with Zod integration
6. Database Architecture
Single Instance, Schema Separation
All modules share a single PostgreSQL instance. Each module owns a dedicated schema for its domain data. Cross-cutting concerns (tenancy, audit, events) live in platform schemas.
PostgreSQL Instance
├── public # Shared types, extensions (uuid-ossp, pgcrypto)
├── tenancy # Tenants, tenant_config, data_sharing_agreements
├── identity # Users, organisations, roles, permissions, credentials
├── catalog # Products, services, taxonomy, specifications
├── procurement # RFQs, offers, evaluations, orders
├── projects # Programmes, projects, stages, gates, tasks, tickets, findings, audits, certifications
├── documents # Documents, versions, access_records
├── supply_chain # Nodes, edges, risk_scores, forecasts
├── compliance # Licences, screening_results, rules
├── audit # Append-only audit entries (cross-cutting)
├── events # Event log + outbox (cross-cutting)
└── notifications # Notification queue + preferences (cross-cutting)
Row-Level Security Strategy
Every data table includes a tenant_id column (NOT NULL, with a foreign key to identity.tenants).
Note: Earlier drafts of this document referenced tenancy.tenants. That schema name is superseded — the tenant registry lives in the identity schema, managed by the Directory module. All implementations use identity.tenants.
RLS policy pattern:
-- Applied to every tenant-scoped table
ALTER TABLE catalog.products ENABLE ROW LEVEL SECURITY;
CREATE POLICY tenant_isolation ON catalog.products
USING (tenant_id::text = current_setting('app.tenant_id', true));
The @constellation-platform/db package sets app.tenant_id via SET LOCAL app.tenant_id = $1 at the start of every database transaction, derived from the authenticated request's tenant context.
Note: Earlier drafts of this document used app.current_tenant — that name is superseded. All implementations use app.tenant_id.
Cross-Module Data Access
Modules do NOT directly query each other's schemas. Instead:
- Read path: Modules call each other's APIs (HTTP) to read data they don't own. Example: Procurement reads product data from Catalog's API.
- Event path: Modules react to events from other modules. Example: Procurement subscribes to
Project.milestoneReachedevents for payment triggers. - Shared identity: All modules read from the
identityschema for user/org/role resolution (mediated by@constellation-platform/auth-core).
Exception: The identity schema is readable (but not writable) by all modules, because user/org/role lookup is required on every authenticated request. This is the only cross-schema read permitted.
Migration Strategy
Each module manages its own Prisma migrations independently. The root turbo.json orchestrates migration order:
packages/platform/dbmigrations (tenancy, audit, events schemas)apps/directorymigrations (identity schema)- All other module migrations (parallel, independent)
7. Identity, Tenancy and Access Control
This is the most critical architectural component. The specification (Section 6) demands a hybrid RBAC/ABAC model with configurable data classification, regional access controls, and programme-scoped roles. The existing project-tracker has a simple MANUFACTURER | CUSTOMER role string. The gap is large.
Tenancy Model
Tenant
├── id: UUID
├── name: string
├── slug: string (unique)
├── type: BUYER | SUPPLIER | PROGRAMME_OFFICE | PLATFORM_OPERATOR
├── parentTenantId: UUID? (for hierarchical tenants, e.g., division of a prime)
├── settings: JSON (classification scheme, allowed features, etc.)
└── status: ACTIVE | SUSPENDED | DECOMMISSIONED
Every request carries a tenant context. Users may belong to multiple tenants (e.g., a consultant working for two suppliers). Tenant switching is explicit (UI tenant selector), never implicit.
User and Organisation Model (Directory Module)
Organisation
├── id, tenantId, name, legalName, registrationNumber
├── type: BUYER | PRIME_CONTRACTOR | SUB_TIER_SUPPLIER | SME | PROGRAMME_OFFICE
├── parentOrganisationId (for modelling divisions/subsidiaries)
├── certifications[], securityClearances[], competenceDomains[]
└── status, verifiedAt, verifiedBy
User
├── id, tenantId, email, name, avatarUrl
├── organisationId (primary)
├── dataClassificationLevel: UNCLASSIFIED | RESTRICTED | CONFIDENTIAL | SECRET (tenant-configurable)
├── regionalCaveats: string[] (e.g., ["DEU", "FRA"] for region-restricted access)
├── status: ACTIVE | SUSPENDED | DEACTIVATED
└── federatedIdentityLinks[] (SAML, OIDC)
Role (scoped to tenant + optional programme/project)
├── id, tenantId, name, description
├── scopeType: GLOBAL | PROGRAMME | PROJECT | MODULE
├── scopeId: UUID? (programmeId or projectId, null for GLOBAL)
└── permissions[]
Permission
├── resource: string (e.g., "procurement.rfq", "documents.classified")
├── action: string (e.g., "create", "read", "update", "delete", "approve")
└── conditions: JSON? (ABAC conditions, e.g., {"clearance": ">=SECRET"})
Access Control Evaluation
canAccess(user, action, resource, context) → boolean
Where context includes:
- tenantId
- programmeId? (scoping)
- projectId? (scoping)
- resourceClassification? (for classification enforcement)
- resourceOwnerId? (for ownership checks)
Evaluation order:
- Tenant isolation — user must belong to the tenant (or have cross-tenant access grant)
- Role check (RBAC) — user must have a role that grants the action on the resource
- Attribute check (ABAC) — user's data classification level, regional caveats, and contextual attributes must satisfy the permission's conditions
- Classification enforcement — if the resource has a classification label, user must have sufficient access level
What the Project-Tracker Currently Has vs. What's Needed
| Aspect | Project-Tracker (current) | Directory Module (target) |
|---|---|---|
| User.role | Single string: MANUFACTURER | CUSTOMER | Role objects scoped to tenant/programme/project |
| Permissions | Hardcoded if (user.role !== "MANUFACTURER") | Dynamic permission evaluation via @constellation-platform/auth-core |
| Tenancy | No tenant concept | tenant_id on every row, RLS enforced |
| Organisations | Basic Organisation model, optional | Required, with hierarchy and qualification tracking |
| Classifications | None | Per-user data classification levels and regional caveats |
| Identity federation | None (Supabase or mock only) | SAML 2.0, OIDC via Keycloak or equivalent |
Audit Trail Architecture
Enterprise procurement requires tamper-evident audit trails. Audit capture is part of @constellation-platform/db and available to all modules from Phase 1.
Audit Schema (audit schema, managed by platform)
audit_entries
├── id: UUID
├── tenant_id: UUID
├── actor_id: UUID (user who performed the action)
├── actor_type: USER | SYSTEM | AGENT (distinguishes human, automated, and AI actions)
├── action: string (e.g., "create", "update", "delete", "approve", "classify")
├── resource_type: string (e.g., "directory.organisation", "procurement.rfq")
├── resource_id: UUID
├── module: string (source module name)
├── changes: JSON (before/after diff for updates, null for creates/deletes)
├── classification: UNCLASSIFIED | RESTRICTED | CONFIDENTIAL | SECRET (label of the affected resource)
├── ip_address: string? (request origin, null for system/agent actions)
├── correlation_id: string (traces the request chain)
├── created_at: timestamptz
└── CONSTRAINT: No UPDATE or DELETE operations permitted on this table
Audit Rules
- Immutability: The
audit_entriestable is append-only. No UPDATE or DELETE permissions are granted to any application role. Database triggers reject mutation attempts. - Automatic capture: All state-changing operations (create, update, delete, approve, status transitions) are audited via a shared
auditAction()utility in@constellation-platform/db. Modules must not implement their own audit logic. - Retention: Audit entries are retained indefinitely in the SaaS tier. Dedicated Cloud and On-Prem tiers allow configurable retention policies per tenant. Entries are never deleted — they are archived to cold storage after the retention window.
- Tamper evidence (Dedicated Cloud / On-Prem only): Audit entries include a chained hash (
previous_hash + SHA-256(entry)) enabling integrity verification. The hash chain is validated nightly by a scheduled job. Breaks in the chain trigger an alert. - Classification inheritance: Audit entries inherit the classification of the resource they describe. An audit entry for a SECRET document is itself SECRET.
- RLS enforcement: Audit entries are tenant-scoped. Users can only query audit entries for their own tenant. Platform operators can query cross-tenant for platform-level investigations.
Querying
Modules expose an audit endpoint via a shared API route pattern:
GET /api/audit?resource_type=procurement.rfq&resource_id=<uuid>&limit=50
The @constellation-platform/db package provides queryAuditTrail({ tenantId, resourceType?, resourceId?, actorId?, dateRange?, limit }) for programmatic access.
8. Event-Driven Integration
Integration Map (7 Modules)
Derived from Spec Section 8, rationalised to 7 modules:
| Source Module | Event | Consumer Module(s) | Integration |
|---|---|---|---|
| Directory | User.created, Org.verified, Role.assigned | All modules | Identity context for all modules |
| Directory | Credential.expired, Qualification.updated | Procurement, Compliance | Supplier eligibility changes |
| Catalog | Product.published, Product.updated | Procurement | Available products for RFQs |
| Catalog | Product.deprecated | Supply Chain, Projects | Dependency impact notification |
| Procurement | RFQ.published, Offer.submitted | Directory (supplier activity scores) | Activity tracking |
| Procurement | Order.placed | Projects, Documents, Compliance | Triggers project creation, contract storage, compliance check |
| Projects | Task.completed, Stage.gateReviewed | Documents | Deliverable submission |
| Projects | Milestone.reached | Procurement (milestone payments) | Payment triggers |
| Projects | Ticket.created, Finding.resolved | Directory (supplier quality scores) | Supplier profile enrichment |
| Projects | Audit.scheduled, CertExpiring | Directory, Documents | Compliance tracking |
| Documents | Document.uploaded, Document.classified | Audit, Compliance | Audit trail, classification enforcement |
| Supply Chain | RiskScore.changed, Dependency.broken | Projects, Procurement | Risk-aware project management |
| Compliance | ScreeningResult.flagged, Licence.expired | Procurement, Projects | Blocking action on non-compliant items |
Event Schema Standard
Every event follows this envelope:
interface ConstellationEvent<T> {
eventId: string; // UUID, globally unique
eventType: string; // e.g., "directory.user.created"
version: number; // Schema version (starts at 1)
tenantId: string; // UUID
actorId: string; // UUID of user who triggered the event
timestamp: string; // ISO 8601
correlationId?: string; // For tracing chains of events
payload: T; // Event-specific typed payload
}
9. Deployment Strategy and Enterprise Readiness
Three Deployment Tiers
The specification (Section 7) requires three deployment models. Rather than trying to build all three on day one, the architecture supports a progressive migration path.
Tier 1: SaaS Prototype (Current + Near-Term)
| Component | Provider | Purpose |
|---|---|---|
| Auth | Supabase Auth | Email/password, MFA |
| Database | Supabase PostgreSQL (EU) | Managed, pooled via PgBouncer |
| File Storage | Supabase Storage | S3-compatible |
| Hosting | Vercel | Serverless Next.js, EU region |
| Resend | Transactional emails |
Use case: Business plan validation, demo instances, low-classification SaaS customers.
Tier 2: Dedicated Cloud (Medium-Term)
| Component | Provider | Purpose |
|---|---|---|
| Auth | Keycloak on VM/container | Federation, SAML, OIDC |
| Database | Self-managed PostgreSQL | Dedicated instance, full RLS |
| File Storage | MinIO or cloud S3 | Dedicated buckets |
| Hosting | Docker Compose / K8s | Containerised Next.js |
| SMTP relay | Organisation-controlled |
Use case: Dedicated customer instances, EU sovereign cloud (OVHcloud, T-Systems).
Tier 3: On-Premises / Air-Gapped (Long-Term)
| Component | Provider | Purpose |
|---|---|---|
| Auth | Keycloak with HSM integration | FIPS 140-2, CAC/PIV smart cards |
| Database | PostgreSQL on bare metal | Customer-managed encryption keys |
| File Storage | MinIO on bare metal | Encrypted at rest, customer-managed keys |
| Hosting | K8s / Docker on-prem | Air-gapped, no external network |
| Internal SMTP / disabled | May not be available in air-gapped |
Use case: Regulated enterprise deployments (defence, government, critical infrastructure), classified environments.
Provider Abstraction Pattern
Every infrastructure dependency is accessed through an interface, never directly. The existing project-tracker's AUTH_PROVIDER pattern is the model:
// Configured via environment variable
const authProvider = createAuthProvider(process.env.AUTH_PROVIDER); // 'supabase' | 'keycloak' | 'mock'
const storageProvider = createStorageProvider(process.env.STORAGE_PROVIDER); // 'supabase' | 'minio' | 'local'
const emailProvider = createEmailProvider(process.env.EMAIL_PROVIDER); // 'resend' | 'smtp' | 'mock'
What Must Be True for Enterprise Readiness
These are not Phase 1 requirements, but the architecture must not make them impossible:
| Requirement | Spec Section | Architecture Implication |
|---|---|---|
| RLS tenant isolation | 6 | tenant_id on every table from day one |
| Hybrid RBAC/ABAC | 6 | Permission evaluation engine in @constellation-platform/auth-core |
| Classification enforcement | 6 | Classification labels in shared contracts + module policies |
| Per-tenant encryption keys | 6 | KMS abstraction implemented behind platform packages |
| HSM support | 6 | Key management interface, not implementation |
| FIPS 140-2 | 6 | Use of standard crypto primitives, no custom cryptography |
| Air-gapped deployment | 7 | No build-time or runtime calls to external services |
| Identity federation | 4.1 | SAML 2.0 / OIDC via Keycloak provider |
| Append-only audit | 5 | Dedicated audit schema, no UPDATE/DELETE |
| EU data sovereignty | 2 | Deploy on EU sovereign cloud providers |
10. AI-Assisted Development Model
This section governs both AI coding-agent ergonomics and future AI-assisted runtime features. Constellation is not a free-form agent runtime, but it is designed so bounded automation and later agentic workflows can be added safely.
Agent Architecture
The codebase is designed for development by AI coding agents (Claude, Copilot, or similar), supervised by human reviewers.
Agent Types
| Agent Role | Scope | Allowed to Modify |
|---|---|---|
| Platform Agent | packages/* | Shared packages only |
| Module Agent (per module) | apps/<module>/* | One module only |
| CI/Infra Agent | .github/, docker/, root config | Infrastructure only |
Rules for AI Agent Sessions
- One agent per module — An agent session NEVER modifies two apps simultaneously
- Shared packages are read-only for module agents — Only the Platform Agent modifies
packages/ - Each app has its own CLAUDE.md — Contains domain context, data model, integration points, and conventions
- Module stays under 150 files — If a module exceeds this, it is a signal to refactor or split
- Event contracts are append-only — No agent may modify an existing event schema
- All PRs require human review — AI agents create PRs, humans approve them
- Tests are mandatory — No PR merges without passing unit + integration tests
- Tools before CRUD — agents implement business-capability tools and workflows before adding thin endpoint wrappers
- Automation cannot bypass the Domain Core — jobs, handlers, and AI-assisted flows call services/tools, never repositories directly
Context Document Standard
Every app's CLAUDE.md must contain:
# CLAUDE.md — [Module Name]
## Purpose
One-paragraph module description and its role in the platform.
## Tech Stack
Versions and key libraries.
## Commands
Dev, build, test, lint commands.
## Directory Structure
File tree with annotations.
## Data Model
Key entities and their relationships.
## API Routes
Endpoint inventory with auth requirements.
## Event Contracts
Events this module publishes and subscribes to.
## Tools
Business-capability tools exposed by this module.
## Workflows
Automation flows, jobs, approvals, and resumable processes.
## Evals
How tools/workflows are verified when automation or AI-assisted decisions are present.
## Integration Points
Which modules this one depends on and how.
## Conventions
Module-specific patterns and gotchas.
Module Scaffold Generator
tools/create-module provides a CLI that generates a new module with:
- Next.js app boilerplate with App Router
- Prisma schema with tenant_id base model
- CLAUDE.md template
- Auth middleware using
@constellation-platform/auth-coreandauth-next - Tool and workflow boilerplate
- Event publisher/subscriber boilerplate
- Test setup (Vitest + fast-check + integration harness)
- Eval scaffold when the module includes automation features
- CI integration
npx create-constellation-module --name catalog --port 3002
Code Quality Enforcement
| Check | Tool | Scope |
|---|---|---|
| Type safety | TypeScript strict mode | All packages and apps |
| Linting | ESLint with shared config | All packages and apps |
| Module boundaries | eslint-plugin-boundaries | Cross-app import prevention |
| Test coverage | Vitest + fast-check + integration tests | Per-module, min 80% for shared packages |
| File count | Custom CI check | Warn at 120, fail at 200 per module |
| Event contract validation | Zod schema check | All event contracts |
| CLAUDE.md presence | CI check | Every app must have one |
| Tool/eval presence | CI check for automation modules | Tools and workflows require matching evals |
11. Existing Project-Tracker: Migration Strategy
The project-tracker (apps/project-tracker/) is a functioning Next.js application with:
- 25 Prisma models (including Organisation, User, Role, RolePermission, UserRole, ProjectCollaborator, etc.)
- Dual auth provider (Supabase + Mock) with MFA support
- 48+ API routes
- Programme/stage-gate/task management
- Phases 1-2 complete, Phases 3-5 planned
Migration Approach: Strangler Fig Pattern
The project-tracker is NOT refactored upfront. Instead:
-
Step 1 (Week 1): Move project-tracker into
apps/project-tracker/within the monorepo. It continues to use its own Prisma schema, its own auth, and its own database. No functional changes. -
Step 2 (After Directory module is built): The project-tracker begins consuming
@constellation-platform/auth-coreand@constellation-platform/auth-nextfor authentication, replacing its localsrc/lib/auth-providers/. The Directory module becomes the source of truth for users, orgs, and roles. -
Step 3 (After shared packages are proven across 2-3 modules): The project-tracker migrates its Prisma schema to use
tenant_id, moves to theprojectsdatabase schema, and adopts RLS. ItsOrganisation,User,Role,RolePermission,UserRolemodels are replaced by references to theidentityschema. -
Step 4 (Ongoing): Project-tracker Phases 3-5 (polish, advanced features, production hardening) are built using the shared platform packages.
Risk Mitigation
- The project-tracker remains fully functional at every step
- Each migration step is a separate PR with rollback capability
- The live deployment at
constellation-project-tracker.vercel.appis unaffected until each step is validated
12. Relationship to Stella Catalog
Context
The team also develops Stella Catalog, an AI-first, open-source PIM system (NestJS, PostgreSQL + pgvector, originally designed for Open Mercato). The original architecture (v1.0) treated Stella as a separate product with a shared technology platform. After further analysis, the decision has been revised.
Decision: Stella Is Merged into the Constellation Catalog Module (Option 3)
Revised April 2026. Stella Catalog's full feature set is absorbed into Constellation's Catalog module (apps/catalog/). The Catalog module is rebuilt as a Next.js application that incorporates Stella's core capabilities natively, rather than maintaining two separate products.
What the Catalog Module Absorbs from Stella
| Stella Capability | Constellation Catalog Implementation |
|---|---|
| Semantic search (pgvector) | Native vector search with embedding dirty-checking, integrated with RLS for tenant-scoped results |
| CPQ (Configure-Price-Quote) | Product configuration and pricing engine, feeds directly into Procurement module |
| Supplier matching | AI-powered supplier-product matching, scoring, and recommendation within tenant context |
| Channel syndication | Marketplace and channel export (Amazon, Shopify, etc.) as optional tenant-level feature |
| AI-powered enrichment | Automated product data enrichment, classification suggestion, and attribute completion |
| CanonicalProduct data model | Adapted into Constellation's product model with tenant isolation and configurable taxonomy support |
| SupplierOffer model | Merged into Catalog's supplier-product relationship, linked to Directory supplier profiles |
Industry-Specific Features as Tenant Configuration
Features that were previously considered domain-specific are now configurable per tenant rather than hardcoded:
| Feature | Implementation |
|---|---|
| Product taxonomy | Configurable industry taxonomy (LTREE). Options include NATO Supply Classification, CPV, UNSPSC, GPC, or custom tenant-defined taxonomies |
| Data classification labels | Optional per-tenant feature. Tenants in regulated industries (defence, government, healthcare) enable classification enforcement; commercial tenants may disable it |
| Access-filtered search | Vector search + RLS. When classification is enabled, search results are filtered by user access level. When disabled, standard tenant-scoped RLS applies |
| Compliance metadata | Optional fields for export control, origin tracking, and regulatory attributes — activated per tenant based on industry requirements |
Migration Approach
The merger follows a phased approach:
- Phase 1: Constellation Catalog (
apps/catalog/) is built as a Next.js app using Constellation's shared platform packages. Core product CRUD, taxonomy (LTREE), and tenant-scoped search are implemented natively. - Phase 2: Stella's AI capabilities (semantic search, embedding dirty-checking, AI enrichment) are ported from NestJS to Next.js API routes, adapting Stella's PostgreSQL + pgvector patterns directly.
- Phase 3: Advanced features (CPQ, supplier matching, channel syndication) are added as optional tenant-level modules within the Catalog app. These features are enabled via tenant configuration, not separate deployments.
What Happens to the Stella Repository
- Stella's open-source repository remains available as a standalone NestJS PIM for the broader community.
- Constellation does not depend on or import from the Stella codebase at runtime.
- Stella's patterns, data models, and AI techniques inform the Constellation Catalog implementation, but all code is rewritten for the Next.js / Constellation platform stack.
- The
Common_Technology_Platform_v1.mddocument is superseded by this merger for Constellation's purposes. Shared technology patterns are now simply part of the Constellation platform packages.
13. Patterns Adopted from Open Mercato
Context
The team also develops Open Mercato, an AI-supportive CRM/ERP foundation framework (Next.js, MikroORM, PostgreSQL, Redis, 14 shared packages, 20+ modules). A formal analysis evaluated 15 Open Mercato patterns for applicability to Constellation's enterprise commerce context.
See: Open_Mercato_Pattern_Analysis_v1.md for the full evaluation.
Adopted Patterns (8 of 15)
| Pattern | Adaptation for Constellation |
|---|---|
| Event-Driven Decoupling | Typed events with as const, domain-verb naming, transactional outbox (PostgreSQL), audit-critical events are synchronous |
| Module Setup Lifecycle | setup.ts per module with onTenantCreated, seedDefaults, defaultRoleFeatures. seedExamples hard-gated from production |
| No Cross-Module ORM Relations | FK IDs only across modules. Typed service contracts in packages/contracts. Prisma schema lint in CI |
| Spec-Driven Development | .kiro/specs/ directory. Tasks with 3+ file changes require a spec before implementation |
| Field-Level Encryption | Tenant-scoped DEKs, Prisma $extends for transparent encrypt/decrypt. HSM in production, local in dev. No deterministic hashing in regulated environments |
| CLAUDE.md Task Routing | Root CLAUDE.md with task routing table. Per-module CLAUDE.md with domain context + import tables + known pitfalls |
| Module-Decoupling Test | CI check: each module builds independently. Import boundary lint. Prisma schema cross-reference check |
| Import Pattern Tables | Each module CLAUDE.md maps "need X → import from Y → NOT from Z" |
Adapted Patterns (2 of 15)
| Pattern | What Changed |
|---|---|
| CRUD Factory → Tool-Oriented Composition | Enterprise entities are too varied for a single factory. Use explicit services, tools, and workflows with composable policy/audit helpers instead |
| Dual Queue → Postgres-First Runtime | PostgreSQL outbox + jobs are canonical. BullMQ is optional for high-throughput. In-memory adapter for unit tests |
Skipped Patterns (5 of 15)
| Pattern | Why Skipped |
|---|---|
| Module Auto-Discovery | Next.js App Router already provides conventions. 7 modules can be listed explicitly. |
| Awilix DI Container | Runtime opacity hinders compliance auditing. React cache() + factory functions suffice. |
| Widget Injection | Classification boundaries could be violated. Use explicit imports + parallel routes. |
| Custom Entities / EAV | Regulated schemas are compliance-driven and rigid. Use metadata Json? for narrow cases only. |
| Overlay/Override System | Enterprise deployments need deterministic, auditable builds. Use feature flags instead. |
14. Execution Roadmap
Phase 0: Monorepo Setup (1 week)
| Task | Deliverable |
|---|---|
| Initialise Turborepo workspace | turbo.json, root package.json |
Move project-tracker into apps/ | Working project-tracker in monorepo |
Create packages/tsconfig and packages/eslint-config | Shared configs |
| Set up Docker Compose for local PostgreSQL | docker/docker-compose.yml |
Create root CLAUDE.md with task routing table + universal rules | AI agent governance (Open Mercato pattern) |
Create .kiro/specs/ directory with template | Spec-driven development (Open Mercato pattern) |
| Set up module-decoupling CI check | Isolation guardrail (Open Mercato pattern) |
| Set up GitHub Actions CI for monorepo | Lint, type-check, test across all apps |
Phase 1: Directory Module + Platform Packages (6-8 weeks) — API Only
| Task | Deliverable |
|---|---|
| Design Directory data model (Users, Orgs, Roles, Permissions, Tenants) | Prisma schema for identity + tenancy schemas |
Build @constellation-platform/auth-core and auth-next | Auth packages with permission evaluation engine |
Build @constellation-platform/db with RLS + audit helpers | DB package with tenant-aware Prisma client |
| Build Directory API (CRUD for users, orgs, roles, credentials) | REST API with auth middleware |
Build @constellation-platform/events with pg LISTEN/NOTIFY | Event bus with typed contracts |
Build @constellation-platform/jobs | Postgres-first job abstraction |
| Write Directory CLAUDE.md | AI agent context for the module |
Note: Directory UI is deferred to Phase 3a (UI Layer). Phase 1 delivers the complete backend API and platform packages.
packages/uiis built as part of Phase 3a when all core module backends are available.
Phase 2: Extract and Stabilise (2 weeks)
| Task | Deliverable |
|---|---|
| Extract patterns from Directory into packages | Stabilised shared packages |
Build tools/create-module scaffold generator | CLI tool for new modules |
| Document shared package APIs | Package-level documentation |
| Validate shared packages by building a minimal Catalog skeleton | Proof that packages work for a second module |
Phase 3: Catalog + Procurement (8-12 weeks)
| Task | Deliverable |
|---|---|
| Build Catalog module (products, taxonomy, search, shortlisting) | Full catalog application |
| Build Procurement module (RFQ, CPQ, scoring, orders) | Full procurement application |
| Integrate Catalog → Procurement flow (shortlists become RFQs) | End-to-end procurement workflow |
| Integrate Procurement → Projects (orders trigger project creation) | Event-driven integration |
Phase 3a: UI Layer (4-6 weeks)
Build the user-facing frontend for the core modules. This phase delivers the first end-to-end user experience and coincides with the Business Plan Phase 2 "Commercial Pilot Release" milestone.
| Task | Deliverable |
|---|---|
Build packages/ui — shared component library (shadcn, Tailwind, layouts) | Design system package consumed by all module apps |
Build apps/auth/ — login, registration, MFA, password reset, session mgmt | Standalone auth app wrapping Supabase (or Keycloak) |
| Build Directory UI — org profiles, user management, role admin, certs | Frontend in apps/directory/ consuming Directory API |
| Build Catalog UI — catalog entry creation, search, filter, shortlisting | Frontend in apps/catalog/ consuming Catalog API |
| Build Procurement UI — RFQ creation from shortlists, offer submission | Frontend in apps/procurement/ consuming Procurement API |
| Implement shared navigation component across module apps | Cross-module nav via URL links (no micro-frontend) |
Dependencies: Phase 1 (Directory API), Phase 3 (Catalog + Procurement APIs). This phase produces packages/ui/ as a deliverable.
Key decisions:
apps/auth/is the central entry point for all authentication flows. Other modules redirect toapps/auth/for login and rely on a shared auth cookie for session continuity.- Each module app owns its own UI routes. Shared components (nav, layouts, form primitives) live in
packages/ui/. - Supplier self-service (org profile, catalog management, RFQ response) is a frontend route group within the existing module apps, not a separate application.
Phase 4: Project-Tracker Migration (2-3 weeks)
| Task | Deliverable |
|---|---|
Replace project-tracker auth with @constellation-platform/auth-core and auth-next | Shared auth in project-tracker |
Add tenant_id to project-tracker schema | Tenant isolation |
| Complete project-tracker Phases 3-5 using shared packages | Production-ready project coordination |
Phase 5: Remaining Modules (8-13 weeks, parallel AI agents)
| Module | Estimated Duration | Dependencies |
|---|---|---|
| Documents | 4-5 weeks | Directory (auth), Security (classification) |
| Supply Chain | 3-4 weeks | Directory (auth), Catalog (product references) |
| Compliance | 3-4 weeks | Directory (auth), Procurement (screening integration) |
Phase 6: Enterprise Readiness (Ongoing, parallel)
| Task | Trigger |
|---|---|
| Keycloak auth provider implementation | First customer requiring SAML/OIDC federation |
| MinIO storage provider | First customer requiring on-prem file storage |
| Docker/K8s deployment manifests | First dedicated/on-prem deployment |
| HSM key management integration | First customer requiring FIPS 140-2 |
| Full RLS audit and penetration testing | Before any regulated or classified data enters platform |
Phase Crosswalk: Architecture vs Business Plan
The architecture roadmap and business plan use independent phase numbering. This table maps between them to avoid confusion.
| Architecture Phase | Business Plan Phase | Key Deliverables |
|---|---|---|
| Phase 0: Platform Bootstrap | Phase 1 (Foundation) — infrastructure | Monorepo, platform packages, scaffold generator |
| Phase 1: Directory Module | Phase 1 (Foundation) — first module | Identity, orgs, roles, tenancy backend API |
| Phase 2: Extract & Stabilise | Phase 1 (Foundation) — hardening | Contracts package, package docs, Catalog skeleton |
| Phase 3: Catalog + Procurement | Phase 2 (Commercial Pilot) — backends | Full Catalog and Procurement backend APIs |
| Phase 3a: UI Layer | Phase 2 (Commercial Pilot) — frontends | packages/ui, apps/auth/, module UIs, end-to-end UX |
| Phase 4: Project-Tracker Migration | Phase 2 (Commercial Pilot) — migration | Project-tracker on shared platform |
| Phase 5: Remaining Modules | Phase 3 (Operational Delivery) | Documents, Supply Chain, Compliance |
| Phase 6: Enterprise Readiness | Ongoing | Keycloak, MinIO, K8s, HSM, penetration testing |
Rule of thumb: Architecture phases are sequenced by technical dependency. Business plan phases are grouped by commercial milestone. When in doubt, the architecture spec governs build order; the business plan governs release scope.
15. Risk Register
R1: Premature Abstraction (HIGH)
Risk: Shared packages are designed based on assumptions from the project-tracker and the spec, but they don't fit the real needs of the Directory or subsequent modules. The abstractions are wrong and need repeated refactoring.
Mitigation: Build the Directory module FIRST. Extract shared packages FROM the Directory after it works. The packages are derived from reality, not speculation. Validate by building a second module (Catalog) before declaring packages stable.
R2: Schema Drift Across Modules (MEDIUM)
Risk: Different AI agent sessions build modules with inconsistent patterns: different error response formats, different naming conventions, different approaches to pagination and filtering.
Mitigation: The module scaffold generator (create-module) enforces structural consistency. The root CLAUDE.md defines binding conventions. ESLint rules enforce import boundaries. CI validates structural compliance.
R3: Enterprise Stack Incompatibility (HIGH)
Risk: The SaaS prototype (Supabase + Vercel) becomes so deeply embedded that migrating to self-hosted infrastructure requires a rewrite.
Mitigation: Provider abstractions from day one. No direct Supabase imports outside of SupabaseAuthProvider and SupabaseStorageProvider. No Vercel-specific features (like Vercel KV) in application code. Regular "self-hosted smoke tests" using Docker Compose.
R4: Scope Creep from AI Agents (MEDIUM)
Risk: AI agents, given a detailed spec section, over-engineer features. They build the full SCORM-compatible LMS instead of a stub that integrates with a COTS LMS. They implement a complete industry-standard document format parser when a simple classification label would suffice.
Mitigation: Each module's CLAUDE.md includes explicit scope boundaries: "Build these features. Do NOT build these features. These are deferred to Phase X." Human reviewers enforce scope during PR review.
R5: Single Database Bottleneck (LOW-MEDIUM)
Risk: A single PostgreSQL instance serving 7 modules becomes a performance bottleneck as data volume grows.
Mitigation: Schema separation means modules can be migrated to dedicated databases later with minimal application code changes (change the Prisma datasource URL). PostgreSQL's built-in partitioning, read replicas, and connection pooling provide significant headroom. This becomes a concern only at scale, not during initial development.
R6: Project-Tracker Migration Fails (MEDIUM)
Risk: The project-tracker's 25-model Prisma schema, 48 API routes, and hardcoded auth patterns resist clean migration to the shared platform. The migration takes longer than building a new module.
Mitigation: The Strangler Fig approach means migration is incremental and each step is reversible. In the worst case, the project-tracker remains as a "legacy" module with its own auth, and users access it through a different login flow. This is suboptimal but not catastrophic.
R7: Team Capacity vs. Module Count (HIGH)
Risk: Even with 7 modules instead of 13, the total scope (7 modules + shared packages + infrastructure + testing + deployment) exceeds what 1-2 humans + AI agents can deliver in a reasonable timeframe.
Mitigation: The phased roadmap prioritises the modules with the highest business value and the most dependencies (Directory, Catalog, Procurement). Modules 5-7 (Documents, Supply Chain, Compliance) are lower priority and can be deferred if necessary without blocking the core platform value.
16. Resolved Architecture Decisions (formerly Open Questions)
The following questions were resolved during the architecture review session (March 2026). Each decision is now locked and binding on implementation.
Q1: Monorepo Scope → Option B: Add modules as development begins
Decision: Only project-tracker and directory exist initially in apps/. New module directories are scaffolded from create-module when development begins.
Rationale: Empty scaffolds mislead about progress, consume AI agent context unnecessarily, and create merge conflicts on files nobody is editing. The create-module generator is a Phase 0 deliverable and must exist before Phase 1 begins.
Rule: A module directory MUST NOT be created until its design spec (.kiro/specs/<module>/design.md) is approved.
Q2: Database Architecture → Schema-per-module (single instance)
Decision: Schema-per-module within a single PostgreSQL instance. This was already recommended; now locked.
Rationale: 1-2 humans cannot operate 7 databases. Schema separation provides sufficient isolation. Migration to separate databases later requires only changing the Prisma datasource URL per module — the application code is unchanged.
Rule: Each module owns its schema. Cross-schema reads are forbidden in application code (exception: identity schema is shared read). Cross-module data access is via API or event-driven projections only.
Q3: Framework Strategy → Next.js for all Constellation modules
Decision: All Constellation modules use Next.js 16+ App Router. No mixed framework exceptions.
Rationale: Consistency eliminates framework-selection debates for each new module. The deployment model, auth middleware, and UI component library work identically everywhere. API-heavy modules (e.g., Compliance) use Next.js API routes — the overhead is negligible compared to the team productivity gain. If a module later proves that Next.js is a bottleneck, it can be extracted — but this is a future optimisation, not a starting assumption.
Rule: All apps/<module>/ directories use Next.js. No Fastify, Hono, or Express modules in the Constellation monorepo.
Q4: Keycloak Timing → Option B: Defer until customer requires it
Decision: Use Supabase Auth for all auth in Phase 0-3. Implement KeycloakAuthProvider only when a customer contract requires SAML 2.0 federation or on-premises identity management.
Rationale: Keycloak adds significant operational complexity (Java runtime, realm configuration, theme customisation). The provider abstraction ensures the swap is mechanical, not architectural. Building Keycloak support before a customer needs it is premature optimisation for a 1-2 person team.
Rule: No direct Supabase Auth imports outside of SupabaseAuthProvider. All auth goes through @constellation-platform/auth-core and @constellation-platform/auth-next. This makes the Keycloak swap a single provider implementation, not a codebase refactor.
Q5: UI Architecture → Independent apps with shared auth cookie + dedicated auth app
Decision: Each module is a fully independent Next.js application with its own URL prefix. Shared authentication via cookie/session. No micro-frontend shell. A dedicated apps/auth/ application handles all authentication flows (login, registration, MFA, password reset, session management).
Rationale: Independent apps are dramatically simpler for AI agents to reason about (one module = one context). A shared shell introduces runtime complexity (module federation, shared state, routing conflicts) that is disproportionate for a small team. Users navigate between modules via a shared navigation component (packages/ui) that renders links to sibling module URLs. Authentication is separated into its own app because every module depends on it, and it should not be coupled to any single module's deployment lifecycle.
Rules:
- No module may import from another module's
src/. Cross-module navigation is via URL links only. Shared UI components live inpackages/ui/. - All login/registration/MFA flows live in
apps/auth/. Module apps redirect toapps/auth/for unauthenticated users. apps/auth/consumes@constellation-platform/auth-coreand@constellation-platform/auth-next. It does NOT own user/org/role data — that remains in the Directory module.- Temporary exception (project-tracker migration):
apps/auth/does not exist yet (Phase 3a). Until it is built, modules that are migrated into the monorepo (starting with project-tracker) retain their own/api/auth/*routes for login, logout, register, and MFA. Whenapps/auth/is built, these module-local auth routes are removed and replaced with redirects toapps/auth/. This exception is tracked in the project-tracker migration spec.
Q6: Air-Gapped Development Toolchain → Deferred to Phase 6 (Enterprise Readiness)
Decision: The development toolchain remains cloud-connected (npm registry, GitHub Actions CI, cloud AI assistants) for Phase 0-5. Air-gapped toolchain requirements are addressed in Phase 6 when a customer contract requires it.
Rationale: Vendoring node_modules, self-hosting CI, and running local-only AI agents adds weeks of infrastructure work that delivers zero product value. The development team currently operates in unclassified environments. Air-gapped requirements apply to the deployment environment, not the development environment, until classified data enters the development workflow.
Rule: No air-gapped toolchain work before Phase 6. Document all external dependencies (npm packages, CI services, AI APIs) in a dependency manifest to prepare for eventual vendoring.
This document was reviewed, challenged, and revised in March 2026. All questions are resolved. Implementation may proceed following the phased roadmap in §14.