Architecture
One Next.js service, layered domain code, metadata and vocabulary as data.
Runtime shape
Browser
│ /docs → static HTML (public/docs)
│ UI → Server Components + server actions
▼
Next.js 15 (App Router) — ECS Fargate "web", port 3000
├── app/ routes, actions
├── server/services/ business rules
├── server/repositories/ Prisma only
├── server/ai/ providers, extraction, scoring
├── server/integrations/ MaRLay client (mock today)
└── domain/ Zod + types
▼
PostgreSQL (schema assaylink)
/health— 200 without touching the DB (ALB health check)./api/readiness— DB-touching readiness for humans/dashboards.
Layering rules
| Layer | May import | Must not |
|---|---|---|
app/**, components |
services, domain | Prisma, repositories |
server/services/** |
repositories, domain, ai | React / Next request APIs beyond auth helpers |
server/repositories/** |
db.ts, domain |
services (no cycles) |
server/ai/** |
domain, repositories (read) | services |
domain/** |
zod only | everything else |
Domain data model (summary)
User— demo users with roles.Vocabulary/VocabularyTerm— governed terminology.MetadataSection— steward-named groups of fields (authoring pages by these).MetadataFieldDefinition— the assay metadata model as rows.MetadataRule— conditional show / require / restrict-term logic.AssayTemplate/AssayTemplateVersion— definitions + immutable snapshots.TemplateFieldValue— values for a version (term and/or text).TemplateReview— steward decisions and requested changes.SuggestedTerm— pending vocabulary candidates.ProjectAssayLink— project ↔ template version + context.ChangeLog— audit trail.
Full narrative: repo docs/domain-model.md and ADRs 0002, 0007–0010.
Schema: src/web/prisma/schema.prisma. AssayLink does not store
experimental execution data.
AI architecture
- Provider interface in
server/ai/provider.ts. - Implementations:
mock(default),claude,azure-openai. -
Extraction: mock/rules first, optional LLM merge
(
metadata-extractor.ts), parse viaparse-raw-extraction.ts. - Scoring / ranking stays deterministic over governed terms (
template-recommender.ts,scoring.ts). - Unrecognised values →
SuggestedTerm, never auto-activated. - After ranking, the matcher runs the same metadata-rule evaluator as authoring and attaches rule-required gaps plus a Metadata Quality Score estimate.
See docs/adr/0005-ai-provider-abstraction.md and docs/ai-recommendation.md.
Authentication & authorisation
POC: signed cookie + role switcher (ENABLE_ROLE_SWITCHER).
Pages: guardCapability. Mutations:
requireCapability. Capability map in
server/auth/roles.ts. Production path: ALB OIDC header →
map groups to roles in current-user.ts only
(docs/adr/0006-auth-strategy.md).
Deployment constraints
- Change infra only via
config.yml. Never editterraform/. -
Docker:
docker build -f src/web/Dockerfile ./src/web— build context is the service folder. -
DATABASE_URLmay be composed fromPOSTGRES_*parts at runtime (server/db-url.ts).
Architecture decision records
| ADR | Topic |
|---|---|
| 0001 | Tech stack (Next.js monolith under src/web) |
| 0002 | Domain model / metadata as data |
| 0003 | Template versioning (subtypes deferred) |
| 0004 | Vocabulary governance |
| 0005 | AI provider abstraction |
| 0006 | Auth strategy |
| 0007 | Metadata sections |
| 0008 | Conditional metadata rules |
| 0009 | Metadata Quality Score |
| 0010 | Definitions, not experimental results |
Source files: docs/adr/*.md in the repository root.