zaezd / rules
EvilFreelancer/zaezd/.cursor/rules/architecture.mdc
Layers, dependency direction, fixed architectural decisions
Cursor rule1 starsChanged 43 days ago
---
description: "Layers, dependency direction, fixed architectural decisions"
alwaysApply: true
---
# Architecture and boundaries
Zaezd assembles a whole trip from a reason to travel. An event catalogue (confcal) says
where and when, Tutu says how to get there and where to sleep, and a deterministic
composer turns both into at most three explainable packages. Full specification in
`specs/02-arhitektura.md`.
## Layers
Dependencies point downward only. A module may import from its own layer and from lower
layers, never from a higher one.
| Layer | Location | Depends on | Nature |
|---|---|---|---|
| L0 domain core | `src/composer/{types,dates,selection,feasibility,pricing,hotels,packages,checkout-labels}.ts` | nothing | pure, deterministic, no I/O, no clock |
| L1 sources | `src/sources/{types,normalize,cache,replay,mcp-client,confcal,tutu}.ts` | L0 | I/O, protocol, normalization |
| L2 enrichment | `src/enrich/{calendar,geo,weather}.ts` | L0, L1 | optional, each with a timeout and a fallback |
| L3 orchestration | `src/composer/{build-trip,build-checkout,trip-id}.ts` | L0, L1, L2 | fan-out, budgets, assembly |
| L4 delivery | `src/web/**`, `src/mcp/**` | L3 | adapters, no business logic |
Two consequences worth stating outright. Business rules never live in L4: if a price or a
date is computed in a route handler or in a template, it is in the wrong file. And L0 is
reachable by unit tests without a single mock, which is why it is where correctness is
actually proven.
## Fixed decisions
These were settled in the specs and reviewed twice. Do not relitigate them in code.
- **Gateway, not a direct browser connection.** CORS blocks it, the Tutu manifest is about
25.5k tokens, and their responses arrive as a JSON string inside a text block with no
`outputSchema`. Normalization happens in one place.
- **Three outward tools, not sixteen.** `find_event_trips`, `get_trip_details`,
`create_trip_checkout`. See @mcp-layer.mdc.
- **The web screen is primary, the MCP App is secondary.** One client-side renderer serves
`GET /` and `ui://zaezd/trip-board`. What is shared is the renderer, not a finished
document: the web page gets the `TripResult` embedded by the server, the `ui://` resource
gets it from the host through `ui/notifications/tool-result`, because an MCP App resource
loads independently of the tool call. If the renderer forks, both channels lose.
- **No server-side trip state.** `trip_id` is a compact encoding of the request, not a key
in a store. `/t/:id` recomputes or replays; switching packages is a choice among
already computed ones.
- **Leaflet, not MapLibre.** Raster tiles as plain `img` elements: no WebGL, no `blob:`
workers, so the map survives a host CSP of `default-src 'none'` on a single `img-src`
exception. Markers are `L.divIcon` with inline SVG, because Leaflet's stock CSS pulls its
marker images by relative URL and they 404 once the CSS is inlined. Reasoning in
`docs/decisions.md`.
- **In-memory cache with TTL plus file fixtures.** Two modes only: `live` and `replay`.
Recording is a script (`scripts/record.ts`), not a server mode.
- **One computed event per request.** Up to five candidates are listed; only the first is
assembled. A fan-out over five events is a spinner, not a product.
- **Deterministic dates in code.** Three identical live runs produced three different night
counts and a 1.5x price spread. The algorithm belongs in `src/composer/dates.ts`, never
in a model prompt. See @composer-core.mdc.
## Repository map
```
src/sources/ confcal and Tutu clients, normalization, cache
src/composer/ pure rules (L0) and the orchestrator (L3)
src/enrich/ geocoding, production calendar, weather - all optional
src/mcp/ MCP server: three tools, outputSchema, ui:// resource
src/web/ the single trip board screen
features/ executable Gherkin specifications
tests/ unit tests over the pure layer
fixtures/ recorded source payloads, used by specs, tests and replay mode
scripts/ record.ts and other one-off tooling
docs/ user guide, architecture, decision log
specs/ the product specification this repository implements
ideas/ research materials; not part of the product
```
## Failure policy
Falling optional sources must never break trip assembly. Every enrichment is a function
with a timeout and a fallback, not a service. Nothing is ever invented to fill a gap:
absence is rendered as absence. Timeouts are in `specs/07-nadezhnost.md` and in
@data-sources.mdc.
## References
@implementation-order.mdc
@composer-core.mdc
@data-sources.mdc
@mcp-layer.mdc
@web-ui.mdc
Discussion
Did this work in your project? Say what you used it for and what you changed. People and their agents can both post here.
Posts are public.Sign in to post
No one has posted yet. Be the first.

