Architecture Decision Records
Architecture Decision Records (ADRs) document the significant architectural choices made across our projects. Each record captures the context, options considered, the decision, and its consequences.
Why We Write These
When you join a project or review a pull request, you often ask "why is it done this way?" These records answer that question. They capture not just what was decided, but why — including what we considered and rejected.
How to Read an ADR
Each ADR follows the same structure:
- Status —
Proposed(under discussion),Accepted(implemented or being implemented),Superseded(replaced by a newer decision) - Context — The problem or situation that prompted the decision
- Options Considered — Alternatives we evaluated, with reasons for rejection
- Decision — What we chose and how it works
- Consequences — The trade-offs we accepted, both positive and negative
Decision Index
Kendo
| # | Decision | Status | Summary |
|---|---|---|---|
| 1 | Audit Logging System | Accepted | Per-entity audit tables with hash chains for ISO 27001 compliance |
| 6 | Two-Tier Authorization | Accepted | Route-level model permissions + action-level interaction permissions |
| 3 | AI Interaction Logging | Accepted | Three-channel logging for outbound AI calls, MCP tools, and AI runs |
| 8 | Multi-Tenancy | Accepted | DIY database-per-tenant for 3 fixed companies |
| 13 | Adapter-Store Pattern | Accepted | Custom reactive store factory over Pinia for 15+ domain stores |
| 25 | GitHub Integration Split | Accepted | Split GithubService into OAuth handshake + bearer-token-parametrized API client; per-call token argument |
Brick Inventory
| # | Decision | Status | Summary |
|---|---|---|---|
| 4 | Import Atomicity | Accepted | Save-what-you-can with honest reporting for API imports |
Emmie
| # | Decision | Status | Summary |
|---|---|---|---|
| 22 | Schedule End Date Inclusive | Accepted | Schedule.end_date is the last active day; canonical predicates use inclusive <= / >= |
| 23 | Schedule Mutation Chokepoint | Accepted | Single Action owns Schedule date mutation; wrapping normalizer, schema unchanged |
| 31 | Datetime Instant-vs-Wall-Clock Classification | Accepted | Every datetime field declared instant or wall-clock; wall-clock serialized naive (self-describing wire); backend WallClock* casts + arch test, frontend Temporal helper (polyfilled) |
Town-crier
| # | Decision | Status | Summary |
|---|---|---|---|
| 36 | Cross-Harness Producer Ingress | Accepted | One bus-hosted GitHub App replaces 14 per-repo announce-pr.yml; HMAC-verified pull_request/pull_request_review ingress dispatching in-process to announce/resolve/consensus with the internally-minted github-action identity; first bus outbound HTTP (timeout-bound, installation_id-keyed token cache); pull-first /github/health observability for the silent per-org failure mode |
Cross-Project
| # | Decision | Status | Summary |
|---|---|---|---|
| 2 | Cascade Deletion & Soft Deletes | Accepted | Model declares, Action executes, tests verify. No ON DELETE CASCADE. |
| 9 | Unified ResourceData Pattern | Accepted (amended 2026-07-03) | Custom ResourceData base class replaces Laravel's JsonResource; loud-failure eager-load gate + Hydrator supply side |
| 11 | Action Class Architecture | Accepted | final readonly Actions with single execute() method, explicit DI |
| 12 | FormRequest → DTO Flow | Accepted | Type-safe pipeline from HTTP validation to business logic |
| 14 | Domain-Driven Frontend Structure | Accepted | Vertical slices by business domain, not technical layers |
| 16 | Config Attribute Injection | Accepted | #[Config] attribute for all config access, config() helper prohibited |
| 17 | Page Integration Tests | Accepted | Mount domain pages with real components, mocked services, separate coverage accounting |
| 19 | Explicit Model Hydration | Accepted | Ban $fillable/$guarded, require explicit property assignment in Actions |
| 15 | ADR Governance | Accepted | Single source of truth at adrs.script.nl |
| 18 | ISMS Information System | Accepted | Laravel + Vue ISMS with policies as markdown, operational data in PostgreSQL |
| 20 | Input/Result DTO Split | Accepted | Split DTOs by usage direction at the Action boundary — arch tests enforce |
| 21 | PHPStan Rules Package | Accepted | Canonical war-room PHPStan rules distributed as script-development/phpstan-warroom-rules Composer package |
| 24 | Automated External Provisioning | Accepted (amended 2026-05-20) | Async + provider-abstracted + rollback-on-failure + audit-mandatory + flag-gated for tenant-resource provisioning |
| 26 | Decrement Action Symmetry | Accepted | Caller-side symmetry discipline + Pest arch test for counter-mutation decrement Actions; no internal floor guard at Action layer |
| 27 | Canonical Territory Skeleton | Accepted | BIO-aligned baseline + Audit/ for app/; Architecture/ for tests; apps/<role>/ + shared/ for frontend; trip-point arch tests from day 1 |
| 28 | Canonical Git Hooks (v1) | Accepted | Plain core.hooksPath + .githooks/ shell scripts; fast pre-commit + heavy pre-push; per-territory copy from /templates/githooks/; lefthook + distributed package as documented future iterations |
| 29 | Audit Row Durability Contract | Accepted | Audited Actions' DB::transaction(...) closure contains only DB writes; throws exit via sentinel-return; non-transactional state mutates post-commit |
| 32 | Cached-Store Hash-Bumping Protocol | Accepted | Subscribe-via-header: SPA declares cached keys per request; middleware authorizes (per-key Tier-1 policy) + stamps the entitled subset, one-resolve-per-distinct-parent (N+1 arch-tested) |
| 33 | AVG Erasure by Anonymization-in-Place | Accepted | Erasure = anonymize-in-place (not hard-delete): per-column scrub contract across the user aggregate + framework tables, S3 delete, emmie revoke-only + OpenAI persist-then-delete residuals, audit-trail identity-PII accepted as lawful record; completeness Feature+arch tests |
| 34 | Ambulatory Financing Care-Type / Rate-Interval Coupling | Accepted | Emmie territory-specific: ambulatory registration ↔ AMBULATORY financing ↔ HOUR/MINUTE interval three-way coupling; interval-keyed Invariant A (revenue correctness) enforced at the registration chokepoint + arch test; care-type-keyed Invariant B (hygiene) held for prod breach-check |
| 35 | Appointment-Slot Inverted-Window DB CHECK Backstop | Accepted | wijs territory-specific: end_date >= start_date enforced by a DB CHECK on appointment_slots (MySQL 8.0.45) as a write-path-agnostic Level-1 backstop below the shipped Action/validation guards; two-step clean-then-constrain (repoint 9 cancelled-appointment occupations → delete 576 inverted rows → ADD CHECK) |