"""P1:lesson 证据强制、升格 merge、回放与生产 flow 同一入口、web 写面闭集。""" from __future__ import annotations import inspect import json import os import pathlib import sqlite3 import sys import tempfile import unittest ROOT = next( parent for parent in (pathlib.Path(__file__).resolve().parent, *pathlib.Path(__file__).resolve().parents) if (parent / "AGENTS.md").is_file() and (parent / ".git").exists() ) if str(ROOT) not in sys.path: sys.path.insert(0, str(ROOT)) from unittest import mock from muse import replay as replay_mod # noqa: E402 from muse.flow import dispatch as dispatch_mod # noqa: E402 from muse.flow.adopt import adopt_candidate # noqa: E402 from muse.store import ( # noqa: E402 add_review, approve_and_merge_lesson, connect, register_lesson, ) from web import app as webapp # noqa: E402 class CompoundingTest(unittest.TestCase): def setUp(self) -> None: self.tmp = pathlib.Path(tempfile.mkdtemp()) self.db = self.tmp / "muse.db" with connect(self.db) as conn: conn.execute( """INSERT INTO runs(id, created_at, kind, input_json, output_text, meta_json, skill_set_hash) VALUES ('run-p1', datetime('now'), 'agent.writer', '{"k":1}', '旧稿', '{}', 'hash')""" ) conn.commit() self.review_id = add_review( run_id="run-p1", target="candidate", action="revise", reviewer="qingse", reason="改钩子", before_text="旧稿", after_text="新稿", path=self.db, ) def test_empty_evidence_ids_are_rejected_by_python_and_db(self) -> None: with self.assertRaises(ValueError): register_lesson( lesson_id="bad", source_run_ids=[], source_review_ids=[str(self.review_id)], kind="craft", title="空证据", content="x", target_ref="打磨小说语言", lessons_dir=self.tmp / "lessons", path=self.db, ) with connect(self.db) as conn: with self.assertRaises(sqlite3.IntegrityError): conn.execute( """INSERT INTO lessons(id, source_run_ids, source_review_ids, kind, title, content_path, status) VALUES ('bad2', '[]', '[]', 'craft', 'x', 'p.md', 'proposed')""" ) def test_lesson_merges_into_skill_references_with_source_ids(self) -> None: register_lesson( lesson_id="hook-1", source_run_ids=["run-p1"], source_review_ids=[str(self.review_id)], kind="craft", title="钩子再紧", content="章末只留一个未解动作。\n", target_ref="打磨小说语言", lessons_dir=self.tmp / "pending", path=self.db, ) dest = approve_and_merge_lesson( "hook-1", decided_by="qingse", rationale="人审通过", references_dir=self.tmp / "打磨小说语言" / "references", path=self.db, ) self.assertTrue(dest.is_file()) self.assertIn("未解动作", dest.read_text(encoding="utf-8")) with connect(self.db) as conn: row = conn.execute("SELECT * FROM lessons WHERE id='hook-1'").fetchone() self.assertEqual(row["status"], "promoted") self.assertEqual(json.loads(row["source_run_ids"]), ["run-p1"]) self.assertEqual(json.loads(row["source_review_ids"]), [str(self.review_id)]) def test_replay_imports_the_same_production_flow(self) -> None: self.assertIs(replay_mod.production_run_dispatch, dispatch_mod.run_dispatch) source = inspect.getsource(replay_mod.replay_run) self.assertIn("production_run_dispatch", source) def test_web_write_surface_is_reviews_revisions_adopt_only(self) -> None: self.assertEqual(webapp.WRITE_ACTIONS, frozenset({"reviews", "revisions", "adopt"})) source = pathlib.Path(webapp.__file__).read_text(encoding="utf-8") self.assertNotIn("INSERT INTO cards", source) self.assertNotIn("INSERT INTO runs", source) os.environ["MUSE_DB"] = str(self.db) self.addCleanup(os.environ.pop, "MUSE_DB", None) self.assertIn("adopt_candidate", inspect.getsource(webapp.adopt)) with mock.patch.object(webapp, "adopt_candidate", wraps=adopt_candidate) as spy: review_id = webapp.adopt("run-p1", "qingse", "收下") spy.assert_called_once_with("run-p1", "qingse", "收下") self.assertIsInstance(review_id, int) revise_id = webapp.revise("run-p1", "qingse", "改", "旧", "新") with connect(self.db) as conn: actions = [ row[0] for row in conn.execute( "SELECT action FROM reviews WHERE run_id='run-p1' ORDER BY id" ) ] revision_n = conn.execute("SELECT COUNT(*) FROM revisions").fetchone()[0] self.assertIn("adopt", actions) self.assertIn("revise", actions) self.assertGreaterEqual(revision_n, 2) self.assertNotEqual(review_id, revise_id) if __name__ == "__main__": unittest.main()