feat: Phase 1 ingestion hardening + Phase 5 Trusted Project State
Phase 1 - Ingestion hardening: - Encoding fallback (UTF-8/UTF-8-sig/Latin-1/CP1252) - Delete detection: purge DB/vector entries for removed files - Ingestion stats endpoint (GET /stats) Phase 5 - Trusted Project State: - project_state table with categories (status, decision, requirement, contact, milestone, fact, config) - CRUD API: POST/GET/DELETE /project/state - Upsert semantics, invalidation (supersede) support - Context builder integrates project state at highest trust precedence - Project state gets 20% budget allocation, appears first in context - Trust precedence: Project State > Retrieved Chunks (per Master Plan) 33/33 tests passing. Validated end-to-end with GigaBIT M1 project data. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -10,7 +10,13 @@ from atocore.context.builder import (
|
||||
get_last_context_pack,
|
||||
_pack_to_dict,
|
||||
)
|
||||
from atocore.ingestion.pipeline import ingest_file, ingest_folder
|
||||
from atocore.context.project_state import (
|
||||
CATEGORIES,
|
||||
get_state,
|
||||
invalidate_state,
|
||||
set_state,
|
||||
)
|
||||
from atocore.ingestion.pipeline import ingest_file, ingest_folder, get_ingestion_stats
|
||||
from atocore.observability.logger import get_logger
|
||||
from atocore.retrieval.retriever import retrieve
|
||||
from atocore.retrieval.vector_store import get_vector_store
|
||||
@@ -57,6 +63,26 @@ class ContextBuildResponse(BaseModel):
|
||||
chunks: list[dict]
|
||||
|
||||
|
||||
class ProjectStateSetRequest(BaseModel):
|
||||
project: str
|
||||
category: str
|
||||
key: str
|
||||
value: str
|
||||
source: str = ""
|
||||
confidence: float = 1.0
|
||||
|
||||
|
||||
class ProjectStateGetRequest(BaseModel):
|
||||
project: str
|
||||
category: str | None = None
|
||||
|
||||
|
||||
class ProjectStateInvalidateRequest(BaseModel):
|
||||
project: str
|
||||
category: str
|
||||
key: str
|
||||
|
||||
|
||||
# --- Endpoints ---
|
||||
|
||||
|
||||
@@ -127,6 +153,58 @@ def api_build_context(req: ContextBuildRequest) -> ContextBuildResponse:
|
||||
)
|
||||
|
||||
|
||||
@router.post("/project/state")
|
||||
def api_set_project_state(req: ProjectStateSetRequest) -> dict:
|
||||
"""Set or update a trusted project state entry."""
|
||||
try:
|
||||
entry = set_state(
|
||||
project_name=req.project,
|
||||
category=req.category,
|
||||
key=req.key,
|
||||
value=req.value,
|
||||
source=req.source,
|
||||
confidence=req.confidence,
|
||||
)
|
||||
except ValueError as e:
|
||||
raise HTTPException(status_code=400, detail=str(e))
|
||||
except Exception as e:
|
||||
log.error("set_state_failed", error=str(e))
|
||||
raise HTTPException(status_code=500, detail=f"Failed to set state: {e}")
|
||||
return {"status": "ok", "id": entry.id, "category": entry.category, "key": entry.key}
|
||||
|
||||
|
||||
@router.get("/project/state/{project_name}")
|
||||
def api_get_project_state(project_name: str, category: str | None = None) -> dict:
|
||||
"""Get trusted project state entries."""
|
||||
entries = get_state(project_name, category=category)
|
||||
return {
|
||||
"project": project_name,
|
||||
"entries": [
|
||||
{
|
||||
"id": e.id,
|
||||
"category": e.category,
|
||||
"key": e.key,
|
||||
"value": e.value,
|
||||
"source": e.source,
|
||||
"confidence": e.confidence,
|
||||
"status": e.status,
|
||||
"updated_at": e.updated_at,
|
||||
}
|
||||
for e in entries
|
||||
],
|
||||
"categories": CATEGORIES,
|
||||
}
|
||||
|
||||
|
||||
@router.delete("/project/state")
|
||||
def api_invalidate_project_state(req: ProjectStateInvalidateRequest) -> dict:
|
||||
"""Invalidate (supersede) a project state entry."""
|
||||
success = invalidate_state(req.project, req.category, req.key)
|
||||
if not success:
|
||||
raise HTTPException(status_code=404, detail="State entry not found or already invalidated")
|
||||
return {"status": "invalidated", "project": req.project, "category": req.category, "key": req.key}
|
||||
|
||||
|
||||
@router.get("/health")
|
||||
def api_health() -> dict:
|
||||
"""Health check."""
|
||||
@@ -138,6 +216,12 @@ def api_health() -> dict:
|
||||
}
|
||||
|
||||
|
||||
@router.get("/stats")
|
||||
def api_stats() -> dict:
|
||||
"""Ingestion statistics."""
|
||||
return get_ingestion_stats()
|
||||
|
||||
|
||||
@router.get("/debug/context")
|
||||
def api_debug_context() -> dict:
|
||||
"""Inspect the last assembled context pack."""
|
||||
|
||||
Reference in New Issue
Block a user