# SPDX-License-Identifier: AGPL-3.0-only # Copyright (c) 2026 sol pbc import json from datetime import UTC, datetime import pytest _DAY_MS = 86_400_000 def _utc_ms(value: str) -> int: return int(datetime.fromisoformat(value.replace("Z", "+00:00")).timestamp() * 1000) def _ledger_close_edits(record: dict) -> list[dict]: return [ edit for edit in record.get("edits", []) if isinstance(edit, dict) and edit.get("fields") == ["ledger_close"] ] def _minimal_facet_tree(tmp_path, facets=("work",), *, muted_facets=()) -> None: for facet in facets: facet_dir = tmp_path / "facets" / facet facet_dir.mkdir(parents=True, exist_ok=True) (facet_dir / "activities").mkdir(exist_ok=True) (facet_dir / "facet.json").write_text( json.dumps( { "title": facet.title(), "description": "", "color": "", "emoji": "", "muted": facet in set(muted_facets), } ), encoding="utf-8", ) def _activity_record( record_id: str, created_at: int, *, activity: str = "meeting", hidden: bool = False, ) -> dict: return { "id": record_id, "activity": activity, "description": f"{record_id} description", "segments": [f"{record_id.split('_', 1)[-1]}"], "created_at": created_at, "hidden": hidden, } def _commitment( *, owner: str = "Mina", owner_entity_id: str | None = "mina", action: str = "send proposal", counterparty: str = "Ravi", counterparty_entity_id: str | None = "ravi", when: str = "tomorrow", context: str = "Commitment context.", ) -> dict: return { "owner": owner, "owner_entity_id": owner_entity_id, "action": action, "counterparty": counterparty, "counterparty_entity_id": counterparty_entity_id, "when": when, "context": context, } def _closure( *, owner: str = "Mina", owner_entity_id: str | None = "mina", action: str = "send proposal", counterparty: str = "Ravi", counterparty_entity_id: str | None = "ravi", resolution: str = "sent", context: str = "Closure context.", ) -> dict: return { "owner": owner, "owner_entity_id": owner_entity_id, "action": action, "counterparty": counterparty, "counterparty_entity_id": counterparty_entity_id, "resolution": resolution, "context": context, } def _decision( *, owner: str = "Mina", owner_entity_id: str | None = "mina", action: str = "move launch review", context: str = "Decision context.", ) -> dict: return { "owner": owner, "owner_entity_id": owner_entity_id, "action": action, "context": context, } def _write_story_activity( facet: str, day: str, record_id: str, created_at: int, *, commitments: list[dict] | None = None, closures: list[dict] | None = None, decisions: list[dict] | None = None, hidden: bool = False, ) -> None: from solstone.think.activities import append_activity_record, merge_story_fields append_activity_record( facet, day, _activity_record(record_id, created_at, hidden=hidden), ) merge_story_fields( facet, day, record_id, story={ "talent": "story", "body": f"{record_id} summary", "topics": ["ledger"], "confidence": 0.9, }, commitments=commitments or [], closures=closures or [], decisions=decisions or [], relations=[], actor="story", ) def test_append_edit_payload_validation(): from solstone.think.activities import append_edit merged = append_edit( {"id": "coding_090000_300"}, actor="cli:update", fields=["details"], note="updated", payload={"foo": "bar"}, ) assert merged["edits"][-1]["foo"] == "bar" with pytest.raises( ValueError, match="payload cannot overwrite canonical edit fields" ): append_edit( {"id": "coding_090000_300"}, actor="cli:update", fields=["details"], note="updated", payload={"timestamp": "x"}, ) with pytest.raises(TypeError, match="payload must be dict\\[str, Any\\]"): append_edit( {"id": "coding_090000_300"}, actor="cli:update", fields=["details"], note="updated", payload="not a dict", ) def test_pairing_happy_path(tmp_path, monkeypatch): from solstone.think.surfaces import ledger as ledger_surface monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) _minimal_facet_tree(tmp_path) _write_story_activity( "work", "20260410", "meeting_090000_300", _utc_ms("2026-04-10T09:00:00Z"), commitments=[_commitment()], ) _write_story_activity( "work", "20260412", "meeting_110000_300", _utc_ms("2026-04-12T11:00:00Z"), closures=[_closure()], ) items = ledger_surface.list(state="closed") assert len(items) == 1 assert items[0].state == "closed" assert items[0].summary == "send proposal" assert len(items[0].sources) == 2 def test_action_fuzzy_just_above_threshold(tmp_path, monkeypatch): from solstone.think.surfaces import ledger as ledger_surface monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) _minimal_facet_tree(tmp_path) _write_story_activity( "work", "20260410", "meeting_090000_300", _utc_ms("2026-04-10T09:00:00Z"), commitments=[_commitment(action="send proposal")], ) _write_story_activity( "work", "20260411", "meeting_100000_300", _utc_ms("2026-04-11T10:00:00Z"), closures=[_closure(action="sent the proposal")], ) assert ledger_surface._actions_match( ledger_surface._normalize_action("send proposal"), ledger_surface._normalize_action("sent the proposal"), ) assert len(ledger_surface.list(state="closed")) == 1 def test_action_fuzzy_just_below_threshold(tmp_path, monkeypatch): from solstone.think.surfaces import ledger as ledger_surface monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) _minimal_facet_tree(tmp_path) _write_story_activity( "work", "20260410", "meeting_090000_300", _utc_ms("2026-04-10T09:00:00Z"), commitments=[_commitment(action="send proposal")], ) _write_story_activity( "work", "20260411", "meeting_100000_300", _utc_ms("2026-04-11T10:00:00Z"), commitments=[_commitment(action="share the proposal")], ) assert not ledger_surface._actions_match( ledger_surface._normalize_action("send proposal"), ledger_surface._normalize_action("share the proposal"), ) actions = [item.action for item in ledger_surface.list(state="open")] assert actions == ["send proposal", "share the proposal"] def test_cross_facet_dedup(tmp_path, monkeypatch): from solstone.think.surfaces import ledger as ledger_surface monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) _minimal_facet_tree(tmp_path, facets=("work", "personal")) _write_story_activity( "work", "20260410", "meeting_090000_300", _utc_ms("2026-04-10T09:00:00Z"), commitments=[_commitment()], ) _write_story_activity( "personal", "20260412", "meeting_100000_300", _utc_ms("2026-04-12T10:00:00Z"), closures=[_closure()], ) items = ledger_surface.list(state="closed") assert len(items) == 1 assert {source.facet for source in items[0].sources} == {"work", "personal"} def test_manual_close_round_trip(tmp_path, monkeypatch): from solstone.think.activities import load_activity_records from solstone.think.surfaces import ledger as ledger_surface monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) _minimal_facet_tree(tmp_path) _write_story_activity( "work", "20260410", "meeting_090000_300", _utc_ms("2026-04-10T09:00:00Z"), commitments=[_commitment()], ) item = ledger_surface.list(state="open")[0] closed = ledger_surface.close(item.id, note="done") record = load_activity_records("work", "20260410", include_hidden=True)[0] manual_edit = _ledger_close_edits(record)[0] assert closed.state == "closed" assert closed.closed_at == _utc_ms(manual_edit["timestamp"]) refreshed = ledger_surface.get(item.id) assert refreshed is not None assert refreshed.state == "closed" assert refreshed.closed_at == closed.closed_at assert any(source.field == "edits" for source in refreshed.sources) def test_idempotent_reclose(tmp_path, monkeypatch): from solstone.think.activities import load_activity_records from solstone.think.surfaces import ledger as ledger_surface monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) _minimal_facet_tree(tmp_path) _write_story_activity( "work", "20260410", "meeting_090000_300", _utc_ms("2026-04-10T09:00:00Z"), commitments=[_commitment()], ) item = ledger_surface.list(state="open")[0] ledger_surface.close(item.id, note="first") ledger_surface.close(item.id, note="second") record = load_activity_records("work", "20260410", include_hidden=True)[0] closes = [ edit for edit in record["edits"] if edit.get("fields") == ["ledger_close"] ] assert len(closes) == 1 def test_close_as_dropped(tmp_path, monkeypatch): from solstone.think.surfaces import ledger as ledger_surface monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) _minimal_facet_tree(tmp_path) _write_story_activity( "work", "20260410", "meeting_090000_300", _utc_ms("2026-04-10T09:00:00Z"), commitments=[_commitment()], ) item = ledger_surface.list(state="open")[0] dropped = ledger_surface.close(item.id, note="not needed", as_state="dropped") assert dropped.state == "dropped" def test_close_with_new_as_state_appends_and_latest_manual_wins(tmp_path, monkeypatch): from solstone.think.activities import load_activity_records from solstone.think.surfaces import ledger as ledger_surface monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) _minimal_facet_tree(tmp_path) _write_story_activity( "work", "20260410", "meeting_090000_300", _utc_ms("2026-04-10T09:00:00Z"), commitments=[_commitment()], ) item = ledger_surface.list(state="open")[0] ledger_surface.close(item.id, note="done", as_state="closed") dropped = ledger_surface.close(item.id, note="actually dropped", as_state="dropped") assert dropped.state == "dropped" record = load_activity_records("work", "20260410", include_hidden=True)[0] closes = _ledger_close_edits(record) assert [edit["ledger_close"]["as_state"] for edit in closes] == [ "closed", "dropped", ] assert dropped.closed_at == _utc_ms(closes[-1]["timestamp"]) def test_decisions_dedup(tmp_path, monkeypatch): from solstone.think.surfaces import ledger as ledger_surface monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) _minimal_facet_tree(tmp_path) _write_story_activity( "work", "20260410", "meeting_090000_300", _utc_ms("2026-04-10T09:00:00Z"), decisions=[_decision()], ) _write_story_activity( "work", "20260410", "meeting_100000_300", _utc_ms("2026-04-10T10:00:00Z"), decisions=[_decision(context="Later duplicate.")], ) results = ledger_surface.decisions() assert len(results) == 1 assert results[0].created_at == _utc_ms("2026-04-10T09:00:00Z") assert results[0].source.activity_id == "meeting_090000_300" def test_missing_entity_id_pairing(tmp_path, monkeypatch): from solstone.think.surfaces import ledger as ledger_surface monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) _minimal_facet_tree(tmp_path, facets=("work", "personal")) _write_story_activity( "work", "20260410", "meeting_090000_300", _utc_ms("2026-04-10T09:00:00Z"), commitments=[_commitment(owner_entity_id=None)], ) _write_story_activity( "personal", "20260411", "meeting_100000_300", _utc_ms("2026-04-11T10:00:00Z"), closures=[_closure(owner_entity_id=None, action="sent the proposal")], ) matched = ledger_surface.list(state="closed") assert len(matched) == 1 _write_story_activity( "work", "20260412", "meeting_110000_300", _utc_ms("2026-04-12T11:00:00Z"), commitments=[_commitment(owner_entity_id=None, action="draft status update")], ) _write_story_activity( "personal", "20260413", "meeting_120000_300", _utc_ms("2026-04-13T12:00:00Z"), closures=[ _closure( owner_entity_id="mina", action="draft status update", counterparty_entity_id="ravi", ) ], ) open_items = ledger_surface.list(state="open") assert any(item.action == "draft status update" for item in open_items) def test_missing_counterparty_id_pairing_falls_back_to_text(tmp_path, monkeypatch): from solstone.think.surfaces import ledger as ledger_surface monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) _minimal_facet_tree(tmp_path, facets=("work", "personal")) _write_story_activity( "work", "20260410", "meeting_090000_300", _utc_ms("2026-04-10T09:00:00Z"), commitments=[ _commitment( counterparty="Finance Team", counterparty_entity_id=None, owner_entity_id=None, ) ], ) _write_story_activity( "personal", "20260411", "meeting_100000_300", _utc_ms("2026-04-11T10:00:00Z"), closures=[ _closure( action="sent the proposal", counterparty="Finance Team", counterparty_entity_id=None, owner_entity_id=None, ) ], ) items = ledger_surface.list(state="closed") assert len(items) == 1 def test_explicit_facets_include_muted_facet(tmp_path, monkeypatch): from solstone.think.surfaces import ledger as ledger_surface monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) _minimal_facet_tree(tmp_path, facets=("work", "quiet"), muted_facets=("quiet",)) _write_story_activity( "quiet", "20260410", "meeting_090000_300", _utc_ms("2026-04-10T09:00:00Z"), commitments=[_commitment(action="muted facet item")], ) assert ledger_surface.list(state="open") == [] explicit = ledger_surface.list(state="open", facets=["quiet"]) assert [item.action for item in explicit] == ["muted facet item"] def test_hidden_record_exclusion(tmp_path, monkeypatch): from solstone.think.surfaces import ledger as ledger_surface monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) _minimal_facet_tree(tmp_path) _write_story_activity( "work", "20260410", "meeting_090000_300", _utc_ms("2026-04-10T09:00:00Z"), commitments=[_commitment()], hidden=True, ) assert ledger_surface.list(state="all") == [] def test_sort_default_varies_by_state(tmp_path, monkeypatch): from solstone.think.surfaces import ledger as ledger_surface monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) _minimal_facet_tree(tmp_path) now_ms = int(datetime.now(UTC).timestamp() * 1000) _write_story_activity( "work", "20260410", "meeting_090000_300", now_ms - (3 * _DAY_MS), commitments=[_commitment(action="older open")], ) _write_story_activity( "work", "20260411", "meeting_100000_300", now_ms - _DAY_MS, commitments=[_commitment(action="newer open")], ) open_items = ledger_surface.list(state="open") assert [item.action for item in open_items] == ["older open", "newer open"] _write_story_activity( "work", "20260412", "meeting_110000_300", now_ms - (10 * _DAY_MS), commitments=[_commitment(action="older closed")], ) _write_story_activity( "work", "20260413", "meeting_120000_300", now_ms - (5 * _DAY_MS), closures=[_closure(action="older closed")], ) _write_story_activity( "work", "20260414", "meeting_130000_300", now_ms - (8 * _DAY_MS), commitments=[_commitment(action="newer closed")], ) _write_story_activity( "work", "20260415", "meeting_140000_300", now_ms - _DAY_MS, closures=[_closure(action="newer closed")], ) closed_items = ledger_surface.list(state="closed") assert [item.action for item in closed_items] == ["newer closed", "older closed"] def test_manual_dropped_overrides_earlier_story_closure(tmp_path, monkeypatch): from solstone.think.activities import load_activity_records from solstone.think.surfaces import ledger as ledger_surface monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) _minimal_facet_tree(tmp_path) _write_story_activity( "work", "20260410", "meeting_090000_300", _utc_ms("2026-04-10T09:00:00Z"), commitments=[_commitment()], ) _write_story_activity( "work", "20260411", "meeting_100000_300", _utc_ms("2026-04-11T10:00:00Z"), closures=[_closure()], ) item = ledger_surface.list(state="closed")[0] refreshed = ledger_surface.close( item.id, note="operator says drop", as_state="dropped" ) record = load_activity_records("work", "20260410", include_hidden=True)[0] manual_edit = _ledger_close_edits(record)[-1] assert refreshed.state == "dropped" assert refreshed.closed_at == _utc_ms(manual_edit["timestamp"]) assert refreshed.closed_at != _utc_ms("2026-04-11T10:00:00Z") assert any(source.field == "closures" for source in refreshed.sources) assert any( source.field == "edits" and source.activity_id == "meeting_090000_300" for source in refreshed.sources )