M1: Add Calendar API vertical (proto, daemon, CLI, E2E) #185

Closed
opened 2026-07-28 14:04:20 +00:00 by KofTwentyTwo · 1 comment
KofTwentyTwo commented 2026-07-28 14:04:20 +00:00 (Migrated from github.com)

Context

Story for milestone M1. Read docs/STORY-WORKFLOW.md first — it defines the process, gates, and patterns; this issue defines only the specific work.

crates/nuncio-cal is a fully real, tested engine crate (RFC 5545 iCalendar parsing, a real CalDavClient REPORT transport, a CalendarBackend trait, MockCalendarBackend) — but it is completely disconnected from the running daemon. nunciod/Cargo.toml:15 declares nuncio-cal as a dependency, yet grep -r nuncio_cal crates/nunciod/src returns zero matches: it is a genuinely orphaned crate. nuncio-store::db::DatabaseEngine already has working, tested calendar CRUD (save_calendar_event/list_calendar_events/get_calendar_event, crates/nuncio-store/src/db.rs:918-1035) that nothing ever calls in production. Meanwhile the CLI's nuncio cal list/nuncio cal sync (crates/nuncio-cli/src/runner.rs:345-361) fabricates its output: list always prints "Calendar Events: 0 events found" and sync only flips a local EventBus status flag via CoreCommand::SyncAll — no calendar backend is ever touched. This story wires all of that together into a real, authenticated gRPC vertical, the same way the Mail service already does for email.

Goal

Add a nuncio.v1.Calendar gRPC service with an honest surface — Sync, ListEvents, GetEvent — backed by the daemon's real store and (for Sync, when injected) a real CalendarBackend. ListEvents/GetEvent MUST always read genuine persisted data (never a fabricated event). Sync MUST NOT fake a CalDAV write: there is currently no way to persist a per-account CalDAV collection URL/credential (nuncio_core::AccountConfig has no CalDAV fields), so the production (non-test) Sync path has nothing real to connect to yet — it must return an honest error (e.g. "no CalDAV configuration exists for this account") rather than pretending to sync. Only the test-injection seam (mirroring MailEngineOverrides) exercises a working, mocked Sync. No create/delete RPCs — this story does not add real CalDAV write support. The CLI's cal list/cal sync must become real thin gRPC clients, replacing the fabricated strings in runner.rs:345-361.

Files to touch / patterns to mirror

Mirror the Mail vertical throughout — it is the fully-built reference for this exact shape:

  • crates/nuncio-proto/proto/nuncio/v1/nuncio.proto — add service Calendar (mirror the service Mail doc comment and SECURITY note at lines 223-230) plus its messages. New messages:
    • message CalendarEvent mirroring nuncio_core::model::CalendarEvent (crates/nuncio-core/src/model.rs:104-122) field-for-field: id, account_id, calendar_id, summary, start_time (int64), end_time (int64), rrule (optional string), location (optional string). (Named CalendarEvent, not Event — Event is already taken by System's push-event wrapper at proto line 48.)
    • message CalendarSyncRequest { string account_id = 1; string calendar_id = 2; int64 start_window = 3; int64 end_window = 4; } — carries calendar_id+window because CalendarBackend::fetch_events (see below) is inherently scoped that way, unlike Mail's account-wide Sync.
    • message CalendarSyncResponse { uint64 synced_count = 1; }
    • message ListEventsRequest { string account_id = 1; string calendar_id = 2; int64 start_window = 3; int64 end_window = 4; }
    • message ListEventsResponse { repeated CalendarEvent events = 1; }
    • message GetEventRequest { string event_id = 1; }
    • message GetEventResponse { CalendarEvent event = 1; }
    • After editing, regenerate the golden per §5 of STORY-WORKFLOW.md (recipe is also printed by the failing test in crates/nuncio-proto/src/lib.rs:97-116).
  • crates/nuncio-proto/src/client.rs — add AuthenticatedCalendarClient type alias + connect_calendar(addr, token), mirroring AuthenticatedMailClient/connect_mail exactly (lines 96-98, 167-182). Add matching unit tests mirroring connect_mail_fails_closed_on_invalid_token/connect_mail_reports_transport_error_when_daemon_unreachable (lines 317-332).
  • crates/nunciod/Cargo.toml — no change needed; nuncio-cal is already a dependency (line 15), just finally used.
  • crates/nunciod/src/lib.rs — add pub mod calendar_sync; (alongside the existing pub mod sync; at line 8).
  • crates/nunciod/src/calendar_sync.rs (new file) — mirror crates/nunciod/src/sync.rs's shape (its whole doc comment explains the pattern). Add:
    • CalendarSyncError enum (thiserror), mirroring SyncError.
    • pub async fn sync_with_backend(db: &DatabaseEngine, backend: &dyn CalendarBackend, calendar_id: &str, start_window: i64, end_window: i64) -> Result<usize, CalendarSyncError> — calls backend.fetch_events(calendar_id, start_window, end_window), then db.save_calendar_event(&event) for each result, returning the count. Pure fetch-and-persist, unit-testable directly with MockCalendarBackend, exactly like sync.rs's fetch_and_persist.
    • There is deliberately no run_account_sync/run_all_accounts_sync production entry point yet (mirroring sync.rs's real-backend builders) — there is no persisted per-account CalDAV config to build a real CalDavClient from. Document this gap in a doc comment; a later story adds account-level CalDAV config and the real production entry point.
  • crates/nunciod/src/grpc.rs — mirror the Mail wiring throughout:
    • CalendarEngineOverrides { pub calendar_backend: Option<Arc<dyn nuncio_cal::CalendarBackend>> } (Clone, Default, custom Debug), mirroring MailEngineOverrides (lines 439-456).
    • CalendarGrpcService { db: Arc<DatabaseEngine>, overrides: CalendarEngineOverrides } implementing a new nuncio_proto::v1::calendar_server::Calendar trait:
      • list_events/get_event: always real — call db.list_calendar_events(&req.account_id, req.start_window, req.end_window) / db.get_calendar_event(&req.event_id) directly (mirror MailGrpcService::list_messages/get_message, lines 496-538), map nuncio_core::model::CalendarEvent <-> proto CalendarEvent (new map_calendar_event_to_proto helper, mirroring map_email_to_proto).
      • sync: if overrides.calendar_backend is Some, call calendar_sync::sync_with_backend; if None, return Status::internal("no CalDAV configuration exists for account '{account_id}'; per-account CalDAV configuration is not yet implemented") — never a fabricated success (mirror the doc-comment style of MailGrpcService::sync, lines 646-677, but note production always errors today).
    • Add Calendar to serve_on_listener_with_overrides's signature (new calendar_overrides: CalendarEngineOverrides param) and mount it behind its OWN BearerAuthInterceptor, exactly like every other service (lines 1199-1219 for the Mail/Filters pattern) — update the function's doc comment's invariant list and serve/serve_on_listener's call sites to pass CalendarEngineOverrides::default().
    • Add a calendar_rpcs_reject_missing_bearer_token test in grpc.rs's own #[cfg(test)] mod tests, mirroring mail_rpcs_reject_missing_bearer_token (lines 1942-1974).
  • crates/nuncio-cli/src/args.rs — CalSubcommand (lines 337-345) stays List/Sync only (no create/delete — matches the honest surface); no change needed unless request params are added (optional: keep nuncio cal sync/nuncio cal list argument-free for now, matching the current CLI surface; document in Acceptance Criteria if you choose to add flags).
  • crates/nuncio-cli/src/runner.rs:
    • Replace the fabricated Commands::Cal handler (lines 345-361) with real connect_calendar_client() (mirror connect_mail_client, lines 1569-1581) + RPC calls, following the handle_sync/handle_list_folder shape (lines 683-729).
    • Delete/replace the now-wrong test assertions at lines ~1779-1798 (cal_list.contains(r#""events":[]"#)", cal_sync.contains(...)) — add a calendar_rpcs_round_trip_over_grpc_to_a_stub_daemon test mirroring mail_rpcs_round_trip_over_grpc_to_a_stub_daemon (lines 2094-2260+): a stub nuncio.v1.Calendar tonic service on an ephemeral port, driven by the real HeadlessRunner.
  • crates/nunciod/tests/spine_e2e_test.rs or a new crates/nunciod/tests/calendar_e2e_test.rs — full-daemon offline E2E mirroring spine_e2e_test.rs's boot_daemon/full_mail_spine_round_trips_... shape (lines 87-133, 140+): boot the real daemon with an injected MockCalendarBackend via CalendarEngineOverrides, drive connect_calendar's Sync then ListEvents/GetEvent over the authenticated gRPC API, and assert an unauthenticated call is rejected (Code::Unauthenticated), mirroring grpc_auth_test.rs's pattern (lines 59-79).

Implementation steps

  1. Add the proto service + messages; regenerate the golden; confirm cargo test -p nuncio-proto passes.
  2. Add connect_calendar/AuthenticatedCalendarClient to nuncio-proto/src/client.rs with tests.
  3. Add nunciod::calendar_sync::sync_with_backend + CalendarSyncError, with direct unit tests using MockCalendarBackend (no daemon/gRPC needed for these).
  4. Add CalendarEngineOverrides + CalendarGrpcService + mount it in serve_on_listener_with_overrides behind its own BearerAuthInterceptor; add the auth-reject test in grpc.rs.
  5. Wire the CLI: connect_calendar_client, real cal sync/cal list handlers, update/replace the stale fabricated-output tests, add the stub-daemon round-trip test.
  6. Add the full-daemon offline E2E (mock backend injected, unauth rejection asserted).
  7. Run the local gate (cargo fmt --all -- --check + cargo check-all + cargo test-all).

Acceptance criteria

  • nuncio.v1.Calendar service added: Sync, ListEvents, GetEvent RPCs + their request/response messages, listed above; golden regenerated; nuncio-proto's contract_stability::descriptor_matches_committed_golden test passes.
  • Calendar is mounted behind its own BearerAuthInterceptor in serve_on_listener_with_overrides; an offline E2E test asserts an unauthenticated call is rejected with Code::Unauthenticated.
  • ListEvents/GetEvent return only genuinely persisted events (via DatabaseEngine::list_calendar_events/get_calendar_event) — never fabricated data.
  • Sync with no injected backend returns an honest error (never a fabricated synced_count); with an injected MockCalendarBackend it genuinely fetches and persists events, verified end to end by ListEvents/GetEvent immediately after.
  • nuncio cal sync/nuncio cal list are real gRPC clients of the new service; the old fabricated "Calendar Events: 0 events found" / "calendar_sync_started" strings are gone from runner.rs.
  • Offline E2E (wiremock/MockCalendarBackend) proves Sync → ListEvents/GetEvent end to end over the authenticated gRPC API; no live network.
  • the local gate (cargo fmt --all -- --check + cargo check-all + cargo test-all) green (fmt + clippy -D warnings all-targets + test-all), 0 ignored tests.

Definition of done & process

Follow docs/STORY-WORKFLOW.md: no fabricated data; comment convention (intent-only, no story/GH/phase refs in .rs/.proto); mock-only tests. Branch story/m1-calendar-api off dev; PR to dev with the gate output pasted as evidence; the PO reviews before the issue is closed.

Dependencies / notes

  • [PROTO]: this story changes nuncio.proto and its golden. Confirm with the PO that no other proto story (currently also M2-S2) is in flight before starting; only one proto-touching story lands at a time.
  • M1-S2 ("Wire rrule recurrence") depends on ListEvents existing from this story — it should not start (or should coordinate on ordering) until this PR merges, since it wires occurrence-expansion into CalendarGrpcService::list_events.
  • Prior art: a patch wip-calendar-3b2-partial.patch may exist in the handoff materials — treat it as reference only, not trustworthy; re-implement fresh against this issue and the real current tree, and re-verify anything it suggests against crates/nuncio-cal and crates/nuncio-store/src/db.rs as they exist today (they may have diverged since that patch was drafted).
  • Real per-account CalDAV configuration (a caldav_url/credential per account, needed for Sync to ever do something real in production) is explicitly out of scope here and is a follow-up story — do not attempt to bolt it on.
  • Size: L.
## Context Story for milestone M1. **Read [`docs/STORY-WORKFLOW.md`](https://github.com/KofTwentyTwo/nuncio/blob/dev/docs/STORY-WORKFLOW.md) first** — it defines the process, gates, and patterns; this issue defines only the specific work. `crates/nuncio-cal` is a fully real, tested engine crate (RFC 5545 iCalendar parsing, a real `CalDavClient` REPORT transport, a `CalendarBackend` trait, `MockCalendarBackend`) — but it is completely disconnected from the running daemon. `nunciod/Cargo.toml:15` declares `nuncio-cal` as a dependency, yet `grep -r nuncio_cal crates/nunciod/src` returns **zero matches**: it is a genuinely orphaned crate. `nuncio-store::db::DatabaseEngine` already has working, tested calendar CRUD (`save_calendar_event`/`list_calendar_events`/`get_calendar_event`, `crates/nuncio-store/src/db.rs:918-1035`) that nothing ever calls in production. Meanwhile the CLI's `nuncio cal list`/`nuncio cal sync` (`crates/nuncio-cli/src/runner.rs:345-361`) fabricates its output: `list` always prints `"Calendar Events: 0 events found"` and `sync` only flips a local `EventBus` status flag via `CoreCommand::SyncAll` — no calendar backend is ever touched. This story wires all of that together into a real, authenticated gRPC vertical, the same way the `Mail` service already does for email. ## Goal Add a `nuncio.v1.Calendar` gRPC service with an honest surface — `Sync`, `ListEvents`, `GetEvent` — backed by the daemon's real store and (for `Sync`, when injected) a real `CalendarBackend`. `ListEvents`/`GetEvent` MUST always read genuine persisted data (never a fabricated event). `Sync` MUST NOT fake a CalDAV write: there is currently no way to persist a per-account CalDAV collection URL/credential (`nuncio_core::AccountConfig` has no CalDAV fields), so the production (non-test) `Sync` path has nothing real to connect to yet — it must return an honest error (e.g. "no CalDAV configuration exists for this account") rather than pretending to sync. Only the test-injection seam (mirroring `MailEngineOverrides`) exercises a working, mocked `Sync`. **No create/delete RPCs** — this story does not add real CalDAV write support. The CLI's `cal list`/`cal sync` must become real thin gRPC clients, replacing the fabricated strings in `runner.rs:345-361`. ## Files to touch / patterns to mirror Mirror the **Mail** vertical throughout — it is the fully-built reference for this exact shape: - `crates/nuncio-proto/proto/nuncio/v1/nuncio.proto` — add `service Calendar` (mirror the `service Mail` doc comment and SECURITY note at lines 223-230) plus its messages. New messages: - `message CalendarEvent` mirroring `nuncio_core::model::CalendarEvent` (`crates/nuncio-core/src/model.rs:104-122`) field-for-field: `id`, `account_id`, `calendar_id`, `summary`, `start_time` (`int64`), `end_time` (`int64`), `rrule` (`optional string`), `location` (`optional string`). (Named `CalendarEvent`, not `Event` — `Event` is already taken by `System`'s push-event wrapper at proto line 48.) - `message CalendarSyncRequest { string account_id = 1; string calendar_id = 2; int64 start_window = 3; int64 end_window = 4; }` — carries `calendar_id`+window because `CalendarBackend::fetch_events` (see below) is inherently scoped that way, unlike `Mail`'s account-wide `Sync`. - `message CalendarSyncResponse { uint64 synced_count = 1; }` - `message ListEventsRequest { string account_id = 1; string calendar_id = 2; int64 start_window = 3; int64 end_window = 4; }` - `message ListEventsResponse { repeated CalendarEvent events = 1; }` - `message GetEventRequest { string event_id = 1; }` - `message GetEventResponse { CalendarEvent event = 1; }` - After editing, regenerate the golden per §5 of `STORY-WORKFLOW.md` (recipe is also printed by the failing test in `crates/nuncio-proto/src/lib.rs:97-116`). - `crates/nuncio-proto/src/client.rs` — add `AuthenticatedCalendarClient` type alias + `connect_calendar(addr, token)`, mirroring `AuthenticatedMailClient`/`connect_mail` exactly (lines 96-98, 167-182). Add matching unit tests mirroring `connect_mail_fails_closed_on_invalid_token`/`connect_mail_reports_transport_error_when_daemon_unreachable` (lines 317-332). - `crates/nunciod/Cargo.toml` — no change needed; `nuncio-cal` is already a dependency (line 15), just finally used. - `crates/nunciod/src/lib.rs` — add `pub mod calendar_sync;` (alongside the existing `pub mod sync;` at line 8). - `crates/nunciod/src/calendar_sync.rs` (**new file**) — mirror `crates/nunciod/src/sync.rs`'s shape (its whole doc comment explains the pattern). Add: - `CalendarSyncError` enum (thiserror), mirroring `SyncError`. - `pub async fn sync_with_backend(db: &DatabaseEngine, backend: &dyn CalendarBackend, calendar_id: &str, start_window: i64, end_window: i64) -> Result<usize, CalendarSyncError>` — calls `backend.fetch_events(calendar_id, start_window, end_window)`, then `db.save_calendar_event(&event)` for each result, returning the count. Pure fetch-and-persist, unit-testable directly with `MockCalendarBackend`, exactly like `sync.rs`'s `fetch_and_persist`. - There is deliberately **no** `run_account_sync`/`run_all_accounts_sync` production entry point yet (mirroring `sync.rs`'s real-backend builders) — there is no persisted per-account CalDAV config to build a real `CalDavClient` from. Document this gap in a doc comment; a later story adds account-level CalDAV config and the real production entry point. - `crates/nunciod/src/grpc.rs` — mirror the `Mail` wiring throughout: - `CalendarEngineOverrides { pub calendar_backend: Option<Arc<dyn nuncio_cal::CalendarBackend>> }` (`Clone`, `Default`, custom `Debug`), mirroring `MailEngineOverrides` (lines 439-456). - `CalendarGrpcService { db: Arc<DatabaseEngine>, overrides: CalendarEngineOverrides }` implementing a new `nuncio_proto::v1::calendar_server::Calendar` trait: - `list_events`/`get_event`: always real — call `db.list_calendar_events(&req.account_id, req.start_window, req.end_window)` / `db.get_calendar_event(&req.event_id)` directly (mirror `MailGrpcService::list_messages`/`get_message`, lines 496-538), map `nuncio_core::model::CalendarEvent` <-> proto `CalendarEvent` (new `map_calendar_event_to_proto` helper, mirroring `map_email_to_proto`). - `sync`: if `overrides.calendar_backend` is `Some`, call `calendar_sync::sync_with_backend`; if `None`, return `Status::internal("no CalDAV configuration exists for account '{account_id}'; per-account CalDAV configuration is not yet implemented")` — never a fabricated success (mirror the doc-comment style of `MailGrpcService::sync`, lines 646-677, but note production always errors today). - Add `Calendar` to `serve_on_listener_with_overrides`'s signature (new `calendar_overrides: CalendarEngineOverrides` param) and mount it behind its OWN `BearerAuthInterceptor`, exactly like every other service (lines 1199-1219 for the `Mail`/`Filters` pattern) — update the function's doc comment's invariant list and `serve`/`serve_on_listener`'s call sites to pass `CalendarEngineOverrides::default()`. - Add a `calendar_rpcs_reject_missing_bearer_token` test in `grpc.rs`'s own `#[cfg(test)] mod tests`, mirroring `mail_rpcs_reject_missing_bearer_token` (lines 1942-1974). - `crates/nuncio-cli/src/args.rs` — `CalSubcommand` (lines 337-345) stays `List`/`Sync` only (no create/delete — matches the honest surface); no change needed unless request params are added (optional: keep `nuncio cal sync`/`nuncio cal list` argument-free for now, matching the current CLI surface; document in Acceptance Criteria if you choose to add flags). - `crates/nuncio-cli/src/runner.rs`: - Replace the fabricated `Commands::Cal` handler (lines 345-361) with real `connect_calendar_client()` (mirror `connect_mail_client`, lines 1569-1581) + RPC calls, following the `handle_sync`/`handle_list_folder` shape (lines 683-729). - Delete/replace the now-wrong test assertions at lines ~1779-1798 (`cal_list.contains(r#""events":[]"#)"`, `cal_sync.contains(...)`) — add a `calendar_rpcs_round_trip_over_grpc_to_a_stub_daemon` test mirroring `mail_rpcs_round_trip_over_grpc_to_a_stub_daemon` (lines 2094-2260+): a stub `nuncio.v1.Calendar` tonic service on an ephemeral port, driven by the real `HeadlessRunner`. - `crates/nunciod/tests/spine_e2e_test.rs` or a **new** `crates/nunciod/tests/calendar_e2e_test.rs` — full-daemon offline E2E mirroring `spine_e2e_test.rs`'s `boot_daemon`/`full_mail_spine_round_trips_...` shape (lines 87-133, 140+): boot the real daemon with an injected `MockCalendarBackend` via `CalendarEngineOverrides`, drive `connect_calendar`'s `Sync` then `ListEvents`/`GetEvent` over the authenticated gRPC API, and assert an unauthenticated call is rejected (`Code::Unauthenticated`), mirroring `grpc_auth_test.rs`'s pattern (lines 59-79). ## Implementation steps 1. Add the proto service + messages; regenerate the golden; confirm `cargo test -p nuncio-proto` passes. 2. Add `connect_calendar`/`AuthenticatedCalendarClient` to `nuncio-proto/src/client.rs` with tests. 3. Add `nunciod::calendar_sync::sync_with_backend` + `CalendarSyncError`, with direct unit tests using `MockCalendarBackend` (no daemon/gRPC needed for these). 4. Add `CalendarEngineOverrides` + `CalendarGrpcService` + mount it in `serve_on_listener_with_overrides` behind its own `BearerAuthInterceptor`; add the auth-reject test in `grpc.rs`. 5. Wire the CLI: `connect_calendar_client`, real `cal sync`/`cal list` handlers, update/replace the stale fabricated-output tests, add the stub-daemon round-trip test. 6. Add the full-daemon offline E2E (mock backend injected, unauth rejection asserted). 7. Run the local gate (`cargo fmt --all -- --check` + `cargo check-all` + `cargo test-all`). ## Acceptance criteria - [ ] `nuncio.v1.Calendar` service added: `Sync`, `ListEvents`, `GetEvent` RPCs + their request/response messages, listed above; golden regenerated; `nuncio-proto`'s `contract_stability::descriptor_matches_committed_golden` test passes. - [ ] `Calendar` is mounted behind its own `BearerAuthInterceptor` in `serve_on_listener_with_overrides`; an offline E2E test asserts an unauthenticated call is rejected with `Code::Unauthenticated`. - [ ] `ListEvents`/`GetEvent` return only genuinely persisted events (via `DatabaseEngine::list_calendar_events`/`get_calendar_event`) — never fabricated data. - [ ] `Sync` with no injected backend returns an honest error (never a fabricated `synced_count`); with an injected `MockCalendarBackend` it genuinely fetches and persists events, verified end to end by `ListEvents`/`GetEvent` immediately after. - [ ] `nuncio cal sync`/`nuncio cal list` are real gRPC clients of the new service; the old fabricated `"Calendar Events: 0 events found"` / `"calendar_sync_started"` strings are gone from `runner.rs`. - [ ] Offline E2E (wiremock/`MockCalendarBackend`) proves `Sync` → `ListEvents`/`GetEvent` end to end over the authenticated gRPC API; no live network. - [ ] the local gate (`cargo fmt --all -- --check` + `cargo check-all` + `cargo test-all`) green (fmt + clippy `-D warnings` all-targets + test-all), 0 ignored tests. ## Definition of done & process Follow [`docs/STORY-WORKFLOW.md`](https://github.com/KofTwentyTwo/nuncio/blob/dev/docs/STORY-WORKFLOW.md): no fabricated data; comment convention (intent-only, no story/GH/phase refs in `.rs`/`.proto`); mock-only tests. Branch `story/m1-calendar-api` off `dev`; PR to `dev` with the gate output pasted as evidence; the PO reviews before the issue is closed. ## Dependencies / notes - **[PROTO]**: this story changes `nuncio.proto` and its golden. Confirm with the PO that no other proto story (currently also **M2-S2**) is in flight before starting; only one proto-touching story lands at a time. - **M1-S2** ("Wire rrule recurrence") depends on `ListEvents` existing from this story — it should not start (or should coordinate on ordering) until this PR merges, since it wires occurrence-expansion into `CalendarGrpcService::list_events`. - Prior art: a patch `wip-calendar-3b2-partial.patch` may exist in the handoff materials — treat it as reference only, **not** trustworthy; re-implement fresh against this issue and the real current tree, and re-verify anything it suggests against `crates/nuncio-cal` and `crates/nuncio-store/src/db.rs` as they exist today (they may have diverged since that patch was drafted). - Real per-account CalDAV configuration (a `caldav_url`/credential per account, needed for `Sync` to ever do something real in production) is explicitly **out of scope** here and is a follow-up story — do not attempt to bolt it on. - Size: L.
KofTwentyTwo commented 2026-07-29 22:19:17 +00:00 (Migrated from github.com)

Delivered via PR #214 (merged to dev). Adversarial PO review passed — spec complete, auth invariant SAFE (Calendar behind its own BearerAuthInterceptor; E2E asserts Code::Unauthenticated), anti-fabrication CLEAN (honest Sync error, real reads). One deferral to M1-S2 (#186): ListEvents accepts calendar_id but doesn't filter on it (returns a documented account+window superset) — to be applied when #186 reworks list_events.

Delivered via PR #214 (merged to dev). Adversarial PO review passed — spec complete, auth invariant SAFE (Calendar behind its own BearerAuthInterceptor; E2E asserts Code::Unauthenticated), anti-fabrication CLEAN (honest Sync error, real reads). One deferral to M1-S2 (#186): ListEvents accepts calendar_id but doesn't filter on it (returns a documented account+window superset) — to be applied when #186 reworks list_events.
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: KofTwentyTwo/nuncio#185