Per docs/plans/engineering-v1-completion-plan.md Phase V1-A. Scope is
deliberately tight: a single shape fix on Q-001 and an integration test
that proves the four pillar queries (Q-001 subsystem-scoped, Q-005,
Q-006, Q-017) work end-to-end against one seed graph on
p05-interferometer.
The shape fix:
- New subsystem_contents() in src/atocore/engineering/queries.py.
Returns {subsystem, contains: [{id, entity_type, name, status}]}
by walking inbound part_of edges (the inverse of CONTAINS), filtered
to active children.
- New route GET /entities/Subsystem/{subsystem_id}?expand=contains
matching the spec invocation in
docs/architecture/engineering-query-catalog.md Q-001 verbatim.
400 on unsupported expand value, 404 on missing/wrong-type id.
- Aliased under /v1.
The existing project-wide /engineering/projects/{name}/systems route
stays as-is for Q-004 (full project tree). The two queries answer
different operator questions and both belong in V1.
V1-A acceptance test (test_v1a_pillar_queries_run_end_to_end_against_
single_seed):
- Q-001 subsystem-scoped: Optics returns Primary Mirror + Diverger Lens
- Q-005 requirements_for: Primary Mirror has its single satisfied req
- Q-006 orphan_requirements: the orphan surfaces, the satisfied does not
- Q-017 evidence_chain: supported claim has FEA result via 'supports';
unsupported claim has no 'supports' edge
If this single test fails, V1-A is not done.
Tests: 596 -> 602 (+6). Per the plan: "~4 tests"; the +2 are basic
helper-function negatives (missing id, wrong type) kept separate from
the route and integration tests.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
141 lines
4.4 KiB
Python
141 lines
4.4 KiB
Python
"""AtoCore — FastAPI application entry point."""
|
|
|
|
from contextlib import asynccontextmanager
|
|
|
|
from fastapi import APIRouter, FastAPI
|
|
from fastapi.routing import APIRoute
|
|
|
|
from atocore import __version__
|
|
from atocore.api.routes import router
|
|
import atocore.config as _config
|
|
from atocore.context.project_state import init_project_state_schema
|
|
from atocore.engineering.service import init_engineering_schema
|
|
from atocore.ingestion.pipeline import get_source_status
|
|
from atocore.models.database import init_db
|
|
from atocore.observability.logger import get_logger, setup_logging
|
|
|
|
|
|
log = get_logger("main")
|
|
|
|
|
|
@asynccontextmanager
|
|
async def lifespan(app: FastAPI):
|
|
"""Run setup before the first request and teardown after shutdown.
|
|
|
|
Replaces the deprecated ``@app.on_event("startup")`` hook with the
|
|
modern ``lifespan`` context manager. Setup runs synchronously (the
|
|
underlying calls are blocking I/O) so no await is needed; the
|
|
function still must be async per the FastAPI contract.
|
|
"""
|
|
setup_logging()
|
|
_config.ensure_runtime_dirs()
|
|
init_db()
|
|
init_project_state_schema()
|
|
init_engineering_schema()
|
|
log.info(
|
|
"startup_ready",
|
|
env=_config.settings.env,
|
|
db_path=str(_config.settings.db_path),
|
|
chroma_path=str(_config.settings.chroma_path),
|
|
source_status=get_source_status(),
|
|
)
|
|
yield
|
|
# No teardown work needed today; SQLite connections are short-lived
|
|
# and the Chroma client cleans itself up on process exit.
|
|
|
|
|
|
app = FastAPI(
|
|
title="AtoCore",
|
|
description="Personal Context Engine for LLM interactions",
|
|
version=__version__,
|
|
lifespan=lifespan,
|
|
)
|
|
|
|
app.include_router(router)
|
|
|
|
|
|
# Public API v1 — stable contract for external clients (AKC, OpenClaw, etc.).
|
|
# Paths listed here are re-mounted under /v1 as aliases of the existing
|
|
# unversioned handlers. Unversioned paths continue to work; new endpoints
|
|
# land at the latest version; breaking schema changes bump the prefix.
|
|
_V1_PUBLIC_PATHS = {
|
|
"/entities",
|
|
"/entities/{entity_id}",
|
|
"/entities/{entity_id}/promote",
|
|
"/entities/{entity_id}/reject",
|
|
"/entities/{entity_id}/invalidate",
|
|
"/entities/{entity_id}/supersede",
|
|
"/entities/{entity_id}/audit",
|
|
# V1-A: Q-001 subsystem-scoped variant per engineering-query-catalog
|
|
"/entities/Subsystem/{subsystem_id}",
|
|
"/relationships",
|
|
"/ingest",
|
|
"/ingest/sources",
|
|
"/context/build",
|
|
"/query",
|
|
"/projects",
|
|
"/projects/{project_name}",
|
|
"/projects/{project_name}/refresh",
|
|
"/projects/{project_name}/mirror",
|
|
"/projects/{project_name}/mirror.html",
|
|
"/memory",
|
|
"/memory/{memory_id}",
|
|
"/memory/{memory_id}/audit",
|
|
"/memory/{memory_id}/promote",
|
|
"/memory/{memory_id}/reject",
|
|
"/memory/{memory_id}/invalidate",
|
|
"/memory/{memory_id}/supersede",
|
|
"/project/state",
|
|
"/project/state/{project_name}",
|
|
"/interactions",
|
|
"/interactions/{interaction_id}",
|
|
"/interactions/{interaction_id}/reinforce",
|
|
"/interactions/{interaction_id}/extract",
|
|
"/health",
|
|
"/sources",
|
|
"/stats",
|
|
# Issue F: asset store + evidence query
|
|
"/assets",
|
|
"/assets/{asset_id}",
|
|
"/assets/{asset_id}/thumbnail",
|
|
"/assets/{asset_id}/meta",
|
|
"/entities/{entity_id}/evidence",
|
|
# Issue D: engineering query surface (decisions, systems, components,
|
|
# gaps, evidence, impact, changes)
|
|
"/engineering/projects/{project_name}/systems",
|
|
"/engineering/decisions",
|
|
"/engineering/components/{component_id}/requirements",
|
|
"/engineering/changes",
|
|
"/engineering/gaps",
|
|
"/engineering/gaps/orphan-requirements",
|
|
"/engineering/gaps/risky-decisions",
|
|
"/engineering/gaps/unsupported-claims",
|
|
"/engineering/impact",
|
|
"/engineering/evidence",
|
|
}
|
|
|
|
_v1_router = APIRouter(prefix="/v1", tags=["v1"])
|
|
for _route in list(router.routes):
|
|
if isinstance(_route, APIRoute) and _route.path in _V1_PUBLIC_PATHS:
|
|
_v1_router.add_api_route(
|
|
_route.path,
|
|
_route.endpoint,
|
|
methods=list(_route.methods),
|
|
response_model=_route.response_model,
|
|
response_class=_route.response_class,
|
|
name=f"v1_{_route.name}",
|
|
include_in_schema=True,
|
|
)
|
|
app.include_router(_v1_router)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
import uvicorn
|
|
|
|
uvicorn.run(
|
|
"atocore.main:app",
|
|
host=_config.settings.host,
|
|
port=_config.settings.port,
|
|
reload=True,
|
|
)
|