# SPDX-License-Identifier: AGPL-3.0-only # Copyright (c) 2026 sol pbc from __future__ import annotations import json import pytest from solstone.talent import pulse from solstone.think.day_accumulator import read_latest @pytest.fixture def journal(tmp_path, monkeypatch): monkeypatch.setenv("SOLSTONE_JOURNAL", str(tmp_path)) return tmp_path def _select_local_provider(journal_path): config_dir = journal_path / "config" config_dir.mkdir(parents=True, exist_ok=True) (config_dir / "journal.json").write_text( json.dumps( { "providers": { "active": {"provider": "local", "model": "local/qwen3.5-4b"}, } } ), encoding="utf-8", ) def _pulse_payload(**overrides): payload = { "title": "Focused morning", "one_sentence": "The morning is centered on a launch review.", "full_details": "The owner has a coherent launch-review block in motion.", "needs_you": ["Review the launch checklist."], } payload.update(overrides) return payload def test_post_process_persists_valid_pulse_record(journal): config = { "day": "20260611", "model": "test-model", "_pulse_window_note": { "segments": 1, "activities": 0, "input_segments": 1, "input_activities": 0, "since_ms": 123, "gaps": [], }, } returned = pulse.post_process(json.dumps(_pulse_payload()), config) summary = json.loads(returned) assert summary == _pulse_payload() record = read_latest("20260611", "pulse", lookback_days=0) assert record is not None for key, value in _pulse_payload().items(): assert record[key] == value assert record["model"] == "test-model" assert record["generated_at"].endswith("Z") assert isinstance(record["ts"], int) assert record["window"] == config["_pulse_window_note"] @pytest.mark.parametrize("result", ["not json", json.dumps({"title": "Incomplete"})]) def test_post_process_falls_back_for_malformed_model_output(journal, result): default = _pulse_payload( title="Fallback", one_sentence="Fallback sentence.", full_details="Fallback details.", needs_you=["Use the deterministic fallback."], ) config = {"day": "20260611", "model": "test-model", "_pulse_default": default} returned = pulse.post_process(result, config) summary = json.loads(returned) assert summary == default record = read_latest("20260611", "pulse", lookback_days=0) assert record is not None for key, value in default.items(): assert record[key] == value assert record["model"] == "test-model" assert "generated_at" in record assert "ts" in record assert "window" in record def test_normalize_pulse_coerces_and_clamps_fields(): default = _pulse_payload( title="Fallback", one_sentence="Fallback sentence.", full_details="Fallback details.", needs_you=["Fallback need."], ) raw = { "title": "T" * 100, "one_sentence": "S" * 260, "full_details": "D" * 1900, "needs_you": [ "one", 42, None, "", "x" * 300, "five", "six", "seven", "eight", "nine", ], "ignored": "dropped", } summary = pulse._normalize_pulse(raw, default) assert set(summary) == {"title", "one_sentence", "full_details", "needs_you"} assert summary["title"] == "T" * pulse._TITLE_MAX assert summary["one_sentence"] == "S" * pulse._SENTENCE_MAX assert summary["full_details"] == "D" * pulse._DETAILS_MAX assert summary["needs_you"] == [ "one", "42", "x" * pulse._NEED_MAX, "five", "six", "seven", "eight", ] assert ( pulse._normalize_pulse( { "title": "", "one_sentence": " ", "full_details": "", "needs_you": ["ignored"], }, default, ) == default ) def test_pre_process_includes_segment_timeline_and_missing_gap(journal, monkeypatch): day = "20260611" segment = "101500_300" seg_dir = journal / "chronicle" / day / "desktop" / segment seg_dir.mkdir(parents=True) (seg_dir / "timeline.json").write_text( json.dumps( { "title": "Launch review", "description": "The segment focused on launch readiness.", } ), encoding="utf-8", ) (journal / "identity").mkdir() (journal / "identity" / "partner.md").write_text( "Partner context", encoding="utf-8" ) monkeypatch.setattr(pulse, "get_current", lambda: {"attention": "clear"}) monkeypatch.setattr(pulse, "get_imports", lambda: {"pending": []}) monkeypatch.setattr(pulse, "get_facets", lambda: []) monkeypatch.setattr(pulse, "load_recent_entity_names", lambda limit=12: ["Alice"]) result = pulse.pre_process( { "day": day, "cadence_window": { "since_ms": 1000, "segments": [ {"stream": "desktop", "segment": segment, "ts": 2000}, {"stream": None, "segment": "missing_300", "ts": 1000}, ], "activities": [], }, } ) assert result is not None template_vars = result["template_vars"] assert "Launch review" in template_vars["completed_since"] assert ( "The segment focused on launch readiness." in template_vars["completed_since"] ) assert "no timeline.json found for segment missing_300" in template_vars["gaps"] assert "Partner context" == template_vars["partner_profile"] assert pulse._compact_json(["Alice"]) == template_vars["recent_entities"] def test_pre_process_total_failure_returns_skip_reason(monkeypatch): monkeypatch.setattr( pulse, "_completed_since", lambda day, config, gaps: (_ for _ in ()).throw(RuntimeError("boom")), ) result = pulse.pre_process({"day": "20260611"}) assert result is not None assert result["skip_reason"] == "pulse pre-hook failed: boom" def test_accumulate_suppresses_single_file_output_path(journal): from solstone.think.talent import get_talent, get_talent_configs from solstone.think.talents import prepare_config from solstone.think.thinking import _apply_output_persistence raw_config = get_talent_configs()["pulse"] pulse_config = get_talent("pulse") assert raw_config["output"] == "json" assert raw_config["schema"] == "pulse.schema.json" assert pulse_config["output"] == "json" assert pulse_config["json_schema"]["required"] == [ "title", "one_sentence", "full_details", "needs_you", ] assert pulse_config["accumulate"] is True request_config = {} _apply_output_persistence(request_config, pulse_config, force_refresh=False) assert "output" not in request_config assert "refresh" not in request_config _select_local_provider(journal) prepared = prepare_config({"name": "pulse", "day": "20260611"}) assert "output_path" not in prepared