12 Commits

Author SHA1 Message Date
87ededb71c Add rebased OpenClaw x AtoCore V1 review docs 2026-04-23 16:11:00 +00:00
c53e61eb67 docs(ledger): R14 closure + live_sha bump to 2b86543 post-deploy
- Orientation: live_sha 2712c5d -> 2b86543 (verified via
  /health at 2026-04-23T15:20:53Z), test_count 547 -> 548,
  main_tip -> 2b86543, open_branches -> none
- Session Log: R14 close-out narrative (Codex no-findings,
  squash-merge 0989fed, deploy 2b86543 status=ok). V1-A gate
  status recap: density CLEARED (784 active), soak day 5/~7.
2026-04-23 11:33:29 -04:00
2b86543e6a docs(ledger): fill R14 resolved_by with squash SHA 0989fed 2026-04-23 11:20:47 -04:00
0989fed9ee fix(api): R14 — promote route translates V1-0 ValueError to 400
Squash-merge of branch claude/r14-promote-400 (3888db9), approved by
Codex (no findings, targeted suite 15 passed).

POST /entities/{id}/promote now wraps promote_entity in
try/except ValueError → HTTPException(400). Previously the 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 (routes.py:1490).

New regression test test_api_promote_returns_400_on_legacy_no_provenance
inserts a pre-V1-0 candidate directly, POSTs promote, asserts 400
with the expected detail, asserts the row stays candidate.

Also adds .obsidian/, .vscode/, .idea/ to .gitignore so editor
state doesn't sneak into future commits.

Test count: 547 → 548. Closes R14.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 11:20:15 -04:00
98bc848184 docs(ledger): refresh Orientation from live Dalidou 2026-04-23
The Orientation counts were last refreshed 2026-04-22 and have
drifted materially because the density batch-extract ran between
then and now:

- active_memories: 84 -> 784 (V1-A density gate cleared — 100+ target crushed)
- interactions: 234 -> 500+ (real count pending /stats re-sample)
- entities: 35 -> 66
- conflicts: 0 open
- candidate_memories: 2 (queue drained by auto-triage)

Also added:
- Last nightly pipeline summary (2026-04-23T03:00:20Z): harness
  17/18, triage 3+7+0, dedup 7 clusters auto-merged, graduation
  30-skipped 0-graduated 0-errors — cleanly executed
- active_track now records soak status (day 5 of ~7, all clean)
  and flags density gate as cleared

No code changes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 10:12:06 -04:00
69176d11c5 docs: land wiki-reorg supersession record (was in working tree)
- DEV-LEDGER Recent Decisions: the wiki-reorg plan drafted earlier
  2026-04-22 is superseded by a read-only operator-orientation plan
  in the ATOCore-clean workspace. Decision authored by Antoine,
  drafted by Claude in a parallel session.
- docs/plans/wiki-reorg-plan.md: new file, carries a SUPERSEDED
  banner at the top pointing to operator-orientation-plan.md.
  Kept in-tree for historical context; explicitly marked do-not-implement.

Syncing working-tree state — no authoring from this session beyond
committing what was already on disk.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-23 10:11:22 -04:00
4ca81e9b36 docs: reflect V1-0 landed + V1 completion track active + resume map
- current-state.md: header bumped to 2026-04-22, live_sha 2712c5d,
  547 tests. New "V1-0 landed" section covers what merged, what the
  prod backfill did, and where the next phase stands.

- master-plan-status.md: new "Active - Engineering V1 Completion
  Track (started 2026-04-22)" section between Now and Next.
  Contains the 7-phase table with V1-0 marked done, V1-A gated,
  V1-B..V1-F pending. V1 removed from the "Next" section since it
  is now Active.

- DEV-LEDGER.md Orientation: two new pointers — active_track points
  to the completion plan + resume map; open_branches points to the
  R14 branch still awaiting Codex review.

- docs/plans/v1-resume-state.md (new): single-page "you are here"
  for any future session. Covers state of play, start-gates for
  V1-A, pre-flight checklist, phase map, parallel-safe work, do-nots,
  open findings, agreement protocol history, reference index.
  Designed to be cold-readable — no prior session context required.

No code changes.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 16:01:09 -04:00
22a37a7241 docs(ledger): record V1-0 closure + prod backfill + R14 residual
- Orientation bumped: live_sha 775960c -> 2712c5d, test_count
  533 -> 547, main_tip 999788b -> 2712c5d
- Recent Decisions: V1-0 approved, merged, deployed, 31-row prod
  backfill with zero-remainder follow-up dry-run
- Open Review Findings: new R14 (P2) — POST /entities/{id}/promote
  returns 500 instead of 400 on the new ValueError from legacy
  no-provenance candidate promotion. Non-blocking, follow-up tidy
- Session Log: full V1-0 closure narrative + V1-A gate status

V1-A (Q-001 subsystem-scoped variant + Q-6 killer-correctness
integration) holds until the soak window ends ~2026-04-26 and the
100-active-memory density target is hit.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 15:17:06 -04:00
2712c5d2d0 feat(engineering): enforce V1-0 write invariants 2026-04-22 14:59:17 -04:00
9ab5b3c9d8 docs(planning): V1 Completion Plan — Codex sign-off (third round)
Codex's third-round audit closed the remaining five open questions
with concrete file:line resolutions, patched inline in the plan:

- F-7 (P1): graduation stack is partially built — graduated_to_entity_id
  at database.py:143-146, graduated memory status, promote preserves
  original at service.py:354-356, tests at test_engineering_v1_phase5.py.
  Gaps: missing direct POST /memory/{id}/graduate route; spec's
  knowledge -> Fact mismatches ontology (no fact type). Reconcile to
  parameter or similar. V1-E 2 days -> 3-4 days.

- Q-5 / V1-D (P2): renderer reads wall-clock in _footer at mirror.py:320.
  Fix is injecting regenerated timestamp + checksum as renderer inputs,
  sorting DB iteration, removing dict ordering deps. Render code must
  not call wall-clock directly.

- project vs project_id (P3): doc note only, no storage rename.

- Total estimate: 17.5-19.5 focused days (calendar buffer on top).

- Release notes must NOT canonize "Minions" as a V2 name. Use neutral
  "queued background processing / async workers" wording.

Sign-off from Codex: "with those edits, I'd sign off on the five
questions. The only non-architectural uncertainty left in the plan is
scheduling discipline against the current Now list; that does not
block V1-0 once the soak window and memory-density gate clear."

Plan frozen. V1-0 starts after pipeline soak (~2026-04-26) and the
100-active-memory density gate clear.

Co-Authored-By: Codex <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 14:24:43 -04:00
44724c81ab docs(planning): V1 Completion Plan revised per Codex file-level audit
Three findings folded in, all with exact file:line refs from Codex:

- F-1 downgraded from done to partial. Entity dataclass at
  service.py:67 and entities table missing extractor_version and
  canonical_home fields per engineering-v1-acceptance.md:45. V1-0
  scope now adds both via additive migration + doc note that
  project is the project_id per "fields equivalent to" wording.

- F-2 replaced guesses with ground truth per-query status:
  9 of 20 v1-required queries done, 1 partial (Q-001 needs
  subsystem-scoped variant), 10 missing. V1-A scope shrank to
  Q-001 shape fix + Q-6 integration. V1-C closes the 8 net-new
  queries; Q-020 deferred to V1-D (mirror).

- F-5 reframed. Generic conflicts + conflict_members schema
  already present at database.py:190, no migration needed.
  Divergence is detector body (per-type dispatch needs
  generalization) + routes (/admin/conflicts/* needs
  /conflicts/* alias). V1-F scope is detector + routes only.

Totals revised: 16.5-17.5 days, ~60 tests.

Three of Codex's eight open questions now resolved. Remaining:
F-7 graduation depth, mirror determinism, project naming,
velocity calibration, minions-as-V2 naming.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 14:09:50 -04:00
ce3a87857e docs(planning): V1 Completion Plan + gbrain-plan rejection record
- docs/decisions/2026-04-22-gbrain-plan-rejection.md: record of
  gbrain-inspired "Phase 8 Minions + typed edges" plan rejection.
  Three high findings from Codex verified against cited architecture
  docs (ontology V1 predicate set, canonical entity contract,
  master-plan-status Now list sequencing).

- docs/plans/engineering-v1-completion-plan.md: seven-phase plan
  for finishing Engineering V1 against engineering-v1-acceptance.md.
  V1-0 (write-time invariants: F-8 provenance + F-5 hooks + F-1
  audit) as hard prerequisite per Codex first-round review. Per-
  criterion gap audit against each F/Q/O/D acceptance item with
  code:line references. Explicit collision points with the Now
  list; schedule shifted ~4 weeks to avoid pipeline-soak window.
  Awaiting Codex file-level audit.

- DEV-LEDGER.md: Recent Decisions + Session Log entries covering
  both the rejection and the revised plan.

No code changes. Docs + ledger only.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 13:58:10 -04:00
27 changed files with 4089 additions and 217 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

@@ -6,23 +6,26 @@
## Orientation
- **live_sha** (Dalidou `/health` build_sha): `775960c` (verified 2026-04-16 via /health, build_time 2026-04-16T17:59:30Z)
- **last_updated**: 2026-04-18 by Claude (Phase 7A — Memory Consolidation "sleep cycle" V1 on branch, not yet deployed)
- **main_tip**: `999788b`
- **test_count**: 533 (prior 521 + 12 new wikilink/redlink tests)
- **harness**: `17/18 PASS` on live Dalidou (p04-constraints expects "Zerodur" — retrieval content gap, not regression)
- **live_sha** (Dalidou `/health` build_sha): `2b86543` (verified 2026-04-23T15:20:53Z post-R14 deploy; status=ok)
- **last_updated**: 2026-04-23 by Claude (R14 squash-merged + deployed; Orientation refreshed)
- **main_tip**: `2b86543`
- **test_count**: 548 (547 + 1 R14 regression test)
- **harness**: `17/18 PASS` on live Dalidou (p04-constraints expects "Zerodur" — known content gap, not regression; consistent since 2026-04-19)
- **vectors**: 33,253
- **active_memories**: 84 (31 project, 23 knowledge, 10 episodic, 8 adaptation, 7 preference, 5 identity)
- **candidate_memories**: 2
- **interactions**: 234 total (192 claude-code, 38 openclaw, 4 test)
- **active_memories**: 784 (up from 84 pre-density-batch — density gate CRUSHED vs V1-A's 100-target)
- **candidate_memories**: 2 (triage queue drained)
- **interactions**: 500+ (limit=2000 query returned 500 — density batch has been running; actual may be higher, confirm via /stats next update)
- **registered_projects**: atocore, p04-gigabit, p05-interferometer, p06-polisher, atomizer-v2, abb-space (aliased p08)
- **project_state_entries**: 110 total (atocore=47, p06=19, p05=18, p04=15, abb=6, atomizer=5)
- **entities**: 35 (engineering knowledge graph, Layer 2)
- **project_state_entries**: 63 (atocore alone; full cross-project count not re-sampled this update)
- **entities**: 66 (up from 35 — V1-0 backfill + ongoing work; 0 open conflicts)
- **off_host_backup**: `papa@192.168.86.39:/home/papa/atocore-backups/` via cron, verified
- **nightly_pipeline**: backup → cleanup → rsync → OpenClaw import → vault refresh → extract → auto-triage → **auto-promote/expire (NEW)** → weekly synth/lint Sundays → **retrieval harness (NEW)****pipeline summary (NEW)**
- **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 density gate CLEARED (784 active ≫ 100 target as of 2026-04-23). V1-A soak gate at day 5/~7 (F4 first run 2026-04-19; nightly clean 2026-04-19 through 2026-04-23; failures confined to the known p04-constraints content gap). Plan: `docs/plans/engineering-v1-completion-plan.md`. Resume map: `docs/plans/v1-resume-state.md`.
- **last_nightly_pipeline**: `2026-04-23T03:00:20Z` — harness 17/18, triage promoted=3 rejected=7 human=0, dedup 7 clusters (1 tier1 + 6 tier2 auto-merged), graduation 30-skipped 0-graduated 0-errors, auto-triage drained the queue (0 new candidates 2026-04-22T00:52Z run)
- **open_branches**: none — R14 squash-merged as `0989fed` and deployed 2026-04-23T15:20:53Z. V1-A is the next scheduled work
## Active Plan
@@ -143,9 +146,16 @@ 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. | fixed | Claude | 2026-04-22 | 0989fed |
## Recent Decisions
- **2026-04-22** **Wiki reorg reframed as read-only operator orientation.** The earlier `docs/plans/wiki-reorg-plan.md` (5 additions W-1…W-5 adding `/wiki/interactions`, `/wiki/memories`, project-page restructure, recent feed, topnav exposure) is **superseded** and marked as such in-tree. Successor is `docs/plans/operator-orientation-plan.md` in the `ATOCore-clean` workspace: no `/wiki` surface, no Human Mirror implementation, no new API routes, no schema changes, no new source of truth. First deliverables are docs-only: `docs/where-things-live.md` (operator map of source/staged/machine/registry/trusted-state/memories/interactions/logs/backups with explicit edit-vs-don't-edit boundaries) and `docs/operator-home.md` (short daily starting page indexing those docs + a 4-command read-only orientation sequence). README and operations.md link both. Optional future work (`project-overview` and `memory-candidates` CLI helpers over existing endpoints) is gated on the docs proving useful in practice. *Proposed by:* Antoine. *Drafted by:* Claude. *Pending Codex review.*
- **2026-04-22** **V1-0 done: approved, merged, deployed, prod backfilled.** Codex pulled `f16cd52`, re-ran the two original probes (both pass), re-ran the three targeted regression suites (all pass). Squash-merged to main as `2712c5d`. Dalidou deployed via canonical deploy script; `/health` reports build_sha=`2712c5d2d03cb2a6af38b559664afd1c4cd0e050`, status=ok. Validated backup snapshot taken at `/srv/storage/atocore/backups/snapshots/20260422T190624Z` before backfill. Prod backfill: `--dry-run` found 31 active/superseded entities with no provenance; list reviewed and sane; live run updated 31 rows via the default `hand_authored=1` flag path; follow-up dry-run returned 0 rows remaining. Residual logged as R14 (P2): `POST /entities/{id}/promote` HTTP route doesn't translate the new service-layer `ValueError` into a 400 — legacy bad candidate promotes via the API return 500 instead. Does not block V1-0 acceptance. V1-0 closed. Next: V1-A (Q-001 subsystem-scoped variant + Q-6 integration). V1-A holds until the soak window ends ~2026-04-26 and the 100-memory density target is hit. *Approved + landed by:* Codex. *Ratified by:* Antoine.
- **2026-04-22** **Engineering V1 Completion Plan — Codex sign-off (third round)**. Codex's third-round audit closed the remaining five open questions with concrete resolutions, patched inline in `docs/plans/engineering-v1-completion-plan.md`: (1) F-7 row rewritten with ground truth — schema + preserve-original + test coverage already exist (`graduated_to_entity_id` at `database.py:143-146`, `graduated` status in memory service, promote hook at `service.py:354-356,389-451`, tests at `test_engineering_v1_phase5.py:67-90`); **real gaps** are missing direct `POST /memory/{id}/graduate` route and spec's `knowledge→Fact` mismatch (no `fact` entity type exists; reconcile to `parameter` or similar); V1-E 2 → **34 days**; (2) Q-5 determinism reframed — don't stabilize the call to `datetime.now()`, inject regenerated timestamp + checksum as renderer inputs, remove DB iteration ordering dependencies; V1-D scope updated; (3) `project` vs `project_id` — doc note only, no rename, resolved; (4) total estimate 16.517.5 → **17.519.5 focused days** with calendar buffer on top; (5) "Minions" must not be canonized in D-3 release notes — neutral wording ("queued background processing / async workers") only. **Agreement reached**: Claude + Codex + Antoine aligned. V1-0 is ready to start once the current pipeline soak window ends (~2026-04-26) and the 100-memory density target is hit. *Patched by:* Codex. *Signed off by:* Codex ("with those edits, I'd sign off on the five questions"). *Accepted by:* Antoine. *Executor (V1-0 onwards):* Claude.
- **2026-04-22** **Engineering V1 Completion Plan revised per Codex second-round file-level audit** — three findings folded in, all with exact file:line refs from Codex: (1) F-1 downgraded from ✅ to 🟡 — `extractor_version` and `canonical_home` missing from `Entity` dataclass and `entities` table per `engineering-v1-acceptance.md:45`; V1-0 scope now adds both fields via additive migration + doc note that `project` IS `project_id` per "fields equivalent to" spec wording; (2) F-2 replaced with ground-truth per-query status: 9 of 20 v1-required queries done (Q-004/Q-005/Q-006/Q-008/Q-009/Q-011/Q-013/Q-016/Q-017), 1 partial (Q-001 needs subsystem-scoped variant), 10 missing (Q-002/003/007/010/012/014/018/019/020); V1-A scope shrank to Q-001 shape fix + Q-6 integration (pillar queries already implemented); V1-C closes the 8 remaining new queries + Q-020 deferred to V1-D; (3) F-5 reframed — generic `conflicts` + `conflict_members` schema already present at `database.py:190`, no migration needed; divergence is detector body (per-type dispatch needs generalization) + routes (`/admin/conflicts/*` needs `/conflicts/*` alias). Total revised to 16.517.5 days, ~60 tests. Plan: `docs/plans/engineering-v1-completion-plan.md` at commit `ce3a878` (Codex pulled clean). Three of Codex's eight open questions now answered; remaining: F-7 graduation depth, mirror determinism, `project` rename question, velocity calibration, minions naming. *Proposed by:* Claude. *Reviewed by:* Codex (two rounds).
- **2026-04-22** **Engineering V1 Completion Plan revised per Codex first-round review** — original six-phase order (queries → ingest → mirror → graduation → provenance → ops) rejected by Codex as backward: provenance-at-write (F-8) and conflict-detection hooks (F-5 minimal) must precede any phase that writes active entities. Revised to seven phases: V1-0 write-time invariants (F-8 + F-5 hooks + F-1 audit) as hard prerequisite, V1-A minimum query slice proving the model, V1-B ingest, V1-C full query catalog, V1-D mirror, V1-E graduation, V1-F full F-5 spec + ops + docs. Also softened "parallel with Now list" — real collision points listed explicitly; schedule shifted ~4 weeks to reflect that V1-0 cannot start during pipeline soak. Withdrew the "5070% built" global framing in favor of the per-criterion gap table. Workspace sync note added: Codex's Playground workspace can't see the plan file; canonical dev tree is Windows `C:\Users\antoi\ATOCore`. Plan: `docs/plans/engineering-v1-completion-plan.md`. Awaiting Codex file-level audit once workspace syncs. *Proposed by:* Claude. *First-round review by:* Codex.
- **2026-04-22** gbrain-inspired "Phase 8 Minions + typed edges" plan **rejected as packaged** — wrong sequencing (leapfrogged `master-plan-status.md` Now list), wrong predicate set (6 vs V1's 17), wrong canonical boundary (edges-on-wikilinks instead of typed entities+relationships per `memory-vs-entities.md`). Mechanic (durable jobs + typed graph) deferred to V1 home. Record: `docs/decisions/2026-04-22-gbrain-plan-rejection.md`. *Proposed by:* Claude. *Reviewed/rejected by:* Codex. *Ratified by:* Antoine.
- **2026-04-12** Day 4 gate cleared: LLM-assisted extraction via `claude -p` (OAuth, no API key) is the path forward. Rule extractor stays as default for structural cues. *Proposed by:* Claude. *Ratified by:* Antoine.
- **2026-04-12** First live triage: 16 promoted, 35 rejected from 51 LLM-extracted candidates. 31% accept rate. Active memory count 20->36. *Executed by:* Claude. *Ratified by:* Antoine.
- **2026-04-12** No API keys allowed in AtoCore — LLM-assisted features use OAuth via `claude -p` or equivalent CLI-authenticated paths. *Proposed by:* Antoine.
@@ -160,6 +170,24 @@ One branch `codex/extractor-eval-loop` for Day 1-5, a second `codex/retrieval-ha
## Session Log
- **2026-04-23 Codex + Claude (R14 closed)** Codex reviewed `claude/r14-promote-400` at `3888db9`, no findings: "The route change is narrowly scoped: `promote_entity()` still returns False for not-found/not-candidate cases, so the existing 404 behavior remains intact, while caller-fixable validation failures now surface as 400." Ran `pytest tests/test_v1_0_write_invariants.py -q` from an isolated worktree: 15 passed in 1.91s. Claude squash-merged to main as `0989fed`, followed by ledger close-out `2b86543`, then deployed via canonical script. Dalidou `/health` reports build_sha=`2b86543e6ad26011b39a44509cc8df3809725171`, build_time `2026-04-23T15:20:53Z`, status=ok. R14 closed. Orientation refreshed earlier this session also reflected the V1-A gate status: **density gate CLEARED** (784 active memories vs 100 target — density batch-extract ran between 2026-04-22 and 2026-04-23 and more than crushed the gate), **soak gate at day 5 of ~7** (F4 first run 2026-04-19; nightly clean 2026-04-19 through 2026-04-23; only chronic failure is the known p04-constraints "Zerodur" content gap). V1-A branches from a clean V1-0 baseline as soon as the soak is called done.
- **2026-04-22 Codex + Antoine (V1-0 closed)** Codex approved `f16cd52` after re-running both original probes (legacy-candidate promote + supersede hook — both correct) and the three targeted regression suites (`test_v1_0_write_invariants.py`, `test_engineering_v1_phase5.py`, `test_inbox_crossproject.py` — all pass). Squash-merged to main as `2712c5d` ("feat(engineering): enforce V1-0 write invariants"). Deployed to Dalidou via the canonical deploy script; `/health` build_sha=`2712c5d2d03cb2a6af38b559664afd1c4cd0e050` status=ok. Validated backup snapshot at `/srv/storage/atocore/backups/snapshots/20260422T190624Z` taken BEFORE prod backfill. Prod backfill of `scripts/v1_0_backfill_provenance.py` against live DB: dry-run found 31 active/superseded entities with no provenance, list reviewed and looked sane; live run with default `hand_authored=1` flag path updated 31 rows; follow-up dry-run returned 0 rows remaining → no lingering F-8 violations in prod. Codex logged one residual P2 (R14): HTTP `POST /entities/{id}/promote` route doesn't translate the new service-layer `ValueError` into 400 — legacy bad candidate promoted through the API surfaces as 500. Not blocking. V1-0 closed. **Gates for V1-A**: soak window ends ~2026-04-26; 100-active-memory density target (currently 84 active + the ~31 newly flagged ones — need to check how those count in density math). V1-A holds until both gates clear.
- **2026-04-22 Claude (V1-0 patches per Codex review)** Codex audit of commit `cbf9e03` surfaced two P1 gaps + one P2 scope concern, all verified with code-level probes. **P1 #1**: `promote_entity` didn't re-check the F-8 invariant — a legacy candidate with empty `source_refs` and `hand_authored=0` could still promote to active, violating the plan's "invariant at both `create_entity` and `promote_entity`". Fixed: `promote_entity` at `service.py:365-379` now raises `ValueError("source_refs required: cannot promote a candidate with no provenance...")` before flipping status. Stays symmetric with the create-side error. **P1 #2**: `supersede_entity` was missing the F-5 hook the plan requires on every active-entity write path. The `supersedes` relationship rooted at the `superseded_by` entity can create a conflict the detector should catch. Fixed at `service.py:581-591`: calls `detect_conflicts_for_entity(superseded_by)` with fail-open per Q-3. **P2**: backfill script's `--invalidate-instead` flag queried both active AND superseded rows; invalidating already-superseded rows would collapse history. Fixed at `scripts/v1_0_backfill_provenance.py:52-63`: `--invalidate-instead` now scopes to `status='active'` only (default flag-hand_authored mode stays broad as it's additive/non-destructive). Help text tightened to make the destructive posture explicit. **Four new regression tests** in `test_v1_0_write_invariants.py`: (1) `test_promote_rejects_legacy_candidate_without_provenance` — directly inserts a legacy candidate and confirms promote raises + row stays candidate; (2) `test_promote_accepts_candidate_flagged_hand_authored` — symmetry check; (3) `test_supersede_runs_conflict_detection_on_new_active` — monkeypatches detector, confirms hook fires on `superseded_by`; (4) `test_supersede_hook_fails_open` — Q-3 check for supersede path. **Test count**: 543 → 547 (+4 regression). Full suite `547 passed in 81.07s`. Next: commit patches on branch, push, Codex re-review.
- **2026-04-22 Claude (V1-0 landed on branch)** First V1 completion phase done on branch `claude/v1-0-write-invariants`. **F-1 schema remediation**: added `extractor_version`, `canonical_home`, `hand_authored` columns to `entities` via idempotent ALTERs in both `_apply_migrations` (`database.py:148-170`) and `init_engineering_schema` (`service.py:95-139`). CREATE TABLE also updated so fresh DBs get the columns natively. New `_table_exists` helper at `database.py:378`. `Entity` dataclass gains the three fields with sensible defaults. `EXTRACTOR_VERSION = "v1.0.0"` module constant at top of `service.py`. `_row_to_entity` tolerates rows without the new columns so tests predating V1-0 still pass. **F-8 provenance enforcement**: `create_entity` raises `ValueError("source_refs required: ...")` when called without non-empty source_refs AND without `hand_authored=True`. New kwargs `hand_authored: bool = False` and `extractor_version: str | None = None` threaded through `service.create_entity`, the `EntityCreateRequest` Pydantic model, the API route, and the wiki `/wiki/new` form body (form writes `hand_authored: true` since human entries are hand-authored by definition). **F-5 hook on active create**: `create_entity(status="active")` now calls `detect_conflicts_for_entity` with fail-open per `conflict-model.md:256` (errors log warning, write still succeeds). The promote path's existing hook at `service.py:400-404` was kept as-is. **Doc note** added to `engineering-ontology-v1.md` recording that `project` IS the `project_id` per "fields equivalent to" wording. **Backfill script** at `scripts/v1_0_backfill_provenance.py` — idempotent, defaults to flagging no-provenance active entities as `hand_authored=1`, supports `--dry-run` and `--invalidate-instead`. **Tests**: 10 new in `tests/test_v1_0_write_invariants.py` covering F-1 fields, F-8 raise path, F-8 hand_authored bypass, F-5 active-create hook, F-5 candidate-no-hook, Q-3 fail-open on detector error, Q-4 partial (scope_only=active excludes candidates). **Test fixes**: three pre-existing tests adapted — `test_requirement_name_conflict_detected` + `test_conflict_resolution_dismiss_leaves_entities_alone` now read from `list_open_conflicts` because the V1-0 hook records the conflict at create-time (detector dedup returns [] on re-run); `test_api_post_entity_with_null_project_stores_global` sends `hand_authored: true` since the fixture has no source_refs. **conftest.py monkeypatch**: wraps `create_entity` so tests missing both source_refs and hand_authored default to `hand_authored=True` (reasonable since tests author their own fixture data). Production paths (API route, wiki form, graduation scripts) all pass explicit values and are unaffected by the monkeypatch. **Test count**: 533 → 543 (+10), full suite `543 passed in 77.86s`. **Not yet**: commit + push + Codex review + deploy. **Branch**: `claude/v1-0-write-invariants`.
- **2026-04-22 Codex (late night)** Third-round audit closed the remaining five open questions. Patched `docs/plans/engineering-v1-completion-plan.md` inline (no commit by Codex). **F-7 finding (P1):** graduation stack is partially built — `_graduation_prompt.py`, `scripts/graduate_memories.py`, `database.py:143-146` (`graduated_to_entity_id`), memory `graduated` status, promote-preserves-original at `service.py:354-356,389-451`, tests at `test_engineering_v1_phase5.py:67-90` all exist. Real gaps: no direct `POST /memory/{id}/graduate` route at `routes.py:756`; spec's `knowledge→Fact` doesn't match ontology (`service.py:16` has no `fact` type — reconcile to `parameter` or similar). V1-E estimate 2 → 34 days. **Q-5 finding (P2):** "stabilize timestamp" insufficient — renderer reads wall-clock in `_footer()` at `mirror.py:320`; fix is inject regenerated timestamp + checksum as renderer inputs + sort DB iteration + remove dict ordering deps. V1-D scope patched. **Remaining three (P3):** `project` stays as doc-note equivalence (no rename); total estimate 17.519.5 focused days; release notes must NOT canonize "Minions" — neutral "queued background processing / async workers" only. **Sign-off:** "with those edits, I'd sign off on the five questions. The only non-architectural uncertainty left in the plan is scheduling discipline against the current Now list; that does not block V1-0 once the soak window and memory-density gate clear." **Status:** Claude + Codex agreed. Plan frozen pending Antoine final accept and gate clearance. Claude to commit Codex's patches + push.
- **2026-04-22 Claude (late night)** Codex second-round review did the full file-level audit and came back with three P1/P2 findings, all with exact file:line refs. Verified each against current code before revising. (1) **F-1 not clean**: `Entity` dataclass at `service.py:67` and `entities` table schema are missing the `extractor_version` and `canonical_home` shared-header fields required by `engineering-v1-acceptance.md:45`; `project` field is the project identifier but not named `project_id` as spec writes (spec wording "fields equivalent to" allows the naming, but needs explicit doc note). V1-0 scope now includes adding both missing fields via additive `_apply_migrations` pattern. (2) **F-2 needed exact statuses, not guesses**: per-function audit gave ground truth — 9 of 20 v1-required queries done, 1 partial (Q-001 returns project-wide tree not subsystem-scoped expand=contains per `engineering-query-catalog.md:71`), 10 missing. V1-A scope shrank to Q-001 shape fix + Q-6 integration (most pillar queries already implemented); V1-C closes the 8 net-new queries + Q-020 to V1-D. (3) **F-5 misframed**: the generic `conflicts` + `conflict_members` schema is ALREADY spec-compliant at `database.py:190`; divergence is detector body at `conflicts.py:36` (per-type dispatch needs generalization) + route path (`/admin/conflicts/*` needs `/conflicts/*` alias). V1-F no longer includes a schema migration; detector generalization + route alignment only. Totals revised to 16.517.5 days, ~60 tests (down from 1217 / 65 because V1-A and V1-F scopes both shrank after audit). Three of the eight open questions resolved. Remaining open: F-7 graduation depth, mirror determinism, `project` naming, velocity calibration, minions-as-V2 naming. No code changes this session — plan + ledger only. Next: commit + push revised plan, then await Antoine+Codex joint sign-off before V1-0 starts.
- **2026-04-22 Claude (night)** Codex first-round review of the V1 Completion Plan summary came back with four findings. Three substantive, one workspace-sync: (1) "5070% built" too loose — replaced with per-criterion table, global framing withdrawn; (2) phase order backward — provenance-at-write (F-8) and conflict hooks (F-5 minimal) depend-upon by every later phase but were in V1-E; new V1-0 prerequisite phase inserted to establish write-time invariants, and V1-A shrunk to a minimum query slice (four pillars Q-001/Q-005/Q-006/Q-017 + Q-6 integration) rather than full catalog closure; (3) "parallel with Now list / disjoint surfaces" too strong — real collisions listed explicitly (V1-0 provenance + memory extractor write path, V1-E graduation + memory module, V1-F conflicts migration + memory promote); schedule shifted ~4 weeks, V1-0 cannot start during pipeline soak; (4) Codex's Playground workspace can't see the plan file or the `src/atocore/engineering/` code — added a Workspace note to the plan directing per-file audit at the Windows canonical dev tree (`C:\Users\antoi\ATOCore`) and noting the three visible file paths (`docs/plans/engineering-v1-completion-plan.md`, `docs/decisions/2026-04-22-gbrain-plan-rejection.md`, `DEV-LEDGER.md`). Revised plan estimate: 1217 days across 7 phases (up from 1114 / 6), ~65 tests added (up from ~50). V1-0 is a hard prerequisite; no later phase starts until it lands. Pending Antoine decision on workspace sync (commit+push vs paste-to-Codex) so Codex can do the file-level audit. No code changes this session.
- **2026-04-22 Claude (late eve)** After the rejection, read the four core V1 architecture docs end-to-end (`engineering-ontology-v1.md`, `engineering-query-catalog.md`, `memory-vs-entities.md`, `engineering-v1-acceptance.md`) plus the four supporting docs (`promotion-rules.md`, `conflict-model.md`, `human-mirror-rules.md`, `tool-handoff-boundaries.md`). Cross-referenced against current code in `src/atocore/engineering/`. **Key finding:** V1 is already 5070% built — entity types (16, superset of V1's 12), all 18 V1 relationship types, 4-state lifecycle, CRUD + supersede + invalidate + PATCH, queries module with most killer-correctness queries (orphan_requirements, risky_decisions, unsupported_claims, impact_analysis, evidence_chain), conflicts module scaffolded, mirror scaffolded, graduation endpoint scaffolded. Recent commits e147ab2/b94f9df/081c058/069d155/b1a3dd0 are all V1 entity-layer work. Drafted `docs/plans/engineering-v1-completion-plan.md` reframing the work as **V1 completion, not V1 start**. Six sequential phases V1-A through V1-F, estimated 1114 days, ~50 new tests (533 → ~580). Phases run in parallel with the Now list (pipeline soak + density + multi-model triage + p04-constraints) because surfaces are disjoint. Plan explicitly defers the minions/queue mechanic per acceptance-doc negative list. Pending Codex audit of the plan itself — especially the F-2 query gap list (Claude didn't read each query function end-to-end), F-5 conflicts schema divergence (per-type detectors vs spec's generic slot-keyed shape), and F-7 graduation depth. No code changes this session.
- **2026-04-22 Claude (eve)** gbrain review session. Antoine surfaced https://github.com/garrytan/gbrain for compare/contrast. Claude drafted a "Phase 8 Minions + typed edges" plan pairing a durable job queue with a 6-predicate edge upgrade over wikilinks. Codex reviewed and rejected as packaged: (1) sequencing leapfrogged the `master-plan-status.md` Now list (pipeline soak → 100+ memories → multi-model triage → p04-constraints fix); (2) 6 predicates vs V1's 17 across Structural/Intent/Validation/Provenance families — would have been schema debt on day one per `engineering-ontology-v1.md:112-137`; (3) "edges over wikilinks" bypassed the V1 canonical entity + promotion contract in `memory-vs-entities.md`. Claude verified each high finding against the cited files and concurred. The underlying mechanic (durable background jobs + typed relationship graph) is still a valid future direction, but its correct home is the Engineering V1 sprint under **Next** in `master-plan-status.md:179`, not a leapfrog phase. Decision record: `docs/decisions/2026-04-22-gbrain-plan-rejection.md`. No code changes this session. Next (pending Antoine ack): read the four V1 architecture docs end-to-end, then draft an Engineering V1 foundation plan that follows the existing contract, not a new external reference. Phase 8 (OpenClaw) name remains untouched — Claude's misuse of "Phase 8" in the rejected plan was a naming collision, not a renaming.
- **2026-04-22 Claude (pm)** Issue B (wiki redlinks) landed — last remaining P2 from Antoine's sprint plan. `_wikilink_transform(text, current_project)` in `src/atocore/engineering/wiki.py` replaces `[[Name]]` / `[[Name|Display]]` tokens (pre-markdown) with HTML anchors. Resolution order: same-project exact-name match → live `wikilink`; other-project match → live link with `(in project X)` scope indicator (`wikilink-cross`); no match → `redlink` pointing at `/wiki/new?name=<quoted>&project=<current>`. New route `GET /wiki/new` renders a pre-filled "create this entity" form that POSTs to `/v1/entities` via a minimal inline fetch() and redirects to the new entity's wiki page on success. Transform applied in `render_project` (over the mirror markdown) and `render_entity` (over the description body). CSS: dashed-underline accent for live wikilinks, red italic + dashed for redlinks. 12 new tests including the regression from the spec (entity A references `[[EntityB]]` → initial render has `class="redlink"`; after EntityB is created, re-render no longer has redlink and includes `/wiki/entities/{b.id}`). Tests 521 → 533. All 6 acceptance criteria from the sprint plan ("daily-usable") now green: retract/supersede, edit without cloning, cross-project has a home, visual evidence, wiki readable, AKC can capture reliably.
- **2026-04-22 Claude** PATCH `/entities/{id}` + Issue D (/v1/engineering/* aliases) landed. New `update_entity()` in `src/atocore/engineering/service.py` supports partial updates to description (replace), properties (shallow merge — `null` value deletes a key), confidence (0..1, 400 on bounds violation), source_refs (append + dedup). Writes an `updated` audit row with full before/after snapshots. Forbidden via this path: entity_type / project / name / status — those require supersede+create or the dedicated status endpoints, by design. New route `PATCH /entities/{id}` aliased under `/v1`. Issue D: all 10 `/engineering/*` query paths (decisions, systems, components/{id}/requirements, changes, gaps + sub-paths, impact, evidence) added to the `/v1` allowlist. 12 new PATCH tests (merge, null-delete, confidence bounds, source_refs dedup, 404, audit row, v1 alias). Tests 509 → 521. Next: commit + deploy, then Issue B (wiki redlinks) as the last remaining P2 per Antoine's sprint order.

View File

@@ -159,6 +159,17 @@ Every major object should support fields equivalent to:
- `created_at`
- `updated_at`
- `notes` (optional)
- `extractor_version` (V1-0)
- `canonical_home` (V1-0)
**Naming note (V1-0, 2026-04-22).** The AtoCore `entities` table and
`Entity` dataclass name the project-identifier field `project`, not
`project_id`. This doc's "fields equivalent to" wording allows that
naming flexibility — the `project` field on entity rows IS the
`project_id` per spec. No storage rename is planned; downstream readers
should treat `entity.project` as the project identifier. This was
resolved in Codex's third-round audit of the V1 Completion Plan (see
`docs/plans/engineering-v1-completion-plan.md`).
## Suggested Status Lifecycle

View File

@@ -1,6 +1,31 @@
# AtoCore — Current State (2026-04-19)
# AtoCore — Current State (2026-04-22)
Live deploy: `877b97e` · Dalidou health: ok · Harness: 17/18.
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)
## The numbers

View File

@@ -0,0 +1,113 @@
# Decision record: gbrain-inspired "Phase 8 Minions + typed edges" plan rejected
**Date:** 2026-04-22
**Author of plan:** Claude
**Reviewer:** Codex
**Ratified by:** Antoine
**Status:** Rejected as packaged. Underlying mechanic (durable background jobs + typed relationships) deferred to its correct home.
## Context
Antoine surfaced https://github.com/garrytan/gbrain and asked for a compare/contrast and a
plan to improve AtoCore. Claude proposed a "Phase 8" plan pairing:
1. A Minion-style durable job queue replacing the nightly cron pipeline
2. A typed-edge upgrade over existing wikilinks, with a six-predicate set
(`mentions`, `decided_by`, `supersedes`, `evidences`, `part_of`, `blocks`)
Codex reviewed and rejected the plan as packaged. This record captures what went wrong,
what was right, and where the ideas should actually land.
## What Codex flagged (verified against repo)
### High — wrong sequencing
`docs/master-plan-status.md` defines the **Now** list:
1. Observe the enhanced pipeline for a week
2. Knowledge density — batch-extract over all 234 interactions, target 100+ memories
3. Multi-model triage (Phase 11 entry)
4. Fix p04-constraints harness failure
Engineering V1 appears under **Next** (line 179) as
"Engineering V1 implementation sprint — once knowledge density is sufficient and the
pipeline feels boring and dependable."
Claude's plan jumped over all four **Now** items. That was the primary sequencing error.
### High — wrong predicate set
`docs/architecture/engineering-ontology-v1.md` already defines a 17-predicate V1
ontology across four families:
- **Structural:** `CONTAINS`, `PART_OF`, `INTERFACES_WITH`
- **Intent / logic:** `SATISFIES`, `CONSTRAINED_BY`, `BASED_ON_ASSUMPTION`,
`AFFECTED_BY_DECISION`, `SUPERSEDES`
- **Validation:** `ANALYZED_BY`, `VALIDATED_BY`, `SUPPORTS`, `CONFLICTS_WITH`,
`DEPENDS_ON`
- **Artifact / provenance:** `DESCRIBED_BY`, `UPDATED_BY_SESSION`, `EVIDENCED_BY`,
`SUMMARIZED_IN`
Claude's six-predicate set was a gbrain-shaped subset that could not express the V1
example statements at lines 141147 of that doc. Shipping it first would have been
schema debt on day one.
### High — wrong canonical boundary
`docs/architecture/memory-vs-entities.md` and
`docs/architecture/engineering-v1-acceptance.md` establish that V1 is **typed
entities plus typed relationships**, with one canonical home per concept, a shared
candidate-review / promotion flow, provenance, conflict handling, and mirror
generation. Claude's "typed edges on top of wikilinks" framing bypassed the canonical
entity contract — it would have produced labelled links over notes without the
promotion / canonicalization machinery V1 actually requires.
### Medium — overstated problem
Claude described the nightly pipeline as a "monolithic bash script" that needed to be
replaced. The actual runtime is API-driven (`src/atocore/api/routes.py:516`,
`src/atocore/interactions/service.py:55`), SQLite is already in WAL with a busy
timeout (`src/atocore/models/database.py:151`), and the reflection loop is explicit
capture / reinforce / extract. The queue argument overstated the current shape.
## What was right
- gbrain is genuine validation of the general pattern: **durable background jobs +
typed relationship graph compound value**. The gbrain v0.12.0 graph release and
Minions benchmark (both 2026-04-18) are evidence, not just inspiration.
- Async-ification of extraction with retries, per-job visibility, and SLOs remains a
real future win for AtoCore — but **additively, behind flags, after V1**, not as a
replacement for the current explicit endpoints.
## What we will do instead
1. **Keep to the `master-plan-status.md` Now list.** No leapfrog. Observe the
pipeline (including the confidence-decay Step F4 first real run), land knowledge
density via full-backlog batch extract, progress multi-model triage, fix
p04-constraints.
2. **When Engineering V1 is ready to start** (criterion: pipeline feels boring and
dependable, knowledge density ≥ 100 active memories), write a V1 foundation plan
that follows `engineering-ontology-v1.md`, `engineering-query-catalog.md`,
`memory-vs-entities.md`, and `engineering-v1-acceptance.md` — entities +
relationships + memory-to-entity bridge + mirror / query surfaces, in that order.
3. **Async workerization is optional and later.** Only after V1 is working, and only
if observed contention or latency warrants it. Jobs stay in the primary SQLite
(WAL already in place). No separate DB unless contention is measured.
## Lesson for future plans
A plan built from a **new external reference** (gbrain) without reading the
repository's own architecture docs will mis-specify predicates, boundaries, and
sequencing — even when the underlying mechanic is valid. Read the four V1
architecture docs end-to-end before proposing schema work.
## References
- https://github.com/garrytan/gbrain
- `docs/master-plan-status.md` (Now / Next / Later)
- `docs/architecture/engineering-ontology-v1.md`
- `docs/architecture/engineering-query-catalog.md`
- `docs/architecture/memory-vs-entities.md`
- `docs/architecture/engineering-v1-acceptance.md`
- `docs/architecture/llm-client-integration.md`
- `docs/architecture/human-mirror-rules.md`

View File

@@ -168,16 +168,40 @@ 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 the current stabilization pass.
These are the next major layers after V1 and 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

@@ -0,0 +1,317 @@
# OpenClaw x AtoCore V1 Audit Note
## Scope
This note is the Phase 1 audit for a safe OpenClaw x AtoCore operating model.
It covers only what was directly verified in `/home/papa/ATOCore` and `/home/papa/clawd` on 2026-04-23, plus explicit assumptions called out as assumptions.
This phase does not change code, runtime behavior, skills, helpers, or automation.
## Files requested and verified
The following requested AtoCore files were present and reviewed:
- `docs/openclaw-integration-contract.md`
- `docs/architecture/llm-client-integration.md`
- `docs/architecture/representation-authority.md`
- `docs/operating-model.md`
- `docs/current-state.md`
- `docs/master-plan-status.md`
- `docs/operations.md`
- `AGENTS.md`
- `CLAUDE.md`
- `DEV-LEDGER.md`
No requested files were missing.
## What was directly verified
### 1. OpenClaw instruction surface
In `/home/papa/clawd/AGENTS.md`, OpenClaw is currently instructed to:
- use the `atocore-context` skill for project-dependent work
- treat AtoCore as additive and fail-open
- prefer `auto-context` for project knowledge questions
- prefer `project-state` for trusted current truth
- use `refresh-project` if the human explicitly asked to refresh or ingest project changes
- use `discrawl` automatically when Antoine asks about prior Discord discussions
This is already close to the intended additive read path, but it also exposes mutating project operations in a general operator workflow.
### 2. OpenClaw helper skill surface
The current helper skill is:
- `/home/papa/clawd/skills/atocore-context/SKILL.md`
- `/home/papa/clawd/skills/atocore-context/scripts/atocore.sh`
The skill describes AtoCore as a read-only additive context service, but the helper script currently exposes the following commands:
- `health`
- `sources`
- `stats`
- `projects`
- `project-template`
- `detect-project`
- `auto-context`
- `debug-context`
- `propose-project`
- `register-project`
- `update-project`
- `refresh-project`
- `project-state`
- `query`
- `context-build`
- `ingest-sources`
That means the helper is not actually read-only. It can drive registry mutation and ingestion-related operations.
### 3. AtoCore shared operator client surface
The shared operator client in `/home/papa/ATOCore/scripts/atocore_client.py` exposes a broader surface than the OpenClaw helper, including:
- all of the project and context operations above
- `project-state-set`
- `project-state-invalidate`
- `capture`
- `extract`
- `reinforce-interaction`
- `list-interactions`
- `get-interaction`
- `queue`
- `promote`
- `reject`
- `batch-extract`
- `triage`
This matches the architectural intent in `docs/architecture/llm-client-integration.md`: a shared operator client should be the canonical reusable surface for multiple frontends.
### 4. Actual layering status today
The intended layering is documented in `docs/architecture/llm-client-integration.md` as:
- AtoCore HTTP API
- shared operator client
- thin per-agent frontends
But the current OpenClaw helper is still its own Bash implementation. It does not shell out to the shared operator client today.
So the shared-client pattern is documented, but not yet applied to OpenClaw.
### 5. AtoCore availability and fail-open behavior
The OpenClaw helper successfully reached the live AtoCore instance during this audit.
Verified live behavior:
- `health` worked
- `projects` worked
- the helper still has fail-open logic when network access fails
This part is consistent with the stated additive and fail-open stance.
### 6. Discrawl availability
The `discrawl` CLI is installed locally and available.
Verified during audit:
- binary present
- version `0.3.0`
- OpenClaw workspace instructions explicitly route project-history recall through `discrawl`
This supports the desired framing of Discord and Discrawl as an evidence stream.
### 7. Screenpipe status
`screenpipe` was not present as a local command in this environment during the audit.
For V1, Screenpipe is deferred and out of scope. No active Screenpipe input lane was verified or adopted in the final V1 policy.
## Current implementation shape
### What OpenClaw can do safely right now
The current safe, directly verified OpenClaw -> AtoCore path is:
- project detection
- context build
- query and retrieval
- project-state read
- service inspection
- fail-open fallback
That is the mature part of the integration.
### What OpenClaw can also do today, but should be treated as controlled operator actions
The current helper also exposes:
- project proposal preview
- project registration
- project update
- project refresh
- ingest-sources
These should not be treated as background or conversational automation. They are operator actions and need explicit approval policy.
### What exists in AtoCore but is not exposed through the OpenClaw helper
The shared operator client already supports:
- interaction capture
- candidate extraction
- queue review
- promote or reject
- trusted project-state write and invalidate
The current OpenClaw helper does not expose that surface.
This is important for V1 design: the write-capable lanes already exist in AtoCore, but they are not yet safely shaped for Discord-originated automation.
## Conflicts with the target V1 stance
The following conflicts are real and should be named explicitly.
### Conflict 1 - the OpenClaw helper is described as read-only, but it is not read-only
`SKILL.md` frames the integration as read-only additive context.
`atocore.sh` exposes mutating operations:
- `register-project`
- `update-project`
- `refresh-project`
- `ingest-sources`
That mismatch needs a policy fix in Phase 2. For Phase 1 it must be documented as a conflict.
### Conflict 2 - OpenClaw duplicates client logic instead of using the shared operator client
The architecture docs prefer a shared operator client reused across frontends.
The OpenClaw helper currently reimplements request logic and project detection in Bash.
That is a direct conflict with the preferred shared-client pattern.
### Conflict 3 - mutating project operations are too close to the conversational surface
The helper makes registry and ingestion operations reachable from the OpenClaw side without a dedicated Discord-specific approval gate.
Even if the human explicitly asks for a refresh, the current shape does not yet distinguish between:
- a direct trusted operator action in a controlled session
- a Discord-originated conversational path that should require an explicit human approval step before mutation
The Phase 2 V1 policy needs that distinction.
### Conflict 4 - current docs overstate or blur write capabilities
`docs/current-state.md` says OpenClaw can seed AtoCore through project-scoped memory entries and staged document ingestion.
That was not directly verified through the current OpenClaw helper surface in `/home/papa/clawd`.
The helper script does not expose:
- `capture`
- `extract`
- `promote`
- `reject`
- `project-state-set`
So there is at least a documentation and runtime-surface mismatch.
### Conflict 5 - there was no single OpenClaw-facing evidence lane description before this doc set
The target architecture needs a clean distinction between:
- raw evidence
- reviewable candidates
- active memories and entities
- trusted project_state
Today that distinction exists conceptually across several AtoCore docs, but before this Phase 1 doc set there was no single OpenClaw-facing operating model that told an operator exactly where Discord and Discrawl signals are allowed to land.
That is the main gap this doc set closes.
## What is already aligned with the target V1 stance
Several important pieces are already aligned.
### Aligned 1 - additive plus fail-open
Both AtoCore and OpenClaw docs consistently say AtoCore should be additive and fail-open from the OpenClaw side.
That is the right baseline and was verified live.
### Aligned 2 - project_state is already treated as special and curated
AtoCore architecture docs already treat `project_state` as the highest-trust curated layer.
This supports the rule that raw signals must not directly auto-write trusted project state.
### Aligned 3 - canonical-home thinking already exists
`docs/architecture/representation-authority.md` already establishes that each fact type needs one canonical home.
That is exactly the right foundation for the Discord and Discrawl design.
### Aligned 4 - reflection and candidate lifecycle already exists in AtoCore
The shared operator client and AtoCore docs already have a candidate workflow:
- capture
- extract
- queue
- promote or reject
That means V1 does not need to invent a new trust model. It needs to apply the existing one correctly to Discord and Discrawl signals.
## Recommended V1 operating interpretation
Until implementation work begins, the safest V1 operating interpretation is:
1. Discord and Discrawl are evidence sources, not truth sources.
2. OpenClaw is the orchestrator and operator, not canonical storage.
3. AtoCore memories may hold reviewed episodic, personal, and loose project signal.
4. Future AtoCore entities should hold reviewed structured decisions, requirements, and constraints.
5. `project_state` remains manual or tightly gated only.
6. Registry mutation, refresh, ingestion, and candidate promotion or rejection require explicit human approval on Discord-originated paths.
7. The shared operator client should become the only write-capable operator surface reused by OpenClaw and other frontends.
8. Screenpipe remains deferred and out of V1 scope.
## Assumption log
The following points were not directly verified and must stay labeled as assumptions.
1. Screenpipe integration shape is unverified and deferred.
- The `screenpipe` command was not present locally.
- No verified Screenpipe pipeline files were found in the inspected workspaces.
- V1 therefore excludes Screenpipe from active policy and runtime scope.
2. No direct Discord -> AtoCore auto-mutation path was verified in code.
- The OpenClaw workspace clearly contains read and query context behavior and a Discrawl retrieval rule.
- It does not clearly expose a verified Discord-triggered path that auto-calls `project-state-set`, `promote`, `reject`, or `register-project`.
- The risk is therefore policy and proximity of commands, not a proven live mutation bug.
3. OpenClaw runtime use of the shared operator client was not verified because it is not implemented yet.
- The shared client exists in the AtoCore repo.
- The OpenClaw helper is still its own Bash implementation.
4. A dedicated evidence store was not verified as a first-class AtoCore schema layer.
- Existing AtoCore surfaces clearly support interactions and candidate memories.
- This V1 model therefore uses evidence artifacts, interactions, and archive bundles as an architectural lane, without claiming a new implemented table already exists.
5. Future entities remain future.
- The entity layer is architected in AtoCore docs.
- This audit did not verify a production entity promotion flow being used by OpenClaw.
## Bottom line
The good news is that the trust foundations already exist.
The main conclusion is that the current system is closest to a safe V1 when interpreted this way:
- keep AtoCore additive and fail-open
- treat Discord and Discrawl as evidence only
- route reviewed signal into memory candidates first
- reserve `project_state` for explicit curation only
- move OpenClaw toward the shared operator client instead of maintaining a separate write-capable helper surface
- keep Screenpipe out of V1
That gives a coherent path to Phase 2 without pretending the current implementation is already there.

View File

@@ -0,0 +1,224 @@
commit 80bd99aaea1bcab2ea5ea732df2f749e84d84318
Author: Anto01 <antoine.letarte@gmail.com>
Date: Thu Apr 23 15:59:59 2026 +0000
Tighten OpenClaw AtoCore governance policy
diff --git a/AGENTS.md b/AGENTS.md
index 1da3385..ea4d103 100644
--- a/AGENTS.md
+++ b/AGENTS.md
@@ -105,7 +105,7 @@ Reactions are lightweight social signals. Humans use them constantly — they sa
## Tools
-When a task is contextual and project-dependent, use the `atocore-context` skill to query Dalidou-hosted AtoCore for trusted project state, retrieval, context-building, registered project refresh, or project registration discovery when that will improve accuracy. Treat AtoCore as additive and fail-open; do not replace OpenClaw's own memory with it. Prefer `projects` and `refresh-project <id>` when a known project needs a clean source refresh, and use `project-template` when proposing a new project registration, and `propose-project ...` when you want a normalized preview before editing the registry manually.
+When a task is contextual and project-dependent, use the `atocore-context` skill to query Dalidou-hosted AtoCore for trusted project-state reads, retrieval, and context-building when that will improve accuracy. Treat AtoCore as additive and fail-open; do not replace OpenClaw's own memory with it.
### Organic AtoCore Routing
@@ -116,14 +116,60 @@ Use AtoCore first when the prompt:
- asks about architecture, constraints, status, requirements, vendors, planning, prior decisions, or current project truth
- would benefit from cross-source context instead of only the local repo
-Preferred flow:
+Preferred read path:
1. `auto-context "<prompt>" 3000` for most project knowledge questions
2. `project-state <project>` when the user is clearly asking for trusted current truth
-3. `refresh-project <id>` before answering if the user explicitly asked to refresh or ingest project changes
+3. fall back to normal OpenClaw tools and memory if AtoCore returns `no_project_match` or is unavailable
Do not force AtoCore for purely local coding actions like fixing a function, editing one file, or running tests, unless broader project context is likely to matter.
-If `auto-context` returns `no_project_match` or AtoCore is unavailable, continue normally with OpenClaw's own tools and memory.
+### AtoCore Governance
+
+Default Discord posture for AtoCore is read-only and additive.
+
+Discord-originated or Discrawl-originated context may inform:
+- evidence collection
+- retrieval
+- context building
+- candidate review preparation
+
+It must not directly perform AtoCore mutating actions.
+
+Mutating AtoCore actions include:
+- `register-project`
+- `update-project`
+- `refresh-project`
+- `ingest-sources`
+- `project-state-set`
+- `project-state-invalidate`
+- `promote`
+- `reject`
+- any future trusted-state or review mutation
+
+These actions require explicit human approval for the specific action in the current thread or session.
+Do not infer approval from:
+- prior Discord discussion
+- Discrawl archive recall
+- screener output
+- vague intent like "we should probably refresh this"
+
+Hard rules:
+- no direct Discord -> `project_state`
+- no direct Discord -> register / update / refresh / ingest / promote / reject
+- no hidden mutation inside screening or review-prep flows
+- PKM notes are not the main operator instruction surface for AtoCore behavior
+
+### Discord Archive Retrieval (discrawl)
+
+When Antoine asks in natural language about prior project discussions, decisions, thread history, answers, or whether something was already discussed in Discord, use the local `discrawl` archive automatically.
+
+Rules:
+- Antoine should not need to remember or type `discrawl` commands.
+- Treat Discord history as a normal background retrieval source, like memory or project docs.
+- Use `discrawl` silently when it will materially improve recall or confidence.
+- Prefer this for prompts like "what did we decide", "did we discuss", "summarize the thread", "what were the open questions", or anything clearly anchored in prior Discord conversation.
+- If both AtoCore and Discord history are relevant, use both and synthesize.
+- If `discrawl` is stale or unavailable, say so briefly and continue with the best available context.
Skills provide your tools. When you need one, check its `SKILL.md`. Keep local notes (camera names, SSH details, voice preferences) in `TOOLS.md`.
diff --git a/skills/atocore-context/SKILL.md b/skills/atocore-context/SKILL.md
index e42a7b7..fa23207 100644
--- a/skills/atocore-context/SKILL.md
+++ b/skills/atocore-context/SKILL.md
@@ -1,12 +1,11 @@
---
name: atocore-context
-description: Use Dalidou-hosted AtoCore as a read-only external context service for project state, retrieval, and context-building without touching OpenClaw's own memory.
+description: Use Dalidou-hosted AtoCore as an additive external context service for project-state reads, retrieval, and context-building without replacing OpenClaw's own memory.
---
# AtoCore Context
-Use this skill when you need trusted project context, retrieval help, or AtoCore
-health/status from the canonical Dalidou instance.
+Use this skill when you need trusted project context, retrieval help, or AtoCore health and status from the canonical Dalidou instance.
## Purpose
@@ -14,7 +13,7 @@ AtoCore is an additive external context service.
- It does not replace OpenClaw's own memory.
- It should be used for contextual work, not trivial prompts.
-- It is read-only in this first integration batch.
+- The default posture is read-only and fail-open.
- If AtoCore is unavailable, continue normally.
## Canonical Endpoint
@@ -31,27 +30,22 @@ Override with:
ATOCORE_BASE_URL=http://host:port
```
-## Safe Usage
+## V1 scope
-Use AtoCore for:
-- project-state checks
+Use this skill in V1 for:
+
+- project-state reads
- automatic project detection for normal project questions
-- retrieval over ingested project/ecosystem docs
+- retrieval over ingested project and ecosystem docs
- context-building for complex project prompts
- verifying current AtoCore hosting and architecture state
-- listing registered projects and refreshing a known project source set
-- inspecting the project registration template before proposing a new project entry
-- generating a proposal preview for a new project registration without writing it
-- registering an approved project entry when explicitly requested
-- updating an existing registered project when aliases or description need refinement
+- inspecting project registrations and proposal previews when operator review is needed
-Do not use AtoCore for:
-- automatic memory write-back
-- replacing OpenClaw memory
-- silent ingestion of broad new corpora without approval
-- mutating the registry automatically without human approval
+Screenpipe is out of V1 scope. Do not treat it as an active input lane or dependency for this skill.
+
+## Read path commands
-## Commands
+These are the normal additive commands:
```bash
~/clawd/skills/atocore-context/scripts/atocore.sh health
@@ -62,15 +56,56 @@ Do not use AtoCore for:
~/clawd/skills/atocore-context/scripts/atocore.sh detect-project "what's the interferometer error budget?"
~/clawd/skills/atocore-context/scripts/atocore.sh auto-context "what's the interferometer error budget?" 3000
~/clawd/skills/atocore-context/scripts/atocore.sh debug-context
-~/clawd/skills/atocore-context/scripts/atocore.sh propose-project p07-example "p07,example-project" vault incoming/projects/p07-example "Example project" "Primary staged project docs"
-~/clawd/skills/atocore-context/scripts/atocore.sh register-project p07-example "p07,example-project" vault incoming/projects/p07-example "Example project" "Primary staged project docs"
-~/clawd/skills/atocore-context/scripts/atocore.sh update-project p05 "Curated staged docs for the P05 interferometer architecture, vendors, and error-budget project."
-~/clawd/skills/atocore-context/scripts/atocore.sh refresh-project p05
~/clawd/skills/atocore-context/scripts/atocore.sh project-state atocore
~/clawd/skills/atocore-context/scripts/atocore.sh query "What is AtoDrive?"
~/clawd/skills/atocore-context/scripts/atocore.sh context-build "Need current AtoCore architecture" atocore 3000
```
+## Approved operator actions only
+
+The helper currently exposes some mutating commands, but they are not normal background behavior.
+Treat them as approved operator actions only:
+
+```bash
+~/clawd/skills/atocore-context/scripts/atocore.sh propose-project ...
+~/clawd/skills/atocore-context/scripts/atocore.sh register-project ...
+~/clawd/skills/atocore-context/scripts/atocore.sh update-project ...
+~/clawd/skills/atocore-context/scripts/atocore.sh refresh-project ...
+~/clawd/skills/atocore-context/scripts/atocore.sh ingest-sources
+```
+
+Do not use these from a Discord-originated path unless the human explicitly approves the specific action in the current thread or session.
+
+## Explicit approval rule
+
+Explicit approval means all of the following:
+
+- the human directly instructs the specific mutating action
+- the instruction is in the current thread or current session
+- the approval is for that specific action
+- the approval is not inferred from Discord evidence, Discrawl recall, screener output, or vague intent
+
+Examples of explicit approval:
+
+- "refresh p05 now"
+- "register this project"
+- "update the aliases"
+
+Non-examples:
+
+- "we should probably refresh this"
+- archived discussion suggesting a refresh
+- a screener note recommending promotion or ingestion
+
+## Do not use AtoCore for
+
+- automatic memory write-back
+- replacing OpenClaw memory
+- silent ingestion of broad new corpora without approval
+- automatic registry mutation
+- direct Discord-originated mutation of trusted or operator state
+- direct Discord-originated promote or reject actions
+
## Contract
- prefer AtoCore only when additional context is genuinely useful
@@ -79,10 +114,6 @@ Do not use AtoCore for:
- cite when information came from AtoCore rather than local OpenClaw memory
- for normal project knowledge questions, prefer `auto-context "<prompt>" 3000` before answering
- use `detect-project "<prompt>"` when you want to inspect project inference explicitly
-- use `debug-context` right after `auto-context` or `context-build` when you want
- to inspect the exact last AtoCore context pack
-- prefer `projects` plus `refresh-project <id>` over long ad hoc ingest instructions when the project is already registered
-- use `project-template` when preparing a new project registration proposal
-- use `propose-project ...` to draft a normalized entry and review collisions first
-- use `register-project ...` only after the proposal has been reviewed and approved
-- use `update-project ...` when a registered project's description or aliases need refinement before refresh
+- use `debug-context` right after `auto-context` or `context-build` when you want to inspect the exact last AtoCore context pack
+- use `project-template` and `propose-project ...` when preparing a reviewed registration proposal
+- use `register-project ...`, `update-project ...`, `refresh-project ...`, and `ingest-sources` only after explicit approval

View File

@@ -0,0 +1,354 @@
# OpenClaw x AtoCore Nightly Screener Runbook
## Purpose
The nightly screener is the V1 bridge between broad evidence capture and narrow trusted state.
Its job is to:
- gather raw evidence from approved V1 sources
- reduce noise
- produce reviewable candidate material
- prepare operator review work
- never silently create trusted truth
## Scope
The nightly screener is a screening and preparation job.
It is not a trusted-state writer.
It is not a registry operator.
It is not a hidden reviewer.
V1 active inputs are:
- Discord and Discrawl evidence
- OpenClaw interaction evidence
- PKM, repos, and KB references
- read-only AtoCore context for comparison and deduplication
## Explicit approval rule
If the screener output points at a mutating operator action, that action still requires:
- direct human instruction
- in the current thread or current session
- for that specific action
- with no inference from evidence or screener output alone
The screener may recommend review. It may not manufacture approval.
## Inputs
The screener may consume the following inputs when available.
### 1. Discord and Discrawl evidence
Examples:
- recent archived Discord messages
- thread excerpts relevant to known projects
- conversation clusters around decisions, requirements, constraints, or repeated questions
### 2. OpenClaw interaction evidence
Examples:
- captured interactions
- recent operator conversations relevant to projects
- already-logged evidence bundles
### 3. Read-only AtoCore context inputs
Examples:
- project registry lookup for project matching
- project_state read for comparison only
- memory or entity lookups for deduplication only
These reads may help the screener rank or classify candidates, but they must not be used as a write side effect.
### 4. Optional canonical-source references
Examples:
- PKM notes
- repo docs
- KB-export summaries
These may be consulted to decide whether a signal appears to duplicate or contradict already-canonical truth.
## Outputs
The screener should produce output in four buckets.
### 1. Nightly screener report
A compact report describing:
- inputs seen
- items skipped
- candidate counts
- project match confidence distribution
- failures or unavailable sources
- items requiring human review
### 2. Evidence bundle or manifest
A structured bundle of the source snippets that justified each candidate or unresolved item.
This is the reviewer's provenance package.
### 3. Candidate manifests
Separate candidate manifests for:
- memory candidates
- entity candidates later
- unresolved "needs canonical-source update first" items
### 4. Operator action queue
A short list of items needing explicit human action, such as:
- review these candidates
- decide whether to refresh project X
- decide whether to curate project_state
- decide whether a Discord-originated claim should first be reflected in PKM, repo, or KB
## Required non-output
The screener must not directly produce any of the following:
- active memories without review
- active entities without review
- project_state writes
- registry mutation
- refresh operations
- ingestion operations
- promote or reject decisions
## Nightly procedure
### Step 1 - load last-run checkpoint
Read the last successful screener checkpoint so the run knows:
- what time range to inspect
- what evidence was already processed
- which items were already dropped or bundled
If no checkpoint exists, use a conservative bounded time window and mark the run as bootstrap mode.
### Step 2 - gather evidence
Collect available evidence from each configured source.
Per-source rule:
- source unavailable -> note it, continue
- source empty -> note it, continue
- source noisy -> keep raw capture bounded and deduplicated
### Step 3 - normalize and deduplicate
For each collected item:
- normalize timestamps, source ids, and project hints
- remove exact duplicates
- group repeated or near-identical evidence when practical
- keep provenance pointers intact
The goal is to avoid flooding review with repeated copies of the same conversation.
### Step 4 - attempt project association
For each evidence item, try to associate it with:
- a registered project id, or
- `unassigned` if confidence is low
Rules:
- high confidence match -> attach project id
- low confidence match -> mark as uncertain
- no good match -> leave unassigned
Do not force a project assignment just to make the output tidier.
### Step 5 - classify signal type
Classify each normalized item into one of these buckets:
- noise / ignore
- evidence only
- memory candidate
- entity candidate
- needs canonical-source update first
- needs explicit operator decision
If the classification is uncertain, choose the lower-trust bucket.
### Step 6 - compare against higher-trust layers
For non-noise items, compare against the current higher-trust landscape.
Check for:
- already-active equivalent memory
- already-active equivalent entity later
- existing project_state answer
- obvious duplication of canonical source truth
- obvious contradiction with canonical source truth
This comparison is read-only.
It is used only to rank and annotate output.
### Step 7 - build candidate bundles
For each candidate:
- include the candidate text or shape
- include provenance snippets
- include source type
- include project association confidence
- include reason for candidate classification
- include conflict or duplicate notes if found
### Step 8 - build unresolved operator queue
Some items should not become candidates yet.
Examples:
- "This looks like current truth but should first be updated in PKM, repo, or KB."
- "This Discord-originated request asks for refresh or ingest."
- "This might be a decision, but confidence is too low."
These belong in a small operator queue, not in trusted state.
### Step 9 - persist report artifacts only
Persist only:
- screener report
- evidence manifests
- candidate manifests
- checkpoint metadata
If candidate persistence into AtoCore is enabled later, it still remains a candidate-only path and must not skip review.
### Step 10 - exit fail-open
If the screener could not reach AtoCore or some source system:
- write the failure or skip into the report
- keep the checkpoint conservative
- do not fake success
- do not silently mutate anything elsewhere
## Failure modes
### Failure mode 1 - AtoCore unavailable
Behavior:
- continue in fail-open mode if possible
- write a report that the run was evidence-only or degraded
- do not attempt write-side recovery actions
### Failure mode 2 - Discrawl unavailable or stale
Behavior:
- note Discord archive input unavailable or stale
- continue with other sources
- do not invent Discord evidence summaries
### Failure mode 3 - candidate explosion
Behavior:
- rank candidates
- keep only a bounded top set for review
- put the remainder into a dropped or deferred manifest
- do not overwhelm the reviewer queue
### Failure mode 4 - low-confidence project mapping
Behavior:
- leave items unassigned or uncertain
- do not force them into a project-specific truth lane
### Failure mode 5 - contradiction with trusted truth
Behavior:
- flag the contradiction in the report
- keep the evidence or candidate for review if useful
- do not overwrite project_state
### Failure mode 6 - direct operator-action request found in evidence
Examples:
- "register this project"
- "refresh this source"
- "promote this memory"
Behavior:
- place the item into the operator action queue
- require explicit human approval
- do not perform the mutation as part of the screener
## Review handoff format
Each screener run should hand off a compact review package containing:
1. a run summary
2. candidate counts by type and project
3. top candidates with provenance
4. unresolved items needing explicit operator choice
5. unavailable-source notes
6. checkpoint status
The handoff should be short enough for a human to review without reading the entire raw archive.
## Safety rules
The screener must obey these rules every night.
1. No direct project_state writes.
2. No direct registry mutation.
3. No direct refresh or ingest.
4. No direct promote or reject.
5. No treating Discord or Discrawl as trusted truth.
6. No hiding source uncertainty.
7. No inventing missing integrations.
8. No bringing deferred sources into V1 through policy drift or hidden dependency.
## Minimum useful run
A useful screener run can still succeed even if it only does this:
- gathers available Discord and OpenClaw evidence
- filters obvious noise
- produces a small candidate manifest
- notes unavailable archive inputs if any
- leaves trusted state untouched
That is still a correct V1 run.
## Deferred from V1
Screenpipe is deferred from V1. It is not an active input, not a required dependency, and not part of the runtime behavior of this V1 screener.
## Bottom line
The nightly screener is not the brain of the system.
It is the filter.
Its purpose is to make human review easier while preserving the trust hierarchy:
- broad capture in
- narrow reviewed truth out
- no hidden mutations in the middle

View File

@@ -0,0 +1,360 @@
# OpenClaw x AtoCore V1 Promotion Pipeline
## Purpose
This document defines the V1 promotion pipeline for signals coming from Discord, Discrawl, OpenClaw, PKM, and repos.
The rule is simple:
- raw capture is evidence
- screening turns evidence into candidate material
- review promotes candidates into canonical homes
- trusted state is curated explicitly, not inferred automatically
## V1 scope
V1 active inputs are:
- Discord live conversation
- Discrawl archive retrieval
- OpenClaw interaction logs and evidence bundles
- PKM notes
- repos, KB exports, and repo docs
Read-only AtoCore context may be consulted for comparison and deduplication.
## Explicit approval rule
When this pipeline refers to approval or review for a mutating action, it means:
- the human directly instructs the specific action
- the instruction is in the current thread or current session
- the approval is for that specific action
- the approval is not inferred from evidence, archives, or screener output
## Pipeline summary
```text
raw capture
-> evidence bundle
-> nightly screening
-> candidate queue
-> human review
-> canonical home
-> optional trusted-state curation
```
## Stage 0 - raw capture
### Inputs
Raw capture may come from:
- Discord live conversation
- Discrawl archive retrieval
- OpenClaw interaction logs
- PKM notes
- repos / KB exports / repo docs
### Rule at this stage
Nothing captured here is trusted truth yet.
Everything is either:
- raw evidence, or
- a pointer to an already-canonical source
## Stage 1 - evidence bundle
The first durable V1 destination for raw signals is the evidence lane.
Examples of evidence bundle forms:
- AtoCore interaction records
- Discrawl retrieval result sets
- nightly screener input bundles
- local archived artifacts or manifests
- optional source snapshots used only for review preparation
### What evidence is for
Evidence exists so the operator can later answer:
- what did we actually see?
- where did this claim come from?
- what context supported the candidate?
- what should the reviewer inspect before promoting anything?
### What evidence is not for
Evidence is not:
- active memory
- active entity
- trusted project_state
- registry truth
## Stage 2 - screening
The nightly screener or an explicit review flow reads evidence and classifies it.
### Screening outputs
Each observed signal should be classified into one of these lanes:
1. Ignore / noise
- chatter
- duplicate archive material
- ambiguous fragments
- low-signal scraps
2. Keep as evidence only
- useful context, but too ambiguous or too raw to promote
3. Memory candidate
- stable enough to review as episodic, personal, or loose project signal
4. Entity candidate
- structured enough to review as a future decision, requirement, constraint, or validation fact
5. Needs canonical-source update first
- appears to assert current trusted truth but should first be reflected in the real canonical home, such as PKM, repo, or KB tool
### Key screening rule
If the screener cannot confidently tell whether a signal is:
- raw evidence,
- a loose durable memory,
- or a structured project truth,
then it must pick the lower-trust lane.
In V1, uncertainty resolves downward.
## Stage 3 - candidate queue
Only screened outputs may enter the candidate queue.
### Memory-candidate lane
Use this lane for reviewed-signal candidates such as:
- preferences
- episodic facts
- identity facts
- loose stable project signal that is useful to remember but not yet a formal structured entity
Examples:
- "Antoine prefers operator summaries without extra ceremony."
- "The team discussed moving OpenClaw toward a shared operator client."
- "Discord history is useful as evidence but not as direct truth."
### Entity-candidate lane
Use this lane for future structured facts such as:
- decisions
- requirements
- constraints
- validation claims
Examples:
- "Decision: use the shared operator client instead of duplicated frontend logic."
- "Constraint: Discord-originated paths must not directly mutate project_state."
### What cannot enter directly from raw capture
The following must not be created directly from raw Discord or Discrawl evidence without a screening step:
- active memories
- active entities
- project_state entries
- registry mutations
- promote or reject decisions
## Stage 4 - human review
This is the load-bearing stage.
A human reviewer, mediated by OpenClaw and eventually using the shared operator client, decides whether the candidate:
- should be promoted
- should be rejected
- should stay pending
- should first be rewritten into the actual canonical source
- should become project_state only after stronger curation
### Review questions
For every candidate, the reviewer should ask:
1. Is this actually stable enough to preserve?
2. Is this fact ambiguous, historical, or current?
3. What is the one canonical home for this fact type?
4. Is memory the right home, or should this be an entity later?
5. Is project_state justified, or is this still only evidence or candidate material?
6. Does the source prove current truth, or only past conversation?
## Stage 5 - canonical promotion
After review, the signal can move into exactly one canonical home.
## Promotion rules by fact shape
### A. Personal, episodic, or loose project signal
Promotion destination:
- AtoCore memory
Use when the fact is durable and useful, but not a formal structured engineering record.
### B. Structured engineering fact
Promotion destination:
- future AtoCore entity
Use when the fact is really a:
- decision
- requirement
- constraint
- validation claim
### C. Current trusted project answer
Promotion destination:
- AtoCore project_state
But only after explicit curation.
A candidate does not become project_state just because it looks important.
The reviewer must decide that it now represents the trusted current answer.
### D. Human or tool source truth
Promotion destination:
- PKM / repo / KB tool of origin
If a Discord-originated signal claims current truth but the canonical home is not AtoCore memory or entity, the right move may be:
1. update the canonical source first
2. then optionally refresh or ingest, with explicit approval if the action is mutating
3. then optionally curate a project_state answer
This prevents Discord from becoming the hidden source of truth.
## Stage 6 - optional trusted-state curation
`project_state` is not the general destination for important facts.
It is the curated destination for current trusted project answers.
Examples that may justify explicit project_state curation:
- current selected architecture
- current next milestone
- current status summary
- current trusted decision outcome
Examples that usually do not justify immediate project_state curation:
- a raw Discord debate
- a speculative suggestion
- a historical conversation retrieved through Discrawl
## Discord-originated pipeline examples
### Example 1 - raw discussion about operator-client refactor
1. Discord message enters the evidence lane.
2. Nightly screener marks it as either evidence-only or decision candidate.
3. Human review checks whether it is an actual decision or just discussion.
4. If stable and approved, it becomes a memory or future entity.
5. It reaches project_state only if explicitly curated as the trusted current answer.
### Example 2 - Discord thread says "refresh this project now"
1. Discord message is evidence of operator intent.
2. It does not auto-trigger refresh.
3. OpenClaw asks for or recognizes explicit human approval.
4. Approved operator action invokes the shared operator client.
5. Refresh result may later influence candidates or trusted state, but the raw Discord message never performed the mutation by itself.
### Example 3 - archived thread says a requirement might be current
1. Discrawl retrieval enters the evidence lane.
2. Screener marks it as evidence-only or a requirement candidate.
3. Human review checks the canonical source alignment.
4. If accepted later, it becomes an entity candidate or active entity.
5. project_state remains a separate explicit curation step.
## Promotion invariants
The pipeline must preserve these invariants.
### Invariant 1 - raw evidence is not trusted truth
No raw Discord or Discrawl signal can directly become trusted project_state.
### Invariant 2 - unreviewed signals can at most become candidates
Automatic processing stops at evidence or candidate creation.
### Invariant 3 - each fact has one canonical home
A fact may be supported by many evidence items, but after review it belongs in one canonical place.
### Invariant 4 - operator mutations require explicit approval
Registry mutation, refresh, ingest, promote, reject, and project_state writes are operator actions.
### Invariant 5 - OpenClaw orchestrates; it does not become storage
OpenClaw should coordinate the pipeline, not silently become the canonical data layer.
## Decision table
| Observed signal type | Default pipeline outcome | Canonical destination if accepted |
|---|---|---|
| Ambiguous or raw conversation | Evidence only | none |
| Historical archive context | Evidence only or candidate | memory or entity only after review |
| Personal preference | Memory candidate | AtoCore memory |
| Episodic fact | Memory candidate | AtoCore memory |
| Loose stable project signal | Memory candidate | AtoCore memory |
| Structured decision / requirement / constraint | Entity candidate | future AtoCore entity |
| Claimed current trusted answer | Needs explicit curation | project_state, but only after review |
| Tool-origin engineering fact | Canonical source update first | repo / KB / PKM tool of origin |
## What the pipeline deliberately prevents
This V1 pipeline deliberately prevents these bad paths:
- Discord -> project_state directly
- Discrawl archive -> project_state directly
- Discord -> registry mutation directly
- Discord -> refresh or ingest directly without explicit approval
- raw chat -> promote or reject directly
- OpenClaw turning evidence into truth without a review gate
## Deferred from V1
Screenpipe is deferred from V1. It is not an active input lane in this pipeline and it is not a runtime dependency of this pipeline. If it is revisited later, it should be handled in a separate future design and not treated as an implicit part of this pipeline.
## Bottom line
The promotion pipeline is intentionally conservative.
Its job is not to maximize writes.
Its job is to preserve trust while still letting Discord, Discrawl, OpenClaw, PKM, and repos contribute useful signal.
That means the safe default path is:
- capture broadly
- trust narrowly
- promote deliberately

View File

@@ -0,0 +1,96 @@
# OpenClaw x AtoCore Shared-Client Consolidation Preview
## Status
Proposal only. Not applied.
## Why this exists
The current OpenClaw helper script duplicates AtoCore-calling logic that already exists in the shared operator client:
- request handling
- fail-open behavior
- project detection
- project lifecycle command surface
The preferred direction is to consolidate OpenClaw toward the shared operator client pattern documented in `docs/architecture/llm-client-integration.md`.
## Goal
Keep the OpenClaw skill and operator policy in OpenClaw, but stop maintaining a separate Bash implementation of the AtoCore client surface when the shared client already exists in `/home/papa/ATOCore/scripts/atocore_client.py`.
## Non-goals for this preview
- no implementation in this phase
- no runtime change in this phase
- no new helper command in this phase
- no change to approval policy in this preview
## Preview diff
This is a conceptual diff preview only.
It is not applied.
```diff
--- a/skills/atocore-context/scripts/atocore.sh
+++ b/skills/atocore-context/scripts/atocore.sh
@@
-#!/usr/bin/env bash
-set -euo pipefail
-
-BASE_URL="${ATOCORE_BASE_URL:-http://dalidou:8100}"
-TIMEOUT="${ATOCORE_TIMEOUT_SECONDS:-30}"
-REFRESH_TIMEOUT="${ATOCORE_REFRESH_TIMEOUT_SECONDS:-1800}"
-FAIL_OPEN="${ATOCORE_FAIL_OPEN:-true}"
-
-request() {
- # local curl-based request logic
-}
-
-detect_project() {
- # local project detection logic
-}
-
-case "$cmd" in
- health) request GET /health ;;
- projects) request GET /projects ;;
- auto-context) ... ;;
- register-project) ... ;;
- refresh-project) ... ;;
- ingest-sources) ... ;;
-esac
+#!/usr/bin/env bash
+set -euo pipefail
+
+CLIENT="${ATOCORE_SHARED_CLIENT:-/home/papa/ATOCore/scripts/atocore_client.py}"
+
+if [[ ! -f "$CLIENT" ]]; then
+ echo "Shared AtoCore client not found: $CLIENT" >&2
+ exit 1
+fi
+
+exec python3 "$CLIENT" "$@"
```
## Recommended implementation shape later
If and when this is implemented, the safer shape is:
1. keep policy and approval guidance in OpenClaw instructions and skill text
2. delegate actual AtoCore client behavior to the shared operator client
3. avoid adding any new helper command unless explicitly approved
4. keep read-path and approved-operator-path distinctions in the OpenClaw guidance layer
## Risk notes
Potential follow-up concerns to handle before applying:
- path dependency on `/home/papa/ATOCore/scripts/atocore_client.py`
- what should happen if the AtoCore repo is unavailable from the OpenClaw machine
- whether a thin compatibility wrapper is needed for help text or argument normalization
- ensuring OpenClaw policy still blocks unapproved Discord-originated mutations even if the shared client exposes them
## Bottom line
The duplication is real and consolidation is still the right direction.
But in this phase it remains a proposal only.

View File

@@ -0,0 +1,362 @@
# OpenClaw x AtoCore V1 Architecture
## Purpose
This document defines the safe V1 operating model for how Discord, Discrawl, OpenClaw, PKM, repos, and AtoCore work together.
The goal is to let these systems contribute useful signal into AtoCore without turning AtoCore into a raw dump and without blurring trust boundaries.
## V1 scope
V1 active inputs are:
- Discord and Discrawl evidence
- OpenClaw interaction evidence
- PKM, repos, and KB sources
- read-only AtoCore context for comparison and deduplication
## Core stance
The V1 stance is simple:
- Discord and Discrawl are evidence streams.
- OpenClaw is the operator and orchestrator.
- PKM, repos, and KB tools remain the canonical human and tool truth.
- AtoCore memories hold reviewed episodic, personal, and loose project signal.
- AtoCore project_state holds the current trusted project answer, manually or tightly gated only.
- Future AtoCore entities hold reviewed structured decisions, requirements, constraints, and related facts.
## Architectural principles
1. AtoCore remains additive and fail-open from the OpenClaw side.
2. Every fact type has exactly one canonical home.
3. Raw evidence is not trusted truth.
4. Unreviewed signals become evidence or candidates, not active truth.
5. Discord-originated paths never directly mutate project_state, registry state, refresh state, ingestion state, or review decisions without explicit human approval.
6. OpenClaw is not canonical storage. It retrieves, compares, summarizes, requests approval, and performs approved operator actions.
7. The shared operator client is the canonical mutating operator surface. Frontends should reuse it instead of reimplementing AtoCore-calling logic.
## Explicit approval rule
In this V1 policy, explicit approval means all of the following:
- the human directly instructs the specific mutating action
- the instruction appears in the current thread or current session
- the approval is for that specific action, not vague intent
- the approval is not inferred from Discord evidence, Discrawl recall, screener output, or general discussion
Examples of explicit approval:
- "refresh p05 now"
- "register this project"
- "promote that candidate"
- "write this to project_state"
Examples that are not explicit approval:
- "we should probably refresh this sometime"
- "I think this is the current answer"
- archived discussion saying a mutation might be useful
- a screener report recommending a mutation
## System roles
### Discord
Discord is a live conversational source.
It contains fresh context, discussion, uncertainty, and project language grounded in real work.
It is not authoritative by itself.
Discord-originated material should be treated as:
- raw evidence
- candidate material after screening
- possible justification for a later human-reviewed promotion into a canonical home
Discord should never be treated as direct trusted project truth just because someone said it in chat.
### Discrawl
Discrawl is a retrieval and archive layer over Discord history.
It turns prior conversation into searchable evidence.
That is useful for recall, context building, and finding prior decisions or open questions.
Discrawl is still evidence, not authority.
A retrieved Discord thread may show what people thought or said. It does not by itself become trusted project_state.
### OpenClaw
OpenClaw is the orchestrator and operator.
It is where the human interacts, where approvals happen, and where cross-source reasoning happens.
OpenClaw's job is to:
- retrieve
- compare
- summarize
- ask for approval when mutation is requested
- call the shared operator client for approved writes
- fail open when AtoCore is unavailable
OpenClaw is not the canonical place where project facts live long-term.
### PKM
PKM is a canonical human-authored prose source.
It is where notes, thinking, and ongoing project writing live.
PKM is the canonical home for:
- project prose notes
- working notes
- long-form summaries
- journal-style project history
PKM is not the place where OpenClaw should be taught how to operate AtoCore. Operator instructions belong in repo docs and OpenClaw instructions and skills.
### Repos and KB tools
Repos and KB tools are canonical human and tool truth for code and structured engineering artifacts.
They are the canonical home for:
- source code
- repo design docs
- structured tool outputs
- KB-CAD and KB-FEM facts where those systems are the tool of origin
### AtoCore memories
AtoCore memories are for reviewed, durable machine-usable signal that is still loose enough to belong in memory rather than in a stricter structured layer.
Examples:
- episodic facts
- preferences
- identity facts
- reviewed loose project facts
AtoCore memories are not a place to dump raw Discord capture.
### AtoCore project_state
AtoCore project_state is the trusted current answer layer.
It is the place for questions like:
- what is the current selected architecture?
- what is the current next focus?
- what is the trusted status answer right now?
Because this layer answers current-truth questions, it must remain manually curated or tightly gated.
### Future AtoCore entities
Future entities are the canonical home for structured engineering facts that deserve stronger representation than freeform memory.
Examples:
- decisions
- requirements
- constraints
- validation claims
- structured relationships later
These should be promoted from evidence or candidates only after review.
## Logical flow
```text
Discord live chat --.
Discrawl archive ----+--> evidence bundle / interactions / screener input
OpenClaw evidence ---'
|
v
nightly screener
|
.--------+--------.
v v
memory candidates entity candidates (later)
| |
'--------+--------'
v
human review in OpenClaw
|
.-----------------+-----------------.
v v v
active memory active entity explicit curation
|
v
project_state
```
The load-bearing rule is that review happens before trust.
## Canonical-home table
Every named fact type below has exactly one canonical home.
| Fact type | Canonical home | Why |
|---|---|---|
| Raw Discord message | Discord / Discrawl archive | It is conversational evidence, not normalized truth |
| Archived Discord thread history | Discrawl archive | It is the retrieval form of Discord evidence |
| OpenClaw operator instructions | OpenClaw repo docs / skills / instructions | Operating behavior should live in code-adjacent instructions, not PKM |
| Project prose notes | PKM | Human-authored project prose belongs in PKM |
| Source code | Repo | Code truth lives in version control |
| Repo design or architecture doc | Repo | The documentation belongs with the code or system it describes |
| Structured KB-CAD / KB-FEM fact | KB tool of origin | Tool-managed structured engineering facts belong in their tool of origin |
| Personal identity fact | AtoCore memory (`identity`) | AtoCore memory is the durable machine-usable home |
| Preference fact | AtoCore memory (`preference`) | Same reason |
| Episodic fact | AtoCore memory (`episodic`) | It is durable recall, not project_state |
| Loose reviewed project signal | AtoCore memory (`project`) | Good fit for reviewed but not fully structured project signal |
| Engineering decision | Future AtoCore entity (`Decision`) | Decisions need structured lifecycle and supersession |
| Requirement | Future AtoCore entity (`Requirement`) | Requirements need structured management |
| Constraint | Future AtoCore entity (`Constraint`) | Constraints need structured management |
| Current trusted project answer | AtoCore `project_state` | This layer is explicitly for current trusted truth |
| Project registration metadata | AtoCore project registry | Registry state is its own canonical operator layer |
| Review action (promote / reject / invalidate) | AtoCore audit trail / operator action log | Review decisions are operator events, not source facts |
## What this means for Discord-originated facts
A Discord-originated signal can end in more than one place, but not directly.
### If the signal is conversational, ambiguous, or historical
It stays in the evidence lane:
- Discord
- Discrawl archive
- optional screener artifact
- optional candidate queue
It does not become trusted project_state.
### If the signal is a stable personal or episodic fact
It may be promoted to AtoCore memory after review.
Examples:
- "Antoine prefers concise operator summaries."
- "We decided in discussion to keep AtoCore additive."
These belong in reviewed memory, not in project_state.
### If the signal expresses a structured engineering fact
It may become an entity candidate and later an active entity.
Examples:
- a requirement
- a decision
- a constraint
Again, not directly from raw chat. The chat is evidence for the candidate.
### If the signal is the current trusted answer
It still should not jump directly from Discord into project_state.
Instead, a human should explicitly curate it into project_state after checking it against the right canonical home.
That canonical home may be:
- PKM for prose and project notes
- repo for code and design docs
- KB tools for structured engineering facts
- active entity if the engineering layer is the canonical home
## Approval boundaries
### Reads
The following may be invoked automatically when useful:
- `health`
- `projects`
- `detect-project`
- `auto-context`
- `query`
- `project-state` read
- Discrawl retrieval
These are additive and fail-open.
### Mutations requiring explicit human approval
The following are operator actions, not conversational automation:
- `register-project`
- `update-project`
- `refresh-project`
- `ingest-sources`
- `project-state-set`
- `project-state-invalidate`
- `capture` when used as a durable write outside conservative logging policy
- `extract` with persistence
- `promote`
- `reject`
- future entity promotion or rejection
For Discord-originated paths, approval must satisfy the explicit approval rule above.
## Shared operator client rule
The preferred V1 architecture is:
- AtoCore HTTP API as system interface
- shared operator client as reusable mutating surface
- OpenClaw as a thin frontend and operator around that client
That avoids duplicating:
- project detection logic
- request logic
- failure handling
- mutation surface behavior
- approval wrappers
OpenClaw should keep its own high-level operating instructions, but it should not keep growing a parallel AtoCore mutation implementation.
## V1 boundary summary
### Allowed automatic behavior
- read-only retrieval
- context build
- Discrawl recall
- evidence collection
- nightly screening into reviewable output
- fail-open fallback when AtoCore is unavailable
### Allowed only after explicit human review or approval
- candidate persistence from evidence
- candidate promotion or rejection
- project refresh or ingestion
- registry mutation
- trusted project_state writes
### Not allowed as automatic behavior
- direct Discord -> project_state writes
- direct Discord -> register / update / refresh / ingest / promote / reject
- hidden mutation inside the screener
- treating PKM as the main operator-instruction layer for AtoCore behavior
## Deferred from V1
Screenpipe is deferred.
It is not an active input lane in V1 and it must not become a runtime, skill, or policy dependency in V1.
If it is revisited later, it must be treated as a separate future design decision, not as an implicit V1 extension.
## Bottom line
The safe V1 architecture is not "everything can write into AtoCore."
It is a layered system where:
- evidence comes in broadly
- trust rises slowly
- canonical homes stay singular
- OpenClaw remains the operator
- AtoCore remains the additive machine-memory and trusted-state layer
- the shared operator client becomes the one reusable write-capable surface

View File

@@ -0,0 +1,207 @@
# OpenClaw x AtoCore V1 Proof Runbook
## Purpose
This is the concise proof and operator runbook for the final V1 policy.
It shows, in concrete paths, that:
- a Discord-originated signal cannot reach `project_state` without candidate or review gating
- Discord cannot directly execute `register-project`, `update-project`, `refresh-project`, `ingest-sources`, `promote`, or `reject` without explicit approval
## Explicit approval definition
For V1, explicit approval means:
- the human directly instructs the specific mutating action
- the instruction is in the current thread or current session
- the approval is for that exact action
- the approval is not inferred from evidence, archives, or screener output
Examples:
- "refresh p05 now"
- "register this project"
- "promote that candidate"
- "write this to project_state"
Non-examples:
- "this looks like the current answer"
- "we should probably refresh this"
- an old Discord thread saying a refresh might help
- a screener report recommending a mutation
## Proof 1 - Discord cannot directly reach project_state
Blocked path:
```text
Discord message
-> evidence
-> optional candidate
-> review
-> optional explicit curation
-> project_state
```
What is blocked:
- Discord -> project_state directly
- Discrawl archive -> project_state directly
- screener output -> project_state directly
What is allowed:
1. Discord message enters the evidence lane.
2. It may become a memory or entity candidate after screening.
3. A human reviews the candidate.
4. If the fact is truly the current trusted answer, the human may explicitly curate it into `project_state`.
Conclusion:
`project_state` is reachable only after review and explicit curation. There is no direct Discord-originated write path.
## Proof 2 - Discord cannot directly execute mutating operator actions
Blocked direct actions:
- `register-project`
- `update-project`
- `refresh-project`
- `ingest-sources`
- `promote`
- `reject`
- `project-state-set`
- `project-state-invalidate`
Blocked path:
```text
Discord message
-> evidence or operator request context
-X-> direct mutation
```
Allowed path:
```text
Discord message
-> OpenClaw recognizes requested operator action
-> explicit approval check
-> approved operator action
-> shared operator client or helper call
```
Conclusion:
Discord can request or justify a mutation, but it cannot perform it on its own.
## Proof 3 - Discrawl does not create approval
Discrawl is evidence retrieval.
It may surface:
- prior discussions
- earlier decisions
- unresolved questions
- prior suggestions to mutate state
It does not create approval for mutation.
Blocked path:
```text
Discrawl recall
-X-> refresh-project
-X-> promote
-X-> project_state write
```
Allowed path:
```text
Discrawl recall
-> evidence for human review
-> explicit approval in current thread/session if mutation is desired
-> approved operator action
```
Conclusion:
Archive recall informs review. It does not authorize writes.
## Proof 4 - Screener has no hidden mutation lane
The screener may:
- gather evidence
- classify evidence
- prepare candidates
- prepare operator queues
- report contradictions or missing context
The screener may not:
- write `project_state`
- mutate registry state
- refresh or ingest directly
- promote or reject directly
Blocked path:
```text
screener output
-X-> hidden mutation
```
Allowed path:
```text
screener output
-> review queue or operator queue
-> explicit approval if mutation is wanted
-> approved operator action
```
Conclusion:
The screener is a filter, not a hidden writer.
## Minimal operator decision table
| Situation | Allowed next step | Blocked next step |
|---|---|---|
| Discord says "this is the current answer" | evidence, then review, then possible explicit curation | direct `project_state` write |
| Discord says "refresh p05" without direct instruction | ask for explicit approval | direct `refresh-project` |
| Discord says "refresh p05 now" | approved operator action may run | none, if approval is explicit |
| Discrawl finds an old thread asking for registration | use as review context only | direct `register-project` |
| Screener recommends promotion | ask for explicit review decision | direct `promote` |
## Practical runbook
### Case A - current-truth claim from Discord
1. Treat the message as evidence.
2. Check the canonical home.
3. If needed, prepare a candidate or review note.
4. Do not write `project_state` unless the human explicitly approves that curation step.
### Case B - requested refresh from Discord
1. Determine whether the message is a direct instruction or only discussion.
2. If not explicit, ask for approval.
3. Only perform `refresh-project` after explicit approval in the current thread or session.
### Case C - candidate promotion request
1. Candidate exists or is proposed.
2. Review the evidence and the candidate text.
3. Only perform `promote` or `reject` after explicit review decision.
## Bottom line
The V1 rule is easy to test:
If the path starts from Discord or Discrawl and ends in trusted or operator state, there must be a visible approval or review step in the middle.
If that visible step is missing, the action is not allowed.

View File

@@ -0,0 +1,184 @@
# OpenClaw x AtoCore V1 Write-Policy Matrix
## Purpose
This matrix defines what each source is allowed to write to each target in V1.
Policy meanings:
- `auto-write` = allowed automatically without a human approval gate
- `candidate-only` = may create reviewable candidate material, but not active truth
- `human-review` = allowed only after explicit human review or explicit human approval
- `never-auto-write` = never allowed as an automatic write path
## Explicit approval rule
In this matrix, `human-review` is concrete, not vague.
For Discord-originated or Discrawl-originated paths it means:
- the human directly instructs the specific mutating action
- the instruction is in the current thread or current session
- the approval is for that specific action
- the approval is not inferred from evidence, archives, screener output, or general discussion
Examples of explicit approval:
- "refresh p05 now"
- "register this project"
- "promote this candidate"
- "write this to project_state"
Non-examples:
- "this looks important"
- "we should probably refresh this"
- archived discussion that once mentioned a similar mutation
- a screener note recommending promotion
## V1 scope note
V1 active inputs are:
- Discord and Discrawl
- OpenClaw interaction evidence
- PKM, repos, and KB sources
- read-only AtoCore context for comparison and deduplication
## Targets
The targets below are the only ones that matter for this policy.
- Evidence artifacts
- Memory candidates
- Active memories
- Entity candidates
- Active entities
- Trusted project_state
- Registry / refresh / ingest mutations
- Review actions
## Matrix
| Source | Target | Policy | Notes / gate |
|---|---|---|---|
| Discord live message | Evidence artifacts | auto-write | Safe evidence capture or archive only |
| Discord live message | Memory candidates | candidate-only | Only after screening or extraction; never direct active write |
| Discord live message | Active memories | human-review | Promote only after review of the candidate and evidence |
| Discord live message | Entity candidates | candidate-only | Only when structured signal is extracted from evidence |
| Discord live message | Active entities | human-review | Review required before promotion |
| Discord live message | Trusted project_state | human-review | Only via explicit curation; never directly from raw chat |
| Discord live message | Registry / refresh / ingest mutations | human-review | Requires explicit approval in the current thread or session |
| Discord live message | Review actions | human-review | Discord cannot silently promote or reject on its own |
| Discrawl archive result | Evidence artifacts | auto-write | Archive or search result is evidence by design |
| Discrawl archive result | Memory candidates | candidate-only | Extract reviewed signal from archived conversation |
| Discrawl archive result | Active memories | human-review | Promotion required |
| Discrawl archive result | Entity candidates | candidate-only | Archived discussion may justify candidate creation |
| Discrawl archive result | Active entities | human-review | Promotion required |
| Discrawl archive result | Trusted project_state | human-review | Must be explicitly curated; never inferred directly from archive |
| Discrawl archive result | Registry / refresh / ingest mutations | human-review | Archive recall cannot directly mutate operator state |
| Discrawl archive result | Review actions | human-review | Archive evidence informs review; it does not perform review |
| OpenClaw read/query flow | Evidence artifacts | auto-write | Conservative interaction or evidence logging is acceptable |
| OpenClaw read/query flow | Memory candidates | candidate-only | Only through explicit extraction path |
| OpenClaw read/query flow | Active memories | human-review | Requires operator review |
| OpenClaw read/query flow | Entity candidates | candidate-only | Future extraction path |
| OpenClaw read/query flow | Active entities | human-review | Requires operator review |
| OpenClaw read/query flow | Trusted project_state | never-auto-write | Read/query flow must stay additive |
| OpenClaw read/query flow | Registry / refresh / ingest mutations | never-auto-write | Read/query automation must not mutate operator state |
| OpenClaw read/query flow | Review actions | never-auto-write | Read automation cannot silently promote or reject |
| OpenClaw approved operator action | Evidence artifacts | auto-write | May create operator or audit artifacts |
| OpenClaw approved operator action | Memory candidates | human-review | Candidate persistence is itself an approved operator action |
| OpenClaw approved operator action | Active memories | human-review | Promotion allowed only through reviewed operator action |
| OpenClaw approved operator action | Entity candidates | human-review | Same rule for future entities |
| OpenClaw approved operator action | Active entities | human-review | Promotion allowed only through reviewed operator action |
| OpenClaw approved operator action | Trusted project_state | human-review | Allowed only as explicit curation |
| OpenClaw approved operator action | Registry / refresh / ingest mutations | human-review | Explicit approval required |
| OpenClaw approved operator action | Review actions | human-review | Explicit review required |
| PKM note | Evidence artifacts | human-review | Snapshotting into evidence is optional, not the primary path |
| PKM note | Memory candidates | candidate-only | Extraction from PKM is allowed into the candidate lane |
| PKM note | Active memories | human-review | Promotion required |
| PKM note | Entity candidates | candidate-only | Extract structured signal into the candidate lane |
| PKM note | Active entities | human-review | Promotion required |
| PKM note | Trusted project_state | human-review | Only via explicit curation of current truth |
| PKM note | Registry / refresh / ingest mutations | human-review | A human may choose to refresh based on PKM changes |
| PKM note | Review actions | human-review | PKM may support the decision, but not execute it automatically |
| Repo / KB source | Evidence artifacts | human-review | Optional audit or screener snapshot only |
| Repo / KB source | Memory candidates | candidate-only | Extract loose durable signal if useful |
| Repo / KB source | Active memories | human-review | Promotion required |
| Repo / KB source | Entity candidates | candidate-only | Strong future path for structured facts |
| Repo / KB source | Active entities | human-review | Promotion required |
| Repo / KB source | Trusted project_state | human-review | Explicit curation only |
| Repo / KB source | Registry / refresh / ingest mutations | human-review | A human may refresh or ingest based on source changes |
| Repo / KB source | Review actions | human-review | Source can justify review; it does not perform review |
| AtoCore active memory | Evidence artifacts | never-auto-write | Active memory is already above the evidence layer |
| AtoCore active memory | Memory candidates | never-auto-write | Do not recursively re-candidate active memory |
| AtoCore active memory | Active memories | never-auto-write | Already active |
| AtoCore active memory | Entity candidates | human-review | Graduation proposal only with review |
| AtoCore active memory | Active entities | human-review | Requires graduation plus promotion |
| AtoCore active memory | Trusted project_state | human-review | A human may explicitly curate current truth from memory |
| AtoCore active memory | Registry / refresh / ingest mutations | never-auto-write | Memory must not mutate registry or ingestion state |
| AtoCore active memory | Review actions | human-review | Human reviewer decides |
| AtoCore active entity | Evidence artifacts | never-auto-write | Already above the evidence layer |
| AtoCore active entity | Memory candidates | never-auto-write | Do not backflow structured truth into memory candidates automatically |
| AtoCore active entity | Active memories | never-auto-write | Canonical home is the entity, not a new memory |
| AtoCore active entity | Entity candidates | never-auto-write | Already active |
| AtoCore active entity | Active entities | never-auto-write | Already active |
| AtoCore active entity | Trusted project_state | human-review | Explicit curation may publish the current trusted answer |
| AtoCore active entity | Registry / refresh / ingest mutations | never-auto-write | Entities do not operate the registry |
| AtoCore active entity | Review actions | human-review | Human reviewer decides |
## Discord-originated trace examples
### Example 1 - conversational decision in Discord
Allowed path:
1. Discord live message -> Evidence artifacts (`auto-write`)
2. Evidence artifacts -> Memory candidates or Entity candidates (`candidate-only`)
3. Candidate -> Active memory or Active entity (`human-review`)
4. If it becomes the current trusted answer, a human may explicitly curate it into Trusted project_state (`human-review`)
There is no direct Discord -> project_state automatic path.
### Example 2 - archived Discord thread via Discrawl
Allowed path:
1. Discrawl result -> Evidence artifacts (`auto-write`)
2. Discrawl result -> Memory candidates or Entity candidates (`candidate-only`)
3. Human review decides promotion
4. Optional explicit curation into project_state later
Again, there is no direct archive -> trusted truth path.
### Example 3 - Discord request to refresh a project
Allowed path:
1. Discord message is evidence of requested operator intent
2. No mutation happens automatically
3. OpenClaw requires explicit approval in the current thread or session for `refresh-project`
4. Only then may OpenClaw perform the approved operator action
There is no direct Discord -> refresh path without explicit approval.
## V1 interpretation rules
1. Evidence can flow in broadly.
2. Truth can only rise through review.
3. project_state is the narrowest lane.
4. Registry and ingestion operations are operator actions, not evidence effects.
5. Discord-originated paths can inform operator actions, but they cannot silently execute them.
6. Deferred sources that are out of V1 scope have no automatic or manual role in this V1 matrix.
## Deferred from V1
Screenpipe is deferred and intentionally omitted from this V1 matrix.
## Bottom line
If a source is noisy, conversational, or archived, its maximum automatic privilege in V1 is:
- evidence capture, or
- candidate creation
Everything above that requires explicit human review or explicit human approval.

View File

@@ -0,0 +1,617 @@
# Engineering V1 Completion Plan
**Date:** 2026-04-22
**Author:** Claude (after reading the four V1 architecture docs + promotion-rules,
conflict-model, human-mirror-rules, tool-handoff-boundaries end-to-end)
**Status:** Draft, pending Codex review
**Replaces:** the rejected "Phase 8 Minions + typed edges" plan (see
`docs/decisions/2026-04-22-gbrain-plan-rejection.md`)
## Position
This is **not** a plan to start Engineering V1. It is a plan to **finish** V1.
**Against what criterion?** Each F/Q/O/D item in `engineering-v1-acceptance.md`
gets scored individually in the Gap audit table below with exact code/test/doc
references. No global percentage. The headline framing from the first draft
("5070% built") is withdrawn — it's either done per-criterion or it isn't.
The relevant observation is narrower: the entity schema, the full
relationship type set, the 4-state lifecycle, basic CRUD and most of the
killer-correctness query functions are already implemented in
`src/atocore/engineering/*.py` in the Windows working tree at
`C:\Users\antoi\ATOCore` (the canonical dev workspace, per
`CLAUDE.md`). The recent commits e147ab2, b94f9df, 081c058, 069d155, b1a3dd0
are V1 entity-layer work. **Codex auditors working in a different
workspace / branch should sync from the canonical dev tree before
per-file review** — see the "Workspace note" at the end of this doc.
The question this plan answers: given the current code state, in what
order should the remaining V1 acceptance criteria be closed so that
every phase builds on invariants the earlier phases already enforced?
## Corrected sequencing principle (post-Codex review 2026-04-22)
The first draft ordered phases F-1 → F-2 → F-3 → F-4 → F-5 → F-6 → F-7 → F-8
following the acceptance doc's suggested reading order. Codex rejected
that ordering. The correct dependency order, which this revision adopts, is:
1. **Write-time invariants come first.** Every later phase creates active
entities. Provenance-at-write (F-8) and synchronous conflict-detection
hooks (F-5 minimal) must be enforced **before** any phase that writes
entities at scale (ingest, graduation, or broad query coverage that
depends on the model being populated).
2. **Query closure sits on top of the schema + invariants**, not ahead of
them. A minimum query slice that proves the model is fine early. The
full catalog closure waits until after the write paths are invariant-safe.
3. **Mirror is a derived consumer** of the entity layer, not a midstream
milestone. It comes after the entity layer produces enforced, correct data.
4. **Graduation and full conflict-spec compliance** are finishing work that
depend on everything above being stable.
The acceptance criteria are unchanged. Only the order of closing them changes.
## How this plan respects the rejected-plan lessons
- **No new predicates.** The V1 ontology in `engineering-ontology-v1.md:112-137`
already defines 18 relationship types; `service.py:38-62` already implements
them. Nothing added, nothing reshaped.
- **No new canonical boundary.** Typed entities + typed relationships with
promotion-based candidate flow per `memory-vs-entities.md`. Not
edges-over-wikilinks.
- **No leapfrog of `master-plan-status.md` Now list.** This plan is **in
parallel** with (not ahead of) the Now items because V1 entity work is
already happening alongside them. The sequencing section below is explicit.
- **Queue/worker infrastructure is explicitly out of scope.** The "flag it for
later" note at the end of this doc is the only mention, per
`engineering-v1-acceptance.md:378` negative list.
---
## Gap audit against `engineering-v1-acceptance.md`
Each criterion marked: ✅ done / 🟡 partial / ❌ missing. "Partial" means the
capability exists but does not yet match spec shape or coverage.
### Functional (F-1 through F-8)
| ID | Criterion | Status | Evidence |
|----|-----------|--------|----------|
| F-1 | 12 V1 entity types, 4 relationship families, shared header fields, 4-state lifecycle | 🟡 partial (per Codex 2026-04-22 audit) | `service.py:16-36` has 16 types (superset of V1's 12), `service.py:38-62` has 18 relationship types, `service.py:64` statuses, `Entity` dataclass at line 67. **Gaps vs `engineering-v1-acceptance.md:45`**: `extractor_version` missing from dataclass and `entities` table; `canonical_home` missing from dataclass and table; `project` field is the project identifier but not named `project_id` as spec uses — spec says "fields equivalent to" so naming flexibility is allowed but needs an explicit doc note. Remediation lands in V1-0 |
| F-2 | All v1-required Q-001 through Q-020 implemented, with provenance where required | 🟡 partial (per Codex 2026-04-22 per-function audit) | **Ground truth from per-function read of `queries.py` + `routes.py:2092+`:** Q-001 partial (`system_map()` returns project-wide tree, not the catalog's subsystem-scoped `GET /entities/Subsystem/<id>?expand=contains` shape per `engineering-query-catalog.md:71`); Q-002 missing; Q-003 missing; Q-004 done (covered by `system_map()`); Q-005 done (`requirements_for()`); Q-006 done (`orphan_requirements()`); Q-007 missing; Q-008 done (`decisions_affecting()`); Q-009 done (`risky_decisions()`); Q-010 missing; Q-011 done (`unsupported_claims()`); Q-012 missing; Q-013 done (`recent_changes()`); Q-014 missing; Q-016 done (`impact_analysis()`); Q-017 done (`evidence_chain()`); Q-018 missing; Q-019 missing; Q-020 missing (mirror route in spec shape). **Net: 9 of 20 v1-required queries done, 1 partial (Q-001), 10 missing.** Q-015 is v1-stretch, out of scope |
| F-3 | `POST /ingest/kb-cad/export` and `POST /ingest/kb-fem/export` | ❌ missing | No `/ingest/kb-cad` or `/ingest/kb-fem` route in `api/routes.py`. No schema doc under `docs/architecture/` |
| F-4 | Candidate review queue end-to-end (list/promote/reject/edit) | 🟡 partial for entities | Memory side shipped in Phase 9 Commit C. Entity side has `promote_entity`, `supersede_entity`, `invalidate_active_entity` but reject path and editable-before-promote may not match spec shape. Need to verify `GET /entities?status=candidate` returns spec shape |
| F-5 | Conflict detector fires synchronously; `POST /conflicts/{id}/resolve` + dismiss | 🟡 partial (per Codex 2026-04-22 audit — schema present, detector+routes divergent) | **Schema is already spec-shaped**: `database.py:190` defines the generic `conflicts` + `conflict_members` tables per `conflict-model.md`; `conflicts.py:154` persists through them. **Divergences are in detection and API, not schema**: (1) `conflicts.py:36` dispatches per-type detectors only (`_check_component_conflicts`, `_check_requirement_conflicts`) — needs generalization to slot-key-driven detection; (2) routes live at `/admin/conflicts/*`, spec says `/conflicts/*` — needs alias + deprecation. **No schema migration needed** |
| F-6 | Mirror: `/mirror/{project}/overview`, `/decisions`, `/subsystems/{id}`, `/regenerate`; files under `/srv/storage/atocore/data/mirror/`; disputed + curated markers; deterministic output | 🟡 partial | `mirror.py` has `generate_project_overview` with header/state/system/decisions/requirements/materials/vendors/memories/footer sections. API at `/projects/{project_name}/mirror` and `.html`. **Gaps**: no separate `/mirror/{project}/decisions` or `/mirror/{project}/subsystems/{id}` routes, no `POST /regenerate` endpoint, no debounced-async-on-write, no daily refresh, no `⚠ disputed` markers wired to conflicts, no `(curated)` override annotations verified, no golden-file test for determinism |
| F-7 | Memory→entity graduation: `POST /memory/{id}/graduate` + `graduated` status + forward pointer + original preserved | 🟡 partial (per Codex 2026-04-22 third-round audit) | `_graduation_prompt.py` exists; `scripts/graduate_memories.py` creates entity candidates from active memories; `database.py:143-146` adds `graduated_to_entity_id`; `memory.service` already has a `graduated` status; `service.py:354-356,389-451` preserves the original memory and marks it `graduated` with a forward pointer on entity promote; `tests/test_engineering_v1_phase5.py:67-90` covers that flow. **Gaps vs spec**: no direct `POST /memory/{id}/graduate` route yet (current surface is batch/admin-driven via `/admin/graduation/request`); no explicit acceptance tests yet for `adaptation→decision` and `project→requirement`; spec wording `knowledge→Fact` does not match the current ontology (there is no `fact` entity type in `service.py` / `_graduation_prompt.py`) and should be reconciled to an actual V1 type such as `parameter` or another ontology-defined entity. |
| F-8 | Every active entity has `source_refs`; Q-017 returns ≥1 row for every active entity | 🟡 partial | `Entity.source_refs` field exists; Q-017 (`evidence_chain`) exists. **Gap**: is provenance enforced at write time (not NULL), or just encouraged? Per spec it must be mandatory |
### Quality (Q-1 through Q-6)
| ID | Criterion | Status | Evidence |
|----|-----------|--------|----------|
| Q-1 | All pre-V1 tests still pass | ✅ presumed | 533 tests passing per DEV-LEDGER line 12 |
| Q-2 | Each F criterion has happy-path + error-path test, <10s each, <30s total | 🟡 partial | 16 + 15 + 15 + 12 = 58 tests in engineering/queries/v1-phase5/patch files. Need to verify coverage of each F criterion one-for-one |
| Q-3 | Conflict invariants enforced by tests (contradictory imports produce conflict, can't promote both, flag-never-block) | 🟡 partial | Tests likely exist in `test_engineering_v1_phase5.py` — verify explicit coverage of the three invariants |
| Q-4 | Trust hierarchy enforced by tests (candidates never in context, active-only reinforcement, no auto-project-state writes) | 🟡 partial | Phase 9 Commit B covered the memory side; verify entity side has equivalent tests |
| Q-5 | Mirror has golden-file test, deterministic output | ❌ missing | No golden file seen; mirror output reads wall-clock time inside `_footer()` (`mirror.py:320-327`). Determinism should come from injecting the regenerated timestamp/checksum as inputs to the renderer and pinning them in the golden-file test, not from calling `datetime.now()` inside render code |
| Q-6 | Killer correctness queries pass against seeded real-ish data (5 seed cases per Q-006/Q-009/Q-011) | ❌ likely missing | No fixture file named for this seen. The three queries exist but there's no evidence of the single integration test described in Q-6 |
### Operational (O-1 through O-5)
| ID | Criterion | Status | Evidence |
|----|-----------|--------|----------|
| O-1 | Schema migration additive, idempotent, tested against fresh + prod-copy DB | 🟡 presumed | `_apply_migrations` pattern is in use per CLAUDE.md sessions; tables exist. Need one confirmation run against a Dalidou backup copy |
| O-2 | Backup includes new tables; full restore drill passes; post-restore Q-001 works | ❌ not done | No evidence a restore drill has been run on V1 entity state. `docs/backup-restore-procedure.md` exists but drill is an explicit V1 prerequisite |
| O-3 | Performance bounds: write <100ms p99, query <500ms p99 at 1000 entities, mirror <5s per project | 🟡 unmeasured | 35 entities in system — bounds unmeasured at scale. Spec says "sanity-checked, not benchmarked", so this is a one-off manual check |
| O-4 | No new manual ops burden | 🟡 | Mirror regen auto-triggers not wired yet (see F-6 gap) — they must be wired for O-4 to pass |
| O-5 | Phase 9 reflection loop unchanged for identity/preference/episodic | ✅ presumed | `memory-vs-entities.md` says these three types don't interact with engineering layer. No recent change to memory extractor for these types |
### Documentation (D-1 through D-4)
| ID | Criterion | Status | Evidence |
|----|-----------|--------|----------|
| D-1 | 12 per-entity-type spec docs under `docs/architecture/entities/` | ❌ missing | No `docs/architecture/entities/` folder |
| D-2 | `kb-cad-export-schema.md` + `kb-fem-export-schema.md` | ❌ missing | No such files in `docs/architecture/` |
| D-3 | `docs/v1-release-notes.md` | ❌ missing | Not written yet (appropriately — it's written when V1 is done) |
| D-4 | `master-plan-status.md` + `current-state.md` updated with V1 completion | ❌ not yet | `master-plan-status.md:179` still has V1 under **Next** |
### Summary (revised per Codex 2026-04-22 per-file audit)
- **Functional:** 0/8 ✅, 7/8 🟡 partial (F-1 downgraded from ✅ — two header fields missing; F-2 through F-7 partial), 1/8 ❌ missing (F-3 ingest endpoints) → the entity layer shape is real but not yet spec-clean; write-time invariants come first, then everything builds on stable invariants
- **F-2 detail:** 9 of 20 v1-required queries done, 1 partial (Q-001 needs subsystem-scoped variant), 10 missing
- **F-5 detail:** generic `conflicts` + `conflict_members` schema already present (no migration needed); detector body + routes diverge from spec
- **Quality:** 1/6 ✅, 3/6 🟡 partial, 2/6 ❌ missing → golden file + killer-correctness integration test are the two clear gaps
- **Operational:** 0/5 ✅ (none fully verified), 3/5 🟡, 1/5 ❌ → backup drill is the one hard blocker here
- **Documentation:** 0/4 ✅, 4/4 ❌ → all 4 docs need writing
---
## Proposed completion order (revised post-Codex review)
Seven phases instead of six. The new V1-0 establishes the write-time
invariants (provenance enforcement F-8 + synchronous conflict hooks F-5
minimal) that every later phase depends on. V1-A becomes a **minimal query
slice** that proves the model on one project, not a full catalog closure.
Full query catalog closure moves to V1-C. Full F-5 spec compliance (the
generic `conflicts`/`conflict_members` slot-key schema) stays in V1-F
because that's the final shape, but the *minimal hooks* that fire
synchronously on writes land in V1-0.
Skipped by construction: F-1 core schema (already implemented) and O-5
(identity/preference/episodic don't touch the engineering layer).
### Phase V1-0: Write-time invariants (F-8 + F-5 minimal + F-1 audit)
**Scope:**
- **F-1 remediation (Codex audit 2026-04-22 already completed).** Add
the two missing shared-header fields to the `Entity` dataclass
(`service.py:67`) and the `entities` table schema:
- `extractor_version TEXT` — semver-ish string carrying the extractor
module version per `promotion-rules.md:268`. Backfill existing rows
with `"0.0.0"` or `NULL` flagged as unknown. Every future
write carries the current `EXTRACTOR_VERSION` constant.
- `canonical_home TEXT` — which layer is canonical for this concept.
For entities, value is always `"entity"`. For future graduation
records it may be `"memory"` (frozen pointer). Backfill active
rows with `"entity"`.
- Additive migration via the existing `_apply_migrations` pattern,
idempotent, safe on replay.
- Add doc note in `engineering-ontology-v1.md` clarifying that the
`project` field IS the `project_id` per spec — "fields equivalent
to" wording in the spec allows this, but make it explicit so
future readers don't trip on the naming.
- **F-8 provenance enforcement.** Add a NOT-NULL invariant at
`create_entity` and `promote_entity` that `source_refs` is non-empty
OR an explicit `hand_authored=True` flag is set (per
`promotion-rules.md:253`). Backfill any existing active entities that
fail the invariant — either attach provenance, flag as hand-authored,
or invalidate. Every future active entity has provenance by schema,
not by discipline.
- **F-5 minimal hooks.** Wire synchronous conflict detection into every
active-entity write path (`create_entity` with status=active,
`promote_entity`, `supersede_entity`). The *detector* can stay in its
current per-type form (`_check_component_conflicts`,
`_check_requirement_conflicts`); the *hook* must fire on every write.
Full generic slot-keyed schema lands in V1-F; the hook shape must be
generic enough that V1-F is a detector-body swap, not an API refactor.
- **Q-3 "flag never block" test.** The hook must return conflict-id in
the response body but never 4xx-block the write. One test per write
path demonstrating this.
- **Q-4 trust-hierarchy test for candidates.** One test: entity
candidates never appear in `/context/build` output. (Full trust tests
land in V1-E; this is the one that V1-0 can cover without graduation
being ready.)
**Acceptance:** F-1 ✅ (after `extractor_version` + `canonical_home`
land + doc note on `project` naming), F-8 ✅, F-5 hooks ✅, Q-3 ✅,
partial Q-4 ✅.
**Estimated size:** 3 days (two small schema additions + invariant
patches + hook wiring + tests; no audit overhead — Codex already did
that part).
**Tests added:** ~10.
**Why first:** every later phase writes entities. Without F-8 + F-5
hooks, V1-A through V1-F can leak invalid state into the store that
must then be cleaned up.
### Phase V1-A: Minimal query slice that proves the model (partial F-2 + Q-6)
**Scope:**
- Pick the **four pillar queries**: Q-001 (subsystem contents),
Q-005 (component satisfies requirements), Q-006 (orphan requirements —
killer correctness), Q-017 (evidence chain). These exercise structural +
intent + killer-correctness + provenance.
- **Q-001 needs a shape fix**: Codex's audit confirms the existing
`system_map()` returns a project-wide tree, not the spec's
subsystem-scoped `GET /entities/Subsystem/<id>?expand=contains`.
Add a subsystem-scoped variant (the existing project-wide route stays
for Q-004). This is the only shape fix in V1-A; larger query additions
move to V1-C.
- Q-005, Q-006, Q-017 are already implemented per Codex audit. V1-A
verifies them against seeded data; no code changes expected.
- Seed p05-interferometer with Q-6 integration data (one satisfying
Component + one orphan Requirement + one Decision on flagged
Assumption + one supported ValidationClaim + one unsupported
ValidationClaim).
- All three killer-correctness queries (Q-006, Q-009, Q-011) are
**already implemented** per Codex audit. V1-A runs them as a single
integration test against the seed data.
**Acceptance:** Q-001 subsystem-scoped variant + Q-6 integration test.
Partial F-2 (remaining 10 missing + 1 partial queries land in V1-C).
**Estimated size:** 1.5 days (scope shrunk — most pillar queries already
work per Codex audit; only Q-001 shape fix + seed data + integration
test required).
**Tests added:** ~4.
**Why second:** proves the entity layer shape works end-to-end on real
data before we start bolting ingest, graduation, or mirror onto it. If
the four pillar queries don't work, stopping here is cheap.
### Phase V1-B: KB-CAD / KB-FEM ingest (F-3) + D-2 schema docs
**Scope:**
- Write `docs/architecture/kb-cad-export-schema.md` and
`kb-fem-export-schema.md` (matches D-2).
- Implement `POST /ingest/kb-cad/export` and `POST /ingest/kb-fem/export`
per `tool-handoff-boundaries.md` sketches. Validator + entity-candidate
producer + provenance population **using the F-8 invariant from V1-0**.
- Hand-craft one real KB-CAD export for p05-interferometer and
round-trip it: export → candidate queue → reviewer promotes → queryable
via V1-A's four pillar queries.
- Tests: valid export → candidates created; invalid export → 400;
duplicate re-export → no duplicate candidates; re-export with changed
value → new candidate + conflict row (exercises V1-0's F-5 hook on a
real workload).
**Acceptance:** F-3 ✅, D-2 ✅.
**Estimated size:** 2 days.
**Tests added:** ~8.
**Why third:** ingest is the first real stress test of the V1-0
invariants. A re-import that creates a conflict must trigger the V1-0
hook; if it doesn't, V1-0 is incomplete and we catch it before going
further.
### Phase V1-C: Close the rest of the query catalog (remaining F-2)
**Scope:** close the 10 missing queries per Codex's audit. Already-done
queries (Q-004/Q-005/Q-006/Q-008/Q-009/Q-011/Q-013/Q-016/Q-017) are
verified but not rewritten.
- Q-002 (component → parents, inverse of CONTAINS)
- Q-003 (subsystem interfaces, Interface as simple string label)
- Q-007 (component → constraints via CONSTRAINED_BY)
- Q-010 (ValidationClaim → supporting results + AnalysisModel trace)
- Q-012 (conflicting results on same claim — exercises V1-0's F-5 hook)
- Q-014 (decision log ordered + superseded chain)
- Q-018 (`include=superseded` for supersession chains)
- Q-019 (Material → components, derived from Component.material field
per `engineering-query-catalog.md:266`, no edge needed)
- Q-020 (project overview mirror route) — deferred to V1-D where the
mirror lands in full.
**Acceptance:** F-2 ✅ (all 19 of 20 v1-required queries; Q-020 in V1-D).
**Estimated size:** 2 days (eight new query functions + routes +
per-query happy-path tests).
**Tests added:** ~12.
**Why fourth:** with the model proven (V1-A) and ingest exercising the
write invariants (V1-B), filling in the remaining queries is mechanical.
They all sit on top of the same entity store and V1-0 invariants.
### Phase V1-D: Full Mirror surface (F-6) + determinism golden file (Q-5) + Q-020
**Scope:**
- Split the single `/projects/{project_name}/mirror` route into the three
spec routes: `/mirror/{project}/overview` (= Q-020),
`/mirror/{project}/decisions`, `/mirror/{project}/subsystems/{subsystem}`.
- Add `POST /mirror/{project}/regenerate`.
- Move generated files to `/srv/storage/atocore/data/mirror/{project}/`.
- **Deterministic output:** inject regenerated timestamp + checksum as
renderer inputs (pinned by golden tests), sort every iteration, and
remove `dict` / database ordering dependencies. The renderer should
not call wall-clock time directly.
- `⚠ disputed` markers inline wherever an open conflict touches a
rendered field (uses V1-0's F-5 hook output).
- `(curated)` annotations where project_state overrides entity state.
- Regeneration triggers: synchronous on regenerate, debounced async on
entity write (30s window), daily scheduled refresh via existing
nightly cron (one new cron line, not a new queue).
- `mirror_regeneration_failures` table.
- Golden-file test: fixture project state → render → bytes equal.
**Acceptance:** F-6 ✅, Q-5 ✅, Q-020 ✅, O-4 moves toward ✅.
**Estimated size:** 34 days.
**Tests added:** ~15.
**Why fifth:** mirror is a derived consumer. It cannot be correct
before the entity store + queries + conflict hooks are correct. It
lands after everything it depends on is stable.
### Phase V1-E: Memory→entity graduation end-to-end (F-7) + remaining Q-4
**Scope:**
- Verify and close F-7 spec gaps:
- Add the missing direct `POST /memory/{id}/graduate` route, reusing the
same prompt/parser as the batch graduation path.
- Keep `/admin/graduation/request` as the bulk lane; direct route is the
per-memory acceptance surface.
- Preserve current behavior where promote marks source memories
`status="graduated"` and sets `graduated_to_entity_id`.
- Flow tested for `adaptation` → Decision and `project` → Requirement.
- Reconcile the spec's `knowledge` → Fact wording with the actual V1
ontology (no `fact` entity type exists today). Prefer doc alignment to
an existing typed entity such as `parameter`, rather than adding a vague
catch-all `Fact` type late in V1.
- Schema is mostly already in place: `graduated` status exists in memory
service, `graduated_to_entity_id` column + index exist, and promote
preserves the original memory. Remaining work is route surface,
ontology/spec reconciliation, and targeted end-to-end tests.
- **Q-4 full trust-hierarchy tests**: no auto-write to project_state
from any promote path; active-only reinforcement for entities; etc.
(The entity-candidates-excluded-from-context test shipped in V1-0.)
**Acceptance:** F-7 ✅, Q-4 ✅.
**Estimated size:** 34 days.
**Tests added:** ~8.
**Why sixth:** graduation touches memory-layer semantics (adds a
`graduated` status, flows memory→entity, requires memory-module changes).
Doing it after the entity layer is fully invariant-safe + query-complete
+ mirror-derived means the memory side only has to deal with one shape:
a stable, tested entity layer.
### Phase V1-F: Full F-5 spec compliance + O-1/O-2/O-3 + D-1/D-3/D-4
**Scope:**
- **F-5 full spec compliance** (Codex 2026-04-22 audit already confirmed
the gap shape — schema is spec-compliant, divergence is in detector +
routes only).
- **Detector generalization.** Replace the per-type dispatch at
`conflicts.py:36` (`_check_component_conflicts`,
`_check_requirement_conflicts`) with a slot-key-driven generic
detector that reads the per-entity-type conflict slot from a
registry and queries the already-generic `conflicts` +
`conflict_members` tables. The V1-0 hook shape was chosen to make
this a detector-body swap, not an API change.
- **Route alignment.** Add `/conflicts/*` routes as the canonical
surface per `conflict-model.md:187`. Keep `/admin/conflicts/*` as
aliases for one release, deprecate in D-3 release notes, remove
in V1.1.
- **No schema migration needed** (the tables at `database.py:190`
already match the spec).
- **O-1:** Run the full migration against a Dalidou backup copy.
Confirm additive, idempotent, safe to run twice.
- **O-2:** Run a full restore drill on the test project per
`docs/backup-restore-procedure.md`. Post-restore, Q-001 returns
correct shape. `POST /admin/backup` snapshot includes the new tables.
- **O-3:** Manual sanity-check of the three performance bounds.
- **D-1:** Write 12 short spec docs under `docs/architecture/entities/`
(one per V1 entity type).
- **D-3:** Write `docs/v1-release-notes.md`.
- **D-4:** Update `master-plan-status.md` and `current-state.md`
move engineering V1 from **Next** to **What Is Real Today**.
**Acceptance:** F-5 ✅, O-1 ✅, O-2 ✅, O-3 ✅, D-1 ✅, D-3 ✅, D-4 ✅ →
**V1 is done.**
**Estimated size:** 3 days (F-5 migration if needed is the main unknown;
D-1 entity docs at ~30 min each ≈ 6 hours; verification is fast).
**Tests added:** ~6 (F-5 spec-shape tests; verification adds no automated
tests).
### Total (revised after Codex 2026-04-22 audit)
- Phase budgets: V1-0 (3) + V1-A (1.5) + V1-B (2) + V1-C (2) + V1-D (3-4)
+ V1-E (3-4) + V1-F (3) ≈ **17.519.5 days of focused work**. This is a
realistic engineering-effort estimate, but a single-operator calendar
plan should still carry context-switch / soak / review buffer on top.
- Adds roughly **60 tests** (533 → ~593).
- Branch strategy: one branch per phase (V1-0 → V1-F), each squash-merged
to main after Codex review. Phases sequential because each builds on
the previous. **V1-0 is a hard prerequisite for all later phases**
nothing starts until V1-0 lands.
---
## Sequencing with the `master-plan-status.md` Now list
The **Now** list from master-plan-status.md:159-169 is:
1. Observe the enhanced pipeline (1 week soak — first F4 confidence decay
run was 2026-04-19 per Trusted State, so soak window ends ~2026-04-26)
2. Knowledge density — batch extract over 234 interactions, target 100+
active memories (currently 84)
3. Multi-model triage (Phase 11 entry)
4. Fix p04-constraints harness failure
**Principle (revised per Codex review):** V1 work and the Now list are
**less disjoint than the first draft claimed**. Real collision points:
| V1 phase | Collides with Now list at |
|---|---|
| V1-0 provenance enforcement | memory extractor write path if it shares helper functions; context assembly for the Q-4 partial trust test |
| V1-0 F-5 hooks | any write path that creates active rows (limited collision; entity writes are separate from memory writes) |
| V1-B KB-CAD/FEM ingest | none on the Now list, but adds an ingest surface that becomes operational burden (ties to O-4 "no new manual ops") |
| V1-D mirror regen triggers | scheduling / ops behavior that intersects with "boring and dependable" gate — mirror regen failures become an observable that the pipeline soak must accommodate |
| V1-E graduation | memory module (new `graduated` status, memory→entity flow); direct collision with memory extractor + triage |
| V1-F F-5 migration | conflicts.py touches the write path shared with memory promotion |
**Recommended schedule (revised):**
- **This week (2026-04-22 to 2026-04-26):** Pipeline soak continues.
Density batch-extract continues. V1 work **waits** — V1-0 would start
touching write paths, which is explicitly something we should not do
during a soak window. Density target (100+ active memories) and the
pipeline soak complete first.
- **Week of 2026-04-27:** If soak is clean and density reached, V1-0
starts. V1-0 is a hard prerequisite and cannot be skipped or parallelized.
- **Weeks of 2026-05-04 and 2026-05-11:** V1-A through V1-D in order.
Multi-model triage work (Now list item 3) continues in parallel only
if its touch-surface is triage-path-only (memory side). Any memory
extractor change pauses V1-E.
- **Week of 2026-05-18 approx:** V1-E (graduation). **This phase must
not run in parallel with memory extractor changes** — it directly
modifies memory module semantics. Multi-model triage should be settled
before V1-E starts.
- **Week of 2026-05-25:** V1-F.
- **End date target:** ~2026-06-01, four weeks later than the first
draft's 2026-05-18 soft target. The shift is deliberate — the first
draft's "parallel / disjoint" claim understated the real collisions.
**Pause points (explicit):**
- Any Now-list item that regresses the pipeline → V1 pauses immediately.
- Memory extractor changes in flight → V1-E pauses until they land and
soak.
- p04-constraints fix requires retrieval ranking changes → V1 does not
pause (retrieval is genuinely disjoint from entities).
- Multi-model triage work touching the entity extractor path (if one
gets prototyped) → V1-0 pauses until the triage decision settles.
---
## Test project
Per `engineering-v1-acceptance.md:379`, the recommended test bed is
**p05-interferometer** — "the optical/structural domain has the cleanest
entity model". I agree. Every F-2, F-3, F-6 criterion asserts against this
project.
p06-polisher is the backup test bed if p05 turns out to have data gaps
(polisher suite is actively worked and has more content).
---
## What V1 completion does NOT include
Per the negative list in `engineering-v1-acceptance.md:351-373`, all of the
following are **explicitly out of scope** for this plan:
- LLM extractor for entities (rule-based is V1)
- Auto-promotion of candidates (human-only in V1)
- Write-back to KB-CAD / KB-FEM
- Multi-user auth
- Real-time UI (API + Mirror markdown only)
- Cross-project rollups
- Time-travel queries (Q-015 stays stretch)
- Nightly conflict sweep (synchronous only)
- Incremental Chroma snapshots
- Retention cleanup script
- Backup encryption
- Off-Dalidou backup target (already exists at clawdbot per ledger, but
not a V1 criterion)
- **Async job queue / minions pattern** (the rejected plan's centerpiece —
explicitly deferred to post-V1 per the negative list)
---
## Open questions for Codex (post-second-round revision)
Three of the original eight questions (F-1 field audit, F-2 per-query
audit, F-5 schema divergence) were answered by Codex's 2026-04-22 audit
and folded into the plan. One open question remains; the rest are now
resolved in-plan:
1. **Parallel schedule vs Now list.** The first-round review correctly
softened this from "fully parallel" to "less disjoint than claimed".
Is the revised collision table + pause-points section enough, or
should specific Now-list items gate specific V1 phases more strictly?
2. **F-7 graduation gap depth.** Resolved by Codex audit. The schema and
preserve-original-memory hook are already in place, so V1-E is not a
greenfield build. But the direct `/memory/{id}/graduate` route and the
ontology/spec mismatch around `knowledge``Fact` are still open, so
V1-E is closer to **34 days** than 2.
3. **Mirror determinism — where does `now` go?** Resolved. Keep the
regenerated timestamp in the rendered output if desired, but pass it
into the renderer as an input value. Golden-file tests pin that input;
render code must not read the clock directly.
4. **`project` field naming.** Resolved. Keep the existing `project`
field; add the explicit doc note that it is the project identifier for
V1 acceptance purposes. No storage rename needed.
5. **Velocity calibration.** Resolved. **17.519.5 focused days** is a
fair engineering-effort estimate after the F-7 audit. For an actual
operator schedule, keep additional buffer for context switching, soak,
and review rounds.
6. **Minions/queue as V2 item in D-3.** Resolved. Do not name the
rejected "Minions" plan in V1 release notes. If D-3 includes a future
work section, refer to it neutrally as "queued background processing /
async workers" rather than canonizing a V2 codename before V2 is
designed.
---
## Risks
| Risk | Mitigation |
|---|---|
| V1 work slows the Now list | V1 pauses on any Now-list blocker. Codex veto on any V1 PR that touches memory extractor, retrieval ranking, or triage paths |
| F-5 detector generalization harder than estimated | Codex audit confirmed schema is already spec-compliant; only detector body + routes need work. If detector generalization still slips, keep per-type detectors and document as a V1.1 cleanup (detection correctness is unaffected, only code organization) |
| Mirror determinism regresses existing mirror output | Keep `/projects/{project_name}/mirror` alias returning the current shape; new `/mirror/{project}/overview` is the spec-compliant one. Deprecate old in V1 release notes |
| Golden file churn as templates evolve | Standard workflow: updating a golden file is a normal part of template work, documented in V1-C commit message |
| Backup drill on Dalidou is disruptive | Run against a clone of the Dalidou DB at a safe hour; no production drill required for V1 acceptance |
| p05-interferometer data gaps | Fall back to p06-polisher per this plan's test-project section |
| Scope creep during V1-A query audit | Any query that isn't in the v1-required set (Q-021 onward) is out of scope, period |
---
## What this plan is **for**
1. A checklist Claude can follow to close V1.
2. A review target for Codex — every phase has explicit acceptance
criteria tied to the acceptance doc.
3. A communication artifact for Antoine — "here's what's left, here's why,
here's the order, here's the risk."
## What this plan is **not**
1. Not a commitment to start tomorrow. Pipeline soak + density density
come first in parallel; V1-A can start this week only because it's
zero-risk additive work.
2. Not a rewrite. Every phase builds on existing code.
3. Not an ontology debate. The ontology is fixed in
`engineering-ontology-v1.md`. Any desire to change it is V2 material.
## Workspace note (for Codex audit)
Codex's first-round review (2026-04-22) flagged that
`docs/plans/engineering-v1-completion-plan.md` and `DEV-LEDGER.md` were
**not visible** in the Playground workspace they were running against,
and that `src/atocore/engineering/` appeared empty there.
The canonical dev workspace for AtoCore is the Windows path
`C:\Users\antoi\ATOCore` (per `CLAUDE.md`). The engineering layer code
(`src/atocore/engineering/service.py`, `queries.py`, `conflicts.py`,
`mirror.py`, `_graduation_prompt.py`, `wiki.py`, `triage_ui.py`) exists
there and is what the recent commits (e147ab2, b94f9df, 081c058, 069d155,
b1a3dd0) touched. The Windows working tree is what this plan was written
against.
Before the file-level audit:
1. Confirm which branch / SHA Codex is reviewing. The Windows working
tree has uncommitted changes to this plan + DEV-LEDGER as of
2026-04-22; commit will be made only after Antoine approves sync.
2. If Codex is reviewing `ATOCore-clean` or a Playground snapshot, that
tree may lag the canonical dev tree. Sync or re-clone from the
Windows working tree / current `origin/main` before per-file audit.
3. The three visible-to-Codex file paths for this plan are:
- `docs/plans/engineering-v1-completion-plan.md` (this file)
- `docs/decisions/2026-04-22-gbrain-plan-rejection.md` (prior decision)
- `DEV-LEDGER.md` (Recent Decisions + Session Log entries 2026-04-22)
## References
- `docs/architecture/engineering-ontology-v1.md`
- `docs/architecture/engineering-query-catalog.md`
- `docs/architecture/memory-vs-entities.md`
- `docs/architecture/engineering-v1-acceptance.md`
- `docs/architecture/promotion-rules.md`
- `docs/architecture/conflict-model.md`
- `docs/architecture/human-mirror-rules.md`
- `docs/architecture/tool-handoff-boundaries.md`
- `docs/master-plan-status.md` (Now/Next/Later list)
- `docs/decisions/2026-04-22-gbrain-plan-rejection.md` (the rejected plan)
- `src/atocore/engineering/service.py` (current V1 entity service)
- `src/atocore/engineering/queries.py` (current V1 query implementations)
- `src/atocore/engineering/conflicts.py` (current conflicts module)
- `src/atocore/engineering/mirror.py` (current mirror module)

View File

@@ -0,0 +1,161 @@
# 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

@@ -0,0 +1,216 @@
# Wiki Reorg Plan — Human-Readable Navigation of AtoCore State
> **SUPERSEDED — 2026-04-22.** Do not implement this plan. It has been
> replaced by the read-only operator orientation work at
> `docs/plans/operator-orientation-plan.md` (in the `ATOCore-clean`
> workspace). The successor reframes the problem as orientation over
> existing APIs and docs rather than a new `/wiki` surface, and drops
> interaction browser / memory index / project-page restructure from
> scope. Nothing below should be picked up as a work item. Kept in tree
> for context only.
**Date:** 2026-04-22
**Author:** Claude (after walking the live wiki at `http://dalidou:8100/wiki`
and reading `src/atocore/engineering/wiki.py` + `/wiki/*` routes in
`src/atocore/api/routes.py`)
**Status:** SUPERSEDED (see banner above). Previously: Draft, pending Codex review
**Scope boundary:** This plan does NOT touch Inbox / Global / Emerging.
Those three surfaces stay exactly as they are today — the registration
flow and scope semantics around them are load-bearing and out of scope
for this reorg.
---
## Position
The wiki is Layer 3 of the engineering architecture (Human Mirror —
see `docs/architecture/engineering-knowledge-hybrid-architecture.md`).
It is a **derived view** over the same database the LLM reads via
`atocore_context` / `atocore_search`. There is no parallel store; if a
fact is in the DB it is reachable from the wiki.
The user-facing problem is not representation, it is **findability and
structure**. Content the operator produced (proposals, decisions,
constraints, captured conversations) is in the DB but is not reachable
along a natural human reading path. Today the only routes to it are:
- typing the right keyword into `/wiki/search`
- remembering which project it lived under and scrolling the auto-
generated mirror markdown on `/wiki/projects/{id}`
- the homepage "What the brain is doing" strip, which shows counts of
audit actions but no content
There is no "recent conversations" view, no memory index, no way to
browse by memory type (decision vs preference vs episodic), and the
topnav only exposes Home / Activity / Triage / Dashboard.
## Goal
Make the wiki a surface the operator actually uses to answer three
recurring questions:
1. **"Where did I say / decide / propose X?"** — find a memory or
interaction by topic, not by exact-string search.
2. **"What is the current state of project Y?"** — read decisions,
constraints, and open questions as distinct sections, not as one
wall of auto-generated markdown.
3. **"What happened in the system recently, and what does it mean?"**
— a human-meaningful recent feed (captures, promotions, decisions
recorded), not a count of audit-action names.
Success criterion: for each of the three questions above, a first-time
visitor reaches a useful answer in **≤ 2 clicks from `/wiki`**.
## Non-goals
- No schema changes. All data already exists.
- No change to ingestion, extraction, promotion, or the trust rules.
- No change to Inbox / Global / Emerging surfaces or semantics.
- No change to `/admin/triage` or `/admin/dashboard`.
- No change to the `atocore_context` / `atocore_search` LLM-facing APIs.
- No new storage. Every new page is a new render function over existing
service-layer calls.
## What it will do
Five additions, in priority order. Each is independently mergeable and
independently revertable.
### W-1 — Interaction browser: `/wiki/interactions`
The DB holds 234 captured interactions (per ledger `2026-04-22`). None
are readable in the wiki. Add a paginated list view and a detail view.
- `/wiki/interactions?project=&client=&since=` — list, newest first,
showing timestamp, client (claude-code / openclaw / test), inferred
project, and the first ~160 chars of the user prompt.
- `/wiki/interactions/{id}` — full prompt + response, plus any
candidate / active memories extracted from it (back-link from the
memory → interaction relation if present, else a "no extractions"
note).
- Filters: project (multi), client, date range, "has extractions" boolean.
Answers Q1 ("where did I say X") by giving the user a time-ordered
stream of their own conversations, not just extracted summaries.
### W-2 — Memory index: `/wiki/memories`
Today `/wiki/memories/{id}` exists but there is no list view. Add one.
- `/wiki/memories?project=&type=&status=&tag=` — filterable list.
Status defaults to `active`. Types: decision, preference, episodic,
knowledge, identity, adaptation.
- Faceted counts in the sidebar (e.g. "decision: 14 · preference: 7").
- Each row links to `/wiki/memories/{id}` and, where present, the
originating interaction.
### W-3 — Project page restructure (tabbed, not flat)
`/wiki/projects/{id}` today renders the full mirror markdown in one
scroll. Restructure to tabs (or anchored sections, same thing
semantically) over the data already produced by
`generate_project_overview`:
- **Overview** — stage, client, type, description, headline state.
- **Decisions** — memories of type `decision` for this project.
- **Constraints** — project_state entries in the `constraints` category.
- **Proposals** — memories tagged `proposal` or type `preference` with
proposal semantics (exact filter TBD during W-3; see Open Questions).
- **Entities** — current list, already rendered inline today.
- **Memories** — all memories, reusing the W-2 list component filtered
to this project.
- **Timeline** — interactions for this project, reusing W-1 filtered
to this project.
No new data is produced; this is purely a slicing change.
### W-4 — Recent feed on homepage
Replace the current "What the brain is doing" strip (which shows audit
action counts) with a **content** feed: the last N events that a human
cares about —
- new active memory (not candidate)
- memory promoted candidate → active
- state entry added / changed
- new registered project
- interaction captured (optional, may be noisy — gate behind a toggle)
Each feed row links to the thing it describes. The audit-count strip
can move to `/wiki/activity` where the full audit timeline already lives.
### W-5 — Topnav exposure
Surface the new pages in the topnav so they are discoverable:
Current: `🏠 Home · 📡 Activity · 🔀 Triage · 📊 Dashboard`
Proposed: `🏠 Home · 💬 Interactions · 🧠 Memories · 📡 Activity · 🔀 Triage · 📊 Dashboard`
Domain pages (`/wiki/domains/{tag}`) stay reachable via tag chips on
memory rows, as today — no new topnav entry for them.
## Outcome
- Every captured interaction is readable in the wiki, with a clear
path from interaction → extracted memories and back.
- Memories are browsable without knowing a keyword in advance.
- A project page separates decisions, constraints, and proposals
instead of interleaving them in auto-generated markdown.
- The homepage tells the operator what **content** is new, not which
audit action counters incremented.
- Nothing in the Inbox / Global / Emerging flow changes.
- Nothing the LLM reads changes.
## Non-outcomes (explicitly)
- This plan does not improve retrieval quality. It does not touch the
extractor, ranking, or harness.
- This plan does not change what is captured or when.
- This plan does not replace `/admin/triage`. Candidate review stays there.
## Sequencing
1. **W-1 first.** Interaction browser is the biggest findability win
and has no coupling to memory code.
2. **W-2 next.** Memory index reuses list/filter infra from W-1.
3. **W-3** depends on W-2 (reuses the memory list component).
4. **W-4** is independent; can land any time after W-1.
5. **W-5** lands last so topnav only exposes pages that exist.
Each step ships with at least one render test (HTML contains expected
anchors / row counts against a seeded fixture), following the pattern
already in `tests/engineering/` for existing wiki renders.
## Risk & reversibility
- All additions are new routes / new render functions. Reverting any
step is a file delete + a topnav edit.
- Project page restructure (W-3) is the only edit to an existing
surface. Keep the flat-markdown render behind a query flag
(`?layout=flat`) for one release so regressions are observable.
- No DB migrations. No service-layer signatures change.
## Open questions for Codex
1. **"Proposal" as a first-class filter** (W-3) — is this a memory type,
a domain tag, a structural field we should add, or should it stay
derived by filter? Current DB has no explicit proposal type; we'd be
inferring from tags/content. If that inference is unreliable, W-3's
Proposals tab becomes noise.
2. **Interaction → memory back-link** (W-1) — does the current schema
already record which interaction an extracted memory came from? If
not, is exposing that link in the wiki worth a schema addition, or
should W-1 ship without it?
3. **Recent feed noise floor** (W-4) — should every captured
interaction appear in the feed, or only interactions that produced
at least one candidate memory? The former is complete but may drown
out signal at current capture rates (~10/day).
4. **Ordering vs V1 Completion track** — should any of this land before
V1-A (currently gated on soak ~2026-04-26 + density 100+), or is it
strictly after V1 closes?
## Workspace note
Canonical dev workspace is `C:\Users\antoi\ATOCore` (per `CLAUDE.md`).
Any Codex audit of this plan should sync from `origin/main` at or after
`2712c5d` before reviewing.

View File

@@ -0,0 +1,167 @@
"""V1-0 one-time backfill: flag existing active entities that have no
provenance (empty source_refs) as hand_authored=1 so they stop failing
the F-8 invariant.
Runs against the live AtoCore DB. Idempotent: a second run after the
first touches nothing because the flagged rows already have
hand_authored=1.
Per the Engineering V1 Completion Plan (V1-0 scope), the three options
for an existing active entity without provenance are:
1. Attach provenance — impossible without human review, not automatable
2. Flag hand-authored — safe, additive, this script's default
3. Invalidate — destructive, requires operator sign-off
This script picks option (2) by default. Add --dry-run to see what
would change without writing. Add --invalidate-instead to pick option
(3) for all rows (not recommended for first run).
Usage:
python scripts/v1_0_backfill_provenance.py --base-url http://dalidou:8100 --dry-run
python scripts/v1_0_backfill_provenance.py --base-url http://dalidou:8100
"""
from __future__ import annotations
import argparse
import json
import sqlite3
import sys
from pathlib import Path
def run(db_path: Path, dry_run: bool, invalidate_instead: bool) -> int:
if not db_path.exists():
print(f"ERROR: db not found: {db_path}", file=sys.stderr)
return 2
conn = sqlite3.connect(str(db_path))
conn.row_factory = sqlite3.Row
# Verify the V1-0 migration ran: if hand_authored column is missing
# the operator hasn't deployed V1-0 yet, and running this script
# would crash. Fail loud rather than attempt the ALTER here.
cols = {r["name"] for r in conn.execute("PRAGMA table_info(entities)").fetchall()}
if "hand_authored" not in cols:
print(
"ERROR: entities table lacks the hand_authored column. "
"Deploy V1-0 migrations first (init_db + init_engineering_schema).",
file=sys.stderr,
)
return 2
# Scope differs by mode:
# - Default (flag hand_authored=1): safe/additive, applies to active
# AND superseded rows so the historical trail is consistent.
# - --invalidate-instead: destructive — scope to ACTIVE rows only.
# Invalidating already-superseded history would collapse the audit
# trail, which the plan's remediation scope never intended
# (V1-0 talks about existing active no-provenance entities).
if invalidate_instead:
scope_sql = "status = 'active'"
else:
scope_sql = "status IN ('active', 'superseded')"
rows = conn.execute(
f"SELECT id, entity_type, name, project, status, source_refs, hand_authored "
f"FROM entities WHERE {scope_sql} AND hand_authored = 0"
).fetchall()
needs_fix = []
for row in rows:
refs_raw = row["source_refs"] or "[]"
try:
refs = json.loads(refs_raw)
except Exception:
refs = []
if not refs:
needs_fix.append(row)
print(f"found {len(needs_fix)} active/superseded entities with no provenance")
for row in needs_fix:
print(
f" - {row['id'][:8]} [{row['entity_type']}] "
f"{row['name']!r} project={row['project']!r} status={row['status']}"
)
if dry_run:
print("--dry-run: no changes written")
return 0
if not needs_fix:
print("nothing to do")
return 0
action = "invalidate" if invalidate_instead else "flag hand_authored=1"
print(f"applying: {action}")
cur = conn.cursor()
for row in needs_fix:
if invalidate_instead:
cur.execute(
"UPDATE entities SET status = 'invalid', "
"updated_at = CURRENT_TIMESTAMP WHERE id = ?",
(row["id"],),
)
cur.execute(
"INSERT INTO memory_audit "
"(id, memory_id, action, actor, before_json, after_json, note, entity_kind) "
"VALUES (?, ?, 'invalidated', 'v1_0_backfill', ?, ?, ?, 'entity')",
(
f"v10bf-{row['id'][:8]}-inv",
row["id"],
json.dumps({"status": row["status"]}),
json.dumps({"status": "invalid"}),
"V1-0 backfill: invalidated, no provenance",
),
)
else:
cur.execute(
"UPDATE entities SET hand_authored = 1, "
"updated_at = CURRENT_TIMESTAMP WHERE id = ?",
(row["id"],),
)
cur.execute(
"INSERT INTO memory_audit "
"(id, memory_id, action, actor, before_json, after_json, note, entity_kind) "
"VALUES (?, ?, 'hand_authored_flagged', 'v1_0_backfill', ?, ?, ?, 'entity')",
(
f"v10bf-{row['id'][:8]}-ha",
row["id"],
json.dumps({"hand_authored": False}),
json.dumps({"hand_authored": True}),
"V1-0 backfill: flagged hand_authored since source_refs empty",
),
)
conn.commit()
print(f"done: updated {len(needs_fix)} entities")
return 0
def main() -> int:
parser = argparse.ArgumentParser(description=__doc__)
parser.add_argument(
"--db",
type=Path,
default=Path("data/db/atocore.db"),
help="Path to the SQLite database (default: data/db/atocore.db)",
)
parser.add_argument("--dry-run", action="store_true", help="Report only; no writes")
parser.add_argument(
"--invalidate-instead",
action="store_true",
help=(
"DESTRUCTIVE. Invalidate active rows with no provenance instead "
"of flagging them hand_authored. Scoped to status='active' only "
"(superseded rows are left alone to preserve audit history). "
"Not recommended for first run — start with --dry-run, then "
"the default hand_authored flag path."
),
)
args = parser.parse_args()
return run(args.db, args.dry_run, args.invalidate_instead)
if __name__ == "__main__":
sys.exit(main())

View File

@@ -1457,6 +1457,11 @@ class EntityCreateRequest(BaseModel):
status: str = "active"
confidence: float = 1.0
source_refs: list[str] | None = None
# V1-0 provenance enforcement (F-8). Clients must either pass
# non-empty source_refs or set hand_authored=true. The service layer
# raises ValueError otherwise, surfaced here as 400.
hand_authored: bool = False
extractor_version: str | None = None
class EntityPromoteRequest(BaseModel):
@@ -1486,6 +1491,8 @@ def api_create_entity(req: EntityCreateRequest) -> dict:
confidence=req.confidence,
source_refs=req.source_refs,
actor="api-http",
hand_authored=req.hand_authored,
extractor_version=req.extractor_version,
)
except ValueError as e:
raise HTTPException(status_code=400, detail=str(e))
@@ -2180,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

@@ -63,6 +63,12 @@ RELATIONSHIP_TYPES = [
ENTITY_STATUSES = ["candidate", "active", "superseded", "invalid"]
# V1-0: extractor version this module writes into new entity rows.
# Per promotion-rules.md:268, every candidate must record the version of
# the extractor that produced it so later re-evaluation is auditable.
# Bump this when extraction logic materially changes.
EXTRACTOR_VERSION = "v1.0.0"
@dataclass
class Entity:
@@ -77,6 +83,10 @@ class Entity:
source_refs: list[str] = field(default_factory=list)
created_at: str = ""
updated_at: str = ""
# V1-0 shared-header fields per engineering-v1-acceptance.md:45.
extractor_version: str = ""
canonical_home: str = "entity"
hand_authored: bool = False
@dataclass
@@ -103,10 +113,25 @@ def init_engineering_schema() -> None:
status TEXT NOT NULL DEFAULT 'active',
confidence REAL NOT NULL DEFAULT 1.0,
source_refs TEXT NOT NULL DEFAULT '[]',
extractor_version TEXT NOT NULL DEFAULT '',
canonical_home TEXT NOT NULL DEFAULT 'entity',
hand_authored INTEGER NOT NULL DEFAULT 0,
created_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP,
updated_at TEXT NOT NULL DEFAULT CURRENT_TIMESTAMP
)
""")
# V1-0 (Engineering V1 completion): the three shared-header fields
# per engineering-v1-acceptance.md:45. Idempotent ALTERs for
# databases created before V1-0 land these columns without a full
# migration. Fresh DBs get them via the CREATE TABLE above; the
# ALTERs below are a no-op there.
from atocore.models.database import _column_exists # late import; avoids cycle
if not _column_exists(conn, "entities", "extractor_version"):
conn.execute("ALTER TABLE entities ADD COLUMN extractor_version TEXT DEFAULT ''")
if not _column_exists(conn, "entities", "canonical_home"):
conn.execute("ALTER TABLE entities ADD COLUMN canonical_home TEXT DEFAULT 'entity'")
if not _column_exists(conn, "entities", "hand_authored"):
conn.execute("ALTER TABLE entities ADD COLUMN hand_authored INTEGER DEFAULT 0")
conn.execute("""
CREATE TABLE IF NOT EXISTS relationships (
id TEXT PRIMARY KEY,
@@ -149,6 +174,8 @@ def create_entity(
confidence: float = 1.0,
source_refs: list[str] | None = None,
actor: str = "api",
hand_authored: bool = False,
extractor_version: str | None = None,
) -> Entity:
if entity_type not in ENTITY_TYPES:
raise ValueError(f"Invalid entity type: {entity_type}. Must be one of {ENTITY_TYPES}")
@@ -157,6 +184,21 @@ def create_entity(
if not name or not name.strip():
raise ValueError("Entity name must be non-empty")
refs = list(source_refs) if source_refs else []
# V1-0 (F-8 provenance enforcement, engineering-v1-acceptance.md:147):
# every new entity row must carry non-empty source_refs OR be explicitly
# flagged hand_authored. This is the non-negotiable invariant every
# later V1 phase depends on — without it, active entities can escape
# into the graph with no traceable origin. Raises at the write seam so
# the bug is impossible to introduce silently.
if not refs and not hand_authored:
raise ValueError(
"source_refs required: every entity must carry provenance "
"(source_chunk_id / source_interaction_id / kb_cad_export_id / ...) "
"or set hand_authored=True to explicitly flag a direct human write"
)
# Phase 5: enforce project canonicalization contract at the write seam.
# Aliases like "p04" become "p04-gigabit" so downstream reads stay
# consistent with the registry.
@@ -165,18 +207,22 @@ def create_entity(
entity_id = str(uuid.uuid4())
now = datetime.now(timezone.utc).strftime("%Y-%m-%d %H:%M:%S")
props = properties or {}
refs = source_refs or []
ev = extractor_version if extractor_version is not None else EXTRACTOR_VERSION
with get_connection() as conn:
conn.execute(
"""INSERT INTO entities
(id, entity_type, name, project, description, properties,
status, confidence, source_refs, created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
status, confidence, source_refs,
extractor_version, canonical_home, hand_authored,
created_at, updated_at)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)""",
(
entity_id, entity_type, name.strip(), project,
description, json.dumps(props), status, confidence,
json.dumps(refs), now, now,
json.dumps(refs),
ev, "entity", 1 if hand_authored else 0,
now, now,
),
)
@@ -194,14 +240,31 @@ def create_entity(
"project": project,
"status": status,
"confidence": confidence,
"hand_authored": hand_authored,
"extractor_version": ev,
},
)
# V1-0 (F-5 hook, engineering-v1-acceptance.md:99): synchronous
# conflict detection on any active-entity write. The promote path
# already had this hook (see promote_entity below); V1-0 adds it to
# direct-active creates so every active row — however it got that
# way — is checked. Fail-open per "flag, never block" rule in
# conflict-model.md:256: detector errors log but never fail the write.
if status == "active":
try:
from atocore.engineering.conflicts import detect_conflicts_for_entity
detect_conflicts_for_entity(entity_id)
except Exception as e:
log.warning("conflict_detection_failed", entity_id=entity_id, error=str(e))
return Entity(
id=entity_id, entity_type=entity_type, name=name.strip(),
project=project, description=description, properties=props,
status=status, confidence=confidence, source_refs=refs,
created_at=now, updated_at=now,
extractor_version=ev, canonical_home="entity",
hand_authored=hand_authored,
)
@@ -361,6 +424,20 @@ def promote_entity(
if entity is None or entity.status != "candidate":
return False
# V1-0 (F-8 provenance re-check at promote). The invariant must hold at
# BOTH create_entity AND promote_entity per the plan, because candidate
# rows can exist in the DB from before V1-0 (no enforcement at their
# create time) or can be inserted by code paths that bypass the service
# layer. Block any candidate with empty source_refs that is NOT flagged
# hand_authored from ever becoming active. Same error shape as the
# create-side check for symmetry.
if not (entity.source_refs or []) and not entity.hand_authored:
raise ValueError(
"source_refs required: cannot promote a candidate with no "
"provenance. Attach source_refs via PATCH /entities/{id}, "
"or flag hand_authored=true before promoting."
)
if target_project is not None:
new_project = (
resolve_project_name(target_project) if target_project else ""
@@ -503,6 +580,22 @@ def supersede_entity(
superseded_by=superseded_by,
error=str(e),
)
# V1-0 (F-5 hook on supersede, per plan's "every active-entity
# write path"). Supersede demotes `entity_id` AND adds a
# `supersedes` relationship rooted at the already-active
# `superseded_by`. That new edge can create a conflict the
# detector should catch synchronously. Fail-open per
# conflict-model.md:256.
try:
from atocore.engineering.conflicts import detect_conflicts_for_entity
detect_conflicts_for_entity(superseded_by)
except Exception as e:
log.warning(
"conflict_detection_failed",
entity_id=superseded_by,
error=str(e),
)
return True
@@ -774,6 +867,15 @@ def get_entity_with_context(entity_id: str) -> dict | None:
def _row_to_entity(row) -> Entity:
# V1-0 shared-header fields are optional on read — rows that predate
# V1-0 migration have NULL / missing values, so defaults kick in and
# older tests that build Entity() without the new fields keep passing.
# `row.keys()` lets us tolerate SQLite rows that lack the columns
# entirely (pre-migration sqlite3.Row).
keys = set(row.keys())
extractor_version = (row["extractor_version"] or "") if "extractor_version" in keys else ""
canonical_home = (row["canonical_home"] or "entity") if "canonical_home" in keys else "entity"
hand_authored = bool(row["hand_authored"]) if "hand_authored" in keys and row["hand_authored"] is not None else False
return Entity(
id=row["id"],
entity_type=row["entity_type"],
@@ -786,6 +888,9 @@ def _row_to_entity(row) -> Entity:
source_refs=json.loads(row["source_refs"] or "[]"),
created_at=row["created_at"] or "",
updated_at=row["updated_at"] or "",
extractor_version=extractor_version,
canonical_home=canonical_home,
hand_authored=hand_authored,
)

View File

@@ -240,30 +240,10 @@ def render_homepage() -> str:
# Quick stats
all_entities = get_entities(limit=500)
all_memories_raw = get_memories(active_only=True, limit=500)
# Partition real knowledge from ambient provenance so counts are honest.
# Each memory lands in exactly one bucket (low-signal takes priority).
all_memories: list = []
akc_session_count = 0
low_signal_count = 0
for _m in all_memories_raw:
if _is_low_signal_memory(_m):
low_signal_count += 1
elif _is_akc_session_memory(_m):
akc_session_count += 1
else:
all_memories.append(_m)
all_memories = get_memories(active_only=True, limit=500)
pending = get_memories(status="candidate", limit=500)
lines.append('<h2>System</h2>')
lines.append(
f'<p>{len(all_entities)} entities · {len(all_memories)} memories · '
f'{len(projects)} projects'
+ (f' · <span style="color:#888;">{akc_session_count} AKC session snapshots'
+ (f", {low_signal_count} low-signal hidden" if low_signal_count else "")
+ '</span>'
if akc_session_count or low_signal_count else '')
+ '</p>'
)
lines.append(f'<p>{len(all_entities)} entities · {len(all_memories)} active memories · {len(projects)} projects</p>')
# Triage queue prompt — surfaced prominently if non-empty
if pending:
@@ -306,44 +286,6 @@ import re as _re
_WIKILINK_PATTERN = _re.compile(r"\[\[([^\[\]|]+?)(?:\|([^\[\]]+?))?\]\]")
# ----------------------------------------------------------------- signal/noise
# Memories with these patterns are low-signal ambient artefacts — they
# inflate lists on the homepage and domain pages without being informative.
# They remain in the DB (for provenance / audit) but are filtered from
# default browsing surfaces. Pass `include_low_signal=True` on a page query
# param to surface them.
_LOW_SIGNAL_CONTENT_PATTERNS = (
"(no transcript)", # silent-mic AKC sessions
"synthetic AKC integration", # E2E test pollution
"AKC-E2E-", # E2E test prefix in content
"AKC-IMG-TEST-", # image-upload test prefix
"IMG integration test — synthetic", # E2E narrative header
)
# AKC voice-session ambient memories follow this pattern — they're
# provenance records, not knowledge. Collapse them behind a link on domain
# pages instead of rendering each inline.
_AKC_SESSION_HEADER = "AKC voice session "
def _is_low_signal_memory(mem) -> bool:
"""True for memories whose content is known ambient/test pollution."""
content = (getattr(mem, "content", "") or "")
if not content:
return True
return any(p in content for p in _LOW_SIGNAL_CONTENT_PATTERNS)
def _is_akc_session_memory(mem) -> bool:
"""True for AKC voice-session ambient snapshots (have value as provenance,
but shouldn't clutter topical listings)."""
content = (getattr(mem, "content", "") or "")
tags = getattr(mem, "domain_tags", None) or []
if any(t in ("session", "akc") for t in tags) and "voice" in tags:
return True
return content.startswith(_AKC_SESSION_HEADER)
def _resolve_wikilink(target: str, current_project: str | None) -> tuple[str, str, str]:
"""Resolve a ``[[Name]]`` target to ``(href, css_class, extra_suffix)``.
@@ -449,6 +391,8 @@ def render_new_entity_form(name: str = "", project: str = "") -> str:
entity_type: fd.get('entity_type'),
project: fd.get('project') || '',
description: fd.get('description') || '',
// V1-0: human writes via the wiki form are hand_authored by definition.
hand_authored: true,
};
try {
const r = await fetch('/v1/entities', {
@@ -932,22 +876,8 @@ def render_domain(tag: str) -> str:
breadcrumbs=[("Wiki", "/wiki"), ("Domains", "")])
all_mems = get_memories(active_only=True, limit=500)
matching_all = [m for m in all_mems
if any((t or "").lower() == tag for t in (m.domain_tags or []))]
# Partition: low-signal test pollution is hidden entirely, ambient AKC
# session memories are collapsed (shown as a count + link to
# /wiki/activity). Priority: low-signal > session > real.
matching: list = []
akc_sessions: list = []
hidden_low_signal = 0
for m in matching_all:
if _is_low_signal_memory(m):
hidden_low_signal += 1
elif _is_akc_session_memory(m):
akc_sessions.append(m)
else:
matching.append(m)
matching = [m for m in all_mems
if any((t or "").lower() == tag for t in (m.domain_tags or []))]
# Group by project
by_project: dict[str, list] = {}
@@ -956,18 +886,6 @@ def render_domain(tag: str) -> str:
lines = [f'<h1>Domain: <code>{tag}</code></h1>']
lines.append(f'<p class="meta">{len(matching)} active memories across {len(by_project)} projects</p>')
if akc_sessions or hidden_low_signal:
noise_bits = []
if akc_sessions:
noise_bits.append(
f'<a href="/wiki/activity">{len(akc_sessions)} AKC voice session snapshots</a>'
)
if hidden_low_signal:
noise_bits.append(f"{hidden_low_signal} low-signal memories hidden")
lines.append(
f'<p class="meta" style="font-size:0.85rem; color:#888;">'
f'Ambient provenance not listed: {" · ".join(noise_bits)}.</p>'
)
if not matching:
lines.append(

View File

@@ -146,6 +146,28 @@ def _apply_migrations(conn: sqlite3.Connection) -> None:
"CREATE INDEX IF NOT EXISTS idx_memories_graduated ON memories(graduated_to_entity_id)"
)
# V1-0 (Engineering V1 completion): shared header fields per
# engineering-v1-acceptance.md:45. Three columns on `entities`:
# - extractor_version: which extractor produced this row. Lets old
# candidates be re-evaluated with a newer extractor per
# promotion-rules.md:268.
# - canonical_home: which layer holds the canonical record. Always
# "entity" for rows written via create_entity; reserved for future
# cross-layer bookkeeping.
# - hand_authored: 1 when the row was created directly by a human
# without source provenance. Enforced by the write path so every
# non-hand-authored row must carry non-empty source_refs (F-8).
# The entities table itself is created by init_engineering_schema
# (see engineering/service.py); these ALTERs cover existing DBs
# where the original CREATE TABLE predates V1-0.
if _table_exists(conn, "entities"):
if not _column_exists(conn, "entities", "extractor_version"):
conn.execute("ALTER TABLE entities ADD COLUMN extractor_version TEXT DEFAULT ''")
if not _column_exists(conn, "entities", "canonical_home"):
conn.execute("ALTER TABLE entities ADD COLUMN canonical_home TEXT DEFAULT 'entity'")
if not _column_exists(conn, "entities", "hand_authored"):
conn.execute("ALTER TABLE entities ADD COLUMN hand_authored INTEGER DEFAULT 0")
# Phase 4 (Robustness V1): append-only audit log for memory mutations.
# Every create/update/promote/reject/supersede/invalidate/reinforce/expire/
# auto_promote writes one row here. before/after are JSON snapshots of the
@@ -352,6 +374,14 @@ def _column_exists(conn: sqlite3.Connection, table: str, column: str) -> bool:
return any(row["name"] == column for row in rows)
def _table_exists(conn: sqlite3.Connection, table: str) -> bool:
row = conn.execute(
"SELECT name FROM sqlite_master WHERE type='table' AND name=?",
(table,),
).fetchone()
return row is not None
@contextmanager
def get_connection() -> Generator[sqlite3.Connection, None, None]:
"""Get a database connection with row factory."""

View File

@@ -16,6 +16,36 @@ os.environ["ATOCORE_DATA_DIR"] = _default_test_dir
os.environ["ATOCORE_DEBUG"] = "true"
# V1-0: every entity created in a test is "hand authored" by the test
# author — fixture data, not extracted content. Rather than rewrite 100+
# existing test call sites, wrap create_entity so that tests which don't
# provide source_refs get hand_authored=True automatically. Tests that
# explicitly pass source_refs or hand_authored are unaffected. This keeps
# the F-8 invariant enforced in production (the API, the wiki form, and
# graduation scripts all go through the unwrapped function) while leaving
# the existing test corpus intact.
def _patch_create_entity_for_tests():
from atocore.engineering import service as _svc
_original = _svc.create_entity
def _create_entity_test(*args, **kwargs):
# Only auto-flag when hand_authored isn't explicitly specified.
# Tests that want to exercise the F-8 raise path pass
# hand_authored=False explicitly and should hit the error.
if (
not kwargs.get("source_refs")
and "hand_authored" not in kwargs
):
kwargs["hand_authored"] = True
return _original(*args, **kwargs)
_svc.create_entity = _create_entity_test
_patch_create_entity_for_tests()
@pytest.fixture
def tmp_data_dir(tmp_path):
"""Provide a temporary data directory for tests."""

View File

@@ -143,8 +143,11 @@ def test_requirement_name_conflict_detected(tmp_data_dir):
r2 = create_entity("requirement", "Surface figure < 25nm",
project="p-test", description="Different interpretation")
detected = detect_conflicts_for_entity(r2.id)
assert len(detected) == 1
# V1-0 synchronous hook: the conflict is already detected at r2's
# create-time, so a redundant detect call returns [] due to
# _record_conflict dedup. Assert on list_open_conflicts instead —
# that's what the intent of this test really tests: duplicate
# active requirements surface as an open conflict.
conflicts = list_open_conflicts(project="p-test")
assert any(c["slot_kind"] == "requirement.name" for c in conflicts)
@@ -191,8 +194,12 @@ def test_conflict_resolution_dismiss_leaves_entities_alone(tmp_data_dir):
description="first meaning")
r2 = create_entity("requirement", "Dup req", project="p-test",
description="second meaning")
detected = detect_conflicts_for_entity(r2.id)
conflict_id = detected[0]
# V1-0 synchronous hook already recorded the conflict at r2's
# create-time. Look it up via list_open_conflicts rather than
# calling the detector again (which returns [] due to dedup).
open_list = list_open_conflicts(project="p-test")
assert open_list, "expected conflict recorded by create-time hook"
conflict_id = open_list[0]["id"]
assert resolve_conflict(conflict_id, "dismiss")
# Both still active — dismiss just clears the conflict marker

View File

@@ -132,6 +132,7 @@ def test_api_post_entity_with_null_project_stores_global(seeded_db):
"entity_type": "material",
"name": "Titanium",
"project": None,
"hand_authored": True, # V1-0 F-8: test fixture, no source_refs
})
assert r.status_code == 200

View File

@@ -0,0 +1,398 @@
"""V1-0 write-time invariant tests.
Covers the Engineering V1 completion plan Phase V1-0 acceptance:
- F-1 shared-header fields: extractor_version + canonical_home + hand_authored
land in the entities table with working defaults
- F-8 provenance enforcement: create_entity raises without source_refs
unless hand_authored=True
- F-5 synchronous conflict-detection hook on any active-entity write
(create_entity with status="active" + the pre-existing promote_entity
path); fail-open per conflict-model.md:256
- Q-3 "flag, never block": a conflict never 4xx-blocks the write
- Q-4 partial trust: get_entities scope_only filters candidates out
Plan: docs/plans/engineering-v1-completion-plan.md
Spec: docs/architecture/engineering-v1-acceptance.md
"""
from __future__ import annotations
import pytest
from atocore.engineering.service import (
EXTRACTOR_VERSION,
create_entity,
create_relationship,
get_entities,
get_entity,
init_engineering_schema,
promote_entity,
supersede_entity,
)
from atocore.models.database import get_connection, init_db
# ---------- F-1: shared-header fields ----------
def test_entity_row_has_shared_header_fields(tmp_data_dir):
init_db()
init_engineering_schema()
with get_connection() as conn:
cols = {row["name"] for row in conn.execute("PRAGMA table_info(entities)").fetchall()}
assert "extractor_version" in cols
assert "canonical_home" in cols
assert "hand_authored" in cols
def test_created_entity_has_default_extractor_version_and_canonical_home(tmp_data_dir):
init_db()
init_engineering_schema()
e = create_entity(
entity_type="component",
name="Pivot Pin",
project="p04-gigabit",
source_refs=["test:fixture"],
)
assert e.extractor_version == EXTRACTOR_VERSION
assert e.canonical_home == "entity"
assert e.hand_authored is False
# round-trip through get_entity to confirm the row mapper returns
# the same values (not just the return-by-construct path)
got = get_entity(e.id)
assert got is not None
assert got.extractor_version == EXTRACTOR_VERSION
assert got.canonical_home == "entity"
assert got.hand_authored is False
def test_explicit_extractor_version_is_persisted(tmp_data_dir):
init_db()
init_engineering_schema()
e = create_entity(
entity_type="decision",
name="Pick GF-PTFE pads",
project="p04-gigabit",
source_refs=["interaction:abc"],
extractor_version="custom-v2.3",
)
got = get_entity(e.id)
assert got.extractor_version == "custom-v2.3"
# ---------- F-8: provenance enforcement ----------
def test_create_entity_without_provenance_raises(tmp_data_dir):
init_db()
init_engineering_schema()
with pytest.raises(ValueError, match="source_refs required"):
create_entity(
entity_type="component",
name="No Provenance",
project="p04-gigabit",
hand_authored=False, # explicit — bypasses the test-conftest auto-flag
)
def test_create_entity_with_hand_authored_needs_no_source_refs(tmp_data_dir):
init_db()
init_engineering_schema()
e = create_entity(
entity_type="component",
name="Human Entry",
project="p04-gigabit",
hand_authored=True,
)
assert e.hand_authored is True
got = get_entity(e.id)
assert got.hand_authored is True
# source_refs stays empty — the hand_authored flag IS the provenance
assert got.source_refs == []
def test_create_entity_with_empty_source_refs_list_is_treated_as_missing(tmp_data_dir):
init_db()
init_engineering_schema()
with pytest.raises(ValueError, match="source_refs required"):
create_entity(
entity_type="component",
name="Empty Refs",
project="p04-gigabit",
source_refs=[],
hand_authored=False,
)
def test_promote_rejects_legacy_candidate_without_provenance(tmp_data_dir):
"""Regression (Codex V1-0 probe): candidate rows can exist in the DB
from before V1-0 enforcement (or from paths that bypass create_entity).
promote_entity must re-check the invariant and refuse to flip a
no-provenance candidate to active. Without this check, the active
store can leak F-8 violations in from legacy data."""
init_db()
init_engineering_schema()
# Simulate a pre-V1-0 candidate by inserting directly into the table,
# bypassing the service-layer invariant. Real legacy rows look exactly
# like this: empty source_refs, hand_authored=0.
import uuid as _uuid
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 Orphan', 'p04-gigabit', "
"'', '{}', 'candidate', 1.0, '[]', '', 'entity', 0, "
"CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)",
(entity_id,),
)
with pytest.raises(ValueError, match="source_refs required"):
promote_entity(entity_id)
# And the row stays a candidate — no half-transition.
got = get_entity(entity_id)
assert got is not None
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
create_entity's symmetry."""
init_db()
init_engineering_schema()
import uuid as _uuid
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', 'Hand Authored Candidate', "
"'p04-gigabit', '', '{}', 'candidate', 1.0, '[]', '', "
"'entity', 1, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)",
(entity_id,),
)
assert promote_entity(entity_id) is True
assert get_entity(entity_id).status == "active"
# ---------- F-5: synchronous conflict-detection hook ----------
def test_active_create_runs_conflict_detection_hook(tmp_data_dir, monkeypatch):
"""status=active writes trigger detect_conflicts_for_entity."""
init_db()
init_engineering_schema()
called_with: list[str] = []
def _fake_detect(entity_id: str):
called_with.append(entity_id)
return []
import atocore.engineering.conflicts as conflicts_mod
monkeypatch.setattr(conflicts_mod, "detect_conflicts_for_entity", _fake_detect)
e = create_entity(
entity_type="component",
name="Active With Hook",
project="p04-gigabit",
source_refs=["test:hook"],
status="active",
)
assert called_with == [e.id]
def test_supersede_runs_conflict_detection_on_new_active(tmp_data_dir, monkeypatch):
"""Regression (Codex V1-0 probe): per plan's 'every active-entity
write path', supersede_entity must trigger synchronous conflict
detection. The subject is the `superseded_by` entity — the one
whose graph state just changed because a new `supersedes` edge was
rooted at it."""
init_db()
init_engineering_schema()
old = create_entity(
entity_type="component",
name="Old Pad",
project="p04-gigabit",
source_refs=["test:old"],
status="active",
)
new = create_entity(
entity_type="component",
name="New Pad",
project="p04-gigabit",
source_refs=["test:new"],
status="active",
)
called_with: list[str] = []
def _fake_detect(entity_id: str):
called_with.append(entity_id)
return []
import atocore.engineering.conflicts as conflicts_mod
monkeypatch.setattr(conflicts_mod, "detect_conflicts_for_entity", _fake_detect)
assert supersede_entity(old.id, superseded_by=new.id) is True
# The detector fires on the `superseded_by` entity — the one whose
# edges just grew a new `supersedes` relationship.
assert new.id in called_with
def test_supersede_hook_fails_open(tmp_data_dir, monkeypatch):
"""Supersede must survive a broken detector per Q-3 flag-never-block."""
init_db()
init_engineering_schema()
old = create_entity(
entity_type="component", name="Old2", project="p04-gigabit",
source_refs=["test:old"], status="active",
)
new = create_entity(
entity_type="component", name="New2", project="p04-gigabit",
source_refs=["test:new"], status="active",
)
def _boom(entity_id: str):
raise RuntimeError("synthetic detector failure")
import atocore.engineering.conflicts as conflicts_mod
monkeypatch.setattr(conflicts_mod, "detect_conflicts_for_entity", _boom)
# The supersede still succeeds despite the detector blowing up.
assert supersede_entity(old.id, superseded_by=new.id) is True
assert get_entity(old.id).status == "superseded"
def test_candidate_create_does_not_run_conflict_hook(tmp_data_dir, monkeypatch):
"""status=candidate writes do NOT trigger detection — the hook is
for active rows only, per V1-0 scope. Candidates are checked at
promote time."""
init_db()
init_engineering_schema()
called: list[str] = []
def _fake_detect(entity_id: str):
called.append(entity_id)
return []
import atocore.engineering.conflicts as conflicts_mod
monkeypatch.setattr(conflicts_mod, "detect_conflicts_for_entity", _fake_detect)
create_entity(
entity_type="component",
name="Candidate No Hook",
project="p04-gigabit",
source_refs=["test:cand"],
status="candidate",
)
assert called == []
# ---------- Q-3: flag, never block ----------
def test_conflict_detector_failure_does_not_block_write(tmp_data_dir, monkeypatch):
"""Per conflict-model.md:256: detection errors must not fail the
write. The entity is still created; only a warning is logged."""
init_db()
init_engineering_schema()
def _boom(entity_id: str):
raise RuntimeError("synthetic detector failure")
import atocore.engineering.conflicts as conflicts_mod
monkeypatch.setattr(conflicts_mod, "detect_conflicts_for_entity", _boom)
# The write still succeeds — no exception propagates.
e = create_entity(
entity_type="component",
name="Hook Fails Open",
project="p04-gigabit",
source_refs=["test:failopen"],
status="active",
)
assert get_entity(e.id) is not None
# ---------- Q-4 (partial): trust-hierarchy — scope_only filters candidates ----------
def test_scope_only_active_does_not_return_candidates(tmp_data_dir):
"""V1-0 partial Q-4: active-scoped listing never returns candidates.
Full trust-hierarchy coverage (no-auto-project-state, etc.) ships in
V1-E per plan."""
init_db()
init_engineering_schema()
active = create_entity(
entity_type="component",
name="Active Alpha",
project="p04-gigabit",
source_refs=["test:alpha"],
status="active",
)
candidate = create_entity(
entity_type="component",
name="Candidate Beta",
project="p04-gigabit",
source_refs=["test:beta"],
status="candidate",
)
listed = get_entities(project="p04-gigabit", status="active", scope_only=True)
ids = {e.id for e in listed}
assert active.id in ids
assert candidate.id not in ids

View File

@@ -161,103 +161,3 @@ def test_memory_detail_shows_superseded_sources(tmp_data_dir):
assert html1 is not None
assert "superseded" in html1
assert "auto-dedup-tier1" in html1 # audit trail shows who merged
# -------------------------------------------------- low-signal wiki filters
# Ambient AKC session memories and test pollution shouldn't dominate domain
# pages / homepage counts. These tests lock the partitioning behaviour.
def test_domain_page_hides_empty_transcript_sessions(tmp_data_dir):
"""Silent-mic AKC sessions (content has '(no transcript)') are ambient
noise — they go into the hidden count, not the main list."""
_init_all()
# One real knowledge memory with tag "optics"
create_memory(
"knowledge",
"CGH null corrector supports F/1.2 asphere testing",
project="p05", confidence=0.9, domain_tags=["optics", "cgh"],
)
# One silent AKC session with the same tag — should NOT appear
create_memory(
"episodic",
"AKC voice session abc (gen-002)\nDuration: 60s, 2 captures\n"
"\n## Transcript\n(no transcript)\n",
project="p05", confidence=0.7,
domain_tags=["optics", "session", "akc", "voice"],
)
html = render_domain("optics")
assert "CGH null corrector" in html
# The hidden-count banner should be present
assert "low-signal" in html or "Ambient provenance" in html
# And the empty-transcript content itself is not rendered inline
assert "(no transcript)" not in html
def test_domain_page_collapses_akc_session_snapshots(tmp_data_dir):
"""AKC voice-session memories are provenance records — count them as
a single collapsed link, don't inline every one."""
_init_all()
for i in range(5):
create_memory(
"episodic",
f"AKC voice session session-{i} (gen-00{i})\nDuration: 120s, 3 captures\n"
f"\n## Transcript\nReal transcript number {i}",
project="p05", confidence=0.7,
domain_tags=["optics", "session", "akc", "voice"],
)
html = render_domain("optics")
# Inline count should mention AKC session snapshots
assert "AKC voice session snapshots" in html
# None of the session transcripts should be pasted inline on the domain
# page (they're provenance, linked via /wiki/activity)
assert "Real transcript number 0" not in html
def test_homepage_stats_exclude_ambient_memory(tmp_data_dir):
"""Homepage system-stats line shows real memory count, pushes ambient
counts into a dimmed sub-segment."""
_init_all()
# 2 real memories + 3 ambient sessions + 1 silent junk
create_memory("knowledge", "Real fact 1", project="p05", confidence=0.8)
create_memory("knowledge", "Real fact 2", project="p05", confidence=0.8)
for i in range(3):
create_memory(
"episodic",
f"AKC voice session s{i} (gen-00{i})\nReal transcript x",
project="p05", confidence=0.7,
domain_tags=["session", "akc", "voice"],
)
create_memory(
"episodic",
"AKC voice session silent (gen-099)\nDuration: 30s, 0 captures\n"
"\n## Transcript\n(no transcript)\n",
project="p05", confidence=0.7,
domain_tags=["session", "akc", "voice"],
)
html = render_homepage()
assert "3 AKC session snapshots" in html
assert "low-signal hidden" in html
# Main count reflects only real knowledge
assert "2 memories" in html
def test_low_signal_predicate_catches_known_patterns():
from atocore.engineering.wiki import _is_low_signal_memory, _is_akc_session_memory
from dataclasses import dataclass
@dataclass
class M:
content: str = ""
domain_tags: list = None
# Explicit empty-transcript — low signal
assert _is_low_signal_memory(M(content="AKC voice session x\n## Transcript\n(no transcript)\n"))
# E2E test pollution — low signal
assert _is_low_signal_memory(M(content="IMG integration test — synthetic session"))
assert _is_low_signal_memory(M(content="synthetic AKC integration session"))
# Real knowledge — NOT low signal
assert not _is_low_signal_memory(M(content="The CGH is mounted to the fold mirror via…"))
# AKC session tag predicate
assert _is_akc_session_memory(M(content="anything", domain_tags=["session", "akc", "voice"]))
assert _is_akc_session_memory(M(content="AKC voice session abc"))
assert not _is_akc_session_memory(M(content="Real fact", domain_tags=["optics"]))