- Add BOOTSTRAP__AI_Agent_Quickstart.md — fast-path entry doc for AI agents covering critical rules, V3 action patterns, Redis/auth/logger_reset gotchas, and a mistakes-agents-have-made section - Expand ARCH__V3_DEVELOPMENT_STANDARDS.md with merged content from ARCH__V3_CRUD_LEARNINGS: dependency injection reference, security/isolation model detail, and FastAPI/Pydantic gotchas - Archive 4 outdated docs: GUIDE__LOCAL_DEVELOPMENT (predates Docker), FRONTEND_API_SAMPLES (belongs in SvelteKit repo), ARCH__UNIFIED_AGENT (replaced by MCP ecosystem), ARCH__V3_CRUD_LEARNINGS (content merged above) Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
8.2 KiB
Aether V3 Development Standards & Strategy
This document serves as the master guide for the Aether API V3. It combines core architectural principles with the finalized infrastructure standards established in January 2026.
1. Core Principles
id_randomPrimary:id_random(URL-safe string) is the only identifier exposed to clients. Internal integeridis a private implementation detail.- Singular naming: All object types and prefixes use singular form (e.g.,
journal, notjournals). - Inheritance: All models must inherit from
CoreObjectto ensure consistent base fields. - Separation of Concerns: Business logic lives in
app/methods/, CRUD helpers inapp/lib_sql_crud.py, and routing inapp/routers/.
2. Infrastructure Standards
Finalized Jan 15, 2026, to ensure boot stability.
Application Entry Point (app/main.py)
- Registry Pattern: All routers are registered via
app/routers/registry.py. - Lifespan Management: All startup tasks (logging setup, DB bootstrap, engine refresh) MUST reside within the
@contextlib.asynccontextmanagerlifespan. - No Top-Level Logic: No SQL queries or heavy initialization should execute at the module level.
Database Layering
lib_sql_core.py: The foundational "Source of Truth" for the SQLAlchemy engine and globaldbconnection.lib_sql_crud.py: High-level logic forsql_insert,sql_select, etc.db_sql.py: A backward-compatible facade. code should import from here.
Logging
- Explicit Setup:
setup_logging(settings)must be called during the lifespan startup. lib_log_v3.py: New home for logging configuration and thelogger_resetdecorator.
3. V3 CRUD Strategy
URL Structure
- Top-Level:
/v3/crud/{obj_type}/ - Nested:
/v3/crud/{parent_type}/{parent_id_random}/{obj_type}/(Enforces parent ownership).
Search API
- POST Based: Complex filtering is handled via
POST /searchwith a JSON body containingand,or, andnotlogic. - Hybrid Filtering: (Proposed) Query parameters should append simple standard filters (e.g.,
?enabled=true) to the complex body logic.
Field Evolution Checklist
When a table or view gains, loses, or renames fields, keep the API contract and search registry in sync:
- Update the Pydantic model in
app/models/first so CRUD serialization matches the new shape. - Update the SQL view or table projection so
GETandSEARCHresponses actually return the field. - Update
searchable_fieldsinapp/object_definitions/only for fields that should be searchable. - Add write-only, virtual, or view-only fields to
fields_to_exclude_from_dbwhen they must not be persisted. - Run the schema/search E2E tests that cover the object type before handing the change off.
- Restart the Docker API containers (
docker compose restart ae_api) — Python file changes inside containers are not picked up until restart.
For archive_content, the public field set now includes external_id and code, and future additions should follow the same order of operations.
Alt-view fields (fields only in tbl_alt)
Some objects have a richer alternate SQL view (tbl_alt) that adds JOINed/computed columns absent from the default view (tbl_default). For example, event_session uses v_event_session_w_file_count as its alt view (triggered by ?view=alt on search, or ?inc_file_count=true on GET).
- Fields from
tbl_altmust still be declared in the Pydantic model and insearchable_fields— Pydantic strips undeclared fields, and the search whitelist rejects unknown field names regardless of the view. - When adding such a field, add a comment noting which view provides it (e.g.,
# from v_event_session_w_file_count). - Searching by an alt-view field on the default endpoint returns
400 Unknown column— this is correct behaviour. Clients must pass?view=altto use those fields in a search. - Known alt-view fields restored May 2026:
event_presentation_li_qry_str,event_presenter_li_qry_str(event_session);account_name,account_code, and related convenience fields (site_domain).
Response Views (?view= parameter)
- The nested search router (
api_crud_v3_nested.py) already supports?view=<key>to switch between registered views.view=defaultusestbl_default;view=altusestbl_alt; additional named views can be added to the object registry astbl_<name>/mdl_<name>. - Flat search (
api_crud_v3.py) does not yet support?view=— it always usestbl_default.
4. V3 Dependency Injection Reference
All V3 endpoints use granular, composable Depends() from app/lib_general_v3.py:
| Dependency | Purpose |
|---|---|
get_account_context → AccountContext |
Resolves account_id with precedence: Header → Query Token → Bypass Header. Raises 403 on guest/missing context. |
PaginationParams |
Standardizes limit and offset. |
StatusFilterParams |
Handles enabled and hidden filtering. |
SerializationParams |
Controls Pydantic serialization (by_alias, exclude_unset). |
DelayParams |
Optional latency simulation (?delay=N) via await asyncio.sleep(). |
AccountContext also carries administrator, manager, and super flags, populated by a deferred DB lookup when a JWT is present. These flags control whether account isolation is bypassed for support tasks.
5. Security and Data Isolation
Fail-Closed Strategy
If account_id or auth context is missing, the API defaults to a blocking filter (account_id IS NULL) — it does NOT fall back to returning all records. Never relax this.
Multi-Tenant Isolation
- Forced account filtering:
apply_forced_account_filterinjects anaccount_idWHERE clause into every list/search query for non-super users. - Post-retrieval verification: Single-object GET, PATCH, DELETE include a secondary ownership check (
check_account_access). A mismatch returns 403. - Hierarchical verification: Nested endpoints verify parent ownership before allowing operations on children.
- Creation guard: On POST, the user's
account_idis automatically forced onto the new record.
IDAA Privacy Baseline
No IDAA object (Events, Files, Posts, Meetings) is public by default. All routes require x-account-id context. The sole exception is site_domain (used for site bootstrapping). This is a Sev-1 class constraint — violating it has happened before.
Bypass / Admin Access
x-no-account-id: bypass→ grants super access, resolves toaccount_id=1(One Sky IT Demo). Use only in internal/development utilities; do not expand its use.- JWT query parameter (
?jwt=...) is supported for download links and share URLs where custom headers cannot be provided.
6. FastAPI and Pydantic Gotchas
response: Responseinjection: Use it as a direct type hint in function signature.Depends(Response)is not valid and causes router initialization failures.- Parameter order: In function signatures, arguments without defaults must come before
Depends()arguments. asyncio.sleep()nottime.sleep(): Blocking the event loop in an async endpoint causes worker timeouts and502 Bad Gatewayunder load.- Pydantic V1 only: Do not use V2-only features (
computed_field,model_validator, etc.). The migration is a separate planned project — see strategic goals inTODO__Agents.md. obj_type_kv_liinae_obj_types_def.py: Supports both modern keys (tbl,mdl) and legacy keys (table_name,base_name). Legacy V2 endpoints depend on the legacy keys — do not remove them until V1/V2 are fully retired.
7. Stability Rules
- Baby Step Testing: Restart Docker and verify root health after every modular change.
- Avoid Shadowing: Never name a module part of the
app.package the same as a common instance variable (e.g., avoidapp.middlewarepackage if you useapp = FastAPI()). - Deferred Imports: Use
from app.db_sql import ...inside functions in library modules to prevent circular dependency traps. - Model changes require container restart: Editing Python files on the host does not hot-reload inside Docker. Always run
docker compose restart ae_apiafter model or object-definition changes, then re-run E2E tests.