1 Commits

Author SHA1 Message Date
3888db926f fix(api): R14 — promote route translates V1-0 ValueError to 400
POST /entities/{id}/promote now wraps the promote_entity call in
try/except ValueError → HTTPException(400). Previously the new
V1-0 provenance re-check raised ValueError that the route didn't
catch, so legacy no-provenance candidates promoted via the API
surfaced as 500 instead of 400.

Matches the existing ValueError → 400 handling on POST /entities
(api_create_entity at routes.py:1490).

Test: test_api_promote_returns_400_on_legacy_no_provenance inserts
a pre-V1-0 legacy candidate directly, POSTs to the promote route,
asserts 400 with the expected detail, asserts the row stays
candidate.

Test count: 547 -> 548. Full suite green in 72.91s.

Closes R14.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 15:30:11 -04:00
7 changed files with 58 additions and 224 deletions

5
.gitignore vendored
View File

@@ -14,3 +14,8 @@ venv/
.claude/*
!.claude/commands/
!.claude/commands/**
# Editor / IDE state — user-specific, not project config
.obsidian/
.vscode/
.idea/

View File

@@ -23,8 +23,6 @@
- **capture_clients**: claude-code (Stop hook + cwd project inference), openclaw (before_agent_start + llm_output plugin, verified live)
- **wiki**: http://dalidou:8100/wiki (browse), /wiki/projects/{id}, /wiki/entities/{id}, /wiki/search
- **dashboard**: http://dalidou:8100/admin/dashboard (now shows pipeline health, interaction totals by client, all registered projects)
- **active_track**: Engineering V1 Completion (started 2026-04-22). V1-0 landed (`2712c5d`), V1-A gated on soak (~2026-04-26) + density (100+ active memories, currently 84). Plan: `docs/plans/engineering-v1-completion-plan.md`. Resume map: `docs/plans/v1-resume-state.md`.
- **open_branches**: `claude/r14-promote-400` at `3888db9` — P2 route fix (ValueError → 400), pending Codex review
## Active Plan
@@ -145,7 +143,7 @@ One branch `codex/extractor-eval-loop` for Day 1-5, a second `codex/retrieval-ha
| R11 | Codex | P2 | src/atocore/api/routes.py:773-845 | `POST /admin/extract-batch` still accepts `mode="llm"` inside the container and returns a successful 0-candidate result instead of surfacing that host-only LLM extraction is unavailable from this runtime. That is a misleading API contract for operators. | fixed | Claude | 2026-04-12 | (pending) |
| R12 | Codex | P2 | scripts/batch_llm_extract_live.py:39-190 | The host-side extractor duplicates the LLM system prompt and JSON parsing logic from `src/atocore/memory/extractor_llm.py`. It works today, but this is now a prompt/parser drift risk across the container and host implementations. | fixed | Claude | 2026-04-12 | (pending) |
| R13 | Codex | P2 | DEV-LEDGER.md:12 | The new `286 passing` test-count claim is not reproducibly auditable from the current audit environments: neither Dalidou nor the clean worktree has `pytest` available. The claim may be true in Claude's dev shell, but it remains unverified in this audit. | fixed | Claude | 2026-04-12 | (pending) |
| R14 | Codex | P2 | src/atocore/api/routes.py (POST /entities/{id}/promote) | The HTTP `POST /entities/{id}/promote` route does not translate the new service-layer `ValueError("source_refs required: cannot promote a candidate with no provenance...")` into a 400. A legacy no-provenance candidate promoted through the API currently surfaces as a 500. Does not block V1-0 acceptance; tidy in a follow-up. | open | Claude | 2026-04-22 | |
| R14 | Codex | P2 | src/atocore/api/routes.py (POST /entities/{id}/promote) | The HTTP `POST /entities/{id}/promote` route does not translate the new service-layer `ValueError("source_refs required: cannot promote a candidate with no provenance...")` into a 400. A legacy no-provenance candidate promoted through the API currently surfaces as a 500. Does not block V1-0 acceptance; tidy in a follow-up. | fixed | Claude | 2026-04-22 | (pending) |
## Recent Decisions

View File

@@ -1,31 +1,6 @@
# AtoCore — Current State (2026-04-22)
# AtoCore — Current State (2026-04-19)
Live deploy: `2712c5d` · Dalidou health: ok · Harness: 17/18 · Tests: 547 passing.
## V1-0 landed 2026-04-22
Engineering V1 completion track has started. **V1-0 write-time invariants**
merged and deployed: F-1 shared-header fields (`extractor_version`,
`canonical_home`, `hand_authored`) added to `entities`, F-8 provenance
enforcement at both `create_entity` and `promote_entity`, F-5 synchronous
conflict-detection hook on every active-entity write path (create, promote,
supersede) with Q-3 fail-open. Prod backfill ran cleanly — 31 legacy
active/superseded entities flagged `hand_authored=1`, follow-up dry-run
returned 0 remaining rows. Test count 533 → 547 (+14).
R14 (P2, non-blocking): `POST /entities/{id}/promote` route fix translates
the new `ValueError` into 400. Branch `claude/r14-promote-400` pending
Codex review + squash-merge.
**Next in the V1 track:** V1-A (minimal query slice + Q-6 killer-correctness
integration). Gated on pipeline soak (~2026-04-26) + 100+ active memory
density target. See `docs/plans/engineering-v1-completion-plan.md` for
the full 7-phase roadmap and `docs/plans/v1-resume-state.md` for the
"you are here" map.
---
## Snapshot from previous update (2026-04-19)
Live deploy: `877b97e` · Dalidou health: ok · Harness: 17/18.
## The numbers

View File

@@ -168,40 +168,16 @@ These are the current practical priorities.
"Zerodur" for p04 constraint queries. Investigate if it's a missing
memory or retrieval ranking issue.
## Active — Engineering V1 Completion Track (started 2026-04-22)
The Engineering V1 sprint moved from **Next** to **Active** on 2026-04-22.
The discovery from the gbrain review was that V1 entity infrastructure
had been built incrementally already; the sprint is a **completion** plan
against `engineering-v1-acceptance.md`, not a greenfield build. Full plan:
`docs/plans/engineering-v1-completion-plan.md`. "You are here" single-page
map: `docs/plans/v1-resume-state.md`.
Seven phases, ~17.519.5 focused days, runs in parallel with the Now list
where surfaces are disjoint, pauses when they collide.
| Phase | Scope | Status |
|---|---|---|
| V1-0 | Write-time invariants: F-1 header fields + F-8 provenance enforcement + F-5 hook on every active-entity write + Q-3 flag-never-block | ✅ done 2026-04-22 (`2712c5d`) |
| V1-A | Minimum query slice: Q-001 subsystem-scoped variant + Q-6 killer-correctness integration test on p05-interferometer | 🟡 gated — starts when soak (~2026-04-26) + density (100+ active memories) gates clear |
| V1-B | KB-CAD + KB-FEM ingest (`POST /ingest/kb-cad/export`, `POST /ingest/kb-fem/export`) + D-2 schema docs | pending V1-A |
| V1-C | Close the remaining 8 queries (Q-002/003/007/010/012/014/018/019; Q-020 to V1-D) | pending V1-B |
| V1-D | Full mirror surface (3 spec routes + regenerate + determinism + disputed + curated markers) + Q-5 golden file | pending V1-C |
| V1-E | Memory→entity graduation end-to-end + remaining Q-4 trust tests | pending V1-D (note: collides with memory extractor; pauses for multi-model triage work) |
| V1-F | F-5 detector generalization + route alias + O-1/O-2/O-3 operational + D-1/D-3/D-4 docs | finish line |
R14 (P2, non-blocking): `POST /entities/{id}/promote` route returns 500
on the new V1-0 `ValueError` instead of 400. Fix on branch
`claude/r14-promote-400`, pending Codex review.
## Next
These are the next major layers after V1 and the current stabilization pass.
These are the next major layers after the current stabilization pass.
1. Phase 6 AtoDrive — clarify Google Drive as a trusted operational
source and ingest from it
2. Phase 13 Hardening — Chroma backup policy, monitoring, alerting,
failure visibility beyond log files
3. Engineering V1 implementation sprint — once knowledge density is
sufficient and the pipeline feels boring and dependable
## Later

View File

@@ -1,161 +0,0 @@
# V1 Completion — Resume State
**Last updated:** 2026-04-22 (after V1-0 landed + R14 branch pushed)
**Purpose:** single-page "you are here" so any future session can pick up
the V1 completion sprint without re-reading the full plan history.
## State of play
- **V1-0 is DONE.** Merged to main as `2712c5d`, deployed to Dalidou,
prod backfill ran cleanly (31 legacy entities flagged
`hand_authored=1`, zero violations remaining).
- **R14 is on a branch.** `claude/r14-promote-400` at `3888db9`
HTTP promote route returns 400 instead of 500 on V1-0 `ValueError`.
Pending Codex review + squash-merge. Non-blocking for V1-A.
- **V1-A is next but GATED.** Doesn't start until both gates clear.
## Start-gates for V1-A
| Gate | Condition | Status as of 2026-04-22 |
|---|---|---|
| Soak | Four clean nightly cycles since F4 confidence-decay first real run 2026-04-19 | Day 3 of 4 — expected clear around **2026-04-26** |
| Density | 100+ active memories | 84 active as of last ledger update — need +16. Lever: `scripts/batch_llm_extract_live.py` against 234-interaction backlog |
**When both are green, start V1-A.** If only one is green, hold.
## Pre-flight checklist when resuming
Before opening the V1-A branch, run through this in order:
1. `git checkout main && git pull` — make sure you're at the tip
2. Check `DEV-LEDGER.md` **Orientation** for current `live_sha`, `test_count`, `active_memories`
3. Check `/health` on Dalidou returns the same `build_sha` as Orientation
4. Check the dashboard for pipeline health: http://dalidou:8100/admin/dashboard
5. Confirm R14 branch status — either merged or explicitly deferred
6. Re-read the two core plan docs:
- `docs/plans/engineering-v1-completion-plan.md` — the full 7-phase plan
- `docs/architecture/engineering-v1-acceptance.md` — the acceptance contract
7. Skim the relevant spec docs for the phase you're about to start:
- V1-A: `engineering-query-catalog.md` (Q-001 + Q-006/Q-009/Q-011 killer queries)
- V1-B: `tool-handoff-boundaries.md` (KB-CAD/KB-FEM export shapes)
- V1-C: `engineering-query-catalog.md` (all remaining v1-required queries)
- V1-D: `human-mirror-rules.md` (mirror spec end-to-end)
- V1-E: `memory-vs-entities.md` (graduation flow)
- V1-F: `conflict-model.md` (generic slot-key detector)
## What V1-A looks like when started
**Branch:** `claude/v1-a-pillar-queries`
**Scope (~1.5 days):**
- **Q-001 shape fix.** Add a subsystem-scoped variant of `system_map()`
matching `GET /entities/Subsystem/<id>?expand=contains` per
`engineering-query-catalog.md:71`. The project-wide version stays
(it serves Q-004).
- **Q-6 integration test.** Seed p05-interferometer with five cases:
1 satisfying Component, 1 orphan Requirement, 1 Decision on flagged
Assumption, 1 supported ValidationClaim, 1 unsupported ValidationClaim.
One test asserting Q-006 / Q-009 / Q-011 return exactly the expected
members.
- The four "pillar" queries (Q-001, Q-005, Q-006, Q-017) already work
per Codex's 2026-04-22 audit. V1-A does NOT re-implement them —
V1-A verifies them on seeded data.
**Acceptance:** Q-001 subsystem-scoped variant + Q-6 integration test both
green. F-2 moves from 🟡 partial to slightly-less-partial.
**Estimated tests added:** ~4 (not ~12 — V1-A scope shrank after Codex
confirmed most queries already work).
## Map of the remaining phases
```
V1-0 ✅ write-time invariants landed 2026-04-22 (2712c5d)
V1-A 🟡 minimum query slice gated on soak + density (~1.5d when started)
V1-B KB-CAD/KB-FEM ingest + D-2 ~2d
V1-C close 8 remaining queries ~2d
V1-D full mirror + determinism ~3-4d (biggest phase)
V1-E graduation + trust tests ~3-4d (pauses for multi-model triage)
V1-F F-5 generalization + ops + docs ~3d — V1 done
```
## Parallel work that can run WITHOUT touching V1
These are genuinely disjoint surfaces; pick any of them during the gate
pause or as scheduling allows:
- **Density batch-extract** — *required* to unblock V1-A. Not optional.
- **p04-constraints harness fix** — retrieval-ranking change, fully
disjoint from entities. Safe to do anywhere in the V1 track.
- **Multi-model triage (Phase 11 entry)** — memory-side work, disjoint
from V1-A/B/C/D. **Pause before V1-E starts** because V1-E touches
memory module semantics.
## What NOT to do
- Don't start V1-A until both gates are green.
- Don't touch the memory extractor write path while V1-E is open.
- Don't name the rejected "Minions" plan in any doc — neutral wording
only ("queued background processing / async workers") per Codex
sign-off.
- Don't rename the `project` field to `project_id` — Codex + Antoine
agreed it stays as `project`, with a doc note in
`engineering-ontology-v1.md` that this IS the project_id per spec.
## Open review findings
| id | severity | summary | status |
|---|---|---|---|
| R14 | P2 | `POST /entities/{id}/promote` returns 500 on V1-0 `ValueError` instead of 400 | fixed on branch `claude/r14-promote-400`, pending Codex review |
Closed V1-0 findings: P1 "promote path allows provenance-less legacy
candidates" (service.py:365-379), P1 "supersede path missing F-5 hook"
(service.py:581-591), P2 "`--invalidate-instead` backfill too broad"
(v1_0_backfill_provenance.py:52-63). All three patched and approved in
the squash-merge to `2712c5d`.
## How agreement between Claude + Codex has worked so far
Three review rounds before V1-0 started + three during implementation:
1. **Rejection round.** Claude drafted a gbrain-inspired "Phase 8
Minions + typed edges" plan; Codex rejected as wrong-packaging.
Record: `docs/decisions/2026-04-22-gbrain-plan-rejection.md`.
2. **Completion-plan rewrite.** Claude rewrote against
`engineering-v1-acceptance.md`. Codex first-round review fixed the
phase order (provenance-first).
3. **Per-file audit.** Codex's second-round audit found F-1 / F-2 /
F-5 gaps, all folded in.
4. **Sign-off round.** Codex's third-round review resolved the five
remaining open questions inline and signed off: *"with those edits,
I'd sign off on the five questions."*
5. **V1-0 review.** Codex found two P1 gaps (promote re-check missing,
supersede hook missing) + one P2 (backfill scope too broad). All
three patched. Codex re-ran probes + regression suites, approved,
squash-merged.
6. **V1-0 deploy + prod backfill.** Codex deployed + ran backfill,
logged R14 as P2 residual.
Protocol has been: Claude writes, Codex audits, human Antoine ratifies.
Continue this for V1-A onward.
## References
- `docs/plans/engineering-v1-completion-plan.md` — full 7-phase plan
- `docs/decisions/2026-04-22-gbrain-plan-rejection.md` — prior rejection
- `docs/architecture/engineering-ontology-v1.md` — V1 ontology (18 predicates)
- `docs/architecture/engineering-query-catalog.md` — Q-001 through Q-020 spec
- `docs/architecture/engineering-v1-acceptance.md` — F/Q/O/D acceptance table
- `docs/architecture/promotion-rules.md` — candidate → active flow
- `docs/architecture/conflict-model.md` — F-5 spec
- `docs/architecture/human-mirror-rules.md` — V1-D spec
- `docs/architecture/memory-vs-entities.md` — V1-E spec
- `docs/architecture/tool-handoff-boundaries.md` — V1-B KB-CAD/KB-FEM
- `docs/master-plan-status.md` — Now / Active / Next / Later
- `DEV-LEDGER.md` — Orientation + Open Review Findings + Session Log

View File

@@ -2187,12 +2187,17 @@ def api_promote_entity(
from atocore.engineering.service import promote_entity
target_project = req.target_project if req is not None else None
note = req.note if req is not None else ""
success = promote_entity(
entity_id,
actor="api-http",
note=note,
target_project=target_project,
)
try:
success = promote_entity(
entity_id,
actor="api-http",
note=note,
target_project=target_project,
)
except ValueError as e:
# V1-0 F-8 re-check raises ValueError for no-provenance candidates
# (see service.promote_entity). Surface as 400, not 500.
raise HTTPException(status_code=400, detail=str(e))
if not success:
raise HTTPException(status_code=404, detail=f"Entity not found or not a candidate: {entity_id}")
result = {"status": "promoted", "id": entity_id}

View File

@@ -160,6 +160,42 @@ def test_promote_rejects_legacy_candidate_without_provenance(tmp_data_dir):
assert got.status == "candidate"
def test_api_promote_returns_400_on_legacy_no_provenance(tmp_data_dir):
"""R14 (Codex, 2026-04-22): the HTTP promote route must translate
the V1-0 ValueError for no-provenance candidates into 400, not 500.
Previously the route didn't catch ValueError so legacy bad
candidates surfaced as a server error."""
init_db()
init_engineering_schema()
import uuid as _uuid
from fastapi.testclient import TestClient
from atocore.main import app
entity_id = str(_uuid.uuid4())
with get_connection() as conn:
conn.execute(
"INSERT INTO entities (id, entity_type, name, project, "
"description, properties, status, confidence, source_refs, "
"extractor_version, canonical_home, hand_authored, "
"created_at, updated_at) "
"VALUES (?, 'component', 'Legacy HTTP', 'p04-gigabit', "
"'', '{}', 'candidate', 1.0, '[]', '', 'entity', 0, "
"CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)",
(entity_id,),
)
client = TestClient(app)
r = client.post(f"/entities/{entity_id}/promote")
assert r.status_code == 400
assert "source_refs required" in r.json().get("detail", "")
# Row still candidate — the 400 didn't half-transition.
got = get_entity(entity_id)
assert got is not None
assert got.status == "candidate"
def test_promote_accepts_candidate_flagged_hand_authored(tmp_data_dir):
"""The other side of the promote re-check: hand_authored=1 with
empty source_refs still lets promote succeed, matching