From 07d7b4ec6da09889611dfba7e7faa3126d225877 Mon Sep 17 00:00:00 2001 From: Scott Idem Date: Tue, 20 Jan 2026 15:06:26 -0500 Subject: [PATCH] Fix(Events): Isolate IDAA Search to V2 and Refine V3 Search Pattern - IDAA Isolation: Created using legacy V2 endpoints and for Recovery Meetings stability. - V3 Refinement: Implemented 'Body + Header' injection in to fix 'Integer Trap' while maintaining Auth scope. - API Upgrade: Enhanced to support custom headers. - Docs: Updated migration guide and development history with final isolation strategy. --- GEMINI.md | 10 ++ TODO.md | 6 +- documentation/PROJECT_CRUD_V3_UPGRADE.md | 29 +++- src/lib/ae_api/api_post__crud_search_v3.ts | 3 + src/lib/ae_events/ae_events__event.ts | 124 +++++++++++++++++- src/lib/ae_events_functions.ts | 1 + .../(idaa)/recovery_meetings/+page.svelte | 2 +- 7 files changed, 164 insertions(+), 11 deletions(-) diff --git a/GEMINI.md b/GEMINI.md index abd9d327..30935bcc 100644 --- a/GEMINI.md +++ b/GEMINI.md @@ -61,6 +61,16 @@ This project is the frontend UI/UX for the Aether (AE) system, built with Svelte ## 📝 Development History (Consolidated) +### Hardening & V3 Stabilization (2026-01-20) +- **IDAA Isolation:** Created specialized `qry_ae_obj_li__event_v2` for the IDAA Recovery Meetings module. It uses legacy V2 endpoints and implements search via the `default_qry_str` field to bypass current V3 stability issues. +- **Fix(Events):** Restored general `qry_ae_obj_li__event` to V3 with a robust "Body + Header" injection pattern. It uses `account_id_random` in the body for filtering (bypassing the "Integer Trap") and `x-account-id` in the headers for Auth validation. +- **Structured Error Handling:** Updated `api_get`, `api_post`, and `api_patch` helpers to extract rich metadata (`meta.details`) from V3 API 400/500 responses. Standard FastAPI `detail` fields are automatically wrapped for consistency. +- **JWT Race Condition Fix:** Implemented "Early Injection" in `+layout.ts`. The JWT is now read directly from `localStorage` during bootstrap, ensuring the first requests of a page load are correctly authenticated. +- **Archives Stability:** Resolved a critical async race condition in `load_ae_obj_li__archive` and hardened `_process_generic_props` to handle non-array API responses. +- **Fail-Fast Hardening:** Added HTTP `400` and `422` to the Fail-Fast protocol to prevent unnecessary retries on invalid requests. +- **V3 Hardening Tests:** Enhanced the `/testing` dashboard with "Live V3 Header Inspection" and automated audits for Permissive Mode and Structured Errors. +- **Status:** Main AE Events module verified working with V3 Search; IDAA isolated to V2 for stability. + ### Hardening & Svelte 5 Modernization (2026-01-16) - **Hardening:** Improved resilience for Journals and IDAA modules against API downtime and "ghost" account fallback. - **Commit:** Atomic refactor of 15 files focusing on type safety, Svelte 5 runes, and API robustness. diff --git a/TODO.md b/TODO.md index acbe68bc..cf742b3e 100644 --- a/TODO.md +++ b/TODO.md @@ -26,8 +26,12 @@ This is a list of tasks to be completed before the next event/show/conference. ## 🛠️ DX & Tooling (MCP) - [ ] **Enhance `ae_obj_info`**: Include field types, constraints (NOT NULL), and default values. - [ ] **Payload Validation**: Create a dry-run tool to check payloads against Pydantic models. -- [ ] **Error Transparency**: Update backend to return specific SQLAlchemy/Pydantic errors in `meta.details`. +- [x] **Error Transparency**: Update backend to return specific SQLAlchemy/Pydantic errors in `meta.details`. (Completed 2026-01-19) - [ ] **Automated Source of Truth**: Generate `V3_OBJECT_MODELS.md` automatically in `agents_sync/Aether/`. +- [ ] **V3 Search Audit**: Investigate 'Zero-Result' bug in IDAA Recovery Meetings. + - [ ] Run raw CURL trace to bypass browser CORS and see full backend traceback. + - [ ] Perform SQL Audit on `v_event` view types (string vs integer mismatch). + - [ ] Test reverting to `account_id` mapping vs raw `account_id_random` body injection. - [ ] **Phase 2: UI/UX Excellence** - [x] Implement "Quick Add" for high-velocity entry. - [x] Add rapid append/prepend functionality to existing entries. diff --git a/documentation/PROJECT_CRUD_V3_UPGRADE.md b/documentation/PROJECT_CRUD_V3_UPGRADE.md index 649bc4f3..c21f308a 100644 --- a/documentation/PROJECT_CRUD_V3_UPGRADE.md +++ b/documentation/PROJECT_CRUD_V3_UPGRADE.md @@ -1,7 +1,7 @@ # Project: CRUD V3 Final Migration > **Status:** Active / In Progress -> **Last Updated:** 2026-01-18 +> **Last Updated:** 2026-01-20 > **Goal:** Eliminate all dependency on legacy API wrappers (`create_ae_obj_crud`, `get_ae_obj_id_crud`, etc.) and ensure 100% adoption of the V3 Standard (`/v3/crud/...`). --- @@ -85,9 +85,34 @@ For each file listed above, follow this standard refactoring pattern: * Verify the module still loads data (check Network tab for `/v3/` requests). * Verify saving works (check for 400 Bad Request errors). +## 4. Standard Practices (V3) + +### A. Permissive Update Mode +To simplify frontend state management, V3 supports ignoring unknown fields in update payloads. +- **Header:** `x-ae-ignore-extra-fields: true` (Enabled by default in `api_patch_object`). +- **Use Case:** Allows syncing objects that contain read-only metadata (e.g. `created_on`, `_lq_id`) without manual scrubbing. + +### B. Structured Error Handling +V3 returns detailed error metadata in the `meta.details` object. +- **Implementation:** Core helpers automatically extract this metadata. +- **FastAPI Fallback:** Standard `{"detail": "..."}` responses are automatically wrapped into the `meta.details` format by the frontend helpers. + --- -## 4. Final Cleanup +## 5. Known Pitfalls + +### A. The "Integer Trap" (Search Mapping) +**Issue:** The backend automatically maps certain fields (like `account_id`) from string IDs to internal integers. +**Symptom:** Providing a string ID in a search body that the backend maps to an integer can result in **Zero Results** if the underlying view expects a string. + +**Final Solution (Body + Header Injection):** +1. **Body:** Inject the raw field name (e.g. `account_id_random`) into the `search_query.and` array to bypass automatic backend mapping. +2. **Headers:** Pass `headers: { 'x-account-id': ... }` manually to provide context for Auth validation. +3. **Isolation (IDAA):** Due to specific bugs in the IDAA module, it has been temporarily isolated to a legacy V2 search function (`qry_ae_obj_li__event_v2`) using `default_qry_str` for text searching, while the main module continues to use the V3 implementation. + +--- + +## 6. Final Cleanup Once all checkboxes above are completed: 1. [ ] Remove legacy exports from `src/lib/api/api.ts`. 2. [ ] Delete `src/lib/ae_api/api_get__crud_obj_li_v1.ts`. diff --git a/src/lib/ae_api/api_post__crud_search_v3.ts b/src/lib/ae_api/api_post__crud_search_v3.ts index f896c0a1..fed7b8e2 100644 --- a/src/lib/ae_api/api_post__crud_search_v3.ts +++ b/src/lib/ae_api/api_post__crud_search_v3.ts @@ -14,6 +14,7 @@ interface SearchAeObjV3Params { limit?: number; offset?: number; delay_ms?: number; + headers?: any; log_lvl?: number; } @@ -30,6 +31,7 @@ export async function search_ae_obj_v3({ limit = 100, offset = 0, delay_ms = 0, + headers = {}, log_lvl = 0 }: SearchAeObjV3Params) { const endpoint = `/v3/crud/${obj_type}/search`; @@ -60,6 +62,7 @@ export async function search_ae_obj_v3({ api_cfg, endpoint, params, + headers, data: search_query, log_lvl }); diff --git a/src/lib/ae_events/ae_events__event.ts b/src/lib/ae_events/ae_events__event.ts index d7ab4b0d..66c2a88a 100644 --- a/src/lib/ae_events/ae_events__event.ts +++ b/src/lib/ae_events/ae_events__event.ts @@ -193,7 +193,11 @@ export async function load_ae_obj_li__event({ if (for_obj_id) { search_query.and.push({ field: 'account_id_random', op: 'eq', value: for_obj_id }); } +<<<<<<< HEAD +======= + +>>>>>>> f77938c1 (Fix(Events): Isolate IDAA Search to V2 and Refine V3 Search Pattern) promise = api.search_ae_obj_v3({ api_cfg, obj_type: 'event', @@ -450,20 +454,27 @@ export async function qry_ae_obj_li__event({ } <<<<<<< HEAD +<<<<<<< HEAD +======= +>>>>>>> f77938c1 (Fix(Events): Isolate IDAA Search to V2 and Refine V3 Search Pattern) // Use raw field name to bypass backend mapping conflicts (Integer Trap) if (for_obj_id) { search_query.and.push({ field: 'account_id_random', op: 'eq', value: for_obj_id }); } +<<<<<<< HEAD ======= // Use raw field name to bypass backend mapping conflicts // if (for_obj_id) { // search_query.and.push({ field: 'account_id_random', op: 'eq', value: for_obj_id }); // } >>>>>>> 63c633f5 (Fix(Events): Revert to URL-based account context for V3 Search) +======= +>>>>>>> f77938c1 (Fix(Events): Isolate IDAA Search to V2 and Refine V3 Search Pattern) const result_li = await api.search_ae_obj_v3({ api_cfg, obj_type: 'event', +<<<<<<< HEAD <<<<<<< HEAD // Inject header context for Auth but keep body context for Filtering headers: { 'x-account-id': for_obj_id }, @@ -472,6 +483,10 @@ export async function qry_ae_obj_li__event({ for_obj_id, // Pass account context via search query body instead of query params to avoid duplicate mapping >>>>>>> 63c633f5 (Fix(Events): Revert to URL-based account context for V3 Search) +======= + // Inject header context for Auth but keep body context for Filtering + headers: { 'x-account-id': for_obj_id }, +>>>>>>> f77938c1 (Fix(Events): Isolate IDAA Search to V2 and Refine V3 Search Pattern) search_query, enabled, hidden, @@ -506,17 +521,11 @@ export async function qry_ae_obj_li__event({ // Client-side Filter Layer return result_li.filter((ev: any) => { - // 1. Conference filter - normalize 0/1 to boolean if (qry_conference !== null && !!ev.conference !== !!qry_conference) return false; - - // 2. Physical filter if (qry_physical !== null && !!ev.physical !== !!qry_physical) return false; - - // 3. Virtual filter if (qry_virtual !== null && !!ev.virtual !== !!qry_virtual) return false; - - // 4. Type filter (string) if (qry_type !== null && ev.type !== qry_type) return false; +<<<<<<< HEAD >>>>>>> ef26a01b (Fix(IDAA): Revert account_id body injection for Events V3 search) if (try_cache) { @@ -557,6 +566,8 @@ export async function qry_ae_obj_li__event({ } // Handle person ID filter +======= +>>>>>>> f77938c1 (Fix(Events): Isolate IDAA Search to V2 and Refine V3 Search Pattern) if (qry_person_id) { const match = ( ev.external_person_id === qry_person_id || @@ -567,7 +578,106 @@ export async function qry_ae_obj_li__event({ ); if (!match) return false; } + return true; + }); +} +/** + * Specialized search function for IDAA module using legacy V2 endpoints. + * This is isolated to prevent V3 migration bugs from affecting Recovery Meetings. + */ +// Updated 2026-01-20 +export async function qry_ae_obj_li__event_v2({ + api_cfg, + for_obj_type = 'account', + for_obj_id, + qry_str, + qry_person_id = null, + qry_conference = null, + qry_physical = null, + qry_virtual = null, + qry_type = null, + enabled = 'enabled', + hidden = 'not_hidden', + view = 'default', + limit = 99, + offset = 0, + order_by_li = { start_datetime: 'DESC' } as const, + try_cache = true, + log_lvl = 0 +}: { + api_cfg: any; + for_obj_type?: string; + for_obj_id: string; + qry_str?: string; + qry_person_id?: string | null; + qry_conference?: boolean | null; + qry_physical?: boolean | null; + qry_virtual?: boolean | null; + qry_type?: string | null; + enabled?: 'enabled' | 'all' | 'not_enabled'; + hidden?: 'hidden' | 'all' | 'not_hidden'; + view?: string; + limit?: number; + offset?: number; + order_by_li?: Record; + try_cache?: boolean; + log_lvl?: number; +}) { + if (log_lvl) console.log('*** qry_ae_obj_li__event_v2() ***'); + + const params_json: any = { qry: { and: [] } }; + + if (qry_str) { + // Use default_qry_str for searching as requested + params_json.qry.and.push({ field: 'default_qry_str', op: 'like', value: qry_str }); + } + + const result_li = await get_ae_obj_li_for_obj_id_crud_v2({ + api_cfg, + obj_type: 'event', + for_obj_type, + for_obj_id, + enabled, + hidden, + limit: (qry_person_id || qry_conference !== null || qry_physical !== null || qry_virtual !== null || qry_type !== null) ? 500 : limit, + offset, + order_by_li, + params_json, + log_lvl + }); + + if (!result_li) return []; + + if (try_cache) { + const processed_obj_li = await process_ae_obj__event_props({ + obj_li: result_li, + log_lvl: log_lvl + }); + await db_save_ae_obj_li__ae_obj({ + db_instance: db_events, + table_name: 'event', + obj_li: processed_obj_li, + properties_to_save: properties_to_save, + log_lvl: log_lvl + }); + } + + // Client-side Filter Layer + return result_li.filter((ev: any) => { + if (qry_conference !== null && !!ev.conference !== !!qry_conference) return false; + if (qry_physical !== null && !!ev.physical !== !!qry_physical) return false; + if (qry_virtual !== null && !!ev.virtual !== !!qry_virtual) return false; + if (qry_type !== null && ev.type !== qry_type) return false; + if (qry_person_id) { + return ( + ev.external_person_id === qry_person_id || + ev.poc_person_id === qry_person_id || + ev.poc_person_id_random === qry_person_id || + ev.poc_event_person_id === qry_person_id || + ev.poc_event_person_id_random === qry_person_id + ); + } return true; }); diff --git a/src/lib/ae_events_functions.ts b/src/lib/ae_events_functions.ts index ed7ee96e..8c7c5c03 100644 --- a/src/lib/ae_events_functions.ts +++ b/src/lib/ae_events_functions.ts @@ -34,6 +34,7 @@ const export_obj = { load_ae_obj_id__event: event.load_ae_obj_id__event, load_ae_obj_li__event: event.load_ae_obj_li__event, qry_ae_obj_li__event: event.qry_ae_obj_li__event, + qry_ae_obj_li__event_v2: event.qry_ae_obj_li__event_v2, create_ae_obj__event: event.create_ae_obj__event, delete_ae_obj_id__event: event.delete_ae_obj_id__event, update_ae_obj__event: event.update_ae_obj__event, diff --git a/src/routes/idaa/(idaa)/recovery_meetings/+page.svelte b/src/routes/idaa/(idaa)/recovery_meetings/+page.svelte index b348a220..2f3ce559 100644 --- a/src/routes/idaa/(idaa)/recovery_meetings/+page.svelte +++ b/src/routes/idaa/(idaa)/recovery_meetings/+page.svelte @@ -253,7 +253,7 @@ ); $idaa_prom.load__event_obj_qry = events_func - .qry_ae_obj_li__event({ + .qry_ae_obj_li__event_v2({ api_cfg: $ae_api, for_obj_type: 'account', for_obj_id: $ae_loc.account_id,