#!/usr/bin/env python3 """真写一章 · 阶段二a 补建:第 1 章的开篇上下文与冻结记录。 原则:不存在没有上下文的写作。第 1 章之前也有设定、大纲、细纲——它的上下文是 asOf=0(开篇前冻结线):证据只有设定包与细纲宣告的新事实,历史正文基线为空。 此前第 1 章按"基线章无上下文"的旧口径写成,看板运行详情因此缺一块上下文冻结; 本脚本按合同补建真实的第 1 章上下文并落冻结行(append-only,幂等),补齐记账。 只组装 + 落冻结,不改写第 1 章正文(正文早已接受为正式内容)。 """ import json import sys import uuid from datetime import datetime, timezone from pathlib import Path SCRIPT_DIR = Path(__file__).resolve().parent AGENT_ROOT = SCRIPT_DIR.parents[1] SKILLS = AGENT_ROOT / ".agent" / "skills" for sub in ( "assemble-context/scripts", "write-next-chapter/scripts", "record-run-evidence/scripts", "prevent-ai-flavor/scripts", ): p = str(SKILLS / sub) if p not in sys.path: sys.path.insert(0, p) from muse_db import connect, DSN # noqa: E402 from assemble_writer_context import assemble_context # noqa: E402 from prevent_ai_flavor import ( # noqa: E402 PreventionContractError, build_prevention_contract, persist_prevention, render_writer_constraints, ) from retrieve_writer_sources import ( # noqa: E402 ProductionCardIndexRepository, FrozenProseRepository, build_retrieval_plan, retrieve_writer_sources, ) from persist_context_freeze import persist_freeze # noqa: E402 from run_writer import calculate_dynamic_output_contract # noqa: E402 WORK_ID = 12 TARGET_CHAPTER = 1 AS_OF = 0 SETTING_SOURCE_REF = {"sourceId": "planning-section:3", "sourceVersion": "v1"} OUTLINE_SOURCE_REF = {"sourceId": "fine-outline:work12-ch1", "sourceVersion": "v1"} def main(): run_id = f"retrofit-work12-ch1-context-{uuid.uuid4().hex[:8]}" generated_at = datetime.now(timezone.utc).isoformat() with connect(readonly=True) as conn: setting_row = conn.execute( "SELECT payload FROM example_planning_section WHERE id=3 AND work_id=%s " "AND section_type='setting' AND state='confirmed' AND deleted=false", (WORK_ID,)).fetchone() outline_row = conn.execute( "SELECT payload FROM example_planning_section WHERE work_id=%s AND " "section_type='fine_outline' AND target_chapter=1 AND state='confirmed' AND " "deleted=false ORDER BY version DESC LIMIT 1", (WORK_ID,)).fetchone() if not setting_row or not outline_row: raise SystemExit("设定包或第1章细纲缺失,无法补建上下文。") setting = setting_row[0] if isinstance(setting_row[0], dict) else json.loads(setting_row[0]) fine_outline = outline_row[0] if isinstance(outline_row[0], dict) else json.loads(outline_row[0]) # 第1章细纲的 declaredNewFacts 是旧形状(纯字符串);合同要 {factId,text,sourceRef}。 # 不重写已确认规划行,在读取侧按合同投影。 normalized_declared = [] for index, fact in enumerate(fine_outline.get("declaredNewFacts", []), 1): if isinstance(fact, dict): normalized_declared.append(fact) else: normalized_declared.append({ "factId": f"fine-outline-ch1:{index}", "text": str(fact), "sourceRef": OUTLINE_SOURCE_REF, }) fine_outline = {**fine_outline, "declaredNewFacts": normalized_declared} # 权威事实证据:设定包(正式设定)+ 细纲宣告的新事实 authoritative_facts = [] for key, label in (("world", "世界观"), ("power_system", "力量体系"), ("core_premise", "核心设定")): if setting.get(key): authoritative_facts.append({ "factId": f"setting:{key}", "fact": f"{label}:{setting[key]}", "sourceType": "formal_setting", "sourceRef": SETTING_SOURCE_REF, "riskLevel": "low", }) for index, fact in enumerate(fine_outline.get("declaredNewFacts", []), 1): text = fact["text"] if isinstance(fact, dict) else str(fact) authoritative_facts.append({ "factId": f"fine-outline-ch1:{index}", "fact": text, "sourceType": "fine_outline_declared_new", "sourceRef": OUTLINE_SOURCE_REF, "riskLevel": "low", }) # 检索计划与执行(asOf=0;新书无卡,卡线诚实返空;事实线取权威事实) token_budget = {"maxContextChars": 200000} plan = build_retrieval_plan( run_id=run_id, work_id=WORK_ID, target_chapter=TARGET_CHAPTER, as_of=AS_OF, fine_outline=fine_outline, card_index_version="knowledge-index-v1", prose_index_version="content-block-v1", token_budget=token_budget) retrieval = retrieve_writer_sources( plan=plan, card_repository=ProductionCardIndexRepository(), prose_repository=FrozenProseRepository(dsn=DSN, tenant_id=0), authoritative_facts=authoritative_facts) print(f"检索: 卡={len(retrieval['cards'])} 事实={len(retrieval['factEvidence'])} " f"原文={len(retrieval['proseEvidence'])}(asOf=0,历史正文必为空)") # 组装第 1 章上下文(asOf=0,无历史正文基线)并落冻结 output_contract = calculate_dynamic_output_contract( fine_outline=fine_outline, recent_chapter_bodies=[]) narrative_state = { "time": "故事开篇之前", "location": "深渊星域", "characterPositions": {}, "immediateSituation": "一切尚未发生;按设定与第1章细纲展开。", } authorization_snapshot = {"snapshotId": "auth-work12-production-v1", "allowedPurpose": "production_generation", "verifiedAt": generated_at, "sourceVersion": "v1"} try: humanization_contract = build_prevention_contract(f"work:{WORK_ID}", load_database=True) humanization_contract["writer_constraints"] = render_writer_constraints(humanization_contract) persist_prevention(humanization_contract) except PreventionContractError as exc: raise SystemExit(f"人感前置预防合同失败:{exc}") from exc assembled = assemble_context( run_id=run_id, attempt=1, mode="production", purpose="production", quality_policy_version="writer-production-v1", work_id=WORK_ID, target_chapter=TARGET_CHAPTER, as_of=AS_OF, source_version="outline@v1", authorization_snapshot=authorization_snapshot, source_status="active", retrieval_plan=plan, retrieval_result=retrieval, fine_outline=fine_outline, narrative_state=narrative_state, recent_chapters=[], output_contract=output_contract, token_budget=token_budget, pattern_references=[], humanization_contract=humanization_contract, generated_at=generated_at, evidence_strategy="production_dual_evidence") (SCRIPT_DIR / "artifacts").mkdir(exist_ok=True) (SCRIPT_DIR / "artifacts" / f"{run_id}-writer-context.json").write_text( assembled["contextJson"], encoding="utf-8") frozen = persist_freeze(assembled) print(f"第1章上下文冻结: {json.dumps(frozen, ensure_ascii=False)}") print(f"事实证据 {len(assembled['context']['factEvidence'])} 条," f"正文证据 {len(assembled['context']['proseEvidence'])} 条(应为 0)") print(f"RUN_ID={run_id}") if __name__ == "__main__": main()