EVA Middleware — Epic Completion Audit

121 stories · 19 epics · audited against EVA_Sprints.xlsx · refreshed 2026-07-08 against current EVA-Services + Flutter frontend
3 DONE (2%)  ·  48 PARTIAL (40%)  ·  70 NOT STARTED (58%)

Jump to epic

Cross-Cutting Findings

Read this before the per-epic tables. Every verdict is grounded in a direct read of the cited source (backend and/or Flutter frontend) — not inferred from module names or docstrings. Refreshed 2026-07-08 against the post-restructuring codebase.

E1 — Voice Brief Generation and Delivery (7 stories, P0)

StoryVerdictEvidenceGap
E1-S1 pre-synth <200ms PARTIAL Backend: EVA_Services/EVA_Shared/eva-domain/src/eva_domain/keeper/drafts.py:68-89 (deliver() calls voice_bridge.pre_render()); keeper/voice.py:201-261 (pre_render/pre_render_manifest write to AudioStore); EVA_Services/EVA_Bureau/src/eva_bureau/scheduler.py:69-93,99-118,269-299 (per-principal cron job registered from Supabase schedules at startup, fires _morning_brief_single which enqueues an OpenClaw job whose prompt ends in 'Call eva_deliver_draft' -> pre-render); wiring.py:52-57 (scheduler.configure/start/load_schedules run in app lifespan); EVA_MCP/src/eva_mcp/registry/catalogue.py:466-476 ('sub-200ms playback start' is only a tool-description string, not measured); EVA_Shared/eva-persistence/src/eva_persistence/stores/audio.py:19-36 (AudioStore is a plain in-memory/Supabase-backed object dict, no CDN/edge cache). grep for latency/perf_counter/200ms across EVA_Shared/tests, EVA_Bureau/tests, EVA_MCP/tests: zero hits. An automatic scheduling trigger for pre-synthesis now genuinely exists (cron fires at the principal's chosen delivery time and drives eva_deliver_draft -> pre_render), but it fires exactly at T-0 (delivery time), not 30 minutes ahead of it as a dedicated warm-up. There is still no CDN/edge cache (just an object store), and no automated measurement or test anywhere asserting sub-200ms playback start -- the '<200ms' claim in the MCP tool description is unverified.
E1-S2 setup/3-signals/close ≤90s NOT STARTED Backend: EVA_Services/EVA_Shared/eva-domain/src/eva_domain/keeper/voice.py:263-271 to_ssml() joins all section bodies into one flat paragraph with a single static prosody wrapper -- no setup/signal/close template. EVA_Services/EVA_Bureau/src/eva_bureau/scheduler.py:282-296 shows the actual brief structure used in production is 4 pillar sections (THE DAY / WORLD / RELATIONS / HOLDING), a different shape than 'setup + 3 signals + close'. Repo-wide grep for duration-cap/90s/wpm/max_duration concepts: zero hits. Frontend: EVA-Flutter-App-Frontend/lib/features/briefing/presentation/widgets/morning_brief_widget.dart:127-132,177-182 renders the same 4-tab (§I-§IV) structure with no duration cap/estimate logic in Dart either. No structural template exists anywhere (backend or Flutter) mapping content to setup/3-signals/close, and there is no duration estimate/cap/enforcement toward a 90-second target in either codebase.
E1-S3 SSML tone by sensitivity NOT STARTED EVA_Services/EVA_Shared/eva-domain/src/eva_domain/keeper/voice.py:263-271 to_ssml() applies one fixed <prosody rate/pitch> template to every draft, with no branching. EVA_Services/EVA_Shared/eva-contracts/src/eva_contracts/contracts/drafts.py:26-30 DraftSection has no sensitivity field at all (only title/body/source_event_ids/citations), so there is nothing in the data model to key an SSML mapping off of. Repo-wide grep for 'sensitivity'/'emphasis' (excluding .venv): zero hits. No sensitivity concept exists anywhere in the domain model (DraftSection/BriefDraft), so a sensitivity-to-SSML-tone mapping cannot exist; to_ssml remains a single static template for every brief regardless of content.
E1-S4 interrupt/scrub/replay PARTIAL Backend: EVA_Services/EVA_Shared/eva-domain/src/eva_domain/renderer/playback.py:1-112 (PlaybackProgressTracker -- checkpoint/percent-consumed tracking only, no pause/skip/replay command handling); EVA_Services/EVA_Shared/eva-domain/src/eva_domain/wiring.py:548-549 is its only non-test caller (instantiated into app.state.playback_tracker, never invoked from a route or MCP tool -- confirmed via repo-wide grep for 'playback_tracker'/'record_progress', which only shows EVA_Bureau/tests/test_phase6.py). Repo-wide grep for scrub/replay/pause/skip/interrupt: the only 'interrupt' hits (keeper/drafts.py:59-66, keeper/editor.py:87-103) are about interrupting a LOCKED draft's content assembly, unrelated to audio playback control. Frontend: EVA-Flutter-App-Frontend/lib/features/briefing/presentation/widgets/morning_brief_widget.dart:49-73,100-125 implements a real play/pause toggle over just_audio with position/duration streams driving a waveform (tapping stops and resets to 0, tapping again restarts from the beginning). Repo-wide grep in lib/ for '.seek(' or 'seekTo': zero hits; grep for replay/interrupt/scrub in lib/: zero hits. Backend playback tracker is still wired into app state with zero real (non-test) callers -- same underlying finding as before, just under wiring.py instead of main.py. Frontend now has genuine play/pause, but no seek/scrub anywhere in the app, no replay-a-section control, and no concept of interrupting the brief mid-playback in either codebase. SSML also still has no explicit per-item boundaries (to_ssml joins all sections into one paragraph).
E1-S5 EOD ≤60s adaptive PARTIAL Backend: EVA_Services/EVA_Bureau/src/eva_bureau/routes/brief_routes.py:416-459 eod_brief() returns accomplishments/pending_items/tomorrow_focus with no draft_id field and no duration budget. EVA_Services/EVA_Shared/eva-contracts/src/eva_contracts/contracts/drafts.py:11-15 BriefType.END_OF_DAY exists as an enum value but nothing wires it into LivingDraftManager/voice_bridge. Frontend: EVA-Flutter-App-Frontend/lib/features/briefing/presentation/screens/eod_brief_screen.dart:1-115 is a plain scrollable text/bullet list screen -- no AudioPlayer, no play button, no draft_id reference, no voice element of any kind (contrast with morning_brief_widget.dart which does play audio). No adaptive last-meeting anchor and no duration budget/enforcement exist in the backend; and, newly confirmed via the frontend, the EOD experience in the app has zero voice/audio component at all -- it's purely a text summary, so the '≤60s adaptive' voice-brief requirement is unaddressed at both layers, more starkly than the backend-only view suggested.
E1-S6 persona consistency NOT STARTED EVA_Services/EVA_Shared/eva-domain/src/eva_domain/keeper/voice.py:160-176 VoiceDeliveryBridge.__init__ takes audio_store/cloud_tts/self_hosted_tts/db -- no voice_profile_manager dependency at all; :263-271 to_ssml() never references any VoiceProfile. VoiceProfileManager (voice.py:12-34,36-158) does exist with tone_register/vocabulary/signature_phrases and IS consulted, but only by a different pillar: EVA_Services/EVA_Shared/eva-domain/src/eva_domain/keeper/scribe.py:172-260 ScribeService.create_document() calls self._voice.prompt_prefix(scope=scope) when drafting written documents -- entirely unrelated to the spoken brief. Frontend: repo-wide grep in EVA-Flutter-App-Frontend/lib/ for voice-profile/persona/onboarding: zero hits. The audio-brief SSML path (VoiceDeliveryBridge.to_ssml) still never consults VoiceProfileManager at all, so spoken-brief persona is not fixed/consistent per principal; the only real consumer of the profile is ScribeService for written documents, a different feature. No onboarding-capture UI exists in Flutter either.
E1-S7 verbal hedging NOT STARTED Repo-wide grep across /Users/g33tansh/Desktop/EVA-Services/EVA_Services (excluding .venv) for 'hedg'/'tentativ'/'i think'/'it seems'/'probably'/'emphasis': zero hits. Repo-wide grep across /Users/g33tansh/Desktop/EVA-App/EVA-Flutter-App-Frontend/lib for the same terms: zero hits. No hedging/tentative-language logic, prompt instruction, or SSML emphasis handling exists anywhere in either the backend or the Flutter frontend.

E2 — Cue Card Surface and Cross-Format Continuity (9 stories, P0)

Frontend (Flutter, /Users/g33tansh/Desktop/EVA-App/EVA-Flutter-App-Frontend) checked for all 9 stories and adds no new coverage: the app never calls the cue-card MCP tool at all (briefing_remote_data_source.dart only hits /brief/today, /brief/eod, /brief/schedule, /brief/{id}/audio — no cue-cards endpoint exists or is called). Instead it has three separate, unrelated card-like surfaces — MorningBriefWidget (tabbed voice/text brief), AlertBanner (ephemeral Sentinel toast), DraftReviewScreen (single-item approve/reject cards for Keeper reply drafts, a different "draft" concept than BriefDraft/DraftSection) — none sharing a grammar or contract, which reinforces rather than closes E2-S6. No batch/undo, device-context, playback-progress, or accessibility affordances exist client-side either.

StoryVerdictEvidenceGap
E2-S1 cards match voice brief PARTIAL Backend: EVA_MCP/src/eva_mcp/registry/calendar.py:92-176 (_handle_get_cue_cards) composes cue cards from calendar_orchestrator + relational_graph + commitment_tracker, filtered by attendee-name matching against the meeting title. EVA_Bureau/src/eva_bureau/routes/brief_routes.py:104-211 (today_brief) and :348-399 (_build_speech_text) compose the voice/text brief from commitment_tracker + relational_graph.all_ghosts + crawler + sentinel with no meeting-attendee filtering at all. Frontend: lib/features/briefing/data/datasources/briefing_remote_data_source.dart:15-23 shows the Flutter app only ever fetches /brief/today and /brief/eod — it never fetches cue cards, so the two paths aren't even reconciled client-side. Two independent composition paths with different source sets and selection logic (attendee/meeting-scoped vs. global ghosts/events/alerts); no shared source-item-set or ordering contract between them. Confirmed unchanged from prior audit — file paths moved (old mcp_routes.py -> registry/calendar.py; old brief route -> eva_bureau/routes/brief_routes.py) but underlying gap identical.
E2-S2 demote already-heard items NOT STARTED Backend: EVA_Shared/eva-domain/src/eva_domain/renderer/playback.py:49-91 defines PlaybackProgressTracker.record_progress/consumed_percent/latest_checkpoint; wired into app.state at EVA_Shared/eva-domain/src/eva_domain/wiring.py:548-549 (app.state.playback_tracker = PlaybackProgressTracker()). grep for 'playback_tracker' across EVA_Bureau/src and EVA_MCP/src returns zero hits — no route or MCP handler ever calls record_progress/consumed_percent, and _handle_get_cue_cards (calendar.py:92-176) never reads it. Frontend: lib/features/briefing/presentation/widgets/morning_brief_widget.dart:76-84 (_markRecited) only advances a local tab index and calls onBriefComplete — it never POSTs playback progress anywhere, and no such endpoint exists in briefing_remote_data_source.dart to call. PlaybackProgressTracker remains fully unwired on the backend (instantiated in app.state, never invoked by any handler), and the Flutter client has no client-side notion of 'already heard' either — 'mark recited' is purely a local UI affordance with no round-trip to the tracker.
E2-S3 provenance trace PARTIAL Backend: EVA_Shared/eva-contracts/src/eva_contracts/contracts/drafts.py:29-30 — DraftSection.source_event_ids and .citations fields exist. EVA_MCP/src/eva_mcp/registry/drafts.py:45-73 (_handle_add_section), line 58: `section = DraftSection(title=title, body=body)` — never populates source_event_ids or citations. EVA_MCP/src/eva_mcp/registry/catalogue.py:208-224 (eva_add_section tool schema) doesn't even expose those two fields as inputs, so they can't be populated via the MCP tool surface at all. Frontend: lib/features/keeper/data/models/draft_item.dart:1-29 (the Keeper 'draft' concept the Flutter app actually renders) has no provenance/citation fields either, and is a different draft type (correspondence reply drafts, /keeper/drafts) unrelated to BriefDraft/DraftSection. Provenance fields exist only as unused schema on BriefDraft/DraftSection; no code path populates them and no UI surface displays them. Path moved (old contracts/drafts.py -> EVA_Shared/eva-contracts/.../drafts.py, coincidentally same line numbers) but finding unchanged.
E2-S4 batch actions + 60s undo PARTIAL Backend: EVA_Shared/eva-domain/src/eva_domain/renderer/batch_undo.py:1-113 (BatchUndoStore, _UNDO_WINDOW_SECONDS=60 at line 21) is fully correct and unit-tested (EVA_Bureau/tests/test_phase6.py:548-589, e.g. test_batch_undo_creates_pending_batch, test_batch_undo_cancels_within_window, test_batch_undo_rejects_after_window_expires). Wired at EVA_Shared/eva-domain/src/eva_domain/wiring.py:551-552 (app.state.batch_undo_store = BatchUndoStore()). grep for 'batch_undo_store' across EVA_Bureau/src and EVA_MCP/src returns zero hits — no route or MCP tool calls record_batch/undo/commit_expired. Frontend: grep for 'batch'/'undo' across lib/ returns zero hits; lib/features/keeper/presentation/screens/draft_review_screen.dart:1-241 only supports single-item approve/reject (_DraftCard with per-item _ActionButton), no multi-select or batch UI of any kind. BatchUndoStore is correct in isolation and covered by unit tests, but has zero callers anywhere in either backend service and zero client-side batch-selection UI to drive it.
E2-S5 confidence visual flag PARTIAL Backend: EVA_MCP/src/eva_mcp/registry/calendar.py:120-135 computes data_confidence/staleness_days/source_count, placed into the response at lines 158-162 as `confidence_indicators`. catalogue.py:484-491 documents this as 'Task #47: data_confidence, staleness_days, source_count'. grep for 'confidence_tier'/'confidence_level'/'confidence_flag' across the whole backend returns zero hits. Frontend: grep for 'confidence' in lib/ only turns up lib/features/cartographer/data/models/sentinel_alert.dart:7,14,21 — a raw double `confidence` field on Sentinel alerts, unrelated to cue cards and also not tiered; cue cards themselves are never fetched by the Flutter client at all (per epic note). Produces a raw numeric confidence value plus staleness/source-count, never converted into a tiered high/medium/low visual flag on either backend or frontend, and there is no client surface rendering cue-card confidence in the first place.
E2-S6 consistent card grammar NOT STARTED Backend: only pre-meeting cue cards exist (EVA_MCP/src/eva_mcp/registry/calendar.py:92-176, `eva_get_cue_cards`). grep for card-type concepts ('drift'+card, 'world_signal', 'pattern', 'CardType') across EVA_Bureau/src, EVA_MCP/src, EVA_Shared/*/src turns up only unrelated hits (drift-as-relationship-concept in arbiter.py, CSS `.card` class in admin_routes.py). Frontend: no 'cue card' concept exists anywhere in lib/ (zero grep hits for 'cue' or 'CueCard'); instead three structurally different, independently-styled surfaces exist — MorningBriefWidget (morning_brief_widget.dart, tabbed §I-IV sections), AlertBanner (alert_banner.dart, ephemeral 6s-auto-dismiss toast for Sentinel alerts), and DraftReviewScreen (draft_review_screen.dart, chip+approve/reject card for Keeper reply drafts). 4 of 5 required card types (drift/world-signal/draft/pattern) still don't exist server-side, and the Flutter app independently confirms there is no unified card grammar client-side either — the three existing card-like widgets share no common component, styling contract, or data shape.
E2-S7 handled-today section NOT STARTED grep -rin "handled_today|handled today" across EVA_Bureau/src, EVA_MCP/src, EVA_Shared/*/src, and migrations/ returns zero hits. Same grep across the Flutter lib/ tree also returns zero hits. No 'handled today' concept exists in either the backend contracts/handlers or the Flutter UI — confirmed absent on both sides.
E2-S8 device-context format default PARTIAL Backend: EVA_Shared/eva-domain/src/eva_domain/renderer/device_context.py:1-61 (DeviceContextStore) is correct in isolation and unit-tested (EVA_Bureau/tests/test_phase6.py:442-465). Its own docstring (lines 9-11) claims the context is 'reported by the Flutter app via the x-eva-device-context header or the /api/v1/device-context endpoint' and that 'The Renderer checks this before triggering audio pre-render or read-back' — grep for 'device-context'/'device_context_store'/'x-eva-device-context' across EVA_Bureau/src and EVA_MCP/src returns zero hits; brief_routes.py's stream_audio (lines 214-298) has no device-context check anywhere. Wired at wiring.py:545-546 into app.state.device_context_store, never read. Frontend: grep for 'device.context'/'deviceContext' across lib/ returns zero hits — no header is ever sent and no client API exists to report device context. DeviceContextStore is correct and tested in isolation but has zero callers on the backend (contradicting its own docstring) and zero corresponding client implementation — no header, no endpoint call, no UI for setting device context.
E2-S9 accessibility NOT STARTED Backend: grep -rin 'wcag|screen_reader|playback_speed|contrast|a11y|accessib' across EVA_Bureau/src, EVA_MCP/src, EVA_Shared/*/src returns zero relevant hits. Frontend: grep for 'Semantics(|semanticLabel|excludeSemantics' and 'playback.speed|playbackSpeed|setSpeed' across lib/ returns zero hits; lib/features/briefing/presentation/widgets/morning_brief_widget.dart's audio controls (lines 327-383) only expose play/pause, no speed control, and no explicit semantics/contrast handling anywhere in the widget. No playback-speed control, WCAG/contrast handling, or explicit screen-reader semantics exist anywhere in the backend or in the Flutter client's brief-playback UI.

E3 — Pre-Meeting Recall and Query Bar (5 stories, P0)

StoryVerdictEvidenceGap
E3-S1 card 30 min before meeting PARTIAL Backend: EVA_MCP/src/eva_mcp/registry/calendar.py:64-89 (_handle_pre_meeting_brief, calls calendar_orchestrator.trigger_pre_meeting_brief which opens a window_start=starts_at-30min draft) and :92-175 (_handle_get_cue_cards, builds cue cards with attendee/commitment matching); EVA_Shared/eva-domain/src/eva_domain/keeper/calendar.py:87-98 (trigger_pre_meeting_brief); EVA_Bureau/src/eva_bureau/scheduler.py:1-343 (full file read) has cartographer_crawl, arbiter_confidant, reporter_poll, editor_auto_lock and a per-principal morning-brief cron job, but zero job tied to calendar events / pre-meeting timing. Frontend: grep for cue_card/cueCard/pre_meeting/calendar_event across EVA-Flutter-App-Frontend/lib returned zero matches -- no Flutter screen or model references pre-meeting cards at all. Nothing schedules trigger_pre_meeting_brief automatically 30 minutes before a meeting -- it only fires if an agent explicitly calls the eva_pre_meeting_brief MCP tool. Attendee matching in _handle_get_cue_cards is still crude case-insensitive substring matching of relationship names against the meeting title (registry/calendar.py:111,117). Additionally, the Flutter app has no cue-card/pre-meeting UI surface whatsoever, so even if the backend fired, there's nowhere for the user to see it.
E3-S2 verbatim recall + citations NOT STARTED Backend: EVA_MCP/src/eva_mcp/registry/keeper.py:122-143 `_handle_recall_facts` (the MCP tool an agent/OpenClaw calls during conversational recall) builds its response dict with only fact_id/predicate/object_value/confidence -- citations are dropped even though EVA_Shared/eva-contracts/src/eva_contracts/contracts/memory.py:27 defines `citations: list[Citation]` on MemoryFact and catalogue.py:69-73 promises 'Returns facts with their citations.' EVA_Bureau/src/eva_bureau/consumers.py:125-141 `_on_communication` -- the only automatic fact-writing path from real incoming communications -- never passes a `citations=` argument to `memory_service.write_fact`, so facts written by the live ingestion pipeline never carry a citation to begin with. Separately, EVA_Bureau/src/eva_bureau/routes/keeper_routes.py:284-293 (`/keeper/memory/search`) and :61-70 (`/keeper/memory/facts/{subject}`) do serialize `citations` via `model_dump` if present, and the Flutter EVA-Flutter-App-Frontend/lib/features/memory/presentation/screens/memory_search_screen.dart:168-172 renders `result.citations` when non-empty. The conversational recall path an EVA user would actually invoke (eva_recall_facts MCP tool) still strips citations outright -- unchanged bug. Even the alternate REST+Flutter search path that does wire citations end-to-end at the code level can never show a citation in practice, because the only automatic write path (KeeperConsumer._on_communication) never attaches one. No caller anywhere populates citations for facts written from real events, so 'verbatim recall + citations' cannot function today regardless of entry point.
E3-S3 regenerates on new info NOT STARTED Backend: EVA_Shared/eva-domain/src/eva_domain/keeper/drafts.py:36-51 `add_section` only appends a section and bumps `updated_at`/state to ASSEMBLING; no event-driven trigger anywhere calls it automatically. EVA_Shared/eva-domain/src/eva_domain/keeper/editor.py:78-115 `inject_p0_alert` is the only mechanism that injects a section into an existing (possibly locked) draft, but it is purely a manually-invoked MCP tool (EVA_MCP/src/eva_mcp/registry/drafts.py:141-155 `_handle_inject_p0_alert`) -- nothing in EVA_Bureau/src/eva_bureau/consumers.py or scheduler.py subscribes to new-info events (new memory facts, relationship drift, alerts) to auto-refresh an already-open pre-meeting draft. Frontend: grep across EVA-Flutter-App-Frontend/lib for pre_meeting/calendar_event/cue_card returned nothing, so there is no 'updated since' indicator or draft-refresh UI on the Flutter side either. Same as before: nothing subscribes to events to auto-refresh an open (especially pre-meeting) draft; no 'updated since' indicator exists in backend or frontend.
E3-S4 query bar prominence PARTIAL Frontend (checked directly, not assumed out of scope): EVA-Flutter-App-Frontend/lib/features/memory/presentation/screens/memory_search_screen.dart:1-136 is a real search-bar screen (TextField + search icon, calls memoryRepositoryProvider.searchMemory), but it is only reachable from lib/features/settings/presentation/widgets/settings_widget.dart:420-437, buried as a `_NavRow` under the 'Review' section of Settings (Home -> Folio tab -> scroll -> tap 'Memory Search'). Separately, lib/core/widgets/pillar/pillar_chat_page.dart:561-659 provides a per-pillar chat input bar (text + voice) reachable in 2 taps from home (Constellation tab -> tap a pillar tile), which functions as an ask-EVA-anything box but is scoped to one pillar's conversation, not a unified query bar. lib/features/home/presentation/screens/home_screen.dart:92-186 and lib/features/home/presentation/widgets/controls_bar.dart:1-66 (the persistent bottom nav: Atlas/Brief/Constellation/Folio) contain no search/query affordance at all. Backend: still no dedicated query-bar contract/endpoint (only per-pillar chat and /keeper/memory/search). Two query-like UI primitives exist (a buried memory-search screen and a per-pillar chat box) but neither is the prominent, globally-accessible query bar the story calls for -- one requires drilling into Settings, the other is scoped per-pillar and still 2 taps from the home screen with no persistent visual affordance (e.g. on the Atlas/home screen or nav bar).
E3-S5 privileged -> cue card never voice PARTIAL Backend: EVA_Shared/eva-domain/src/eva_domain/keeper/voice.py:301-304 `_adapter_for_zone` -- `if zone == DataZone.GENERAL: return self.cloud_tts; return self.self_hosted_tts` -- routes PRIVILEGED/MNPI zones to a different (self-hosted) TTS vendor, but `pre_render_manifest` (voice.py:209-261) unconditionally calls `adapter.synthesize(...)` at line 220 regardless of zone -- audio is always generated. `LivingDraftManager.deliver()` (eva-domain/src/eva_domain/keeper/drafts.py:68-89) unconditionally calls `self.voice_bridge.pre_render(...)` for every draft with no zone-based skip. A separate, unrelated device-context voice-suppression mechanism exists (eva-domain/src/eva_domain/renderer/device_context.py:1-61, `DeviceContextStore.voice_suppressed` for in_meeting/public_place/do_not_disturb) but grep confirms it is wired only into app.state in wiring.py:545-546 and referenced only in tests -- no route or voice-delivery call site ever invokes `voice_suppressed()`, so it provides no actual suppression today, zone-based or otherwise. Confirmed via grep that PolicyGate is not referenced anywhere in eva-domain/keeper/ (voice.py, drafts.py), so this session's PolicyGate zone-check removal is orthogonal to this specific mechanism. Still just a TTS-vendor swap, not a true 'never voice' suppression -- privileged/MNPI cue-card content is always synthesized to audio. No private-environment confirmation flow exists, and the one voice-suppression mechanism that does exist in the codebase (device-context based) is dead code, unwired to any endpoint.

E4 — Post-Meeting Voice Debrief (4 stories, P1)

All 4 still NOT_STARTED/PARTIAL as a coherent "post-meeting voice debrief" feature; a generic tap-to-record voice-input affordance now exists in the Flutter chat UI (any pillar), but it is not scoped to meetings/debriefs, has no duration cap, no commitment/tag/signal extraction, and no confirm/cancel gate — so it doesn't newly satisfy any story outright.

StoryVerdictEvidenceGap
E4-S1 tap+speak≤5min, silent transcribe PARTIAL Backend: EVA_Bureau/src/eva_bureau/routes/stt_routes.py:32-95 — generic Whisper (nabh.cloud) proxy, only a 25MB size cap (_MAX_AUDIO_MB=29), no duration cap, no 'debrief' concept. Frontend (new this audit): EVA-Flutter-App-Frontend/lib/core/widgets/pillar/pillar_chat_page.dart:74-107,612-615 implements a real tap-and-hold-to-record mic button (onTapDown starts AudioRecorder, onTapUp/onTapCancel stops and calls chat_repository.transcribe()), and lib/features/chat/data/datasources/chat_remote_data_source.dart:25-32 posts the audio to /stt and returns the transcript, which is inserted into the text input silently (no auto-send, no TTS read-back) — pillar_chat_page.dart:97-102. No 5-minute cap exists anywhere (client records until manual release; server only checks file size, not duration). No 'debrief' or 'meeting' concept — this is a generic per-pillar chat voice-input button, not scoped to post-meeting capture, and nothing links it to LivingDraftManager (eva-persistence/src/eva_persistence/persistence/draft_store.py).
E4-S2 read-back + correction loop NOT STARTED Backend: EVA_Shared/eva-domain/src/eva_domain/keeper/voice.py:160-311 (VoiceDeliveryBridge, ReadBackState enum lines 307-311) and EVA_Bureau/src/eva_bureau/routes/brief_routes.py:301-341 (report_read_back/get_read_back) implement read-back state purely as a client-reported TTS-playback acknowledgment for a brief EVA is speaking out loud (pending/heard/proceeding/dismissed) — confirmed by the route docstring at brief_routes.py:307-313 ('Client-reported playback acknowledgment... once the device has actually finished playing a brief's audio'). Zero hits for 'commitment'/'tag'/'signal' extraction from speech anywhere in EVA_Services (grepped repo-wide). Frontend: pillar_chat_page.dart's post-transcribe flow only drops raw transcript text into an editable TextField (line 99-101) for the user to manually edit before hitting send — there is no structured 'here's what I heard was committed, confirm or correct' loop, no audio read-back, and it isn't debrief-specific. Not wired to any debrief flow; no commitment/tag/signal extraction from speech on either backend or frontend. The existing ReadBackState/brief_routes machinery is a different feature (brief-delivery confirmation) and the Flutter free-text edit box is generic chat-input correction, not a debrief correction loop over extracted items.
E4-S3 confirmed debrief updates surfaces NOT STARTED Backend: EVA_Bureau/src/eva_bureau/routes/chat_routes.py:1-295 forwards any pillar chat message (including a voice-transcribed one from the Flutter mic button) straight to OpenClaw or a keyword fallback; OpenClaw has access to eva_create_commitment (EVA_MCP/src/eva_mcp/registry/catalogue.py:42-54, handler at EVA_MCP/src/eva_mcp/registry/keeper.py:91-101) which creates a commitment immediately with no confirmation step and no 'debrief' framing — 'source' even documents a 'meeting' example value at catalogue.py:48 with zero gating logic behind it. Frontend: no meeting/debrief-specific screen exists in lib/features/ (only settings, home, memory, chat, auth, briefing, cartographer, arbiter, keeper — checked via directory listing). Depends on S1/S2, neither of which exist as a debrief concept. The generic chat pipeline that does exist can update surfaces (commitments) but does so unconfirmed and ungated, which is the opposite of 'confirmed debrief updates surfaces' — there is no debrief object whose confirmation triggers surface updates.
E4-S4 mandatory read-back, cancel discards+logs NOT STARTED EVA_Shared/eva-contracts/src/eva_contracts/contracts/audit.py:12-29 (AuditAction enum) still has no DEBRIEF_* action of any kind (only MCP_TOOL_CALLED, POLICY_GATE_ALLOWED/REFUSED, CROSS_ZONE_ATTEMPT, EVENT_PUBLISHED, STORE_WRITTEN, FORGET_STARTED/COMPLETED, ACTION_LADDER_DECISION, THREAD_ROUTED, ALWAYS_ROUTE_FIRED, DRIFT_EVENT_FIRED, CONFIDANT_FLAG_FIRED, BRIEF_PLAYBACK_PROGRESS). EVA_MCP/src/eva_mcp/registry/keeper.py:91-101 confirms eva_create_commitment commits directly with no mandatory-confirm/cancel branch. Frontend: pillar_chat_page.dart has no cancel/discard affordance tied to a debrief — the mic button's onTapCancel (line 615) just stops+transcribes the same as onTapUp (no discard path), and there is no debrief-cancel screen anywhere in lib/features/. No debrief cancel/discard flow exists on backend or frontend; no DEBRIEF_* audit action to log a discard against even if one existed.

E5 — Inbound Triage (CoVe) and Draft Queue (8 stories, P0)

Backend logic for this epic is byte-for-byte the same as the prior audit (single git commit "Restructured Codebase from EVA-Middleware" moved files with no logic changes: correspondent.py -> EVA_Shared/eva-domain/src/eva_domain/connectors/correspondent.py, cove.py -> EVA_Shared/eva-domain/src/eva_domain/intelligence/cove.py, mcp_routes.py split into EVA_MCP/src/eva_mcp/registry/correspondent.py). The Flutter app's "Draft Review" screen (keeper feature) is NOT this epic's draft queue -- it lists/approves/rejects LivingDraftManager morning-brief BriefDraft objects via /keeper/drafts, a different feature entirely, and its DraftItem.fromJson fields (subject/content/pillar/draft_type) don't even match BriefDraft's actual JSON shape (draft_id/state/sections/brief_type). Correspondent's own queue_for_principal() (the real E5 draft/escalation queue) is never exposed via any REST or MCP route and has zero Flutter coverage.

StoryVerdictEvidenceGap
E5-S1 CoVe verifier, conservative-on-disagreement PARTIAL Backend only: EVA_Shared/eva-domain/src/eva_domain/connectors/correspondent.py:532-582 verify_action() re-checks the candidate's own classification signals (MNPI unverified, high-tier sender + low confidence) and only ever raises the action rank via _LADDER_ORDER (lines 545-582) -- a single self-check, not a dual-scorer disagreement pattern. The actual dual-scorer CoVe pattern lives in EVA_Shared/eva-domain/src/eva_domain/intelligence/cove.py (CoVePatternRunner, lines 4-45, computes confidence_delta between primary/verifier scorers). Grep confirms cove.py is imported only by EVA_Shared/eva-domain/src/eva_domain/cartographer/relevance.py:6 and EVA_Shared/eva-domain/src/eva_domain/arbiter/tone.py:6, and by wiring.py:686 -- never by correspondent.py. Same gap as before, just at new paths: correspondent's "CoVe" is a single self-re-check that can only escalate the ladder rank, not the dual-scorer disagreement/attenuation pattern implemented in intelligence/cove.py, which remains wired to Cartographer and Arbiter but never to Correspondent.
E5-S2 always-route bypasses classification+CoVe PARTIAL Backend only: correspondent.py process_thread() (lines 586-632) always calls self.classify() at line 622 before checking always-route in select_action() (lines 449-457); CoVe (verify_action) is skipped only when candidate.action == ALWAYS_ROUTE (lines 625-628). _ALWAYS_ROUTE_SIGNALS[AlwaysRouteCategory.OUT_OF_PATTERN] is an empty list at line 215, and _always_route_check() (lines 221-226) skips categories with falsy/empty signal lists -- so OUT_OF_PATTERN can never match via that loop despite the comment at lines 216-217 claiming it's "detected structurally"; grep confirms no separate structural check for OUT_OF_PATTERN exists anywhere in the file. Bypass still only skips CoVe verification, not the 6-dimension classification pass that runs unconditionally first. OUT_OF_PATTERN remains a dead category with zero detection logic despite being declared in AlwaysRouteCategory.
E5-S3 drafts use voice profile+Memory NOT STARTED Backend: correspondent.py's ActionDecision dataclass (lines 114-123) has no draft-text/body field, and select_action()/verify_action() never produce reply text -- only an enum action + reasoning string. Grep for voice_profile/ScribeService across correspondent.py returns zero hits. A ScribeService does now exist (EVA_Shared/eva-domain/src/eva_domain/keeper/scribe.py:166-292, create_document() at line 225 does call self._voice.prompt_prefix() and self._memory.recall_subject()) but it drafts board decks/investor memos/contract reviews (DocumentType enum, lines 125-131), not inbox replies, and is never referenced by correspondent.py (confirmed via grep). A LivingDraftManager also exists (EVA_Shared/eva-domain/src/eva_domain/keeper/drafts.py) but it manages morning-brief BriefDraft objects (sections/state/brief_type), also unrelated to Correspondent's ActionDecision. Frontend: the Flutter Draft Review screen (lib/features/keeper/presentation/screens/draft_review_screen.dart) and its DraftItem model (lib/features/keeper/data/models/draft_item.dart) call keeperRepository.listDrafts()/approveDraft()/rejectDraft() -> GET/POST /keeper/drafts (EVA_Bureau/src/eva_bureper/routes/keeper_routes.py:236-273), which is backed by draft_manager.list_for_principal() -- i.e. LivingDraftManager's BriefDraft objects, not Correspondent's email drafts. correspondent.py's own queue_for_principal() (line 665) that would surface DRAFT_AND_HOLD decisions is never called from any route (grep for queue_for_principal across the repo shows it used only in EVA_Shared/tests/test_correspondent.py:416). No code path anywhere turns a Correspondent DRAFT_AND_HOLD decision into actual reply text, let alone one informed by voice profile or Memory. The Flutter Draft Review UI that superficially looks like this feature is wired to an entirely different backend concept (morning-brief Living Drafts), and its own field mapping (subject/content/pillar/draft_type) doesn't match BriefDraft's real JSON shape (draft_id/state/sections/brief_type/window_start/window_end) -- so even that UI would render mostly empty strings against the real endpoint.
E5-S4 dictated reply read back, confirm/cancel NOT STARTED Backend: grep -rni "dictat" across all of EVA_Services (excluding .venv) returns zero matches; grep for "confirm_dispatch"/"voice_dictated"/"read.?back" returns zero matches. Frontend: grep -rni "dictat" across all of lib/ in EVA-Flutter-App-Frontend (excluding build/) returns zero matches; no draft/keeper/chat screen implements a read-back-and-confirm/cancel flow. Feature does not exist in either codebase -- no dictation capture, no read-back, no confirm/cancel gate.
E5-S5 sender opt-out DONE Backend: correspondent.py SenderOptOutStore (lines 128-179) and process_thread() (lines 586-632) check self.opt_out_store.is_opted_out(thread.sender_email) at line 593 as the very first step, before self.classify() is ever called (line 622) -- confirmed by test EVA_Shared/tests/test_correspondent.py:294 test_opted_out_sender_bypasses_always_route, which asserts an opted-out sender's thread produces NOISE even when the subject/body would otherwise match an ALWAYS_ROUTE keyword (e.g. board). MCP wiring: EVA_MCP/src/eva_mcp/registry/correspondent.py:_handle_route_thread (lines 61-77) lets a caller register an opt-out via eva_route_thread (sender_optout_email param), and the tool is registered in EVA_MCP/src/eva_mcp/registry/catalogue.py:536 / dispatch.py:122 (eva_route_thread -> _handle_route_thread). None found for the backend contract itself. No Flutter UI exposes sender opt-out management (no screen calls eva_route_thread with sender_optout_email or a corresponding REST route), but the story as scoped in the prior audit was backend-only ("Checked pre-classification, exactly as required") and that behavior still holds correctly.
E5-S6 DKIM/SPF + new-domain quarantine NOT STARTED Backend: grep -rni "dkim\|spf\b" across all .py files in EVA_Services (excluding .venv) returns zero matches. grep -rni "quarantine" returns one unrelated hit (EVA_Shared/tests/test_keeper.py:66, test_identity_resolution_quarantines_low_confidence_merges -- about identity-resolution merge confidence, not email quarantine). No memory/quarantine module exists under eva-domain or eva-persistence. Frontend: grep -rni "dkim\|quarantine" across lib/ in the Flutter app returns zero matches. No DKIM/SPF verification, no new-sender-domain detection, and no quarantine store exist in either codebase.
E5-S7 MNPI blocks all auto-action, per-message confirm PARTIAL Backend: correspondent.py select_action() lines 459-466 force ActionStep.ESCALATE whenever classification.sensitivity == "mnpi"; verify_action() lines 548-550 also raise the rank to at least ESCALATE if MNPI is detected post-hoc but the candidate ranked lower. _classify_sensitivity() (lines 260-266) is pure keyword matching on {"insider", "material non-public", "mnpi", "tipping", "undisclosed deal"}. Grep for "confirm" across EVA_MCP and EVA_Bureau routes (excluding admin UI copy-to-clipboard confirm dialogs and OAuth status polling) shows no per-message confirmation-gate endpoint or flow anywhere in the backend; Flutter grep for "escalate"/"mnpi" outside memory_search_screen.dart's unrelated category label (line 187) shows no confirmation UI either. ESCALATE still just means "surfaced to principal as a P0 alert" (same ladder rung used for board/legal/regulatory senders) -- there is no distinct, separately-gated per-message confirm-before-send mechanism for MNPI content specifically, and detection remains trivially evadable keyword matching (e.g. writing about an undisclosed acquisition without the literal words "mnpi"/"insider"/"material non-public" sails through as sensitivity="general").
E5-S8 whistleblower non-demotable, P0 incident PARTIAL Backend: AlwaysRouteCategory.WHISTLEBLOWER (correspondent.py:83) and its keyword list (lines 211-214) correctly route whistleblower threads to ALWAYS_ROUTE (confirmed by test EVA_Shared/tests/test_correspondent.py:211-227 test_always_route_categories_bypass_ladder, parametrized case for WHISTLEBLOWER at lines 211-215). But route_thread() (correspondent.py:634-657) takes existing = self._decisions.get(thread_id) at line 642 and unconditionally overwrites it with ActionStep.ROUTE_TO_DELEGATE at lines 645-653 -- no check anywhere in route_thread() for existing.action == ActionStep.ALWAYS_ROUTE, and EVA_MCP/src/eva_mcp/registry/correspondent.py:_handle_route_thread (lines 79-95) calls correspondent.route_thread() directly with the same lack of guard. grep -rni "incident" across the repo returns only the keyword-list string "security incident" (correspondent.py:204) -- no incident-logging system exists anywhere. A whistleblower (or any ALWAYS_ROUTE) decision can still be silently demoted to ROUTE_TO_DELEGATE via eva_route_thread/route_thread with no guard rejecting the override and no P0 incident record created; test_route_thread_overrides_decision_to_delegate (test_correspondent.py:382-394) only exercises overriding a routine thread, not an ALWAYS_ROUTE/whistleblower one, so this gap is untested as well as unfixed.

E6 — World Reading and Sentinel Alerts (12 stories, P1)

StoryVerdictEvidenceGap
E6-S1 5-min regulatory SLA NOT STARTED (and the existing 4h crawl job is broken) Backend: EVA_Bureau/src/eva_bureau/scheduler.py:29-36 wires `_cartographer_crawl` on a `hours=settings.cartographer_crawl_interval_hours` interval; EVA_Shared/eva-core/src/eva_core/core/config.py:97-100 defaults `cartographer_crawl_interval_hours=4`; scheduler.py:46-52 hardcodes reporter poll at `hours=4`. No 'tier'/'regulatory'/'SLA'/'latency_budget' concept anywhere (grep across EVA_Services and the Flutter app returns zero hits besides unrelated enum values/comments). Additionally, scheduler.py:161 calls `relevance_engine.score(text=..., scope=..., context_tags=...)` but the object wired at that name (eva_domain/wiring.py:444-445) is a `RelevanceScenarioEngine` whose only method is `score_event(*, event, interest_tags, zone)` (eva_domain/cartographer/relevance.py:24) — every call raises AttributeError, silently caught by the bare except at scheduler.py:166-168, so even the existing 4h crawl currently never produces an alert candidate. No test covers `_cartographer_crawl` (confirmed via grep) so this regression is undetected by the suite. No tier-one-tag concept, no real-time path, no latency budget — identical gap to prior audit, just re-confirmed on new paths. Also newly found: the cartographer crawl job itself is non-functional at runtime due to a method-name mismatch introduced by the restructuring, which is a separate/deeper problem than the original 'interval too slow' finding.
E6-S2 geopolitical corroboration NOT STARTED Backend: `grep -rin "geopolitic"` across EVA_Services (excluding .venv) returns zero hits. Flutter: same grep across lib/ returns zero hits. eva_domain/cartographer/crawler.py:79-100 `corroborate()` merges provenance/trust_score but has no jurisdiction or source-type gating logic, and is itself called only from EVA_Shared/tests/test_cartographer.py:34 — not wired into any production route, job, or MCP handler. No jurisdiction/source-type gating exists at all; the generic corroboration method itself is dead code in production (only exercised by its unit test).
E6-S3 contradictions preserved PARTIAL Backend: eva_domain/cartographer/crawler.py:29 `contradictions: tuple[str, ...] = ()` field on CrawledEvent, populated only from the caller-supplied `contradictions` param at crawler.py:51,72; EVA_Bureau/src/eva_bureau/routes/cartographer_routes.py:26-33 `IngestEventRequest.contradictions: list[str] = []` shows it is only ever set if the API caller manually supplies it; eva_domain/cartographer/relevance.py:48 `evidence_anchors=event.provenance + event.contradictions` just carries the field through into scenario output. No code path anywhere auto-detects a contradiction (e.g., conflicting facts between two corroborating sources) and populates this field — it defaults to empty. Field exists to carry contradictions but nothing detects and populates them automatically — always empty unless a caller manually passes contradictions in on ingest.
E6-S4 preempt in-flight synthesis PARTIAL Backend: eva_domain/renderer/priority_queue.py:19-25 (RenderPriority enum with P0_ALERT=0) and :102-111 (`_priority_for`) implement real ordering, and it's instantiated at eva_domain/wiring.py:542-543 (`app.state.renderer_queue = RendererPriorityQueue()`), but `grep -rn "renderer_queue"` across EVA_Services shows zero other reference — it is never pushed to or popped from anywhere outside its own file/tests, i.e. dead-wired. A real, tested preemption path does exist elsewhere: eva_domain/keeper/editor.py:78-114 `inject_p0_alert()` calls eva_domain/keeper/drafts.py:59-65 `interrupt_locked()` to force a P0 alert section into an already-LOCKED draft, bypassing the lock; exposed as MCP tool `eva_inject_p0_alert` (EVA_MCP/src/eva_mcp/registry/drafts.py:141-155, catalogue.py:669-681) and tested in EVA_Shared/tests/test_editor.py:124-147. Flutter has no involvement (no priority_queue/preempt/inject_p0 references in lib/). priority_queue.py is confirmed dead code (wired to app.state but never used). The real mechanism (inject_p0_alert/interrupt_locked) operates only on draft *state* (OPEN/ASSEMBLING/LOCKED) — there is no concept of an actively-running/concurrent render or synthesis task that gets cancelled mid-execution, and no sub-1s latency measurement exists anywhere in code or tests.
E6-S5 calibration fast-track propagation NOT STARTED Backend: eva_domain/calibration/push.py:31-76 `CalibrationPushService` (schedule_push/register_callback/debounce) unchanged in substance; instantiated and wired at eva_domain/wiring.py:557-560 (`app.state.calibration_push = calibration_push`) in both EVA_Bureau/src/eva_bureau/main.py:87 and EVA_MCP/src/eva_mcp/main.py:63. `grep -rn "calibration_push\b"` across EVA_Services (excluding tests) shows it is only ever assigned to app.state, never called — EVA_Bureau/src/eva_bureau/routes/router.py:78-135 (the calibration endpoints: upsert_interest_tag, remove_interest_tag, set_threshold_adjustment, record_correction) call only `_calibration_service(request)` methods, never `calibration_push.schedule_push()` or `.register_callback()`. Zero call sites for schedule_push()/register_callback() outside its own file and its unit test — now confirmed to include being wired into app.state without ever being invoked from any actual calibration-change endpoint.
E6-S6 source-health signals PARTIAL Backend: eva_domain/cartographer/reporter.py:37-48 MonitoredSource records last_polled_at/last_item_count/error, populated in `_poll_one` (reporter.py:241-247). NEW this session: EVA_MCP/src/eva_mcp/registry/cartographer.py:172-192 `_handle_list_sources` (the `eva_list_sources` MCP tool, registered in dispatch.py:47,141 and described in catalogue.py:705-711) returns last_polled_at/last_item_count/error for every source — this is a real, reachable surfacing path that did not exist as cited evidence in the prior audit. However catalogue.py:706 describes the tool only as 'List all registered intelligence sources' (no mention of health), and no code anywhere computes an expected-cadence-vs-actual comparison or triggers a health alert. Flutter: lib/features/cartographer/data/datasources/cartographer_remote_data_source.dart has only `listEvents()`/`listAlerts()` — zero source-management or health UI. Raw health fields are now genuinely readable via the eva_list_sources MCP tool (an actual caller/agent could act on them), but there is still no cadence-expectation model, no dedicated health channel, and no automatic surfacing/alerting when a source repeatedly fails — and no Flutter UI exposes any of it.
E6-S7 source opt-out NOT STARTED Backend: eva_domain/cartographer/reporter.py:176-181 `remove_source()` is still a manual per-principal delete of a MonitoredSource registration — same behavior as prior audit's `reporter.py:106-108`. A separate, unrelated opt-out mechanism exists for email correspondence — eva_domain/connectors/correspondent.py:128-181 `SenderOptOutStore` (per-email, used by the Correspondent/Keeper email pillar, wired at eva_domain/wiring.py:276-279) — but this is scoped to inbound email senders, not crawled world-event sources, and is not reachable from reporter.py/crawler.py. Flutter: cartographer_remote_data_source.dart exposes zero source-registration or opt-out methods at all. No opt-out-address mechanism exists for crawled intelligence sources specifically (as distinct from a principal deleting their own subscription) — same gap as before; the SenderOptOutStore that does exist is for a different domain (email) entirely and does not apply here. No Flutter UI for source management exists either.
E6-S8 cooldown on event signature DONE Backend: eva_domain/cartographer/sentinel.py:33-34 computes `fingerprint = self.fingerprint(fingerprint_parts)` from the caller-supplied `fingerprint_parts` tuple in `route_candidate()`; :46 checks `self._in_cooldown(fingerprint=fingerprint, now=now)`; :83-85 `_in_cooldown` keys purely on `fingerprint` against `_last_by_fingerprint`. The persisted variant EVA_Shared/eva-persistence/src/eva_persistence/persistence/sentinel_store.py:100,113,172-174 (`SupabaseSentinelStore`) mirrors identical cooldown-by-fingerprint logic. Confirmed by passing tests in EVA_Shared/tests/test_cartographer.py. Keys correctly on caller-supplied fingerprint, not source — same caveat as prior audit, still true.
E6-S9 daily ceiling + rollup summary PARTIAL Backend: eva_domain/cartographer/sentinel.py:15 `daily_ceiling: int = 20`, :50-53 real ceiling check that suppresses and sets `reason="daily_ceiling"`; suppressions list at :18,71 only appends bare reason strings. `grep -rn "rollup"` across all of EVA_Services returns zero hits, and nothing reads `.suppressions` to build any user-facing summary. Flutter: lib/features/home/presentation/widgets/alert_banner.dart and lib/features/cartographer/data/datasources/sentinel_websocket_data_source.dart forward each SentinelAlert from the WebSocket 1:1 with no batching/rollup logic — confirms the gap holds end-to-end, not just backend. No 'multiple urgent events today' rollup message is ever generated on the backend, and the Flutter alert banner/websocket pipeline also just streams individual alerts with no aggregation — suppressed alerts vanish with no summary anywhere in the stack.
E6-S10 stale-urgent demotion NOT STARTED Backend: `grep -rin "fast_track|slow_track|demote|demotion"` across EVA_Services returns zero hits. Flutter: same grep across lib/ returns zero hits. Zero fast_track/slow_track/demote logic anywhere in either codebase.
E6-S11 geopolitical off-by-default NOT STARTED Backend: same zero-hit grep for 'geopolitic' as E6-S2; no opt-in/category flag concept exists in the calibration interest-tag system (checked eva_domain/wiring.py and router.py calibration endpoints). Flutter: zero hits for 'geopolitic' anywhere in lib/. No geopolitical category or opt-in flag exists at all, in either codebase.
E6-S12 scenario alternatives+ranges, advisory PARTIAL -- two colliding ScenarioAnalysis classes, one still directive Backend: eva_domain/cartographer/relevance.py:10-16 defines a `ScenarioAnalysis` dataclass with `probability_range`/`evidence_anchors`/`route` (no recommendation field) — real probability-range, advisory-only output, correctly built at relevance.py:44-50. A second, colliding `ScenarioAnalysis` dataclass is defined at eva_domain/cartographer/evaluator.py:47-53 with a `recommendation: str` field; `evaluate_scenario()` (evaluator.py:148-214) populates it with directive text such as `rec = "Activate contingency plan. Escalate to board-level oversight."` at evaluator.py:196-197, directly violating the 'no recommended actions, only probability ranges' requirement. Flutter: lib/core/widgets/pillar/pillar_models.dart:264-269 only has static placeholder copy ('NO ACTIVE SCENARIOS' / 'Scenario analysis will appear here as events cluster.') — no real data binding to either backend ScenarioAnalysis class. The evaluator.py ScenarioAnalysis still emits directive recommended actions, colliding in name with the correct, advisory-only relevance.py ScenarioAnalysis — same structural problem as before. Flutter has no real scenario UI at all (only unbound placeholder text), so the frontend doesn't resolve or expose the ambiguity either.

E7 — Relational Reading and Drift Surfacing (5 stories, P1)

Files relocated this session (relationships.py, tone.py, confidant.py, arbiter_routes.py → EVA_Shared/eva-domain, EVA_Bureau) but every gap the prior audit found is still present verbatim. New this pass: the production-wired store (SupabaseRelationshipStore, used whenever supabase_db is configured per wiring.py:409-411) duplicates the same cadence/tone bug independently of the in-memory RelationalGraphService, so the S2 defect exists in two parallel implementations. The Flutter app now has a full "arbiter" feature module with a "Drift" tab, but it only calls GET /arbiter/relationships/ghosts (cadence-only pinned ghosts) — it never calls the tone-score, evaluate_drift, golden-baseline, or confidant-report endpoints, so none of the S1/S2/S3/S5 gaps are closed by the frontend; S4 (pinned ghosts) is the only one the Flutter UI actually renders, still without any Cartographer/Memory corroboration.

StoryVerdictEvidenceGap
E7-S1 drift card w/ composer PARTIAL Backend: EVA_Shared/eva-domain/src/eva_domain/arbiter/relationships.py:101-153 (evaluate_drift, returns RelationshipDriftEvent with only drift_id/relationship_id/composite_score/threshold/reason/confidence_layers); EVA_Bureau/src/eva_bureau/routes/arbiter_routes.py:113-130 (GET /arbiter/relationships/{id}/drift returns {"drift_detected": bool, "event": event.__dict__} — no original-message or composer field); EVA_MCP/src/eva_mcp/registry/arbiter.py:133-169 (_handle_evaluate_drift, same shape, agent-facing). Frontend: EVA-Flutter-App-Frontend/lib/features/arbiter/data/models/relationship_item.dart:1-32 (only id/personId/detail/overdueDays/expectedContactDays parsed — no message/composer fields exist to parse even if the API sent them) and lib/core/widgets/pillar/pillar_chat_page.dart:670-735 (_CardWidget renders title/body/priority only; no onTap, no button, no TextField — cards are read-only, not tappable into a composer). The 'Drift' tab itself (lib/features/arbiter/presentation/providers/arbiter_providers.dart:17-55) is fed only by listGhosts() → GET /arbiter/relationships/ghosts, i.e. cadence-only pinned ghosts, not the tone+cadence evaluate_drift composite at all. No 'no external explanation' safeguard exists anywhere in the drift code; no original-message or inline-composer fields exist in the API response or in any Flutter model/widget. The one UI surface that exists (the 'Drift' tab) is powered by the cadence-only ghosts endpoint, not the full drift-evaluation endpoint, and renders a static, non-interactive card with no reply/composer affordance.
E7-S2 cadence primary, tone corroborator NOT STARTED -- implemented backwards Backend (in-memory fallback): EVA_Shared/eva-domain/src/eva_domain/arbiter/relationships.py:113 `raw = max(cadence_score, tone_drift)` inside evaluate_drift(). Backend (production path, used whenever supabase_db is configured per EVA_Shared/eva-domain/src/eva_domain/wiring.py:409-411): EVA_Shared/eva-persistence/src/eva_persistence/persistence/relationship_store.py:169 `composite = max(cadence_score, tone_drift)` in SupabaseRelationshipStore.evaluate_drift — an independent duplicate of the identical bug. MCP tool EVA_MCP/src/eva_mcp/registry/arbiter.py:139 passes tone_drift straight through as a raw float from the caller. Frontend: no Flutter code calls the drift endpoint or passes tone_drift at all (grep of lib/features/arbiter confirms only listGhosts() is called), so the bug is unreachable via the app UI but is live and agent-reachable via the eva_evaluate_drift MCP tool and the REST /arbiter/relationships/{id}/drift route. Both the in-memory RelationalGraphService and the Supabase-backed production store take the max() of cadence and tone rather than treating cadence as primary/tone as corroborator — tone alone can fire drift with cadence completely normal, the exact behavior the spec forbids, and it now exists in two separately-maintained implementations.
E7-S3 slow-burn via Golden Baseline PARTIAL Backend: EVA_Shared/eva-domain/src/eva_domain/arbiter/tone.py:38-60 (GoldenBaselineImmutableError, ToneDimensions.drift_from) and :146-185 (set_golden_baseline/golden_baseline/golden_baseline_locked_until, with Supabase persistence at :100-138) — genuinely real, 90-day-locked baseline machinery. But evaluate_drift() in both relationships.py:101-108 and relationship_store.py:163-171 only accepts a scalar `tone_drift: float` argument and never calls tone_engine.golden_baseline()/drift_from() internally; the MCP tool EVA_MCP/src/eva_mcp/registry/arbiter.py:139 requires the caller to compute and pass tone_drift itself. Confidant's slow-burn detector, EVA_Shared/eva-domain/src/eva_domain/arbiter/confidant.py:284-317 (_detect_slow_burn), is purely `days_silent >= r.expected_contact_days * 2` — no tone or baseline signal anywhere in it. Frontend: grep of EVA-Flutter-App-Frontend for 'golden'/'baseline' returns zero hits, and there is no route/screen calling eva_confidant_report or eva_set_golden_baseline — the Flutter app doesn't surface Confidant flags or Golden Baseline at all. Golden Baseline machinery is real and persisted but structurally disconnected from both evaluate_drift() (scalar-only) and the Confidant slow-burn detector (cadence-only), in both the in-memory and Supabase-backed implementations. No frontend surface exists for either Golden Baseline or Confidant slow-burn flags, so nothing closes this gap from the UI side either.
E7-S4 pinned ghosts PARTIAL Backend: EVA_Shared/eva-domain/src/eva_domain/arbiter/relationships.py:31 (always_flag field) and :155-162 (pinned_ghosts uses always_flag_threshold=0.45 vs default_threshold=0.7, tighter as designed); duplicated in the production-wired EVA_Shared/eva-persistence/src/eva_persistence/persistence/relationship_store.py:191-198. Exposed at EVA_Bureau/src/eva_bureau/routes/arbiter_routes.py:133-141 (GET /arbiter/relationships/ghosts) and consumed by the weekly scheduler at EVA_Bureau/src/eva_bureau/scheduler.py:228. grep across EVA_Bureau/EVA_MCP/EVA_Shared for 'cartographer' (60+ hits) shows Cartographer is fully wired elsewhere (crawler, alerts, scheduler) but has zero references inside relationships.py or confidant.py — no corroboration call exists. Frontend: EVA-Flutter-App-Frontend/lib/features/arbiter/data/datasources/arbiter_remote_data_source.dart:8-13 calls GET /arbiter/relationships/ghosts, and lib/features/arbiter/presentation/providers/arbiter_providers.dart:17-55 renders the results as a 'Drift' tab of cards in the Arbiter pillar UI — pinned ghosts are genuinely surfaced to the end user now. The tighter always_flag threshold is real and now genuinely visible to users through the Flutter 'Drift' tab, but there is still no Cartographer/Memory corroboration step anywhere in the pinned-ghost pipeline (backend or frontend) — ghosts fire purely on cadence with no cross-check against world-event or memory context.
E7-S5 conservative counsel gating NOT STARTED Backend: grep -rn 'counsel|attorney' across EVA_Bureau/EVA_MCP/EVA_Shared (excluding .venv/tests) returns zero matches anywhere; grep -in 'privilege' matches only the generic DataZone.PRIVILEGED enum member (EVA_Shared/eva-contracts/src/eva_contracts/contracts/enums.py:6) and its use in EVA_Shared/eva-core/src/eva_core/isolation/policy.py and confidant.py:50 (_PRIVILEGED_ZONES gate) — a zone-level governance filter, not a counsel/legal-specific tone-gating rule. Frontend: grep -rniE 'golden.?baseline|counsel|attorney|privileged' across EVA-Flutter-App-Frontend/lib returns only an unrelated string literal in lib/features/memory/presentation/screens/memory_search_screen.dart:185 (a memory-zone filter dropdown case, not tone gating). No counsel/legal/attorney-specific concept exists anywhere in either codebase; the only related mechanism is the generic PRIVILEGED/MNPI DataZone governance filter used by Confidant (which suppresses flags entirely for those zones rather than gating tone conservatively for counsel relationships specifically).

E8 — Memo Drafting and Inline Queries v0.2 (3 stories, P3 -- expected not started)

All 3 confirmed NOT STARTED against the current codebase. The prerequisite "Brief variant abstraction" that E8-S1 presupposes still doesn't exist even in v0.1 form (voice and cue-card briefs remain two hand-coded, duplicated paths per E2-S1/E12-S5), so there is nothing for a memo variant to plug into; E8-S2/S3 have no memo-reading surface in Flutter to hang an inline query or cue-card-suppression behavior off of. Correctly still future/v0.2 scope.

StoryVerdictEvidenceGap
E8-S1 memo variant abstraction NOT STARTED Backend: eva-contracts/src/eva_contracts/contracts/drafts.py:11-15 — BriefType is a closed StrEnum with only MORNING/END_OF_DAY/PRE_MEETING/ALERT, no memo member. EVA_MCP/src/eva_mcp/registry/drafts.py:14-17 — _handle_open_draft explicitly rejects any other value: `return {"error": f"invalid brief_type: {brief_type_raw!r}. Must be one of: morning, end_of_day, pre_meeting, alert"}`. The premise of the story (v0.1 already has a 'voice' and 'cue-card' variant abstraction that a memo variant could be added to without re-architecting) also fails: EVA_Bureau/src/eva_bureau/routes/brief_routes.py:105-211 (today_brief, the JSON/voice-source brief) and EVA_MCP/src/eva_mcp/registry/calendar.py:92-174 (_handle_get_cue_cards) independently re-query commitments/relationships/sentinel/calendar rather than sharing one composition path, and eva-domain/src/eva_domain/keeper/drafts.py's _dedupe_sections is per-draft only — confirmed by this session's own audit findings E2-S1 ('Two independent composition paths; no shared source-item-set/ordering contract') and E12-S5 ('Voice and cue-card paths are entirely separate code with no shared dedup'), just under the new post-restructure file paths. No memo BriefType/variant exists and is explicitly rejected by validation; and there is no real shared 'variant abstraction' in v0.1 to extend in the first place — voice and cue-card composition are still separate, duplicated code paths, so adding memo would require building the abstraction from scratch, not a drop-in extension.
E8-S2 inline voice query during memo reading NOT STARTED Backend: no MCP tool names or handlers reference 'memo' anywhere in EVA_MCP/src/eva_mcp/registry/catalogue.py (grep for the tool-name literal returns zero hits) or elsewhere in EVA_Services. Frontend: grep -rnoE '\bmemo\b' across /Users/g33tansh/Desktop/EVA-App/EVA-Flutter-App-Frontend/lib returns zero hits; there is no memo-reading screen anywhere (lib/features has only home, memory, chat, auth, briefing, cartographer, arbiter, keeper — briefing/presentation/screens/eod_brief_screen.dart and keeper/presentation/screens/draft_review_screen.dart are the closest, neither is a memo reader). grep -rniE 'query.?bar|inline.?quer' across lib also returns zero hits. There is no memo document type, no memo-reading UI surface in Flutter, and no inline-voice-query-while-reading mechanism anywhere in either codebase for it to attach to.
E8-S3 privileged citation as cue card NOT STARTED Backend: grep for 'privileged'/'PRIVILEGED' across eva-domain/src/eva_domain/keeper/voice.py and eva-domain/src/eva_domain/renderer/ returns zero hits — no code anywhere suppresses voice playback or forces cue-card rendering for privileged-zone content. This is compounded by eva-core/src/eva_core/isolation/policy.py:15-27,63-74 (PolicyGate), whose zone-check branch was removed entirely this session per its own docstring ('Zone (general/mnpi/privileged) is no longer an enforced security boundary here... PolicyGate does not restrict cross-zone access for the same principal'); _decide() now only compares caller.principal_id != target.principal_id. Frontend: zero hits for 'memo'/'cue.?card' co-occurring with any privileged-suppression logic in /Users/g33tansh/Desktop/EVA-App/EVA-Flutter-App-Frontend/lib. No memo-context query surface exists to test against, and even the general privileged/voice-suppression primitive this story would need has been removed from PolicyGate this session (the zone boundary is no longer enforced at all, even for the mechanism E18-S5's near-identical 'privileged docs cue-card, never voice' story would rely on).

E9 — Memory Institutional Ledger and Recall (5 stories, P0)

StoryVerdictEvidenceGap
E9-S1 append-only log, byte-identical rebuild PARTIAL (log real, but split into a dead hash-chained half and an unchained live half) Backend: EVA_Shared/eva-bus/src/eva_bus/event_bus/in_memory.py:16-39 (InMemoryRedisStreams, default, non-durable) vs EVA_Shared/eva-bus/src/eva_bus/event_bus/redis_streams.py:9-26 (RedisStreamsEventBus, durable, selected only when Settings.allow_in_memory_backends=False, default True per EVA_Shared/eva-core/src/eva_core/core/config.py:20-23). EVA_Shared/eva-persistence/src/eva_persistence/stores/audit_log.py:18-32 HashChainedAuditLog is genuinely append-only/hash-chained (previous_hash/hash fields, sha256) but is wired at EVA_Shared/eva-domain/src/eva_domain/wiring.py:197-199 as `app.state.audit_log = HashChainedAuditLog()` with zero other references anywhere in the repo (grepped `state.audit_log` and `.append(` — no call sites) -- it is instantiated and never written to or read from. The actually-live audit trail is EVA_Shared/eva-persistence/src/eva_persistence/persistence/audit_sink.py SupabaseAuditSink (wired at wiring.py:159-161 when Supabase configured), which is durable but NOT hash-chained: its own docstring (lines 1-9) claims 'The DB trigger audit_log_set_hash computes the HMAC hash chain automatically', but grepping the whole repo for `audit_log_set_hash` only finds that one docstring -- no such trigger exists. migrations/010_audit_log.sql:6-11 explicitly documents this: 'prev_hash/this_hash columns exist but are never populated by SupabaseAuditSink.record() today -- the only hash-chained implementation ... is in-memory and has zero production call sites.' No test asserts byte-identical rebuild (grepped EVA_Shared/tests/test_stores.py, test_compliance.py). Flutter: no audit/ledger UI exists anywhere (grepped for audit|ledger|hash chain in lib/, zero hits) -- this story has no frontend surface. Two disjoint halves that never merged: the genuinely hash-chained log (HashChainedAuditLog) is dead code with zero production call sites; the live durable sink (SupabaseAuditSink, Supabase-backed) has no hash chain despite its docstring claiming one -- the migration file itself flags this as a known, unfixed gap. No byte-identical rebuild is ever asserted anywhere.
E9-S2 NL recall + verbatim citations PARTIAL Backend: EVA_Shared/eva-domain/src/eva_domain/keeper/memory.py:86-108 search_facts is still keyword/substring matching (linear scan of subject/predicate/object_value, or graph_store.search_facts if present) -- not NL understanding. REST route EVA_Bureau/src/eva_bureau/routes/keeper_routes.py:40-58 (write) and :284-293 (search) both return `fact.model_dump(mode='json')` which includes the full `citations` list (Citation objects with source/source_id/excerpt/url per EVA_Shared/eva-contracts/src/eva_contracts/contracts/memory.py:10-14). The MCP tool actually used by the agent, EVA_MCP/src/eva_mcp/registry/keeper.py:122-143 `_handle_recall_facts`, strips citations -- it only returns fact_id/predicate/object_value/confidence. Frontend (new this session): /Users/g33tansh/Desktop/EVA-App/EVA-Flutter-App-Frontend/lib/features/memory/presentation/screens/memory_search_screen.dart is a real, reachable (wired from lib/features/settings/presentation/widgets/settings_widget.dart:435) NL-style search screen that displays a `memorySources(...)` citations line (lines 168-172). It calls MemoryRemoteDataSource.searchMemory -> POST /keeper/memory/search (lib/features/memory/data/datasources/memory_remote_data_source.dart:8-9), i.e. the citation-preserving REST route, not the citation-stripping MCP tool. However lib/features/memory/data/models/memory_result.dart:18-27 (`MemoryResult.fromJson`) expects response keys `id`, `content`/`summary`, `entity_type`, `timestamp`, and `citations` as a `List<String>` -- none of which match the actual MemoryFact JSON shape (`fact_id`, `subject`/`predicate`/`object_value`, no `entity_type`, `valid_from`/`valid_to`, and `citations` as a list of Citation objects). ApiClient.post (lib/core/network/api_client.dart:33,39-49) passes the response through unmodified. Backend: search is still keyword matching, not real NL understanding, and the MCP tool path the agent actually uses still strips citations. Frontend: a real memory-search + citations UI now exists and hits the correct (citation-preserving) REST endpoint, but its JSON model expects field names the backend never sends, so in practice every search result would render with an empty id/content/entity_type/timestamp and citations would stringify raw Dart Maps instead of clean source text -- the UI is wired to the wrong contract shape and would not actually work.
E9-S3 corrections propagate PARTIAL Backend: EVA_Shared/eva-domain/src/eva_domain/keeper/voice.py:62-92 `update_from_correction` only updates a per-principal VoiceProfile (vocabulary/signature_phrases/tone_register) -- it never touches MemoryFact records. `valid_to` on MemoryFact (EVA_Shared/eva-contracts/src/eva_contracts/contracts/memory.py:25,30-34) is set only by the model validator's temporal-window check and is never assigned anywhere else in the repo (grepped `valid_to` repo-wide -- only the contract file and its own test hit). `write_fact` (EVA_Shared/eva-domain/src/eva_domain/keeper/memory.py:14-46) always creates a brand-new MemoryFact; there is no `correct_fact`/`retract_fact` tool or route anywhere (grepped correct_fact|retract|supersede across all non-test .py -- only calibration/tone/voice-profile 'correction' concepts, unrelated to memory facts). Frontend: grepped Flutter lib/ for correct|retract -- no hits outside an unrelated boilerplate test comment; no correction/retraction UI exists. No general fact-correction or retraction mechanism exists for MemoryFact records at all -- corrections only ever update the voice-delivery profile, never the underlying fact graph. valid_to remains a fully unused field. Frontend has zero coverage of fact correction/retraction.
E9-S4 forget propagates in 24h PARTIAL -- propagate() still never invoked in production Backend: EVA_Shared/eva-domain/src/eva_domain/compliance/erasure.py:38-108 ForgetEngine.request_forget (lines 47-82) is called in production at EVA_Bureau/src/eva_bureau/routes/webhooks.py:138-157 (`POST /forget`) and EVA_MCP/src/eva_mcp/registry/compliance.py:9-33 (`_handle_forget`), but only ever records the request and publishes a FORGET_REQUESTED event -- it never calls `.propagate()`. ForgetEngine.propagate() (erasure.py:84-102) is called only from EVA_Shared/tests/test_compliance.py:48,71 (grepped `.propagate(` repo-wide). No consumer/scheduler job reads FORGET_REQUESTED events (grepped 'FORGET' in EVA_Bureau/src/eva_bureau/consumers.py and scheduler.py -- zero hits), so nothing ever triggers propagation. The MCP tool description itself overclaims this: EVA_MCP/src/eva_mcp/registry/catalogue.py:424-430 `eva_forget` tool description says 'EVA will erase data from all stores (graph, vector, audio) within the 1-hour initiation SLA and 24-hour propagation SLA' -- this is not what the wired handler does. Frontend: no forget/erasure UI anywhere in Flutter (grepped forget|erasure|gdpr|right to across lib/ -- zero hits). Erasure is requested and logged but never executed anywhere in production; propagate() is dead code outside tests, and the tool-facing documentation actively misrepresents this to the calling agent/LLM. No frontend surface exists for this story either.
E9-S5 reconstruction report NOT STARTED Backend: EVA_Shared/eva-domain/src/eva_domain/compliance/recovery.py:19-58 RecoveryCoordinator (snapshot/restore/replay_since) is disaster-recovery/point-in-time-restore infrastructure -- it snapshots per-store state and replays event-bus history, but produces no principal-facing 'here is how your history was reconstructed' report artifact. Grepped the whole backend for 'reconstruction' -- zero hits outside this analysis. Frontend: grepped Flutter lib/ for reconstruct|recovery -- zero hits (the earlier broader audit|ledger grep also returned nothing for this concept). No reconstruction-report feature exists in either codebase -- what exists (RecoveryCoordinator) is DR/backup-restore tooling, not a report artifact describing how a principal's memory/history was reconstructed.

E10 — Identity Resolution with HITL (5 stories, P0)

All 5 stories re-verified against the current backend (EVA_Services, post-restructure) and the Flutter frontend; none of the underlying gaps have actually closed — the identity-resolution engine remains unit-tested-only dead code with zero production callers, and the Flutter app has no identity-merge/review-queue UI at all.

StoryVerdictEvidenceGap
E10-S1 high-sensitivity always confirms NOT STARTED Backend: EVA_Shared/eva-domain/src/eva_domain/keeper/identity.py:5-8 `IdentityResolutionEngine.__init__(self, *, review_threshold: float = 0.85)` — single float threshold gates the only branch (line 20: `if existing is not None and merge_confidence >= self.review_threshold`). EVA_Shared/eva-contracts/src/eva_contracts/contracts/identity.py:25-34 `PersonIdentity` has no sensitivity/category field, only `merge_confidence`/`needs_review`. EVA_Shared/eva-contracts/src/eva_contracts/contracts/enums.py:4-7 `DataZone` = GENERAL/PRIVILEGED/MNPI only, no board/investor/counsel/family categories. Frontend: grep for sensitivity|board_member|investor|counsel|mnpi|hitl across EVA-Flutter-App-Frontend/lib found nothing relevant (one unrelated 'mnpi' zone-badge color case in memory_search_screen.dart:187, not a sensitivity-category concept). No concept of sensitivity categories (board/investor/counsel/MNPI/family) exists anywhere in either codebase; only a single numeric merge_confidence threshold exists, and it is never invoked (see S2). Flutter has no confirmation-prompt UI for high-sensitivity identity merges.
E10-S2 low-confidence quarantine PARTIAL Backend: EVA_Shared/eva-domain/src/eva_domain/keeper/identity.py:19-20,38-49 — threshold logic and `review_queue()` are real and unit-tested (EVA_Shared/tests/test_keeper.py:65-84 `test_identity_resolution_quarantines_low_confidence_merges` passes). Wiring: EVA_Shared/eva-domain/src/eva_domain/wiring.py:297-299 instantiates `IdentityResolutionEngine()` and sets `app.state.identity_engine` but `grep -rn "identity_engine"` across the whole repo (excluding .venv) returns ONLY those two lines in wiring.py — no route, no EVA_MCP/src/eva_mcp/registry/*.py tool (checked keeper.py, drafts.py, dispatch.py, catalogue.py — none reference identity_engine or IdentityResolutionEngine), and no other module ever calls `.resolve()` or `.review_queue()` in production code. Frontend: no Flutter references to identity resolution at all. Engine logic works in isolation and is unit-tested, but `app.state.identity_engine` has zero readers anywhere in the running application — no HTTP route or MCP tool ever calls resolve()/review_queue(). It remains pure dead code outside the test suite. Flutter has no quarantine UI to surface it even if it were wired.
E10-S3 pending-merge queue in brief NOT STARTED Backend: EVA_Shared/eva-domain/src/eva_domain/keeper/identity.py:44-49 `review_queue()` returns a plain list with no depth/count logic. EVA_Bureau/src/eva_bureau/routes/brief_routes.py — full file read (`today_brief`, lines 104-211, and `eod_brief`, lines 416-459): brief assembly pulls only from `commitment_tracker`, `relational_graph`, `crawler`, `sentinel`, and Supabase `briefs`/`composed_sections` — `identity_engine`/`review_queue` is never referenced. Frontend: no pending-merge/review-queue widget anywhere in EVA-Flutter-App-Frontend/lib (grep for review.queue/pending.merge returned nothing). No depth/count threshold exists on the review queue, and brief assembly (both today_brief and eod_brief, the only two brief-construction code paths) never calls review_queue() or surfaces pending identity merges. Flutter brief screens (Morning Brief/EOD) have no corresponding section.
E10-S4 single-letter always quarantined NOT STARTED Backend: full read of EVA_Shared/eva-domain/src/eva_domain/keeper/identity.py (83 lines) and EVA_Shared/eva-contracts/src/eva_contracts/contracts/identity.py (36 lines) — `canonical_name` is validated only with pydantic `Field(min_length=1)` (identity.py contract line 29); no length/initial-only heuristic anywhere, and `resolve()` never inspects `canonical_name` at all except to store it. Frontend: no name-length check found in EVA-Flutter-App-Frontend/lib. No name-length/single-letter-name check exists in either codebase; `canonical_name` is stored verbatim regardless of its form.
E10-S5 quarantine never blocks synthesis NOT STARTED Backend: repo-wide `grep -rln "PersonIdentity"` returns only identity.py (contract), its package `__init__.py` re-export, `keeper/identity.py`, and the two test files — never referenced by the full text of EVA_Shared/eva-domain/src/eva_domain/keeper/drafts.py (`LivingDraftManager.open_draft`/`add_section`/`lock`/`interrupt_locked`/`deliver`, lines 1-123) nor by `keeper/editor.py` nor EVA_MCP/src/eva_mcp/registry/drafts.py. Frontend: no identity/merge/quarantine reference anywhere in EVA-Flutter-App-Frontend/lib. `PersonIdentity.needs_review` (identity.py:34) is a real bool field on the contract, but nothing in the draft/synthesis pipeline or MCP draft tools ever reads it, and no Flutter screen surfaces it — synthesis has no concept of a quarantined identity, so it trivially 'never blocks' only because the feature was never built, not because a deliberate non-blocking design was verified.

E11 — Privileged and MNPI Air-Gap (5 stories, P0)

This session made an explicit, documented product decision (DEV-NOTES.md correction M) to descope DataZone (general/mnpi/privileged) as an enforced security boundary: PolicyGate._decide() now checks only caller.principal_id != target.principal_id — the caller.zone != target.zone branch and the CROSS_ZONE_ATTEMPT audit action were deleted outright, and a new test (test_isolation.py::test_policy_gate_allows_cross_zone_access_for_same_principal) explicitly asserts a GENERAL-zone caller CAN now read PRIVILEGED/MNPI data for the same principal. Since this epic's premise is "air-gap" between privileged/MNPI and general data, this is a direct regression on the epic's core security guarantee, not a neutral refactor. Data remains zone-*namespaced* in storage (ZoneRoutedGraphStore/ZoneRoutedVectorStore still route MNPI/PRIVILEGED to separate backend instances when configured), but the access-control layer that would enforce the air-gap for a single principal across zones is gone. No Flutter frontend exists anywhere on this machine at the path given (/Users/g33tansh/Desktop/EVA-App/EVA-Flutter-App-Frontend or elsewhere, confirmed via find/mdfind) to check for E11; the only related frontend on disk is a Next.js prototype (/Users/g33tansh/Desktop/EVA-App/eva-next) which shows privileged/MNPI zone chips purely as hardcoded mock data (MemorySearchScreen.tsx) with no backend wiring, no KMS/inference-endpoint concept, and no enforcement — it does not change any verdict below.

StoryVerdictEvidenceGap
E11-S1 privileged own KMS/HSM domain PARTIAL (upgraded from stub, still not wired to any data) EVA_Shared/eva-core/src/eva_core/isolation/vault_keys.py:32-70 (VaultKeyProvider, real hvac/HashiCorp Vault Transit client) and :73-121 (SupabaseVaultKeyProvider, real Supabase REST-backed secret provider); EVA_Shared/eva-domain/src/eva_domain/wiring.py:577-593 (_build_key_provider selects Vault/Supabase/InMemory by settings.vault_type) and :182-183 (key_provider = _build_key_provider(settings); app.state.key_provider = key_provider); EVA_Shared/eva-core/src/eva_core/core/config.py:88-91 (VAULT_TYPE/VAULT_ADDR/VAULT_TOKEN/VAULT_TRANSIT_PATH settings, real env surface). Old fake string generator still exists too: EVA_Shared/eva-core/src/eva_core/isolation/keys.py:21-29 (InMemoryKeyProvider, default when no Vault configured). grep across EVA_Bureau/EVA_MCP/EVA_Shared for `data_encryption_key(` shows zero call sites besides the construction in wiring.py and the unit test (EVA_Shared/tests/test_isolation.py:31-42). Nothing ever calls key_provider.data_encryption_key() to wrap/unwrap real data; EVA_Shared/eva-persistence/src/eva_persistence/persistence/credential_store.py:19-20 explicitly documents that OAuth tokens are "stored as-is" and encryption via VaultKeyProvider is left as a future "higher-security deployment" step. So a real, production-grade KMS/HSM-backed key provider now exists and is wired at boot, but it has no consumer anywhere — no data actually gets a privileged-domain key applied to it.
E11-S2 dedicated inference endpoints NOT STARTED EVA_Bureau/src/eva_bureau/routes/nabh_proxy.py:1-36 (single shared /api/v1/nabh proxy to nabh.cloud, no zone parameter or per-zone routing anywhere in the file); EVA_Shared/eva-persistence/src/eva_persistence/stores/graphiti_store.py:50-78 (single shared LLMConfig pointed at the same local proxy/model "qwen3-5-397b" for all callers, no zone-conditional endpoint selection). Frontend checked: /Users/g33tansh/Desktop/EVA-App/eva-next/lib/llm.ts:1-40 also uses one shared pair of providers (OVH AI Cloud, OpenRouter) for every request, no per-zone endpoint concept at all. grep for `inference` and for any LLM-client abstraction across EVA_Bureau/EVA_MCP/EVA_Shared returns nothing resembling a zone-scoped inference endpoint. There is exactly one shared LLM path for every principal/zone; nothing routes privileged or MNPI traffic to an isolated model/endpoint.
E11-S3 boundary audit channel PARTIAL (dead code now auditing a boundary that no longer exists) EVA_Shared/eva-core/src/eva_core/isolation/policy.py:32-61 (PolicyGate.authorize unconditionally calls audit_sink.record(...) regardless of decision.allowed, but never sets `channel=`, so AuditEvent always keeps its default); EVA_Shared/eva-contracts/src/eva_contracts/contracts/audit.py:32-49 (AuditChannel enum with GENERAL/PRIVILEGED/MNPI members and a real audit_channel_for_zone(zone) function, default channel=AuditChannel.GENERAL on AuditEvent at line 56). grep confirms audit_channel_for_zone( has zero call sites outside EVA_Bureau/tests/test_phase6.py. No audit-review route exists (ls EVA_Bureau/src/eva_bureau/routes/ shows admin_routes.py, keys_routes.py, etc. but no audit_routes.py). Identical mechanical gap to the prior audit (path moved from the old app/isolation and app/contracts locations into the eva-core/eva-contracts packages, same substance): audit_channel_for_zone() is real but uncalled, every AuditEvent defaults to channel=GENERAL, and there's no endpoint to review a privileged/MNPI channel even if one were populated. Additionally, this session's DEV-NOTES.md correction M (lines 521-555) and eva-core/isolation/policy.py:18-27 explicitly document that PolicyGate no longer treats zone crossing as a boundary at all (only caller.principal_id != target.principal_id is checked) and the CROSS_ZONE_ATTEMPT AuditAction is now permanently dead (still declared at eva-contracts/contracts/audit.py:16 but never producible). So this dead audit-channel scaffolding no longer even has a conceptual "privileged/MNPI boundary crossing" event it could someday be wired to capture from PolicyGate — only a self-declared zone value in the details dict (policy.py:53-57).
E11-S4 vector queries don't cross zones NOT STARTED -- enforcement mechanism itself was removed this session EVA_Shared/eva-core/src/eva_core/isolation/policy.py:63-74 (_decide() now returns AccessDecision(allowed=True, ...) whenever caller.principal_id == target.principal_id, with no zone comparison at all — confirmed by EVA_Shared/tests/test_isolation.py:84-102 test_policy_gate_allows_cross_zone_access_for_same_principal, which explicitly asserts decision.allowed is True for caller zone=GENERAL vs target zone=MNPI, same principal); EVA_Shared/eva-persistence/src/eva_persistence/stores/pgvector_store.py:93-134 (query() still calls self._policy_gate.authorize(caller=..., target=...) and would return [] only if decision.allowed is False — which per the above can now never happen for a same-principal cross-zone request); EVA_Shared/eva-persistence/src/eva_persistence/stores/zone_router.py:119-129 (ZoneRoutedVectorStore.query delegates straight through). grep confirms the only caller of `.query(` on any vector store in production code is zone_router.py's own pass-through — EVA_Shared/eva-domain/src/eva_domain/keeper/memory.py (the actual MemoryService) has no method that calls vector_store.query at all; it only calls vector_store.upsert (memory.py:36-45) and does subject/text search via graph_store.facts_for_subject/search_facts, always with caller=target=same scope (memory.py:48-56, 86-108; EVA_MCP/src/eva_mcp/registry/keeper.py:128 recall_subject(caller=scope, target=scope, ...)). Both halves of the old gap now exist simultaneously and are worse than before: (1) same as before, no production call site ever passes differing caller/target scopes; and (2), new this session, even if one did, PolicyGate would no longer refuse it — the zone-comparison branch that made the prior PARTIAL verdict's "real refusal logic" description true has been deleted outright (not left unreachable, removed). There is no code path anywhere that can currently produce a cross-zone vector-query refusal.
E11-S5 Cartographer MNPI → private surface PARTIAL EVA_Shared/eva-domain/src/eva_domain/cartographer/relevance.py:24-50, specifically line 41 (route = AlertRoute.PRIVATE_SURFACE if zone == DataZone.MNPI else AlertRoute.MAIN_BRIEF) and line 29 (zone: DataZone = DataZone.GENERAL default param); same pattern in EVA_Shared/eva-domain/src/eva_domain/cartographer/sentinel.py:35 and EVA_Shared/eva-persistence/src/eva_persistence/persistence/sentinel_store.py:102. Call site: EVA_MCP/src/eva_mcp/registry/cartographer.py:49-67 (_handle_query_scenario passes zone=scope.zone into score_event, where scope comes from EVA_MCP/src/eva_mcp/registry/helpers.py:57-63 `_scope()` — a self-declared `args.get("zone", "general")` with no attestation, same pattern DEV-NOTES.md documents as never having been authenticated). EVA_MCP/src/eva_mcp/registry/cartographer.py:9-25 `_handle_list_alerts` returns every alert for a scope with no filtering by `route` value, i.e. no separate delivery channel actually gates PRIVATE_SURFACE-routed alerts from MAIN_BRIEF-routed ones. Same as prior audit: the MNPI→PRIVATE_SURFACE assignment is a real routing label, driven purely by a self-declared `zone` tool argument (not an attested/authenticated zone), and there is still no dedicated encryption/inference endpoint (per E11-S2, still NOT_STARTED) or separate delivery surface for it to actually route to — `_handle_list_alerts` returns all alerts regardless of `route` value. Frontend checked (no Flutter app exists on this machine; closest analog is /Users/g33tansh/Desktop/EVA-App/eva-next): the Next.js prototype's zone chips (components/AgentCard.tsx:18, components/MemorySearchScreen.tsx:33-58) are cosmetic, driven entirely by hardcoded MOCK_MEMORY records, not by any real backend query — no frontend implementation of an actual separate "private surface" exists either.

E12 — Living Draft Synthesis / Editor (6 stories, P0)

Backend fully relocated to /Users/g33tansh/Desktop/EVA-Services/EVA_Services this session (single restructuring commit, no prior history to diff); all 6 story verdicts re-verified against the new paths and are logically unchanged -- same mechanisms, same gaps, just moved under EVA_Shared/eva-domain, EVA_Shared/eva-contracts, EVA_Shared/eva-persistence, EVA_MCP/registry/. A Flutter "Pending Drafts" review screen now exists (Settings -> Review -> Pending Drafts) and is wired to the correct /keeper/drafts, /approve, /reject endpoints, but its DraftItem model expects JSON keys (subject/content/pillar/draft_type/status/created_at) that the actual BriefDraft response never sends (draft_id/principal_id/zone/brief_type/state/sections/audio_object_key/updated_at) -- so it renders blank/default values for real data and surfaces no section-level traceability. This reinforces rather than changes the S4 verdict.

StoryVerdictEvidenceGap
E12-S1 continuous Living Draft, no rendering while accumulating DONE Backend: EVA_Shared/eva-domain/src/eva_domain/keeper/drafts.py:36-51 (add_section) only dedupes/appends sections and sets state=ASSEMBLING -- no render/voice call. voice_bridge.pre_render() is invoked only inside deliver() at drafts.py:73-76. list_for_principal (drafts.py:94-98) scopes strictly by principal_id+zone. The Supabase-backed production store (EVA_Shared/eva-persistence/src/eva_persistence/persistence/draft_store.py:118-134) mirrors the same separation. Frontend: Flutter fetches only the finished brief via GET /brief/today (lib/features/briefing/data/datasources/briefing_remote_data_source.dart:15-18), never the in-progress draft, consistent with 'no rendering while accumulating'. None found -- accumulation and rendering remain genuinely separated, and per-principal/zone scoping is real.
E12-S2 P0 at T-15 makes T-0 brief PARTIAL Backend: EVA_Shared/eva-domain/src/eva_domain/keeper/editor.py:78-114 inject_p0_alert() mechanically injects a P0 section into an ASSEMBLING or LOCKED draft. Its only call path is the manually-invoked MCP tool eva_inject_p0_alert (EVA_MCP/src/eva_mcp/registry/drafts.py:141-155, registered in dispatch.py:139). grep for 'T-15'/'T_MINUS'/'t_zero' across the whole repo returns zero hits. EVA_Bureau/src/eva_bureau/scheduler.py only has a T-30 idle auto-lock job (_editor_auto_lock_job, scheduler.py:313-332) and a morning-brief job enqueue (scheduler.py:269-299) -- neither ties a P0 topic at T-15 to a forced T-0 re-brief. Frontend: no P0/alert UI exists anywhere in the Flutter app (grep for P0/inject_p0/priority_alert across lib/ returns nothing). No T-15/T-0 timing logic exists anywhere in backend or frontend; inject_p0_alert is purely a manually-called tool with no automatic trigger tying it to a real cutoff.
E12-S3 P0 after delivery -> real-time path PARTIAL Backend: EVA_Shared/eva-domain/src/eva_domain/renderer/priority_queue.py:36-99 RendererPriorityQueue is a correct min-heap (P0_ALERT priority always wins). Its only production reference is instantiation at EVA_Shared/eva-domain/src/eva_domain/wiring.py:542-543 (app.state.renderer_queue = RendererPriorityQueue()) -- grep for '.push(' across the repo shows the only push() calls are in EVA_Bureau/tests/test_phase6.py. Separately, real P0-style alerting (sentinel.py, relevance.py in cartographer/) routes through DEVICE_PUSH/PRIVATE_SURFACE channels entirely independent of this queue. Frontend: no code references a post-delivery brief-update mechanism. Zero production callers push onto the queue; nothing detects a post-delivery P0 event and re-renders/re-delivers the brief through this path.
E12-S4 every item traceable PARTIAL Backend: EVA_Shared/eva-contracts/src/eva_contracts/contracts/drafts.py:26-30 DraftSection schema has source_event_ids and citations fields. Every write path leaves them empty: EVA_Bureau/src/eva_bureau/routes/keeper_routes.py:96-99,136 AddSectionRequest/DraftSection(title=body.title, body=body.body) and EVA_MCP/src/eva_mcp/registry/drafts.py:45-73 _handle_add_section's DraftSection(title=title, body=body) (line 58) both construct sections with no source_event_ids/citations. Frontend: lib/features/keeper/data/models/draft_item.dart:1-29 DraftItem.fromJson doesn't parse 'sections' at all, and expects keys (subject/content/pillar/draft_type/status/created_at) that the real GET /keeper/drafts response (BriefDraft.model_dump: draft_id/principal_id/zone/brief_type/state/window_start/window_end/sections/audio_object_key/updated_at, per keeper_routes.py:236-244) never sends -- confirmed no backend route anywhere emits those key names (grep for '"draft_type"'/'"subject".*"content"' returns nothing). lib/features/keeper/presentation/screens/draft_review_screen.dart:170-176 only renders draft.subject/draft.content, both always empty in practice. Schema fields exist but are never populated on write (backend), and the one Flutter screen that reviews drafts (DraftReviewScreen, reachable via Settings > Review > Pending Drafts) doesn't map sections/citations at all and expects an unrelated JSON shape -- so traceability is unimplemented end-to-end, not just schema-only as before.
E12-S5 cross-format dedup on source-item set PARTIAL Backend: EVA_Shared/eva-domain/src/eva_domain/keeper/drafts.py:113-122 _dedupe_sections keys on (title, body) tuple equality, invoked only within add_section/interrupt_locked for one draft's own section list (also reused verbatim by the Supabase store at eva-persistence/persistence/draft_store.py:16,130). The voice path (eva-domain/src/eva_domain/keeper/voice.py to_ssml(), ~line 236) renders from that same already-deduped draft.sections, so text+voice do share the dedup. But the cue-card path (EVA_MCP/src/eva_mcp/registry/calendar.py:92-176 _handle_get_cue_cards) builds cards purely from calendar_orchestrator/relational_graph/commitment_tracker data with zero reference to _dedupe_sections or draft.sections. Frontend: no cross-format aggregation UI exists. Dedup is real but scoped to a single BriefDraft's own sections; the cue-card format is entirely separate code with no shared source-item dedup against brief sections.
E12-S6 edit-lock preserves in-progress edits NOT STARTED -- implemented backwards Backend: EVA_Shared/eva-domain/src/eva_domain/keeper/drafts.py:59-66 interrupt_locked() unlocks a LOCKED draft (state -> ASSEMBLING), calls add_section(..., allow_interrupt=True) to mutate it, then re-locks -- no review/confirmation gate, no branch that preserves or protects the in-progress edit from being overwritten. The production Supabase-backed store replicates the identical logic at EVA_Shared/eva-persistence/src/eva_persistence/persistence/draft_store.py:143-150, and wiring.py:343 selects SupabaseDraftStore whenever supabase_db is configured, confirming this backwards behavior is in the real production path, not just an in-memory fallback. Frontend: DraftReviewScreen has no lock/edit-conflict UI at all (only approve/reject). Silently unlocks, mutates, and re-locks with no review gate -- the literal inverse of 'never silent overwrite,' and this is now confirmed to be the actual production code path (SupabaseDraftStore), not merely the in-memory demo manager.

E13 — Out-of-Band Meta-Signals: HR, MNPI, Privileged (5 stories, P0)

S5 is a genuine regression this session, not just a re-hash of the prior finding: PolicyGate's zone-equality branch was deleted entirely as an explicit, documented product decision (DEV-NOTES.md item 3: "RESOLVED by descoping zone as a security boundary"), and the security red-team corpus had its cross-zone attack case removed with the comment "this is no longer a real attack" (eva-domain/security/corpus.py:40-42). S1-S4 are unchanged from the prior audit -- same gaps, same or trivially-shifted line numbers, just re-verified at the new EVA_Services paths. The Flutter frontend (EVA-App/EVA-Flutter-App-Frontend) has zero HR/MNPI/privileged governance, refusal, or channel-separation UI for any of these 5 stories -- it only renders a cosmetic zone badge (general/privileged/mnpi) on memory-search results, which is unrelated to any of the 5 stories' actual subject matter.

StoryVerdictEvidenceGap
E13-S1 HR suppresses drift NOT STARTED Backend: EVA_Shared/eva-domain/src/eva_domain/arbiter/confidant.py:14 -- the only mention of HR anywhere is the aspirational docstring line 'Governance: privileged/MNPI/HR contacts never auto-surface without override' (same file, same line 14 as the prior audit -- unchanged). EVA_Shared/eva-contracts/src/eva_contracts/contracts/enums.py:4-7 -- DataZone StrEnum has exactly GENERAL/PRIVILEGED/MNPI, no HR member. Repo-wide grep for '\bHR\b|human_resources' across EVA_Shared/EVA_Bureau/EVA_MCP returns only that one docstring line. Frontend: grepped all 77 .dart files in EVA-App/EVA-Flutter-App-Frontend/lib for hr/human_resources/DataZone -- zero hits. No HR field, tag, zone, or concept exists anywhere in the data model or business logic in either codebase -- confirmed by direct grep, not inference. Nothing to suppress drift on.
E13-S2 MNPI signals to private surface only PARTIAL Backend in-memory path: EVA_Shared/eva-domain/src/eva_domain/arbiter/relationships.py:137-142 -- real reason-substitution ('confidential_contact_needs_attention' for MNPI/PRIVILEGED zone/record) in RelationalGraphService.evaluate_drift, byte-for-byte the same lines as the prior audit. Production Supabase path: EVA_Shared/eva-persistence/src/eva_persistence/persistence/relationship_store.py:168-195 (SupabaseRelationshipStore.evaluate_drift) has no equivalent branch at all -- reason is unconditionally 'cadence_or_silence_drift' or 'tone_drift' regardless of zone. Scheduler: EVA_Bureau/src/eva_bureau/scheduler.py:215 (`_arbiter_confidant`) constructs `scope = PrincipalScope(principal_id=principal_id, zone=DataZone.GENERAL)` hardcoded, so `relational_graph.list_all/pinned_ghosts/evaluate_drift` are only ever invoked against the GENERAL-zone namespace in the scheduled production path -- MNPI/PRIVILEGED relationship data is never touched by the weekly scheduler at all. The check exists only in the in-memory/dev implementation; the production persistence path (Supabase) lacks it entirely, and the scheduler's hardcoded GENERAL zone means the check's own code path (whichever store is wired) never actually runs against MNPI/PRIVILEGED data end-to-end.
E13-S3 privileged drift never shared PARTIAL EVA_Shared/eva-domain/src/eva_domain/arbiter/relationships.py:139 -- in-memory reason substitution correctly covers both `DataZone.MNPI` and `DataZone.PRIVILEGED`. EVA_Shared/eva-domain/src/eva_domain/cartographer/sentinel.py:35 -- `route = AlertRoute.PRIVATE_SURFACE if scope.zone == DataZone.MNPI else AlertRoute.DEVICE_PUSH` (checks MNPI only). EVA_Shared/eva-domain/src/eva_domain/cartographer/relevance.py:41 -- `route = AlertRoute.PRIVATE_SURFACE if zone == DataZone.MNPI else AlertRoute.MAIN_BRIEF` (checks MNPI only). Line numbers (35, 41) are identical to the prior audit's citations. The routing layer (sentinel.py, relevance.py) still only special-cases MNPI; PRIVILEGED-zone events fall through to the 'else' branch and get pushed to DEVICE_PUSH/MAIN_BRIEF instead of PRIVATE_SURFACE, i.e. privileged drift can still be shared/pushed like ordinary data.
E13-S4 Confidant produces no HR flags NOT STARTED EVA_Shared/eva-domain/src/eva_domain/arbiter/confidant.py:219-223 (moved from the prior audit's cited 110-114 purely because ~100 lines of Supabase persistence and learning-reconstruction code were added earlier in the file this session): `_PRIVILEGED_ZONES = {DataZone.PRIVILEGED, DataZone.MNPI}` and `eligible = [r for r in relationships if not getattr(r, 'never_flag', False) and r.zone not in _PRIVILEGED_ZONES]` -- excludes only never_flag + privileged/MNPI-zone records. No HR concept exists to filter on (same root cause as E13-S1) -- the governance filter has no HR-aware branch at all, so there is no code path that could ever 'produce no HR flags' as a deliberate behavior versus simply having no HR concept.
E13-S5 deterministic refusal on HR/MNPI/privileged queries NOT STARTED (regressed -- zone refusal removed by design) EVA_Shared/eva-core/src/eva_core/isolation/policy.py:15-74 -- PolicyGate._decide() now reads only `if caller.principal_id != target.principal_id: refuse`; the zone-equality branch has been deleted entirely (confirmed by reading the full current file -- no zone comparison of any kind remains). EVA_MCP/src/eva_mcp/registry/dispatch.py:208-226 -- the sole tools/call site calls `policy_gate.authorize(caller=scope, target=scope, ...)` with the literal same PrincipalScope object for both params (so identity is trivially always true too), with an explicit code comment: 'Zone is intentionally NOT an enforced boundary here ... Zoning is not being kept as a security feature going forward.' DEV-NOTES.md item 3 ('~~PolicyGate's zone check is vacuous~~ — RESOLVED by descoping zone as a security boundary') documents this as a deliberate product decision this session, not a bug fix. EVA_Shared/eva-domain/src/eva_domain/security/corpus.py:40-42 -- the red-team attack corpus's cross-zone-read test case was deleted this session with the comment 'zone is no longer an enforced PolicyGate boundary by design ... this is no longer a real attack.' `AuditAction.CROSS_ZONE_ATTEMPT` (eva-contracts/contracts/audit.py:16) and the new `AuditChannel`/`audit_channel_for_zone` (audit.py:32-49) are defined but never referenced by any production code (grep confirms `AuditChannel.` and `audit_channel_for_zone` appear only in eva-contracts' own definition and one test file, EVA_Bureau/tests/test_phase6.py) -- no dedicated boundary-audit channel exists. Frontend: no refusal/denied/boundary-audit UI found anywhere in EVA-App/EVA-Flutter-App-Frontend/lib. There is no longer any zone-aware refusal in the codebase at all -- not even the prior audit's already-weak 'identity/zone equality' check survives. What remains is a pure cross-principal (tenant isolation) check, unrelated to HR/MNPI/privileged content. No query-content inspection, no HR channel, and now (newly, this session) no dedicated boundary-audit channel or zone check of any kind exist. This is a strictly worse position than the prior PARTIAL credit.

E14 — Calibration Service and Triggered Push (4 stories, P1)

All 4 verdicts unchanged from the 2026-07-06 audit. The backend restructuring this session (single "Restructured Codebase from EVA-Middleware" commit) was a pure path/package move for the calibration code -- eva_domain/calibration/{service,push}.py and eva_bureau/scheduler.py are functionally identical to their pre-move counterparts. No P0-topic/fast-track wiring was added or removed, and the Flutter frontend has zero references to fast-track/push/P0 concepts.

StoryVerdictEvidenceGap
E14-S1 pushes on two channels, never pulls PARTIAL Backend: EVA_Shared/eva-domain/src/eva_domain/calibration/service.py:366-399 (_commit) publishes EventType.CALIBRATION_UPDATED to the event bus on every upsert_interest_tag/set_threshold_adjustment/record_correction call -- the 'standard channel' is real. EVA_Shared/eva-domain/src/eva_domain/calibration/push.py:31-76 defines CalibrationPushService.schedule_push (the 'fast-track channel'); EVA_Shared/eva-domain/src/eva_domain/wiring.py:557-560 (build_calibration_push) only instantiates it into app.state.calibration_push -- a repo-wide grep for 'schedule_push'/'register_callback' shows zero production call sites (only EVA_Bureau/tests/test_phase6.py:308-342 exercises it). EVA_Bureau/src/eva_bureau/scheduler.py:129-156 (_cartographer_crawl), specifically line 153 `snapshot = await calibration_service.snapshot(scope=scope)`, actively pulls a calibration snapshot for every principal on every crawl tick. Fast-track channel remains fully dead code (built, never invoked, no callback ever registered); the scheduler unconditionally pulls a calibration snapshot on each crawl, directly contradicting the story's 'never pulls' premise. Nothing subscribes to CALIBRATION_UPDATED either, so even the 'push' has no consumer besides the scheduler's own pull.
E14-S2 P0 topic → immediate reweight NOT STARTED Backend: EVA_Shared/eva-contracts/src/eva_contracts/contracts/calibration.py:10-16 (InterestTagType enum) has no priority/P0 concept at all. Repo-wide grep for 'p0_topic', 'immediate_reweight', priority-topic detection returns zero hits. Only two production call sites of CalibrationService mutation methods exist: EVA_Bureau/src/eva_bureau/routes/router.py:82-124 (manual POST /calibration/tags, PUT /calibration/relationships/{id}/thresholds, POST /calibration/corrections REST endpoints) and EVA_Bureau/src/eva_bureau/scheduler.py:153 (read-only .snapshot() call, not a mutation). Frontend: Flutter's lib/features/settings/presentation/widgets/settings_widget.dart:107,226 calls addInterestTag() only for language/voice UI preference selection, not topic urgency detection; no Flutter code references 'P0' or urgency-triggered reweighting. No automatic detector anywhere (backend or Flutter) calls upsert_interest_tag/set_threshold_adjustment in response to a P0-classified topic; the only paths to those methods are the manual REST API and a settings-preference side use in Flutter, neither of which is topic-urgency-driven.
E14-S3 fast-track reaches pillars in seconds NOT STARTED Backend: EVA_Shared/eva-domain/src/eva_domain/calibration/push.py:51 defines schedule_push(); EVA_Bureau/src/eva_bureau/main.py:87 and EVA_MCP/src/eva_mcp/main.py:63 both call build_calibration_push(app, settings), which (wiring.py:557-560) only does `app.state.calibration_push = CalibrationPushService()`. A repo-wide grep confirms zero production call sites of schedule_push or register_callback outside EVA_Bureau/tests/test_phase6.py. EVA_Bureau/src/eva_bureau/routes/router.py's calibration endpoints (lines 73-135) never reference calibration_push. EVA_Bureau/src/eva_bureau/scheduler.py uses a static APScheduler cron/interval trigger with no awareness of any pending push queue. Frontend: grep of the Flutter repo for fast-track/push/P0 terms returns zero hits. schedule_push() has zero production call sites in either codebase; the crawl interval remains static and structurally unaware of any push signal, so nothing can reach pillars in seconds via this path.
E14-S4 triggered updates additive PARTIAL Backend: EVA_Shared/eva-domain/src/eva_domain/calibration/service.py:202-245 (upsert_interest_tag) merges aliases via `tuple(sorted({*existing.aliases, *tag.aliases}))` rather than overwriting -- non-destructive merge confirmed by EVA_Bureau/tests/test_calibration.py:16-52, which asserts aliases ('GPU supply',) + ('Inference',) merge to ('GPU supply','Inference') across two calls. This is only reachable via the manual REST API (router.py:82-91, POST /calibration/tags) or the Flutter settings screen's addInterestTag() calls (settings_widget.dart:107,226). Additivity is only verifiable for the manual API path; per E14-S3, no triggered/fast-track push mechanism exists in either codebase, so the story's actual subject (triggered updates specifically) still cannot be exercised at all -- only manual mutations are additive.

E15 — Audit Log, Forget, and Boundary Audit (5 stories, P0)

Backend-only compliance/security epic; Flutter frontend (lib/) has zero references to audit/forget/erasure/gdpr/privacy — confirmed no UI surface exists or is needed for any of these 5 stories.

StoryVerdictEvidenceGap
E15-S1 every action logged w/ reasoning+rules+model version NOT STARTED Backend: eva-contracts/src/eva_contracts/contracts/audit.py:12-30 (AuditAction has 14 enum members now, up from 7+, but only 4 values are ever constructed in production: grep of 'AuditAction\.' across non-test .py shows only MCP_TOOL_CALLED (eva-domain/mcp_boundary/server.py:82), POLICY_GATE_ALLOWED/POLICY_GATE_REFUSED (eva-core/isolation/policy.py:47), FORGET_STARTED/FORGET_COMPLETED (eva-domain/compliance/erasure.py:74,91) have real call sites -- ACTION_LADDER_DECISION, THREAD_ROUTED, ALWAYS_ROUTE_FIRED, DRIFT_EVENT_FIRED, CONFIDANT_FLAG_FIRED, BRIEF_PLAYBACK_PROGRESS, CROSS_ZONE_ATTEMPT have zero constructors anywhere. AuditEvent model (audit.py:52-66) has only `reason: str|None` and `details: dict` -- no rules-fired list, no model-version field anywhere in the contract or any of the 3 production call sites. correspondent.py:53/93 has a `reasoning` field but it's written to its own decision table, never to an AuditEvent (grep for 'audit' in connectors/correspondent.py returns nothing). Same substance as prior audit, just relocated: only 3 of 14 audit actions ever fire in production, none carry rules-fired or model-version data, and reason is a short human string, not a reasoning trace.
E15-S2 privileged actions on separate channel NOT STARTED Backend: eva-contracts/contracts/audit.py:32-49 defines AuditChannel(GENERAL/PRIVILEGED/MNPI) and audit_channel_for_zone(), and AuditEvent.channel defaults to GENERAL (audit.py:56). Grepped every AuditEvent(...) constructor in production code (policy.py:44, erasure.py:71,88, mcp_boundary/server.py:79) -- none passes channel=, so every event is silently GENERAL regardless of zone. Even if it were set, eva-persistence/persistence/audit_sink.py:29-51 SupabaseAuditSink.record() (the sink actually wired in wiring.py:160-161 when Supabase is configured) writes event_type/entity_type/entity_id/zone/actor/payload to the audit_log table but never writes `channel` -- the column doesn't even exist in migrations/010_audit_log.sql:13-25. No review endpoint: grepped all of EVA_Bureau/routes and EVA_MCP/registry for audit_log/AuditEvent/read_scope/WormAuditLog/HashChainedAuditLog usage outside tests -- zero hits; HashChainedAuditLog (eva-persistence/stores/audit_log.py) is only exercised by tests. AuditChannel enum/helper is inert scaffolding (confirmed pre-existing at time of prior audit per E11-S3's identical citation of audit.py:32-49) -- still zero callers, still no separate storage column, still no review endpoint of any kind.
E15-S3 forget propagates in 24h PARTIAL Backend: eva-domain/compliance/erasure.py ForgetEngine.propagate() (lines 84-102) implements real 24h SLA logic (propagation_sla=timedelta(hours=24), within_24h check at line 98) and is wired into app.state at wiring.py:514-518 with real erasure targets (graph_store, vector_store, audio_store, plus 7 Supabase-backed stores when configured, lines 488-511). Both production entry points -- EVA_MCP/registry/compliance.py:19 (_handle_forget) and EVA_Bureau/routes/webhooks.py:148 (POST /forget) -- call only forget_engine.request_forget(...); grepped the whole tree for '.propagate(' and 'FORGET_REQUESTED' consumers -- the only callers of .propagate() are in EVA_Shared/tests/test_compliance.py. No event-bus consumer subscribes to EventType.FORGET_REQUESTED (erasure.py:62) to trigger propagate() later. propagate() is fully implemented and wired to real targets but remains production-uncalled; both HTTP/MCP entry points only ever call request_forget(), so the 24h propagation SLA logic never executes outside tests.
E15-S4 forget itself logged separately PARTIAL Backend: erasure.py:70-82 request_forget() unconditionally calls audit_sink.record(AuditEvent(action=AuditAction.FORGET_STARTED,...)) and IS reached in production via both compliance.py:19 and webhooks.py:148-152. erasure.py:87-101 propagate() calls audit_sink.record(AuditEvent(action=AuditAction.FORGET_COMPLETED,...)) but propagate() is production-uncalled per E15-S3 finding above. FORGET_STARTED does actually fire in production now (both live entry points reach request_forget), but FORGET_COMPLETED still never fires since propagate() is never invoked outside tests -- so 'forget' is only half-logged in practice: the request, not the completion.
E15-S5 cross-zone attempts logged regardless of outcome NOT STARTED -- concept removed, not just unreachable Backend: eva-core/isolation/policy.py:15-27 PolicyGate docstring and _decide() (lines 63-74) now check ONLY `caller.principal_id != target.principal_id` -- the zone-comparison branch is gone entirely, confirmed by DEV-NOTES.md:521-559 ('correction M: Descoped zoning as a security boundary... The caller.zone != target.zone branch and the AuditAction.CROSS_ZONE_ATTEMPT codepath are gone'). EVA_Shared/tests/test_isolation.py:84-102 test_policy_gate_allows_cross_zone_access_for_same_principal asserts a general->mnpi read for the same principal now returns allowed=True with action=POLICY_GATE_ALLOWED (not any zone-specific action). Grepped whole tree for 'AuditAction.CROSS_ZONE_ATTEMPT' in non-contract code: zero hits anywhere, including tests -- the enum value (audit.py:16) is fully dead, unconstructible in current code. This is a regression relative to the prior audit's framing: previously the logging call site was real but its sole caller never produced differing caller/target scopes, making it merely unreachable in practice. This session an explicit product decision (DEV-NOTES.md correction M) removed the zone-comparison branch and the CROSS_ZONE_ATTEMPT codepath from PolicyGate entirely, so there is no longer any code path, even theoretical, that could ever produce a 'cross-zone attempt' audit event -- the security concept the story depends on no longer exists in the policy engine.

E16 — Steward: Calendar and Scheduling (10 stories, P0 -- all NEW EPIC)

StoryVerdictEvidenceGap
E16-S1 OAuth + local mirror + re-auth PARTIAL (still, but wider gap than before) Backend: EVA_Shared/eva-domain/src/eva_domain/connectors/google.py:113-161 and m365.py:92-119 have real query_calendar/write_calendar_entry/create_calendar_entry against live Google/Graph APIs; EVA_Bureau/src/eva_bureau/routes/oauth_routes.py:107-201 does real code-exchange + persists tokens; EVA_Shared/eva-persistence/src/eva_persistence/persistence/credential_store.py:72-139 does real refresh-token rotation on expiry. NEW this session: EVA_Bureau/src/eva_bureau/routes/webhooks.py:26-78 -> connectors/base.py:77-89 handle_inbound() really publishes CALENDAR_CHANGED events to the bus, and EVA_Bureau/src/eva_bureau/consumers.py:143-149 (KeeperConsumer._on_calendar, wired live via EVA_Bureau/src/eva_bureau/wiring.py:48-49 start_consumers) really calls calendar_orchestrator.handle_calendar_event(). BUT EVA_Shared/eva-domain/src/eva_domain/wiring.py:349-351 instantiates CalendarOrchestrator(draft_manager=draft_manager) unconditionally with no Supabase-backed store (unlike draft_manager/commitment_tracker at wiring.py:341-359 which do switch to Supabase when configured) -- the 'mirror' is still an in-memory dict (eva_domain/keeper/calendar.py:25) lost on restart, and write_calendar_entry/create_calendar_entry are never called by propose_meeting (only registered as standalone MCP tools, never invoked from calendar.py). Frontend: EVA-Flutter-App-Frontend lib/features/settings/data/datasources/settings_remote_data_source.dart:16-25 and presentation/providers/settings_providers.dart:9-11 define getOAuthAuthorizeUrl/getOAuthStatus/oauthStatusProvider, but grep across lib/ shows oauthStatusProvider is never referenced by any widget and settings_widget.dart has no 'Connect Google/M365' UI element at all -- no re-auth prompt UX exists anywhere in the app. The webhook->bus->CalendarOrchestrator inbound mirror is now real (new this session), but it is still a non-persistent in-memory dict, never round-trips writes back to the real provider, and there is zero re-auth UX in Flutter (the OAuth-status plumbing exists in the data layer but is dead code, unconsumed by any screen).
E16-S2 propose/accept/decline within preferences NOT STARTED Backend: EVA_MCP/src/eva_mcp/registry/calendar.py:9-37 _handle_propose_meeting always creates the event unconditionally (no accept/decline path, no preference check) and EVA_MCP/src/eva_mcp/registry/catalogue.py:312 describes eva_propose_meeting as create-only ('Personal events are automatically suppressed'). Repo-wide grep for 'preference'/'accept'/'decline' in eva_domain/keeper and eva_mcp/registry returns zero calendar-relevant hits. Frontend: EVA-Flutter-App-Frontend has no calendar screen at all -- lib/core/widgets/pillar/pillar_models.dart:167-176 hardcodes a static 'CALENDAR NOT CONNECTED' placeholder card under the Keeper pillar tab, and lib/features/keeper/presentation/providers/keeper_providers.dart:24-26 explicitly comments 'Keep static Calendar and Drafts tabs until APIs exist for them.' No preference/negotiation model of any kind exists in backend or frontend; calendar UI in the app is a hardcoded static placeholder, not wired to any live API.
E16-S3 conflict detection → draft-and-hold NOT STARTED Backend: grep for 'conflict' across eva_domain/keeper and eva_mcp/registry (calendar.py specifically) returns zero hits; propose_meeting (eva_domain/keeper/calendar.py:27-47) has no overlap check against existing self._events before appending. Frontend: no calendar screen exists in EVA-Flutter-App-Frontend to surface a hold/conflict state. No conflict-check logic anywhere; two overlapping meetings can be proposed with no warning or hold state.
E16-S4 72-hour reschedule guard rail NOT STARTED Backend: grep for 'reschedul' and '72' in eva_domain/keeper/calendar.py and eva_mcp/registry/calendar.py returns zero hits; CalendarOrchestrator (eva_domain/keeper/calendar.py) has no update/reschedule method at all, only propose_meeting (create), work_visible_events, all_events, get_event_by_id. Frontend: no calendar UI exists to expose a reschedule action. Feature does not exist -- there isn't even a reschedule/update method on CalendarOrchestrator to guard.
E16-S5 provider writes + audit per write NOT STARTED Backend: google.write_calendar_entry (connectors/google.py:136-161) and m365.create_calendar_entry (connectors/m365.py:92-110) are real and independently tested (EVA_Shared/tests/test_connectors.py:220-258), but grep for their usage repo-wide shows they are only registered as standalone MCP boundary tools (wiring.py:223-224) -- _handle_propose_meeting (eva_mcp/registry/calendar.py:9-37) never calls either of them. Generic per-call audit exists only at the MCP dispatch layer (eva_mcp/registry/dispatch.py:211-229, PolicyGate.authorize) and applies uniformly to every tool call (reads and writes alike), not as a calendar-write-specific audit entry. propose_meeting never calls the real connector's write_calendar_entry/create_calendar_entry, so proposed meetings never reach the actual provider calendar; there is no write-specific audit trail in calendar code beyond the generic per-MCP-call PolicyGate audit that covers all tool calls indiscriminately.
E16-S6 personal blocks Steward-only PARTIAL Backend: CalendarEvent.suppressed_from_work_surfaces (eva_domain/keeper/calendar.py:19) is real and enforced by work_visible_events (calendar.py:100-105), which filters personal events out entirely; confirmed by EVA_Shared/tests/test_keeper.py:210-235 (test_calendar_orchestrator_opens_pre_meeting_brief_and_suppresses_personal_time) which asserts a 'Family time' personal event is fully absent from work_visible_events. Frontend: no calendar UI in EVA-Flutter-App-Frontend to render any placeholder either way. Personal/suppressed events are excluded entirely from work_visible_events rather than surfaced as an 'unavailable' placeholder block to other viewers of the same calendar.
E16-S7 privileged meetings suppressed unless opt-in NOT STARTED Backend: propose_meeting's only sensitivity signal is the boolean `personal` arg (eva_domain/keeper/calendar.py:35,44), which maps 1:1 to suppressed_from_work_surfaces; there is no DataZone.PRIVILEGED linkage, no separate privileged-meeting model, and no opt-in flag anywhere in calendar.py or eva_mcp/registry/calendar.py. Also relevant: EVA_Shared/eva-core/src/eva_core/isolation/policy.py:15-27 confirms PolicyGate's zone-check branch was removed this session entirely (_decide only compares caller.principal_id vs target.principal_id), so even the generic zone-based enforcement that might have backstopped privileged-meeting suppression no longer exists at all. Frontend: no calendar UI exists to render privileged-meeting opt-in. Only a generic boolean personal/private flag exists; no privileged-meeting-specific model, zone linkage, or opt-in mechanism, and the cross-cutting PolicyGate zone check that could have provided a backstop was removed this session.
E16-S8 sensitive tags affect surfacing NOT STARTED Backend: CalendarEvent dataclass (eva_domain/keeper/calendar.py:12-19) has exactly one sensitivity-adjacent field, the boolean suppressed_from_work_surfaces; handle_calendar_event (calendar.py:49-85) derives `personal` from payload['personal'] or payload['sensitivity']=='private', i.e. collapses any richer tag into one boolean. No tag taxonomy, list field, or per-tag surfacing rule exists anywhere in eva_domain/keeper or eva_mcp/registry. Frontend: no calendar UI exists to render tag-based surfacing. No tag taxonomy beyond a boolean personal/private field; a 'sensitivity' string from a provider payload is collapsed into the same single boolean as 'personal'.
E16-S9 explicit timezone, principal default PARTIAL (mostly done) Backend: CalendarEvent.timezone (eva_domain/keeper/calendar.py:18) is a real, populated field, and eva_mcp/registry/calendar.py:19 (`tz = args.get('timezone', 'UTC')`) and calendar.py:34 (`timezone: str = 'UTC'`) both accept an explicit timezone. Grepped EVA_Shared/eva-contracts/src/eva_contracts/contracts/principal.py:1-13 (PrincipalScope has only principal_id and zone, no timezone field) and repo-wide for principal/default/home/primary timezone -- zero hits anywhere in the codebase. Default is still hardcoded 'UTC'; there is no principal-level timezone preference/profile field anywhere in the contracts or persistence layer to default to.
E16-S10 publishes scheduling events to bus NOT STARTED Backend: grep for 'event_bus'/'publish'/'EventEnvelope' in eva_domain/keeper/calendar.py and eva_mcp/registry/calendar.py returns zero hits; CalendarOrchestrator (eva_domain/keeper/calendar.py:22-114) holds no reference to an EventBus at all (contrast with connectors/google.py:214 which does `await self.event_bus.publish(...)` for Gmail messages). Calendar only consumes bus events (via KeeperConsumer._on_calendar) inbound; it never emits any CALENDAR_CHANGED/scheduling event outbound after propose_meeting or trigger_pre_meeting_brief. CalendarOrchestrator never touches the event bus outbound; propose_meeting/trigger_pre_meeting_brief complete silently with no event published for other pillars/consumers to react to.

E17 — Delegate: Commitment Tracking and Chase (8 stories, P0/P1 -- all NEW EPIC)

All 8 verdicts unchanged from the 2026-07-06 audit (1 PARTIAL, 7 NOT STARTED) despite the backend restructure and a newly-checked Flutter frontend. Flutter's Commitment model/UI (lib/features/keeper/) is a read-only mirror of the backend dataclass and adds no new capability -- no create-with-deadline form, no chase/escalate UI action, no dedup, no delegate/privileged restriction -- so none of the gaps close on the frontend side.

StoryVerdictEvidenceGap
E17-S1 task w/ owner/deadline/dependencies/escalation PARTIAL Backend: EVA_Shared/eva-domain/src/eva_domain/keeper/commitments.py:17-24 -- Commitment(commitment_id, principal_id, zone, description, assignee, source, status) -- no deadline/dependencies/escalation fields. Persisted via EVA_Shared/eva-persistence/src/eva_persistence/persistence/commitment_store.py:29-50 (SupabaseCommitmentStore, table middleware_commitments) and migrations/009_commitments.sql:10-20 (same columns, no deadline/dependencies columns). Frontend: EVA-Flutter-App-Frontend/lib/features/keeper/data/models/commitment.dart:1-24 -- Commitment(id, description, status, source, assignee) mirrors the backend exactly; no deadline/dependencies/escalation fields anywhere in the Dart model or in KeeperRepository (lib/features/keeper/domain/repositories/keeper_repository.dart:4-20). Owner (assignee) exists on both sides; deadline, dependency graph, and escalation-history are absent from the dataclass, the Supabase schema, and the Flutter model/UI -- there is no field to hold them and no create-form UI to input them (createCommitment only accepts description/source/assignee).
E17-S2 auto status from events NOT STARTED Backend: EVA_Bureau/src/eva_bureau/consumers.py:107-152 -- KeeperConsumer.process() switches on EventType.COMMUNICATION_RECEIVED / CALENDAR_CHANGED / DOCUMENT_CHANGED and only touches memory_service and calendar_orchestrator; it never references commitment_tracker or calls update_status. The only callers of update_status are manual: PATCH /keeper/commitments/{id} in EVA_Bureau/src/eva_bureau/routes/keeper_routes.py:211-226 and the MCP complete_commitment handler in EVA_MCP/src/eva_mcp/registry/keeper.py:104-119, both explicit user/agent-invoked actions, not event-driven. Frontend: KeeperRemoteDataSource.updateCommitmentStatus (lib/features/keeper/data/datasources/keeper_remote_data_source.dart:27-29) exists but is never called from any UI widget (grep for callers found none outside the repository layer) -- so even the manual path has no UI trigger. No event-bus consumer (or any code path) transitions commitment status automatically from CALENDAR_CHANGED/COMMUNICATION_RECEIVED/etc; status changes require a direct API/MCP call, and Flutter doesn't even expose a UI control for that manual call.
E17-S3 chase reminders w/ ceiling NOT STARTED Repo-wide grep for 'chase' and 'reminder' across EVA_Services (excluding __pycache__) returns zero hits anywhere near commitment/keeper code -- no scheduler job, no cooldown/ceiling logic comparable to the Sentinel pillar's cooldown pattern. EVA_Bureau/src/eva_bureau/scheduler.py has no commitment-related job (only a 'THE DAY' brief text mention at line 289). Frontend: no 'chase'/'reminder' string anywhere in EVA-Flutter-App-Frontend/lib (grep clean outside of unrelated Flutter framework matches). No chase/reminder subsystem exists for commitments at all, on either backend or frontend -- not even a stub.
E17-S4 escalate overdue/blocked NOT STARTED Backend: CommitmentStatus.OVERDUE is declared at EVA_Shared/eva-domain/src/eva_domain/keeper/commitments.py:12, but grep across EVA_Services shows nothing ever constructs/sets CommitmentStatus.OVERDUE (create() always sets OPEN at commitments.py:39-46 and commitment_store.py:85-92; update_status is only ever invoked with COMPLETED from keeper_routes.py:214-223 and registry/keeper.py:114-116). Frontend: lib/features/keeper/presentation/providers/keeper_providers.dart:71-75 -- _commitmentPriority() checks `if (c.status == 'overdue') return CardPriority.action;` -- live UI logic exists to *render* an overdue state, but since the backend never sets that status, this branch is dead code in practice. Dead enum value on the backend with no code path that ever assigns it; Flutter has a dormant UI branch ready to display it but nothing ever feeds it that status, so no escalation/blocked handling exists end-to-end.
E17-S5 no external auto-chase without auth NOT STARTED Same grep evidence as E17-S3: no chase mechanism exists anywhere in EVA_Services or EVA-Flutter-App-Frontend to gate with an auth check. Moot -- there is no chase mechanism at all (backend or frontend) to require authorization for.
E17-S6 privileged tasks principal-only NOT STARTED Backend: grep for policy_gate/PolicyGate across keeper_routes.py, registry/keeper.py, commitments.py, and commitment_store.py returns zero hits -- commitment create/list/update paths never call PolicyGate.authorize at all. PrincipalScope.storage_namespace (EVA_Shared/eva-contracts/src/eva_contracts/contracts/principal.py:12-13) still keys storage by `principal:{id}:zone:{zone}` (generic namespacing only). Separately, EVA_Shared/eva-core/src/eva_core/isolation/policy.py:63-71 -- PolicyGate._decide() this session was changed to ONLY check `caller.principal_id != target.principal_id`; the zone-check branch was removed entirely (confirmed by reading the file; docstring at lines 21-26 explicitly says zone is 'no longer an enforced security boundary'). Frontend: grep for delegate/privileged/role concepts in EVA-Flutter-App-Frontend/lib found nothing tied to Keeper commitments (only unrelated Flutter framework hits and one unrelated 'privileged' case in the Memory pillar's zone picker, memory_search_screen.dart:185). No purpose-built delegate-exclusion rule for privileged commitments exists in the backend (commitment endpoints never call the policy gate in the first place), and it is now structurally impossible for PolicyGate to enforce one even if wired in, since its zone-check branch was deleted this session. Flutter has no delegate-role concept for the Keeper commitments screen either.
E17-S7 publishes task transitions to bus NOT STARTED EVA_Shared/eva-domain/src/eva_domain/wiring.py:354-359 constructs commitment_tracker as a plain SupabaseCommitmentStore/CommitmentTracker instance with no event_bus argument or decorator. Neither commitments.py nor commitment_store.py imports eva_bus anywhere (grep clean). keeper_routes.py and registry/keeper.py commitment handlers (create/update_status/list) never call event_bus.publish. No Flutter-side equivalent expected since this is a pure backend-to-backend bus concern; confirmed no such publish exists to check against. Commitment create/update_status never touches the event bus (eva_bus.event_bus) -- only logger-less silent state changes in the in-memory cache and Supabase row.
E17-S8 dedup against Memory commitment IDs NOT STARTED CommitmentTracker.create() (EVA_Shared/eva-domain/src/eva_domain/keeper/commitments.py:31-48) and SupabaseCommitmentStore.create() (EVA_Shared/eva-persistence/src/eva_persistence/persistence/commitment_store.py:77-114) both unconditionally call uuid4() for commitment_id with no prior lookup against memory_service or any existing commitment. Grep for 'commitment' inside memory-related code (keeper/, persistence/) filtered to memory context returns no hits -- Memory has no commitment-ID concept to dedup against in the first place. create() always mints a fresh UUID; there is no lookup step against Memory facts or existing commitments before insert, and Memory itself has no notion of a commitment ID to compare against.

E18 — Scribe: Long-Form Drafting and Provenance (7 stories, P0 -- all NEW EPIC)

2 PARTIAL (S1, S2) + 5 NOT STARTED, same mix as prior audit. Cross-cutting: eva-core/isolation/policy.py's PolicyGate had its zone-check branch removed entirely this session (not left merely unreachable) -- it now only compares caller.principal_id vs target.principal_id. This deepens S5/S6's gaps: there is no longer any zone-enforcement primitive in the shared layer for a future privileged/MNPI gate in Scribe to call into. Frontend check: the path given (EVA-Flutter-App-Frontend) does not exist in this environment; the actual current frontend is the Next.js app at /Users/g33tansh/Desktop/EVA-App/eva-next, which has zero Scribe-specific UI (no scribe pillar page, no OAuth-connect flow, and its DraftReviewScreen.tsx only renders hardcoded MOCK_DRAFTS for keeper/arbiter reply drafts, unrelated to Scribe's board-deck/investor-memo doc types) -- no verdict changes on frontend grounds.

StoryVerdictEvidenceGap
E18-S1 reads authorised repos via OAuth PARTIAL Backend: EVA_Bureau/src/eva_bureau/routes/oauth_routes.py:107-152 (google_callback) and :155-200 (m365_callback) exchange OAuth codes and call CredentialBroker.register() (lines 149-150, 197-198) with drive.readonly scope declared at oauth_routes.py:33. EVA_Shared/eva-domain/src/eva_domain/connectors/google.py:163-180 implements google.fetch_document (real Drive v3 export call) and connectors/m365.py:112-119 implements m365.fetch_document (Graph /me/drive/items/{id}/content); both registered as MCP boundary tools (google.py:64, m365.py:56) and allowlisted in wiring.py:223-225. But EVA_Shared/eva-domain/src/eva_domain/wiring.py:374-380 constructs ScribeService with only voice_profile_manager, memory_service, db -- no credential_broker/connector reference at all, and eva_domain/keeper/scribe.py's ScribeService.__init__ (166-183) and create_document (225-292) never call fetch_document or read any external file. eva-contracts/src/eva_contracts/contracts/enums.py:10-18 SourceType has no SHAREPOINT or NOTION member. Frontend: no OAuth/authorize_url code found anywhere under eva-next (grep for 'oauth'/'authorize_url' across .ts/.tsx returns zero hits); no Flutter app exists at the given path. Same gap as before, just at new paths: OAuth read capability lives at the connector/MCP-tool layer only and is still completely disconnected from ScribeService -- create_document has no code path that ever calls google.fetch_document or m365.fetch_document. Still no SharePoint-site or Notion connector/SourceType at all. No frontend surface (Flutter or the actual eva-next app) exposes a 'connect your repos' flow tied to Scribe either.
E18-S2 drafts long-form in principal's voice PARTIAL EVA_Shared/eva-domain/src/eva_domain/keeper/scribe.py: create_document (225-292) pulls memory snippets via memory_service.recall_subject (241-253) and a voice prefix via voice_profile_manager.prompt_prefix (258-262), and builds real per-doc-type section headings from _DOC_SECTIONS (74-122). But _scaffold_section (380-390) still emits the literal placeholder: '[OpenClaw: flesh out this section drawing on the principal's voice and memory context above.]' (line 389) as the section content -- no real prose is generated. ScribeDocument.status is hardcoded 'draft' at creation (line 280) and there is no update/edit method anywhere in the class (only create_document, review_contract, list_documents, get_document), so there is no edit-before-send lifecycle. EVA_Shared/tests/test_scribe.py (all tests) only assert scaffold structure/section counts, never real content, confirming this is the intended current behavior, not a regression. Section content is still a scaffold placeholder string for OpenClaw to fill in later, not actual generated prose; no edit/approve/send lifecycle exists on ScribeDocument beyond create+list+get.
E18-S3 every claim cites a source NOT STARTED eva_domain/keeper/scribe.py's ScribeDocument dataclass (134-146) has fields doc_id/principal_id/zone/doc_type/title/brief/sections/status/created_at/updated_at/memory_context only -- no citation, source, or claim field anywhere. Sections are plain {"heading", "content"} dicts (142, 264-270). EVA_MCP/src/eva_mcp/registry/scribe.py's four handlers (_handle_scribe_create/review/list/get, lines 8-104) never reference citations. grep for 'citation' across the whole EVA_Services tree (excluding .venv) hits zero files under scribe.py or its registry handler. No per-claim citation linkage, no uncited-claim flagging, no source field anywhere in the Scribe data model or handlers.
E18-S4 edits → versioned voice profile NOT STARTED EVA_Shared/eva-domain/src/eva_domain/keeper/voice.py: VoiceProfileManager keeps a single dict self._profiles: dict[str, VoiceProfile] (line 39); update_from_correction (62-92) builds one new VoiceProfile and overwrites the slot at self._profiles[scope.storage_namespace] = profile (line 77), persisting via upsert(on_conflict="principal_id") (lines 80-89) which replaces the row rather than appending. get_profile (94-95) does a single dict lookup with no version parameter. migrations/013_voice_profiles.sql defines voice_profiles with principal_id TEXT PRIMARY KEY (line 8) -- one row per principal, no version column, no history table. No rollback/list-versions method exists on the class. Voice profile is still a single-slot, last-write-wins record both in memory and in Supabase (PK is principal_id alone); there is no version history table, no way to list or roll back to a prior voice profile version.
E18-S5 privileged docs read-only, cue-card citations NOT STARTED -- underlying zone-enforcement primitive also removed this session eva_domain/keeper/scribe.py: ScribeDocument.zone (line 138) is stored on every document but create_document (225-292) never reads or branches on it -- there is no read-only/privileged check anywhere in the class or in EVA_MCP/src/eva_mcp/registry/scribe.py's handlers. Separately, EVA_Shared/eva-core/src/eva_core/isolation/policy.py's PolicyGate._decide (63-74) now only compares caller.principal_id != target.principal_id; the class docstring (15-27) explicitly states 'Zone (general/mnpi/privileged) is no longer an enforced security boundary here' -- the old cross-zone check was removed outright this session, not merely left unreachable. No privileged/general distinction anywhere in create_document, and no cue-card-style citation format exists at all (see S3). Additionally, the shared PolicyGate no longer has any zone-check code path to hook into, so this story now requires rebuilding zone-based access control from scratch, not just wiring an existing check into Scribe.
E18-S6 MNPI per-draft confirm, HR never auto-composed NOT STARTED -- underlying zone-enforcement primitive also removed this session EVA_MCP/src/eva_mcp/registry/scribe.py's _handle_scribe_create (8-28) passes args.get("doc_type"/"title"/"brief"/"memory_context_hints") straight into scribe.create_document with zero confirmation step or zone check. eva_domain/keeper/scribe.py's create_document (225-292) has no MNPI gating logic. eva-core/isolation/policy.py's PolicyGate (see S5 evidence, lines 15-27, 63-74) no longer enforces zone at all -- only principal-id equality. No per-draft MNPI confirmation prompt, no HR-document auto-compose block anywhere in the create path; and the shared PolicyGate that could have been extended to add this check no longer has a zone branch to build on.
E18-S7 publishes events on drafts NOT STARTED eva_domain/keeper/scribe.py's ScribeService.__init__ (172-183) stores only voice_profile_manager, memory_service, db, and self._documents -- no event_bus reference anywhere in the file. create_document's only side-effect notification is logger.info("scribe: created %s document %s for %s", ...) at lines 286-289. grep for 'event_bus' across scribe.py (both the eva_domain module and the EVA_MCP registry handler) returns zero hits. Document creation (and by extension any future edit/finalize action) is still logged locally only; nothing is published to the event bus that other pillars (Cartographer, Arbiter, etc.) could subscribe to.

E19 — Subscription Lifecycle: 90-day demo to downgrade (8 stories, P0 -- all NEW EPIC)

All 8 re-confirmed NOT STARTED with zero code footprint after full re-verification against the restructured backend (EVA_Bureau/EVA_MCP/EVA_Shared under /Users/g33tansh/Desktop/EVA-Services/EVA_Services) and the actual frontend on disk. Note: the task's claimed frontend path /Users/g33tansh/Desktop/EVA-App/EVA-Flutter-App-Frontend does not exist anywhere on this machine (confirmed via find/mdfind across the whole home directory, and no pubspec.yaml exists outside pub-cache) -- the only real frontend present is /Users/g33tansh/Desktop/EVA-App/eva-next, a Next.js/TypeScript demo app (not Flutter), which was checked and also has zero subscription-lifecycle code. All prior false positives persist under new paths (m365.py subscriptionId webhook field, arbiter/tone.py Golden Baseline 90-day lock, connectors/correspondent.py CoVe "never downgrade the action", arbiter/confidant.py reactivation_opportunity contact-lapse flag). One new candidate was found and ruled out this session: eva_domain/compliance/recovery.py's RecoveryCoordinator (generic snapshot/restore infra, wired into app.state in wiring.py but never invoked by any route/tool per grep) -- it is disaster-recovery/backup infrastructure, unrelated to subscription reactivation.

StoryVerdictEvidenceGap
E19-S1 90-day demo lights up pillars NOT STARTED Backend: migrations/006_principals_metadata.sql (the only principals-table migration) has just id, created_at, display_name, email_hint -- no demo/trial/pillar-activation columns. grep -rniE "subscription|tenant|downgrade|trial|demo_period" across EVA_Bureau/, EVA_MCP/, EVA_Shared/ (excluding .venv) returns zero real hits -- only the M365 webhook field `item.get("subscriptionId")` in eva-domain/connectors/m365.py:35 and the unrelated 90-day Golden Baseline lock in eva-domain/arbiter/tone.py:39,157. Frontend: eva-next/lib/*.ts (the only frontend that exists; EVA-Flutter-App-Frontend is absent from disk) has no pillar-lock/demo-countdown logic -- lib/systemPrompts.ts:47 mentions "first 90 days" only in the same unrelated Golden Baseline context. No demo-clock concept, no principal field for demo start/expiry, no pillar-gating logic tied to any timer, in either codebase.
E19-S2 downgrade revokes Arbiter creds NOT STARTED EVA_Shared/eva-domain/src/eva_domain/mcp_boundary/credentials.py:21-25 (CredentialBroker Protocol) exposes only register/inject, no revoke method and no Arbiter-specific type. EVA_Bureau/src/eva_bureau/routes/keys_routes.py:56-63 has a `revoke_key` endpoint, but it revokes a self-service Bureau API key on principal request -- it is not triggered by, or aware of, any subscription/downgrade state, and has no relation to the Arbiter pillar. grep for subscription/downgrade anywhere in credential-related code returns nothing. No downgrade event exists anywhere to trigger credential revocation; no code differentiates Arbiter credentials from other connector credentials for lifecycle purposes.
E19-S3 Editor stops consuming events NOT STARTED EVA_Shared/eva-domain/src/eva_domain/keeper/editor.py (EditorService: lock_draft, auto_lock_pending, inject_p0_alert) has no subscription/account-state check gating its operation. EVA_Bureau/src/eva_bureau/consumers.py (the event-bus consumer wiring) has no branch referencing subscription/downgrade/demo state (grep empty). EVA_Bureau/src/eva_bureau/scheduler.py:314-332 runs Editor's auto-lock job unconditionally for all principals. No mechanism exists anywhere to pause/stop Editor's event consumption based on account/subscription state.
E19-S4 Cartographer stops processing NOT STARTED EVA_Shared/eva-domain/src/eva_domain/cartographer/{crawler.py, evaluator.py, relevance.py} contain no subscription/downgrade gating (grep -rniE "subscription|downgrade|demo|trial" over the cartographer/ package returns only the unrelated severity-taxonomy word "downgrade" in evaluator.py:170's list of financial-impact keywords, e.g. "bankruptcy, recall, breach, fine, penalty, downgrade"). No caller conditions Cartographer processing on any lifecycle/subscription state. No stop/pause switch for Cartographer processing tied to account state exists in code.
E19-S5 Memory retains history NOT STARTED grep -rniE "retention|ttl|expire|expiry" over EVA_Shared/eva-persistence and EVA_Shared/eva-domain returns only unrelated hits: OAuth token expires_at in persistence/credential_store.py:12-94, an undo-batch window in renderer/batch_undo.py:33-105, the Golden Baseline lock in arbiter/tone.py:164-166, and JWT expiry in keeper/attestation.py:69-70. No data-retention policy tied to a demo/downgrade lifecycle exists in either store layer. No retention rule distinguishing "active subscription" vs. "downgraded/lapsed" data-keeping exists anywhere.
E19-S6 Cartographer history read-only NOT STARTED grep -rniE "read_only|readonly|read-only" across the whole backend returns only: an unrelated Google OAuth scope string `drive.readonly` in EVA_Bureau/routes/oauth_routes.py:33, and a zone-isolation test name `test_scoped_store_allows_same_zone_read_only` in EVA_Shared/tests/test_isolation.py:141-148 that tests same-zone data reads, not an account-level read-only lockdown mode. No Cartographer route/tool checks any downgrade/read-only flag before allowing writes. No read-only-mode concept exists for Cartographer at all; nothing to flip on downgrade.
E19-S7 Arbiter graph read-only NOT STARTED Same grep result as E19-S6 -- no read_only/readonly mechanism anywhere in the backend. EVA_Bureau/src/eva_bureau/routes/arbiter_routes.py exposes Arbiter operations with no gating on any lifecycle/subscription state (confirmed no subscription/downgrade token in that file via grep). Note independently: eva-core/isolation/policy.py's PolicyGate now only checks `caller.principal_id != target.principal_id` (the zone-check branch was removed this session) -- but this is irrelevant to E19-S7 since no downgrade-triggered read-only enforcement was ever built on top of PolicyGate to begin with; there is nothing for the zone-check removal to have broken here. No read-only-mode concept exists for Arbiter's relationship graph.
E19-S8 reactivation resumes forward NOT STARTED grep for "reactivat" hits only EVA_Shared/eva-domain/src/eva_domain/arbiter/confidant.py:319-340 (`_detect_reactivation`, a ConfidantAgent flag for a lapsed *contact* relationship, unrelated to account/subscription reactivation) and EVA_Shared/tests/test_confidant.py:165-216 (tests for that same contact-lapse flag). The one plausible piece of adjacent infra, RecoveryCoordinator (EVA_Shared/eva-domain/src/eva_domain/compliance/recovery.py:19-58, wired at EVA_Shared/eva-domain/src/eva_domain/wiring.py:520-525), is a generic point-in-time snapshot/restore utility for disaster recovery/erasure rollback -- grep for `state.recovery`/`app.state.recovery` across EVA_Bureau and EVA_MCP routes returns zero hits, so it is never called from any route or MCP tool, and it has no concept of "resume event processing forward from where a downgraded account left off." No subscription-reactivation flow exists; the only naming-adjacent code (contact reactivation flag, generic recovery snapshotter) is unrelated and/or unwired.

How to Read This Document