Quickly saving in progress changes. We are working on why the IDAA Recovery Meetings are not loading. 403 errors.

This commit is contained in:
Scott Idem
2026-01-19 19:37:18 -05:00
parent 0e411531eb
commit 6380effa90
5 changed files with 92 additions and 27 deletions

View File

@@ -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.

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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 = {};