From 6380effa90342e2076ae5b0f3dcb2f8a986e132c Mon Sep 17 00:00:00 2001 From: Scott Idem Date: Mon, 19 Jan 2026 19:37:18 -0500 Subject: [PATCH] Quickly saving in progress changes. We are working on why the IDAA Recovery Meetings are not loading. 403 errors. --- ...API_HARDENING_SESSION_REPORT_2026-01-19.md | 57 +++++++++++++++++++ src/lib/ae_api/api_get_object.ts | 16 +++--- src/lib/ae_api/api_patch_object.ts | 15 ++--- src/lib/ae_api/api_post_object.ts | 16 +++--- src/routes/+layout.ts | 15 +++++ 5 files changed, 92 insertions(+), 27 deletions(-) create mode 100644 documentation/V3_API_HARDENING_SESSION_REPORT_2026-01-19.md diff --git a/documentation/V3_API_HARDENING_SESSION_REPORT_2026-01-19.md b/documentation/V3_API_HARDENING_SESSION_REPORT_2026-01-19.md new file mode 100644 index 00000000..c2dc7827 --- /dev/null +++ b/documentation/V3_API_HARDENING_SESSION_REPORT_2026-01-19.md @@ -0,0 +1,57 @@ +# Session Report: V3 API Hardening & Stabilization (2026-01-19) + +## 👤 User / Context +- **Developer:** Scott Idem +- **Date:** Monday, January 19, 2026 +- **Objective:** Stabilize V3 API helpers, resolve 403 race conditions, and fix IDAA module crashes. + +--- + +## 🏗️ 1. Completed & Committed Work + +### **A. Core API Helpers (GET/POST/PATCH)** +- **Structured Error Handling:** Updated helpers to extract and return rich metadata (`meta.details`) from V3 API 400/500 responses. This allows for specific debugging (e.g., "Unknown column") instead of generic HTTP codes. +- **Immediate JWT Injection:** Refactored `+layout.ts` to read the JWT directly from `localStorage` during bootstrap. This ensures the first requests of a page load are correctly authenticated, solving race conditions. +- **Hardened Bypass Logic:** Refined the "Bootstrap Paradox" logic to strictly check for valid values (e.g., `bypass`). Placeholder environment variables are now proactively stripped so they don't interfere with account context. +- **Fail-Fast Protocol:** Added `400` and `422` to the Fail-Fast list to prevent unnecessary retries on invalid requests or schema violations. +- **JWT Fallback:** Implemented a direct `localStorage` check for the `ae_loc` key as a final resort in the helpers if the Svelte stores haven't synchronized yet. + +### **B. Module Fixes** +- **Archives Stability:** Resolved a critical async race condition in `load_ae_obj_li__archive` that caused a `TypeError` when loading nested content before the primary promise resolved. +- **Generic Processor Hardening:** Updated `_process_generic_props` to robustly handle non-array API responses, preventing "obj_li is not iterable" crashes. +- **Event Search Logic:** Refactored `qry_ae_obj_li__event` to use raw `account_id_random` in the search body to attempt bypassing backend ID-mapping conflicts. + +### **C. Testing & Diagnostics** +- **Enhanced Dashboard:** Updated `/testing` with a "Live V3 Header Inspection" tool. +- **Hardening Audits:** Added automated tests for **Permissive Mode** (x-ae-ignore-extra-fields) and **Structured Error** extraction. + +--- + +## 🚧 2. The Current Blocker: Zero-Result Search + +The `event/search` endpoint for IDAA Recovery Meetings is consistently returning `200 OK` with `data: []` or intermittent `403 Forbidden`. + +### **Failed Attempts / Observations:** +- **Raw ID Bypass:** Sending `account_id_random` directly in the body did not restore the listings. +- **Hybrid Filtering:** Moving `physical`, `virtual`, and `conference` filters to the client side resolved 400 errors but resulted in empty lists. +- **Header Diagnostics:** Confirmed that `x-account-id` and `x-aether-api-key` are present, but `Authorization` is occasionally marked as `MISSING` in the very first bootstrap trace. + +--- + +## 🧠 3. Best Guesses for Root Cause + +1. **The "Integer Trap":** FastAPI logs show the backend is mapping the account context to an integer (`{'account_id': 13}`). If the backend search logic compares this integer to a string column in a view, the query returns zero rows. +2. **Mapping Conflict:** Injecting `account_id_random` in the search body while the backend automatically injects its own isolation may be creating conflicting `WHERE` clauses. +3. **Whitelisting/Permissions:** The "Authentication required" 403 on search suggests the backend might be rejecting the combination of search fields for the `event` object specifically, or the user's JWT permissions for that object aren't being resolved correctly. + +--- + +## 🚀 4. Plan for Tomorrow + +1. **CURL Trace:** Execute raw CURL requests with the payloads captured today to see the raw backend traceback (bypassing CORS masking). +2. **Backend Audit:** Check the `v_event` view and `mdl_search` Pydantic model in the API for strict type constraints on the `account_id` field. +3. **Filter Re-alignment:** Once backend search whitelisting is confirmed, test reverting to the standard `for_obj_type: 'account'` pattern. +4. **JWT Verification:** Confirm if the JWT provided in `ae_loc` is correctly scoped for the `event` object search. + +--- +**Status:** API helpers are solid; store sync is stable. Issue is narrowed to SQL/Filter logic for V3 Event Search. diff --git a/src/lib/ae_api/api_get_object.ts b/src/lib/ae_api/api_get_object.ts index dcc73eb3..cc060d83 100644 --- a/src/lib/ae_api/api_get_object.ts +++ b/src/lib/ae_api/api_get_object.ts @@ -12,7 +12,7 @@ export const get_object = async function get_object({ headers = {}, params = {}, data = {}, - timeout = 60000, + timeout = 90000, return_meta = false, return_blob = false, filename = '', @@ -104,19 +104,17 @@ export const get_object = async function get_object({ // Auto-inject Authorization header if JWT is present but header is missing let jwt = headers_cleaned['jwt'] || headers_cleaned['JWT'] || - headers_cleaned['x-aether-api-token'] || api_cfg['jwt'] || api_cfg['headers']?.['jwt'] || - api_cfg['headers']?.['JWT'] || - api_cfg['headers']?.['x-aether-api-token']; + api_cfg['headers']?.['JWT']; - // Final Fallback: Check localStorage directly to avoid store sync race conditions + // Final Fallback: Direct check of primary ae_loc key if (!jwt && typeof localStorage !== 'undefined') { try { - const ae_loc_raw = localStorage.getItem('ae_loc'); - if (ae_loc_raw) { - const ae_loc_json = JSON.parse(ae_loc_raw); - jwt = ae_loc_json.jwt || ae_loc_json.token; + const raw = localStorage.getItem('ae_loc'); + if (raw) { + const json = JSON.parse(raw); + if (json.jwt) jwt = json.jwt; } } catch (e) { // Silently fail on storage read diff --git a/src/lib/ae_api/api_patch_object.ts b/src/lib/ae_api/api_patch_object.ts index 3222ef82..aae7038f 100644 --- a/src/lib/ae_api/api_patch_object.ts +++ b/src/lib/ae_api/api_patch_object.ts @@ -86,20 +86,17 @@ export const patch_object = async function patch_object({ // Auto-inject Authorization header if JWT is present but header is missing let jwt = headers_cleaned['jwt'] || headers_cleaned['JWT'] || - headers_cleaned['x-aether-api-token'] || api_cfg['jwt'] || api_cfg['headers']?.['jwt'] || - api_cfg['headers']?.['JWT'] || - api_cfg['headers']?.['x-aether-api-token']; + api_cfg['headers']?.['JWT']; - // Final Fallback: Check localStorage directly to avoid store sync race conditions + // Final Fallback: Direct check of primary ae_loc key if (!jwt && typeof localStorage !== 'undefined') { try { - // Check primary key first - const ae_loc_raw = localStorage.getItem('ae_loc'); - if (ae_loc_raw) { - const ae_loc_json = JSON.parse(ae_loc_raw); - jwt = ae_loc_json.jwt || ae_loc_json.token; + const raw = localStorage.getItem('ae_loc'); + if (raw) { + const json = JSON.parse(raw); + if (json.jwt) jwt = json.jwt; } } catch (e) { // Silently fail on storage read diff --git a/src/lib/ae_api/api_post_object.ts b/src/lib/ae_api/api_post_object.ts index 4f93b22f..e5b4be10 100644 --- a/src/lib/ae_api/api_post_object.ts +++ b/src/lib/ae_api/api_post_object.ts @@ -13,7 +13,7 @@ export const post_object = async function post_object({ params = {}, data = {}, form_data = null, - timeout = 60000, + timeout = 90000, return_meta = false, return_blob = false, filename = '', @@ -106,19 +106,17 @@ export const post_object = async function post_object({ // Auto-inject Authorization header if JWT is present but header is missing let jwt = headers_cleaned['jwt'] || headers_cleaned['JWT'] || - headers_cleaned['x-aether-api-token'] || api_cfg['jwt'] || api_cfg['headers']?.['jwt'] || - api_cfg['headers']?.['JWT'] || - api_cfg['headers']?.['x-aether-api-token']; + api_cfg['headers']?.['JWT']; - // Final Fallback: Check localStorage directly to avoid store sync race conditions + // Final Fallback: Direct check of primary ae_loc key if (!jwt && typeof localStorage !== 'undefined') { try { - const ae_loc_raw = localStorage.getItem('ae_loc'); - if (ae_loc_raw) { - const ae_loc_json = JSON.parse(ae_loc_raw); - jwt = ae_loc_json.jwt || ae_loc_json.token; + const raw = localStorage.getItem('ae_loc'); + if (raw) { + const json = JSON.parse(raw); + if (json.jwt) jwt = json.jwt; } } catch (e) { // Silently fail on storage read diff --git a/src/routes/+layout.ts b/src/routes/+layout.ts index 0defa3cb..1ed1b6ca 100644 --- a/src/routes/+layout.ts +++ b/src/routes/+layout.ts @@ -74,6 +74,21 @@ export async function load({ fetch, params, parent, route, url }) { // Initialize API fetch with SvelteKit fetch ae_api_init['fetch'] = fetch; + // IMMEDIATE AUTH SYNC: Read JWT from localStorage to avoid race conditions + if (typeof localStorage !== 'undefined') { + try { + const ae_loc_raw = localStorage.getItem('ae_loc'); + if (ae_loc_raw) { + const ae_loc_json = JSON.parse(ae_loc_raw); + if (ae_loc_json.jwt) { + ae_api_init['jwt'] = ae_loc_json.jwt; + } + } + } catch (e) { + // Silently fail on storage read + } + } + const ae_loc_init: key_val = {}; const ds_code_li: null | key_val = {};