Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
59 commits
Select commit Hold shift + click to select a range
4ed918c
docs: map existing codebase
gregjoy1 Mar 6, 2026
116c5fa
docs: initialize project
gregjoy1 Mar 6, 2026
6be5c91
chore: add project config
gregjoy1 Mar 6, 2026
c0bdad8
docs: complete project research
gregjoy1 Mar 6, 2026
e7737d1
docs: define v1 requirements
gregjoy1 Mar 6, 2026
93f4053
docs: create roadmap (4 phases)
gregjoy1 Mar 6, 2026
912eaa5
docs(01): capture phase context
gregjoy1 Mar 6, 2026
1f1ed7b
docs(state): record phase 1 context session
gregjoy1 Mar 6, 2026
88010cc
docs(01): research phase safety fixes
gregjoy1 Mar 6, 2026
beb505b
docs(01): add validation strategy
gregjoy1 Mar 6, 2026
e879b12
plan phase 1: three safety fixes in one plan
gregjoy1 Mar 6, 2026
2672563
Safety fixes: bump redis-module to 2.0.4, stub aof_rewrite, replace f…
gregjoy1 Mar 6, 2026
33d288e
docs: complete 01-01 safety fixes plan
gregjoy1 Mar 6, 2026
d2694f6
docs(phase-01): complete phase execution
gregjoy1 Mar 6, 2026
a34121e
docs(02): capture phase context
gregjoy1 Mar 6, 2026
b4869e9
docs(state): record phase 2 context session
gregjoy1 Mar 6, 2026
140c5e6
docs(02): research serde derive chain domain
gregjoy1 Mar 6, 2026
cac4816
docs(phase-02): add validation strategy
gregjoy1 Mar 6, 2026
f1e1ac8
Create phase 2 serde derive chain plans
gregjoy1 Mar 6, 2026
2964f1c
Add serde infrastructure to redical_ical
gregjoy1 Mar 6, 2026
d79f4eb
Derive Serialize/Deserialize on all redical_ical field graph types
gregjoy1 Mar 6, 2026
97fec4e
Complete redical_ical serde derives plan (02-01)
gregjoy1 Mar 6, 2026
3bd50c2
Derive Serialize/Deserialize on redical_core types with serde skip an…
gregjoy1 Mar 6, 2026
eecc7f6
Add bincode round-trip smoke tests for Calendar serialization
gregjoy1 Mar 6, 2026
e44a572
Complete redical_core serde derives plan (02-02)
gregjoy1 Mar 6, 2026
0375b5b
Complete phase 02 serde derive chain execution
gregjoy1 Mar 6, 2026
7124151
docs(03): capture phase context
gregjoy1 Mar 6, 2026
c223e89
docs(state): record phase 3 context session
gregjoy1 Mar 6, 2026
d1e31e0
docs(03): research RDB format phase domain
gregjoy1 Mar 6, 2026
632d82f
docs(03): add research and validation strategy
gregjoy1 Mar 6, 2026
1dfd1af
Phase 3 RDB format plans
gregjoy1 Mar 6, 2026
93bbe60
Add RDBCalendarDump envelope struct with round-trip tests
gregjoy1 Mar 6, 2026
4089dbd
Rewrite rdb_save to produce RDBCalendarDump envelope
gregjoy1 Mar 6, 2026
384a87c
Complete RDBCalendarDump envelope plan (03-01)
gregjoy1 Mar 6, 2026
e11c080
Rewrite rdb_load with three-layer dispatch and catch_unwind
gregjoy1 Mar 6, 2026
1e2e12d
Add unit tests for rdb_load dispatch paths
gregjoy1 Mar 6, 2026
480517d
Complete rdb_load three-layer dispatch plan (03-02)
gregjoy1 Mar 6, 2026
8b175a1
Complete phase 03 RDB format execution
gregjoy1 Mar 6, 2026
868e345
docs(04): capture phase context
gregjoy1 Mar 6, 2026
bb6a192
docs(state): record phase 4 context session
gregjoy1 Mar 6, 2026
e934fe9
docs(04): research phase domain
gregjoy1 Mar 6, 2026
7b1dcee
docs(04): add validation strategy
gregjoy1 Mar 6, 2026
f316f5d
Phase 4 plans: fixtures and integration tests
gregjoy1 Mar 6, 2026
f404cfd
Extract shared build_test_calendar to test_helpers module
gregjoy1 Mar 6, 2026
2bc92d5
Add fixture generator and binary test fixtures
gregjoy1 Mar 6, 2026
1eb3e9a
Complete test helpers and fixtures plan (04-01)
gregjoy1 Mar 6, 2026
f3fbee7
Add fixture-loading and envelope round-trip integration tests
gregjoy1 Mar 6, 2026
a5ae574
Complete fixture-loading integration tests plan (04-02)
gregjoy1 Mar 6, 2026
041f802
docs(v1.0): milestone audit - all 19 requirements satisfied
gregjoy1 Mar 6, 2026
164d4b2
chore: archive phase directories from v1.0 milestone
gregjoy1 Mar 6, 2026
d057115
chore: complete v1.0 milestone
gregjoy1 Mar 6, 2026
2482da9
Manual clippy fixes
gregjoy1 Mar 9, 2026
6d65667
Manual tzid serde de/serialization tweaks
gregjoy1 Mar 9, 2026
5c22402
More manual readability tweaks
gregjoy1 Mar 9, 2026
960a6f2
Rename `rebuild_indexes` to `validate_and_rebuild_indexes`
gregjoy1 Mar 10, 2026
692f404
Remove unnecessary GSD project artifacts
gregjoy1 Mar 10, 2026
0f3abb4
Fix broken envelope_round_trip_produces_correct_calendar test case
gregjoy1 Mar 10, 2026
7aa02a3
Rename RDB dump terminology for clarity
gregjoy1 Mar 11, 2026
5967724
Clarified pure logical RDB dump strategy as legacy
gregjoy1 Mar 11, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
146 changes: 146 additions & 0 deletions .planning/codebase/ARCHITECTURE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,146 @@
# Architecture

**Analysis Date:** 2026-03-06

## Pattern Overview

**Overall:** Layered Rust workspace — iCalendar parsing layer, domain logic layer, Redis module integration layer

**Key Characteristics:**
- Three crates with strict dependency direction: `redical_ical` ← `redical_core` ← `redical_redis`
- `redical_redis` compiles to a `cdylib` loaded into Redis as a native module
- Domain objects (`Calendar`, `Event`) are stored directly in Redis memory via custom `RedisType`; no separate serialization at query time
- iCal text is the wire format for both input (commands) and output (responses)
- Calendar indexes (inverted + geospatial) are maintained incrementally on write, or rebuilt in bulk via `rdcl.cal_idx_rebuild`

## Layers

**iCalendar Parsing (`redical_ical`):**
- Purpose: Parse and render RFC 5545 iCalendar text; no domain logic
- Location: `redical_ical/src/`
- Contains: Grammar combinators (`grammar.rs`), `ContentLine` type, property types (`properties/`), value types (`values/`), `ICalendarEntity` and `ICalendarComponent` traits, `ParserInput`/`ParserResult` type aliases
- Depends on: `nom`, `nom_locate`, `chrono`, `chrono-tz`, `itertools`
- Used by: `redical_core`, `redical_redis`

**Domain Core (`redical_core`):**
- Purpose: Calendar/Event domain model, index structures, occurrence iteration, query execution
- Location: `redical_core/src/`
- Contains: `Calendar`, `Event`, `EventOccurrenceOverride`, `EventInstance`, `EventOccurrenceIterator`, `InvertedCalendarIndex`/`InvertedEventIndex`, `GeoSpatialCalendarIndex`, query subsystem (`queries/`)
- Depends on: `redical_ical`, `rrule`, `rstar`, `geo`, `geohash`, `chrono`, `chrono-tz`
- Used by: `redical_redis`

**Redis Module (`redical_redis`):**
- Purpose: Expose `rdcl.*` Redis commands; own the Redis data type lifecycle (RDB persistence, copy, free)
- Location: `redical_redis/src/`
- Contains: Command handlers (`commands/`), `CALENDAR_DATA_TYPE` (`datatype/`), RDB serialization via `bincode` (`datatype/rdb_data.rs`), `run_with_timeout` utility, allocator shim
- Depends on: `redical_core`, `redical_ical`, `redis-module`, `redis-module-macros`, `bincode`, `rayon`, `libc`
- Used by: Redis server at runtime (loaded as `.so`)

## Data Flow

**Write command (`rdcl.evt_set`):**

1. Redis routes command to `redical_redis/src/commands/rdcl_evt_set.rs`
2. Command handler parses iCal text via `Event::parse_ical` — run in a timeout-guarded thread (`run_with_timeout`) using the configurable `ical-parser-timeout-ms` limit
3. Parsed `Event` is validated
4. LAST-MODIFIED guard: skip update if incoming event is older than stored event
5. `event.rebuild_indexes()` populates per-event inverted index terms
6. `CalendarIndexUpdater` diffs old vs new index terms and applies incremental updates to calendar-level indexes
7. `calendar.insert_event(event)` stores the domain object in Redis key memory
8. `ctx.replicate_verbatim()` replicates the raw command to replicas
9. Keyspace event published via `ctx.notify_keyspace_event`
10. Serialized iCal lines returned to caller as `RedisValue::Array`

**Query command (`rdcl.evi_query` / `rdcl.evt_query`):**

1. Redis routes to command handler in `redical_redis/src/commands/`
2. Handler opens Redis key read-only, retrieves `&Calendar` from `CALENDAR_DATA_TYPE`
3. Query string parsed by `QueryParser` (in `redical_core/src/queries/query_parser.rs`) into a `Query` struct with `WhereConditional`, ordering, range bounds, limit/offset, timezone
4. `query.execute(&calendar)` runs: searches calendar-level inverted/geo indexes to narrow event set, then iterates occurrences via `EventOccurrenceIterator` (backed by `rrule`) merging `EventOccurrenceOverride` data
5. Results returned as iCal-serialized content lines

**RDB Persistence:**

1. On `rdb_save`: `Calendar` → `RDBCalendar` (via `TryFrom`) → `bincode::serialize` → saved as Redis string
2. On `rdb_load`: bytes → `bincode::deserialize` → `RDBCalendar` → `Calendar::try_from` (parallel parse via `rayon`)

**State Management:**
- All calendar state lives in Redis key memory as heap-allocated `Calendar` structs owned by the Redis module type system
- No external database; Redis RDB/AOF provides persistence
- Indexes are in-process data structures inside `Calendar` (`InvertedCalendarIndex`, `GeoSpatialCalendarIndex`)

## Key Abstractions

**`ICalendarEntity` trait:**
- Purpose: Defines `parse_ical(input) -> ParserResult<Self>` and `render_ical() -> String` — the parse/render contract for all iCal value and property types
- Examples: all types in `redical_ical/src/values/`, `redical_ical/src/properties/`
- Pattern: Implemented on concrete structs; `impl_icalendar_entity_traits!` macro derives `FromStr` and `Display`

**`ICalendarComponent` trait:**
- Purpose: Render a composite object (Calendar, Event, EventOccurrenceOverride) as a `BTreeSet<ContentLine>`
- Examples: `redical_core/src/calendar.rs`, `redical_core/src/event.rs`
- Pattern: `to_content_line_set_with_context(context)` with optional `RenderingContext` for timezone/unit conversion

**`Calendar` struct:**
- Purpose: Root domain aggregate; owns events and all indexes
- Location: `redical_core/src/calendar.rs`
- Pattern: `BTreeMap<String, Box<Event>>` for events; separate `InvertedCalendarIndex<T>` fields per indexed property (categories, location_type, related_to, class) plus `GeoSpatialCalendarIndex`

**`InvertedCalendarIndex` / `InvertedEventIndex`:**
- Purpose: Per-property inverted indexes supporting AND/OR/NOT query operations with occurrence-level exceptions
- Location: `redical_core/src/inverted_index.rs`
- Pattern: `IndexedConclusion::Include(exceptions)` / `IndexedConclusion::Exclude(exceptions)` — exceptions are sets of occurrence timestamps that flip the conclusion for specific recurrence instances

**`GeoSpatialCalendarIndex`:**
- Purpose: R-tree spatial index for geo-distance queries
- Location: `redical_core/src/geo_index.rs`
- Pattern: Backed by `rstar::RTree`; stores `GeomWithData<Point, (event_uid, IndexedConclusion)>`

**`Query` trait:**
- Purpose: Polymorphic query execution over `Calendar`; implemented by `EventQuery` and `EventInstanceQuery`
- Location: `redical_core/src/queries/query.rs`
- Pattern: `execute(&Calendar) -> QueryResults<T>`; parsed from iCal-like query text by `QueryParser`

**`CALENDAR_DATA_TYPE`:**
- Purpose: Redis native type registration; owns `rdb_load`, `rdb_save`, `free`, `copy` C-ABI hooks
- Location: `redical_redis/src/datatype/mod.rs`
- Pattern: `RedisType::new(...)` static; `RDBCalendar` intermediate serde struct for bincode persistence

## Entry Points

**Redis module init:**
- Location: `redical_redis/src/lib.rs`
- Triggers: Redis loads `.so` via `MODULE LOAD`
- Responsibilities: Registers all `rdcl.*` commands, `CALENDAR_DATA_TYPE`, keyspace event handlers, and `ical-parser-timeout-ms` config

**Command handlers:**
- Location: `redical_redis/src/commands/rdcl_*.rs`
- Triggers: Client issues `rdcl.*` Redis command
- Responsibilities: Argument parsing, key access, delegation to `redical_core`, iCal serialization of response, keyspace notification, replication

**`Event::parse_ical`:**
- Location: `redical_core/src/event.rs`
- Triggers: Called by write command handlers inside `run_with_timeout`
- Responsibilities: Orchestrates property-by-property nom parsing of iCal event text

## Error Handling

**Strategy:** `Result<T, String>` throughout `redical_core` and `redical_ical`; command handlers map `String` errors to `RedisError::String`; hard parse failures use `nom::Err::Failure` (non-recoverable), soft failures use `nom::Err::Error` (recoverable/backtracking)

**Patterns:**
- `ParserError` carries span, message, and context chain for descriptive error messages
- `convert_error` renders parser errors as single-line strings (Redis-friendly)
- `map_err` / `map_err_message!` macro for enriching recoverable nom errors
- Timeout enforced by `run_with_timeout` returning `TimeoutError`; logged as warning and surfaced as `RedisError::String`

## Cross-Cutting Concerns

**Logging:** `ctx.log_debug(...)` and `ctx.log_warning(...)` via `redis_module::Context`; only available inside command handlers in `redical_redis`

**Validation:** `ICalendarEntity::validate()` on parsed values/properties; `Event::validate()` called before insert in write commands

**Authentication:** Delegated entirely to Redis (no application-level auth)

---

*Architecture analysis: 2026-03-06*
Loading
Loading