agentleFS
Sign inSign up

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.