feat(api): PATCH /entities/{id} + /v1/engineering/* aliases

PATCH lets users edit an active entity's description, properties,
confidence, and source_refs without cloning — closes the duplicate-trap
half-fixed by /invalidate + /supersede. Issue D just adds the
/engineering/* query surface to the /v1 allowlist.

- engineering/service.py: update_entity supports description replace,
  properties shallow merge with null-delete semantics, confidence
  0..1 bounds check, source_refs dedup-append. Writes audit row
- api/routes.py: PATCH /entities/{id} with EntityPatchRequest
- main.py: engineering/* query endpoints aliased under /v1 (Issue D)
- tests/test_patch_entity.py: 12 tests (merge, null-delete, bounds,
  dedup, 404, audit, v1 alias)
- DEV-LEDGER.md: session log + test_count 509 -> 521

Forbidden fields via PATCH (by design): entity_type, project, name,
status. Use supersede+create or the dedicated status endpoints.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-22 09:02:13 -04:00
parent 081c058f77
commit b94f9dff56
5 changed files with 326 additions and 1 deletions

View File

@@ -98,6 +98,18 @@ _V1_PUBLIC_PATHS = {
"/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"])