personal memory agent
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

entities:001 migration: return (entities, 0) on no-facets fresh journal

load_all_legacy_entities was returning bare `[]` on the early-exit
path when `facets/` doesn't exist, but the caller at :261 unpacks
the result as `facet_entities, skipped_detached = ...`. Raises
`ValueError: not enough values to unpack (expected 2, got 0)`.

This maint task auto-runs on every supervisor startup
(think/supervisor.py:1577-1583), so every fresh install with no
`facets/` dir hits the crash before the UI renders. Ramon's
install on 2026-04-22 caught it — silent P0 for new installs.

Fix:
- Return `[], 0` instead of `[]` on the early-exit path.
- Update return type annotation to `tuple[list[FacetEntity], int]`.

Test coverage (tests/test_maint_001_migrate_to_journal_entities.py):
- Fresh journal with no `facets/` dir — asserts `(empty, 0)` return.
- End-to-end migrate_entities() against fresh journal — regression
guard for the ValueError.
- Normal path with populated `facets/` — no regression; skipped
count still reflects detached entities.

Verified the new tests fail against the buggy code and pass against
the fix; all 33 maint-suite tests pass together.

Source: vpe/req_a5zuiqkg (from cpo, Ramon install triage report
b7d4cfa8).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

+78 -2
+4 -2
apps/entities/maint/001_migrate_to_journal_entities.py
··· 88 88 canonical_id: str | None = None 89 89 90 90 91 - def load_all_legacy_entities(journal_path: Path) -> list[FacetEntity]: 91 + def load_all_legacy_entities( 92 + journal_path: Path, 93 + ) -> tuple[list[FacetEntity], int]: 92 94 """Load all entities from legacy facets/*/entities.jsonl files.""" 93 95 facets_dir = journal_path / "facets" 94 96 if not facets_dir.exists(): 95 - return [] 97 + return [], 0 96 98 97 99 all_entities = [] 98 100 skipped_detached = 0
+74
tests/test_maint_001_migrate_to_journal_entities.py
··· 1 + # SPDX-License-Identifier: AGPL-3.0-only 2 + # Copyright (c) 2026 sol pbc 3 + 4 + from __future__ import annotations 5 + 6 + import importlib 7 + import json 8 + from pathlib import Path 9 + 10 + mod = importlib.import_module("apps.entities.maint.001_migrate_to_journal_entities") 11 + 12 + 13 + def test_load_all_legacy_entities_no_facets_dir(tmp_path: Path) -> None: 14 + """Fresh journal with no facets/ dir must return (empty list, 0) — not bare [].""" 15 + journal = tmp_path / "journal" 16 + journal.mkdir() 17 + 18 + entities, skipped = mod.load_all_legacy_entities(journal) 19 + 20 + assert entities == [] 21 + assert skipped == 0 22 + 23 + 24 + def test_migrate_entities_fresh_journal_no_crash(tmp_path: Path) -> None: 25 + """Fresh-install path: running the migration against a journal with no facets/ 26 + must complete cleanly. Regression guard for the ValueError (expected 2, got 0) 27 + that crashed Ramon's install on 2026-04-22.""" 28 + journal = tmp_path / "journal" 29 + journal.mkdir() 30 + 31 + summary = mod.migrate_entities(journal, dry_run=False) 32 + 33 + assert summary == {"loaded": 0, "canonicals": 0, "merges": 0, "relationships": 0} 34 + 35 + 36 + def test_load_all_legacy_entities_with_facets(tmp_path: Path) -> None: 37 + """Normal path: journal with facets/ directory still returns (entities, skipped).""" 38 + journal = tmp_path / "journal" 39 + facet_dir = journal / "facets" / "work" 40 + facet_dir.mkdir(parents=True) 41 + 42 + entities_file = facet_dir / "entities.jsonl" 43 + entities_file.write_text( 44 + json.dumps( 45 + { 46 + "name": "Acme Corp", 47 + "type": "organization", 48 + "description": "A company.", 49 + "aka": ["Acme"], 50 + "is_principal": False, 51 + "detached": False, 52 + } 53 + ) 54 + + "\n" 55 + + json.dumps( 56 + { 57 + "name": "Ghost Entity", 58 + "type": "person", 59 + "description": "Soft-deleted.", 60 + "aka": [], 61 + "is_principal": False, 62 + "detached": True, 63 + } 64 + ) 65 + + "\n", 66 + encoding="utf-8", 67 + ) 68 + 69 + entities, skipped = mod.load_all_legacy_entities(journal) 70 + 71 + assert len(entities) == 1 72 + assert entities[0].name == "Acme Corp" 73 + assert entities[0].facet == "work" 74 + assert skipped == 1