| Dimension | Shared | Only in A | Only in B | Overlap |
|---|---|---|---|---|
| Sections | 0 | 6 | 4 | 0% |
| Commands | 0 | 0 | 0 | — |
| Section tags | 2 | 4 | 1 | 29% |
What each file covers
Sections
0 shared · 6 only in A · 4 only in B- − Acheron — AI agent & contributor guide
- − What this is
- − Layering (keep these boundaries)
- − Store roles (do not blur)
- − Conventions in brief (see the scoped rules for detail)
- − Docs are a first-class deliverable
- + Elasticsearch Mapping & Indexing
- + Mapping is immutable — changes require a reindex
- + Field-type conventions (don't drift)
- + Indexing must tolerate partial failure
Commands
neither file has anySection tags
2 shared · 4 only in A · 1 only in B- − test
- − lint-format
- − agent-behaviour
- − docs
- + types
- code-style
- do-not
Line diff
jleist-clemson/acheron · AGENTS.md
@@ −1 @@
1# Acheron — AI agent & contributor guide
2
3Vendor-neutral project context, shared across AI coding tools. Cursor reads this
4file natively; Claude Code reads it via the `@AGENTS.md` import in `CLAUDE.md`.
5Tool-specific rules live in `.cursor/rules/` (Cursor, glob-scoped via `globs:`)
6and `.claude/rules/` (Claude Code, imported from `CLAUDE.md` so they load every
7session) — keep those mirrors in sync with each other and with this file.
8`ARCHITECTURE.md` is the authoritative design document.
9
10## What this is
11
12`acheron` is a Distributed Event Processing Platform. Write path:
13`POST /events` → bounded in-process `asyncio.Queue` → async worker →
14**MongoDB (source of truth)**. Elasticsearch is a **derived mirror**, populated
15strictly downstream from a Mongo outbox (`es_indexed` marker) by the `EsIndexer`.
16Redis caches the realtime stats summary.
17
18## Layering (keep these boundaries)
19
20- `app/api/` — HTTP only: translate request ↔ domain, map errors to status codes.
21- `app/ingestion/`, `app/worker/` — pipeline logic (enqueue, consume, index, rollup).
22- `app/storage/`, `app/cache/`, `app/queue/` — one backend per module.
23
24Business logic lives in services/stores, never in route handlers.
25
26## Store roles (do not blur)
27
28- **Mongo is authoritative.** If Mongo and ES disagree, Mongo wins.
29- **ES is best-effort and rebuildable** — never fail an authoritative write on it.
30- **Redis is a cache** — a Redis outage must degrade, never lose data.
31- The queue is **in-process and non-durable**; that constraint drives most
32 failure-mode and scaling reasoning. Don't design as if it were durable.
33
34## Conventions in brief (see the scoped rules for detail)
35
36- `from __future__ import annotations` atop every module; Google-style docstrings
37 (ruff `D`, pydocstyle google). Tunables live in `Settings`, not magic numbers.
38- Routes are thin; the events routes declare a Pydantic `response_model`
39 (`/health` and `/metrics` are intentionally exempt). Errors map to status
40 codes (Mongo→503, ES→502, queue full→429, shutdown→503).
41- Tests: pytest with `asyncio_mode=auto`; unit tests are hermetic, Docker-backed
42 tests are marked `integration` and deselected by default.
43
44## Docs are a first-class deliverable
45
46When you change behavior, update `ARCHITECTURE.md` and `README.md` in the same
47change. Code and docs must not drift — the architecture doc is graded.
48
jleist-clemson/acheron · .cursor/rules/elasticsearch-mapping.mdc
@@ +1 @@
1---
2description: Elasticsearch index mapping and indexing constraints
3globs: app/storage/es.py,app/worker/es_indexer.py
4alwaysApply: false
5---
6
7# Elasticsearch Mapping & Indexing
8
9ES is a **derived, rebuildable mirror** of Mongo — never the source of truth.
10The explicit mapping is intentional; document any change in `ARCHITECTURE.md §6`.
11
12## Mapping is immutable — changes require a reindex
13
14A field's mapping type cannot change on an existing index. Editing `_MAPPING`
15only affects newly created indices, so any type change means: create a new
16index with the new mapping and reindex from Mongo (the outbox is the source).
17Call this out explicitly in the PR/commit and the architecture doc.
18
19## Field-type conventions (don't drift)
20
21- Exact-match / aggregatable fields (`event_type`, `user_id`) → `keyword`.
22- `schema_version` → `integer` (producer-declared event schema version).
23- `source_url` → `keyword` with a `text` sub-field for tokenized search.
24- `metadata` → `flattened`: schemaless keys can't explode the mapping and mixed
25 value types across events can't cause index-time conflicts. The trade-off is
26 that `flattened` leaves are exact-match keyword, not analyzed full-text — which
27 is why metadata values are *also* mirrored into `metadata_text` (below).
28- `metadata_text` → `text` (analyzed). Derived at index time from the metadata
29 leaf values and included in the `/search?q=` query, so full-text search hits
30 terms that appear only inside metadata. Search-only: excluded from `/search`
31 responses (`source_excludes`) and never stored in Mongo (ARCHITECTURE §6).
32
33## Indexing must tolerate partial failure
34
35`ensure_mapping()` is idempotent and may run lazily (ES can be down at startup).
36Bulk index with `raise_on_error=False` so one malformed document doesn't discard
37the rest of the batch; log the failures. A transport-level error (ES
38unreachable) **should** propagate so the `EsIndexer` leaves events unindexed and
39retries them next pass — that's the outbox guarantee, not a bug to swallow.
40
@@ −1 +1 @@
1−# Acheron — AI agent & contributor guide
1+---
2+description: Elasticsearch index mapping and indexing constraints
3+globs: app/storage/es.py,app/worker/es_indexer.py
4+alwaysApply: false
5+---
26
3−Vendor-neutral project context, shared across AI coding tools. Cursor reads this
4−file natively; Claude Code reads it via the `@AGENTS.md` import in `CLAUDE.md`.
5−Tool-specific rules live in `.cursor/rules/` (Cursor, glob-scoped via `globs:`)
6−and `.claude/rules/` (Claude Code, imported from `CLAUDE.md` so they load every
7−session) — keep those mirrors in sync with each other and with this file.
8−`ARCHITECTURE.md` is the authoritative design document.
7+# Elasticsearch Mapping & Indexing
98
10−## What this is
9+ES is a **derived, rebuildable mirror** of Mongo — never the source of truth.
10+The explicit mapping is intentional; document any change in `ARCHITECTURE.md §6`.
1111
12−`acheron` is a Distributed Event Processing Platform. Write path:
13−`POST /events` → bounded in-process `asyncio.Queue` → async worker →
14−**MongoDB (source of truth)**. Elasticsearch is a **derived mirror**, populated
15−strictly downstream from a Mongo outbox (`es_indexed` marker) by the `EsIndexer`.
16−Redis caches the realtime stats summary.
12+## Mapping is immutable — changes require a reindex
1713
18−## Layering (keep these boundaries)
14+A field's mapping type cannot change on an existing index. Editing `_MAPPING`
15+only affects newly created indices, so any type change means: create a new
16+index with the new mapping and reindex from Mongo (the outbox is the source).
17+Call this out explicitly in the PR/commit and the architecture doc.
1918
20−- `app/api/` — HTTP only: translate request ↔ domain, map errors to status codes.
21−- `app/ingestion/`, `app/worker/` — pipeline logic (enqueue, consume, index, rollup).
22−- `app/storage/`, `app/cache/`, `app/queue/` — one backend per module.
19+## Field-type conventions (don't drift)
2320
24−Business logic lives in services/stores, never in route handlers.
21+- Exact-match / aggregatable fields (`event_type`, `user_id`) → `keyword`.
22+- `schema_version` → `integer` (producer-declared event schema version).
23+- `source_url` → `keyword` with a `text` sub-field for tokenized search.
24+- `metadata` → `flattened`: schemaless keys can't explode the mapping and mixed
25+ value types across events can't cause index-time conflicts. The trade-off is
26+ that `flattened` leaves are exact-match keyword, not analyzed full-text — which
27+ is why metadata values are *also* mirrored into `metadata_text` (below).
28+- `metadata_text` → `text` (analyzed). Derived at index time from the metadata
29+ leaf values and included in the `/search?q=` query, so full-text search hits
30+ terms that appear only inside metadata. Search-only: excluded from `/search`
31+ responses (`source_excludes`) and never stored in Mongo (ARCHITECTURE §6).
2532
26−## Store roles (do not blur)
33+## Indexing must tolerate partial failure
2734
28−- **Mongo is authoritative.** If Mongo and ES disagree, Mongo wins.
29−- **ES is best-effort and rebuildable** — never fail an authoritative write on it.
30−- **Redis is a cache** — a Redis outage must degrade, never lose data.
31−- The queue is **in-process and non-durable**; that constraint drives most
32− failure-mode and scaling reasoning. Don't design as if it were durable.
33−
34−## Conventions in brief (see the scoped rules for detail)
35−
36−- `from __future__ import annotations` atop every module; Google-style docstrings
37− (ruff `D`, pydocstyle google). Tunables live in `Settings`, not magic numbers.
38−- Routes are thin; the events routes declare a Pydantic `response_model`
39− (`/health` and `/metrics` are intentionally exempt). Errors map to status
40− codes (Mongo→503, ES→502, queue full→429, shutdown→503).
41−- Tests: pytest with `asyncio_mode=auto`; unit tests are hermetic, Docker-backed
42− tests are marked `integration` and deselected by default.
43−
44−## Docs are a first-class deliverable
45−
46−When you change behavior, update `ARCHITECTURE.md` and `README.md` in the same
47−change. Code and docs must not drift — the architecture doc is graded.
35+`ensure_mapping()` is idempotent and may run lazily (ES can be down at startup).
36+Bulk index with `raise_on_error=False` so one malformed document doesn't discard
37+the rest of the batch; log the failures. A transport-level error (ES
38+unreachable) **should** propagate so the `EsIndexer` leaves events unindexed and
39+retries them next pass — that's the outbox guarantee, not a bug to swallow.
4840
