MAR 2026
ADR 0056
7 Mar 2026
Accept all bookings, surface conflicts as notices
Early mutations like and rejected bookings whose dates fell outside the destination's date range. This felt safe but was wrong — real trip planning is messy. You book a...
ADR 0055
21 Mar 2026
Deploy full-stack preview environments for device testing
Testing feature branches means either deploying to production (risky) or testing locally (doesn't catch infra issues). Preview environments give each feature branch its own...
ADR 0051
26 Mar 2026
Use pessimistic row locking for modification ordering
The git-like modification tree (ADR-0046) requires ordered appending — each new modification must point to the current HEAD, and HEAD must advance atomically. With multiple...
ADR 0050
1 Mar 2026
Use SwiftUI previews with mock services
The MV + Environment Services architecture (ADR-0003) means views trigger fetches on and observe service properties. This is correct for the running app but breaks Xcode...
ADR 0046
26 Mar 2026
Use git-like modification tree for versioning
ADR-0033 established that trips are derived state, generated by replaying modifications (event sourcing). The modification history already supports going back in time — you...
ADR 0045
22 Mar 2026
Use newtype IDs to prevent identifier confusion
The codebase uses UUIDs as identifiers for many domain entities: trips, users, snapshots, modifications, invitations, accommodations, reservations, documents, journey legs,...
ADR 0044
18 Mar 2026
Use Apollo-generated types directly in iOS
Apollo iOS generates strongly-typed Swift structs from GraphQL fragments and operations. Early in the project, we maintained a parallel layer of domain model types (, , ,...
ADR 0043
1 Mar 2026
Use parse-don't-validate for type safety
Without a consistent validation approach, validation logic scatters throughout the codebase: Functions repeatedly validate the same inputs Validation failures occur deep in...
FEB 2026
ADR 0042
15 Feb 2026
Use device ID filtering for subscription signals
With real-time subscriptions (ADR-0041), a mutation on Device A publishes a signal via Redis pub/sub. All subscribers to that trip receive the signal — including Device A...
ADR 0041
15 Feb 2026
Adopt signal-not-payload for real-time subscriptions
Issue #111 introduces GraphQL subscriptions for real-time trip updates across a user's devices. The fundamental design question is how much data the subscription carries....
JAN 2026
ADR 0060
30 Jan 2026
Run local Grafana and Tempo for development observability
Production traces go to Grafana Cloud via Alloy (ADR-0053). But iterating on tracing instrumentation against production has a slow feedback loop and costs money per trace....
ADR 0053
30 Jan 2026
Use Alloy as credential boundary for telemetry
The backend and iOS app export OpenTelemetry traces and metrics to Grafana Cloud and Langfuse. The recommended OpenTelemetry approach is to export to a local collector rather...
ADR 0040
6 Jan 2026
Adopt CrowdSec for intrusion detection
The application runs on a public-facing Hetzner cluster with SSH and Traefik exposed to the internet. Any public endpoint receives constant automated scanning from bots...
ADR 0039
2 Jan 2026
Adopt pgBackRest GFS backup strategy
The application uses PostgreSQL as its primary data store with pgBackRest for backup and point-in-time recovery (PITR) to Cloudflare R2. The initial configuration used daily...
DEC 2025
ADR 0049
3 Dec 2025
Use custom error macros over anyhow/thiserror
The backend needs structured error codes for GraphQL (ADR-0006), error source chains for debugging, and automatic tracing on error creation. Standard Rust error crates don't...
OCT 2025
ADR 0057
15 Oct 2025
Use Tailscale for developer and CI access to the cluster
Developers and CI runners need to reach cluster services — deploying via Colmena, running database migrations, accessing preview environments (ADR-0055). This was done over...
ADR 0038
7 Oct 2025
Use Exa.AI for neighborhood discovery
Users need to understand destination neighborhoods before booking accommodations. When planning a trip to Lisbon, you want to know that Bairro Alto is nightlife, Alfama is...
ADR 0037
7 Oct 2025
Use MapKit Place ID for accommodation references
The app captures accommodation bookings as part of destinations. The initial approach considered storing full location data (coordinates, address, name) for each hotel. This...
ADR 0036
2 Oct 2025
Use iOS CLGeocoder for location enrichment
Locations need to be more than strings — coordinates, timezone, country code, and locality enable map visualizations, timezone-aware scheduling, and distance calculations....
SEPT 2025
ADR 0058
1 Sept 2025
Adopt passkey-first authentication with WebAuthn
The app needs user authentication. Passwords are the default but they're a known-weak pattern — reuse, phishing, credential stuffing. Passkeys (WebAuthn/FIDO2) provide...
ADR 0035
15 Sept 2025
Use S-expressions for GPT-5 effect generation
The trip planning system uses GPT-5 with CFG constraints to generate effects (ADR-0032). The original approach used a custom Lark grammar that output a domain-specific...
ADR 0033
3 Sept 2025
Model trips as destinations with effect-based modifications
Traditional trip planning follows a direct manipulation model: start with an empty itinerary, add each element through forms, the trip IS the data you entered. This doesn't...
ADR 0032
3 Sept 2025
Use GPT-5 with CFG for effect generation
The original architecture planned three layers: natural language → intents (via Apple Foundation Models) → effects (via rule interpreter) → trip modifications. This failed...
AUG 2025
ADR 0031
17 Aug 2025
Write component-specific GraphQL queries
A common anti-pattern is treating GraphQL queries like REST endpoints — creating "reusable" queries that fetch all possible fields to serve multiple components. This negates...
JUL 2025
ADR 0054
20 Jul 2025
Use NixOS and Colmena for infrastructure management
The application runs on a multi-node Hetzner cluster. Initial deployments used a custom script, but coordinating deploys across the cluster — ordering, error handling,...
ADR 0052
12 Jul 2025
Inject services via async-graphql Data context
Resolvers need access to services (database pools, external API clients, domain services). Options: a DI framework, a global singleton, or async-graphql's built-in container.
ADR 0048
10 Jul 2025
Use async-graphql for the API layer
The app needs an API layer between the iOS client and Rust backend. REST is the default, but Apollo iOS provides a normalized cache that acts as a source of truth for the UI...
ADR 0047
29 Jul 2025
Adopt OpenTelemetry tracing for iOS
The backend already has OpenTelemetry tracing exported to Grafana Cloud Tempo via an Alloy collector. But mobile requests appear as orphaned traces — the backend shows a 67ms...
ADR 0028
29 Jul 2025
Adopt stateless PASETO authentication
The authentication stack used PASETO tokens as transport for session IDs, with server-side sessions in Redis via tower-sessions and axum-login. ADR-0020 attempted to run...
ADR 0027
27 Jul 2025
Use response headers for token refresh
Key rotation (ADR-0026) means tokens can end up signed by deprecated keys. Mobile clients need updated tokens without explicit refresh requests or authentication interruptions.
ADR 0026
24 Jul 2025
Use tokio-cron for PASETO key rotation
The application runs as a long-lived Rust/Axum server on a 3-node cluster. PASETO key rotation needs to happen automatically — generate new keys, deprecate old ones, revoke...
ADR 0025
24 Jul 2025
Extend session duration for mobile
Users who book trips months in advance (booking a June trip in January) should remain logged in without re-authentication. Mobile devices are personal devices with a...
ADR 0024
24 Jul 2025
Use pasetors crate for PASETO
After choosing PASETO v4.local for session transport (ADR-0019), we needed a Rust crate that could handle the full lifecycle: token creation, validation, key generation, and...
ADR 0023
24 Jul 2025
Use PASERK identifiers for key identification
During key rotation, the server needs to know which key was used to create a given token without exposing key material.
ADR 0022
24 Jul 2025
Use PostgreSQL advisory locks for coordination
The application runs on a 3-node cluster. PASETO key rotation needs to happen on exactly one node at a time.
ADR 0020
24 Jul 2025
Support token and session auth in parallel
After adopting PASETO tokens for transport (ADR-0019), we had token generation working but no token validation middleware. The existing auth stack (tower-sessions,...
ADR 0019
24 Jul 2025
Adopt PASETO for session transport
The app started with HTTP session cookies via tower-sessions and axum-login. This worked on web but caused persistent lost-session bugs on iOS — cookies in native apps...
ADR 0018
20 Jul 2025
Use environment-based auth manager with Apollo interceptors
The iOS app makes authenticated GraphQL requests via Apollo Client. Authentication needs to be transparent to views — they shouldn't manage tokens, handle 401s, or know about...
ADR 0014
15 Jul 2025
Adopt three-tier design tokens in Swift
SwiftUI apps accumulate hardcoded colors, spacing values, and font sizes across components. Without a system, design changes mean hunting through multiple files, dark mode...
ADR 0013
14 Jul 2025
Adopt SnapshotPreviews for iOS visual testing
The iOS app needed visual regression testing — something like Chromatic or Storybook but for SwiftUI. The ecosystem in 2024 doesn't have a good middle ground: open source...
ADR 0012
13 Jul 2025
Use @Observable over @ObservableObject in iOS
SwiftUI historically used with properties for state observation. The problem is it triggers view redraws for any property change, even if the view doesn't use that...
ADR 0011
13 Jul 2025
Use Result types for specific scenarios
The LLM introduced Result types into the iOS authentication service. While Result types force explicit success/failure handling, async throws is the idiomatic Swift pattern...
ADR 0009
12 Jul 2025
Adopt domain-driven design for the backend
The backend will have complex business logic spanning users, trips, accommodations, reservations, journeys, memberships, and more. The traditional alternative is layered...
ADR 0008
11 Jul 2025
Use GraphQL schema evolution for API compatibility
Unlike web applications where frontend and backend deploy together and every client immediately gets the new bundle, native iOS apps update on the device's schedule. Users...
ADR 0007
11 Jul 2025
Use cfg!() runtime checks over #[cfg] conditionals
Rust provides two ways to handle conditional compilation based on build configuration: Compile-time conditionals (): Code is included/excluded at compile time Runtime checks...
ADR 0006
11 Jul 2025
Use structured error codes in GraphQL
We need to decide how to handle error messages across the GraphQL API and iOS client. The backend can either return user-facing error messages in English, or return...
ADR 0005
10 Jul 2025
Develop using thin vertical slices
We want short feedback loops when building features. If an idea takes days of backend and frontend work before you can see it running, that's too long to find out it doesn't...
ADR 0004
10 Jul 2025
Adopt two-layer iOS testing
We want good coverage so we can move fast, but not tests that overlap and create maintenance work. LLMs make generating tests trivial now, so the problem isn't writing enough...
ADR 0003
10 Jul 2025
Adopt MV + Environment Services for iOS
Having used React Query for server state management, the goal was to find a similar pattern for SwiftUI — something where views declare data needs and a cache layer handles...
ADR 0001
10 Jul 2025
Record architecture decisions
We need to record the architectural decisions made on this project. Traditional documentation claims to always be current, creating maintenance burdens and unrealistic...