#!/usr/bin/env python3 """从实验库只读组装回放评测配置。 本适配器只做 SELECT 和临时文件输出,不写数据库。正文读取仅允许通过 `load_frozen_prose_rows()` 在只读快照内读取冻结线以前的 Canonical block; 参考作品目标章仍只进入审计侧 proxy,历史上下文和三臂卡注入严格分开。 """ from __future__ import annotations import argparse import copy import hashlib import json import re from pathlib import Path from typing import Any, Mapping, Sequence import psycopg from psycopg.rows import dict_row from build_snapshot import filter_milestones, filter_outline_windows, normalize_chapter DSN = ( "postgresql://root:f6710e2d0294eb1c10e26a805a64bc54@100.64.0.8:5433/muse-example" "?keepalives=1&keepalives_idle=15&keepalives_interval=5&keepalives_count=3" ) TENANT_ID = 1 REPO_ROOT = Path(__file__).resolve().parents[4] DEFAULT_SNAPSHOT_VERSION = "next_fine_outline_replay_v0" class AdapterError(ValueError): """只读适配输入缺失、越界或不能证明安全时抛出。""" def _safe_json(value: Any) -> str: """用固定格式序列化配置,保证运行版本可复现。""" return json.dumps(value, ensure_ascii=False, sort_keys=True, separators=(",", ":")) def _required_chapter(value: Any, field: str) -> int: """把章号限制为明确正整数,拒绝猜测性转换。""" chapter = normalize_chapter(value) if chapter is None: raise AdapterError(f"{field} 必须是正整数章号") return chapter def _reference_version(work: Mapping[str, Any], reference: Mapping[str, Any]) -> str: """由数据库可见的修订和导入计数形成稳定来源版本。""" work_id = work.get("id") revision = work.get("revision") or 0 imported = reference.get("imported_chapter_count") or 0 return f"db-work-{work_id}-rev-{revision}-imported-{imported}" def _row_id(row: Mapping[str, Any]) -> str: """读取卡行主键并统一为来源 ID 字符串。""" value = row.get("id") if value is None or str(value).strip() == "": raise AdapterError("卡行缺少 id") return str(value) def _normalize_id_list(value: Any, field: str) -> list[str]: """校验预注册卡 ID 列表,不根据目标章临时猜卡。""" if not isinstance(value, list) or not value: raise AdapterError(f"{field} 必须是非空数组") if any( isinstance(item, bool) or not isinstance(item, (int, str)) or not str(item).strip().isdigit() or int(item) <= 0 for item in value ): raise AdapterError(f"{field} 只能包含正整数卡 ID") result = [str(item) for item in value if str(item).strip()] if len(result) != len(value) or len(result) != len(set(result)): raise AdapterError(f"{field} 含空 ID 或重复 ID") return result def _card_history(payload: Mapping[str, Any]) -> list[Mapping[str, Any]]: """只从卡的历史字段取里程碑,不使用终态摘要字段。""" fields = payload.get("字段") if not isinstance(fields, Mapping): raise AdapterError("卡缺少字段对象,拒绝使用静态卡内容") for key in ("演变历程", "演变轨迹", "milestones"): value = fields.get(key) if isinstance(value, list): return value raise AdapterError("卡缺少可按绝对章号冻结的历史字段") def _structured_source_refs(payload: Mapping[str, Any], history: Sequence[Mapping[str, Any]], as_of: int) -> list[dict[str, Any]]: """提取卡内结构化原文指针,文本“出处”不能替代块级引用。""" candidates: list[Any] = [payload.get("sourceRefs")] fields = payload.get("字段") if isinstance(fields, Mapping): candidates.append(fields.get("sourceRefs")) candidates.extend(item.get("sourceRefs") for item in history if isinstance(item, Mapping)) refs: list[dict[str, Any]] = [] for candidate in candidates: if not isinstance(candidate, list): continue for raw_ref in candidate: if not isinstance(raw_ref, Mapping): continue chapter = normalize_chapter(raw_ref.get("chapter") or raw_ref.get("章")) if chapter is None or chapter > as_of: continue normalized = copy.deepcopy(dict(raw_ref)) normalized["chapter"] = chapter normalized.pop("章", None) refs.append(normalized) return refs def project_card(row: Mapping[str, Any], *, as_of: int, source_version: str) -> dict[str, Any]: """将候选卡投影为截至 as_of 的 eval-only 索引视图。""" normalized_as_of = _required_chapter(as_of, "as_of") payload = row.get("draft_payload") if not isinstance(payload, Mapping): raise AdapterError(f"卡 {_row_id(row)} 的 draft_payload 不是对象") card_type = str(payload.get("type") or "") name = str(payload.get("名称") or "") if not card_type or not name: raise AdapterError(f"卡 {_row_id(row)} 缺少 type/名称") history, omitted = filter_milestones(_card_history(payload), normalized_as_of) if not history: raise AdapterError(f"卡 {_row_id(row)} 没有可证明落在 as_of 以前的历史") appearances: list[int] = [] raw_appearances = payload.get("出场章") if isinstance(raw_appearances, list): for value in raw_appearances: chapter = normalize_chapter(value) if chapter is not None and chapter <= normalized_as_of: appearances.append(chapter) appearances = sorted(set(appearances)) card_id = _row_id(row) latest = copy.deepcopy(history[-1]) return { "cardId": card_id, "type": card_type, "name": name, "score": float(row.get("score") or 0), "sourceId": f"eval-draft:{card_id}", "sourceVersion": source_version, "sourceOffset": 0, "milestones": copy.deepcopy(history), "stateAsOf": copy.deepcopy(history), "sourceRefs": _structured_source_refs(payload, history, normalized_as_of), "appearanceChapters": appearances, "derivedState": { "asOfChapter": normalized_as_of, "latestMilestone": latest, }, "source": { "sourceId": f"eval-draft:{card_id}", "sourceVersion": source_version, "scope": "card_projection", "chapterRange": f"1-{normalized_as_of}", }, "evaluationStatus": "eval_draft", "sourceType": "upgrade_book", "sourceKind": "eval_draft", "upstreamStatus": str(row.get("status") or "unknown"), "productionRetrievalEligible": False, "omittedHistoryCount": len(omitted), } def begin_read_snapshot(conn: Any) -> None: """在第一条业务查询前固定可重复读、只读事务。""" conn.execute("SET TRANSACTION ISOLATION LEVEL REPEATABLE READ READ ONLY") def load_frozen_prose_rows( *, dsn: str, tenant_id: int, work_id: int, as_of: int, source_refs: Sequence[Mapping[str, Any]] = (), chapter_numbers: Sequence[int] = (), ) -> list[dict[str, Any]]: """从同一只读快照读取冻结线内的 Canonical 历史原文。 SQL 只存在于 replay-eval 读取适配器;read-context 的作品与回放仓储都 调用本入口,避免再造正文读取旁路。 """ normalized_as_of = _required_chapter(as_of, "as_of") requested_chapters = {_required_chapter(item, "chapter_numbers[]") for item in chapter_numbers} block_ids: set[int] = set() refs_by_block: dict[int, list[Mapping[str, Any]]] = {} for index, ref in enumerate(source_refs): if not isinstance(ref, Mapping): raise AdapterError(f"source_refs[{index}] 必须是对象") chapter = _required_chapter(ref.get("chapter"), f"source_refs[{index}].chapter") if chapter > normalized_as_of: raise AdapterError(f"source_refs[{index}] 包含目标章或未来章") block_id = ref.get("blockId") if isinstance(block_id, bool) or not isinstance(block_id, int) or block_id <= 0: raise AdapterError(f"source_refs[{index}].blockId 必须是正整数") block_ids.add(block_id) refs_by_block.setdefault(block_id, []).append(ref) if any(chapter > normalized_as_of for chapter in requested_chapters): raise AdapterError("chapter_numbers 包含目标章或未来章") if not requested_chapters and not block_ids: return [] with psycopg.connect(dsn, row_factory=dict_row) as conn: begin_read_snapshot(conn) rows = conn.execute( """ SELECT ch.order_no AS chapter,ch.id AS chapter_id,ch.status AS chapter_status, b.id AS block_id,b.order_no AS block_order,b.revision,b.content_text FROM muse_content_chapter ch JOIN muse_content_block b ON b.chapter_id=ch.id WHERE ch.tenant_id=%s AND ch.work_id=%s AND ch.deleted=FALSE AND b.tenant_id=%s AND b.work_id=%s AND b.deleted=FALSE AND ch.order_no<=%s AND ch.status IN ('published','confirmed','canonical') AND (ch.order_no=ANY(%s) OR b.id=ANY(%s)) ORDER BY ch.order_no,b.order_no,b.id """, (tenant_id, work_id, tenant_id, work_id, normalized_as_of, sorted(requested_chapters), sorted(block_ids)), ).fetchall() result: list[dict[str, Any]] = [] for row in rows: text = str(row.get("content_text") or "") block_id = int(row["block_id"]) matching_refs = refs_by_block.get(block_id) if matching_refs: for ref in matching_refs: start = int(ref.get("startCodePoint") or 0) end = int(ref.get("endCodePoint") or len(text)) if start < 0 or end <= start or end > len(text): raise AdapterError(f"block {block_id} 的字符区间越界") fragment = text[start:end] result.append( { "chapter": int(row["chapter"]), "blockId": block_id, "blockOrder": int(row["block_order"]), "sourceRef": copy.deepcopy(dict(ref)), "text": fragment, "contentSha256": "sha256:" + hashlib.sha256(fragment.encode("utf-8")).hexdigest(), } ) elif int(row["chapter"]) in requested_chapters: source_version = f"chapter:{row['chapter']}:block:{block_id}:revision:{row.get('revision') or 0}" result.append( { "chapter": int(row["chapter"]), "blockId": block_id, "blockOrder": int(row["block_order"]), "sourceRef": { "sourceId": f"chapter:{row['chapter']}:block:{block_id}", "sourceVersion": source_version, "chapter": int(row["chapter"]), "blockId": block_id, "startCodePoint": 0, "endCodePoint": len(text), }, "text": text, "contentSha256": "sha256:" + hashlib.sha256(text.encode("utf-8")).hexdigest(), } ) return result def _card_source_version(row: Mapping[str, Any], base_version: str) -> str: """把卡行 revision 纳入来源版本,防止卡内容变更复用旧版本。""" return f"{base_version}:card-{_row_id(row)}-rev-{row.get('revision') or 0}" def _project_outline(row: Mapping[str, Any]) -> dict[str, Any]: """保留窗的结构化摘要和绝对边界,不使用 window_no 作为冻结键。""" start = _required_chapter(row.get("from_order"), "outline.from_order") end = _required_chapter(row.get("to_order"), "outline.to_order") if end < start: raise AdapterError("大纲窗 from_order 大于 to_order") return { "from_order": start, "to_order": end, "windowNo": row.get("window_no"), "outline": str(row.get("outline_text") or ""), "checkStatus": str(row.get("check_status") or "unknown"), "sourceId": f"outline-window:{row.get('id')}", } def _project_scaffold(row: Mapping[str, Any], source_version: str) -> dict[str, Any]: """只取历史章细纲摘要,不查询或复制正文 block。""" chapter = _required_chapter(row.get("chapter"), "scaffold.chapter") result: dict[str, Any] = { "chapter": chapter, "title": str(row.get("title") or ""), "outline": str(row.get("outline_text") or ""), "sourceId": f"scaffold:{row.get('id')}", "sourceVersion": source_version, } if isinstance(row.get("pattern_hints"), list): result["patternHints"] = copy.deepcopy(row["pattern_hints"]) return result def _target_facts_from_scaffold(target_scaffold: Mapping[str, Any], target: int) -> dict[str, Any]: """从目标章 reference scaffold 生成审计侧 proxy,不送入 planner。""" target_id = target_scaffold.get("id") text = str(target_scaffold.get("outline_text") or "").strip() if not text: raise AdapterError("目标章 scaffold 缺少结构化事实,不能执行内容级审计") fragments = [part.strip() for part in re.split(r"[。;;!?!?\n]+", text) if part.strip()] facts: list[dict[str, Any]] = [] seen: set[str] = set() for index, fragment in enumerate([text, *fragments]): if len(fragment) < 4 or fragment in seen: continue seen.add(fragment) facts.append( { "id": f"target-scaffold:{target_id}:{index}", "firstChapter": target, "text": fragment, } ) if not facts: raise AdapterError("目标章 scaffold 没有可用于审计的结构化事实") return { "targetChapter": target, "source": "reference_scaffold_proxy", "forbiddenFacts": facts, } def _validate_selection(selection: Mapping[str, Any]) -> tuple[list[str], list[str]]: """校验正确卡和 placebo 卡的预注册集合。""" if not isinstance(selection, Mapping): raise AdapterError("card selection 必须是对象") correct = _normalize_id_list(selection.get("correctCardIds"), "correctCardIds") placebo = _normalize_id_list(selection.get("placeboCardIds"), "placeboCardIds") if set(correct) & set(placebo): raise AdapterError("correctCardIds 与 placeboCardIds 不能重叠") return correct, placebo def build_replay_config( *, work: Mapping[str, Any], reference: Mapping[str, Any], outline_rows: Sequence[Mapping[str, Any]], scaffold_rows: Sequence[Mapping[str, Any]], target_scaffold: Mapping[str, Any], card_rows: Sequence[Mapping[str, Any]], card_selection: Mapping[str, Any], as_of: int, target: int, evaluation_set_version: str, strategy_version: str, run_id: str | None = None, snapshot_version: str = DEFAULT_SNAPSHOT_VERSION, history_chapter_limit: int = 6, ) -> dict[str, Any]: """把只读查询结果组装为 run_replay 可消费的临时配置。""" normalized_as_of = _required_chapter(as_of, "as_of") normalized_target = _required_chapter(target, "target") if normalized_target != normalized_as_of + 1: raise AdapterError("target 必须等于 as_of+1") if not str(evaluation_set_version).strip() or not str(strategy_version).strip(): raise AdapterError("evaluation_set_version/strategy_version 不能为空") target_chapter = _required_chapter(target_scaffold.get("chapter"), "target_scaffold.chapter") if target_chapter != normalized_target: raise AdapterError("target scaffold 不是目标章,拒绝混用") source_version = _reference_version(work, reference) kept_windows, _ = filter_outline_windows(outline_rows, normalized_as_of) projected_windows = [_project_outline(row) for row in kept_windows] historical = [] for row in scaffold_rows: chapter = _required_chapter(row.get("chapter"), "scaffold.chapter") if chapter <= normalized_as_of: historical.append(row) historical.sort(key=lambda row: _required_chapter(row.get("chapter"), "scaffold.chapter")) if history_chapter_limit <= 0: raise AdapterError("history_chapter_limit 必须为正数") projected_scaffolds = [ _project_scaffold(row, source_version) for row in historical[-history_chapter_limit:] ] correct_ids, placebo_ids = _validate_selection(card_selection) if any(str(row.get("source_type") or "") != "upgrade_book" for row in card_rows): raise AdapterError("卡选择包含非 upgrade_book 来源") rows_by_id = {_row_id(row): row for row in card_rows} if len(rows_by_id) != len(card_rows): raise AdapterError("卡查询结果含重复 id") selected_ids = set(correct_ids + placebo_ids) if set(rows_by_id) != selected_ids: missing = sorted(selected_ids - set(rows_by_id)) unexpected = sorted(set(rows_by_id) - selected_ids) raise AdapterError(f"卡查询结果与预注册不一致: missing={missing}, unexpected={unexpected}") projected_cards = { card_id: project_card( rows_by_id[card_id], as_of=normalized_as_of, source_version=_card_source_version(rows_by_id[card_id], source_version), ) for card_id in sorted(selected_ids) } correct_cards = [projected_cards[card_id] for card_id in correct_ids] placebo_cards = [projected_cards[card_id] for card_id in placebo_ids] source_catalog: list[dict[str, Any]] = [ { "sourceId": f"reference-work:{work.get('id')}", "sourceVersion": source_version, "scope": "metadata", "sourceStatus": str(reference.get("parse_status") or "unknown"), } ] for row in kept_windows: source_catalog.append( { "sourceId": f"outline-window:{row.get('id')}", "sourceVersion": source_version, "from_order": _required_chapter(row.get("from_order"), "outline.from_order"), "to_order": _required_chapter(row.get("to_order"), "outline.to_order"), "scope": "outline_window", } ) for row in historical[-history_chapter_limit:]: source_catalog.append( { "sourceId": f"scaffold:{row.get('id')}", "sourceVersion": source_version, "chapter": _required_chapter(row.get("chapter"), "scaffold.chapter"), "scope": "chapter", } ) for card_id in sorted(selected_ids): source_catalog.append( { "sourceId": f"eval-draft:{card_id}", "sourceVersion": _card_source_version(rows_by_id[card_id], source_version), "chapterRange": f"1-{normalized_as_of}", "scope": "card_projection", "sourceStatus": "eval_draft", } ) recent_source_ids = [item["sourceId"] for item in projected_scaffolds] common_context = { "L0": { "purpose": "offline_evaluation", "scenario": "fine_outline", "targetChapter": normalized_target, "outputContract": "fine_outline_v0", }, "L1": { "asOfChapter": normalized_as_of, "recentScaffoldSourceIds": recent_source_ids, "historyChapterLimit": history_chapter_limit, }, "L2": { "referenceWorkId": str(work.get("id")), "outlineSourceCount": len(projected_windows), "sourceVersion": source_version, }, "L3": { "sourceMode": "eval_draft", "authorizationRequired": True, "targetChapterAvailableOnlyToAudit": True, }, } target_facts = _target_facts_from_scaffold(target_scaffold, normalized_target) reference_work = { "id": str(work.get("id")), "title": str(work.get("title") or ""), "version": source_version, "chapterCount": reference.get("imported_chapter_count"), "declaredChapterCount": reference.get("declared_chapter_count"), } run_id = run_id or f"replay-work-{work.get('id')}-{normalized_target}" return { "runId": run_id, "referenceWork": reference_work, "evaluationSetVersion": str(evaluation_set_version), "strategyVersion": str(strategy_version), "targetChapter": normalized_target, "snapshot": { "asOfChapter": normalized_as_of, "snapshotVersion": snapshot_version, "data": { "outlineWindows": projected_windows, "chapters": projected_scaffolds, "cards": [], }, }, "sources": source_catalog, "commonContext": common_context, "arms": { "outline_only": {"cards": [], "cardSourceIds": [], "cardStrategy": "none"}, "outline_plus_cards": { "cards": correct_cards, "cardSourceIds": [f"eval-draft:{card_id}" for card_id in correct_ids], "cardStrategy": "correct", }, "outline_plus_placebo_cards": { "cards": placebo_cards, "cardSourceIds": [f"eval-draft:{card_id}" for card_id in placebo_ids], "cardStrategy": "placebo", }, }, "authorization": { "sourceStatus": "missing_authorization_snapshot", "copyrightStatus": "unknown", "sourceVersion": source_version, "allowedPurpose": [], "authorizationSnapshot": {}, }, "runPermissions": { "purpose": "offline_evaluation", "mode": "dry_run", "sourceMode": "eval_draft", "writesFormalData": False, }, "leakageAudit": { "auditVersion": "content_fact_audit_v0", "targetFacts": target_facts, }, } def load_reference_rows( *, dsn: str, tenant_id: int, work_id: int, as_of: int, target: int, card_selection: Mapping[str, Any], ) -> dict[str, Any]: """在只读事务中读取组装所需的作品、摘要、卡和目标 proxy。""" normalized_as_of = _required_chapter(as_of, "as_of") normalized_target = _required_chapter(target, "target") correct_ids, placebo_ids = _validate_selection(card_selection) selected_ids = [int(item) if str(item).isdigit() else item for item in correct_ids + placebo_ids] with psycopg.connect(dsn, row_factory=dict_row) as conn: begin_read_snapshot(conn) work = conn.execute( """ SELECT id,title,revision,chapter_count,parse_status,import_status FROM muse_content_work WHERE tenant_id=%s AND id=%s AND deleted=FALSE """, (tenant_id, work_id), ).fetchone() reference = conn.execute( """ SELECT id,work_id,declared_chapter_count,imported_chapter_count, parse_scope,parse_status,source_file,notes,update_time FROM example_reference_work WHERE tenant_id=%s AND work_id=%s AND deleted=FALSE ORDER BY id DESC LIMIT 1 """, (tenant_id, work_id), ).fetchone() if work is None or reference is None: raise AdapterError("作品或参考作品登记不存在") if normalized_target > int(work.get("chapter_count") or 0) + 1: raise AdapterError("目标章超出作品导入范围") outline_rows = conn.execute( """ SELECT id,window_no,from_order,to_order,outline_text,check_status FROM example_parse_outline WHERE tenant_id=%s AND work_id=%s AND deleted=FALSE AND from_order<=%s ORDER BY from_order,to_order,id """, (tenant_id, work_id, normalized_as_of), ).fetchall() scaffold_rows = conn.execute( """ SELECT s.id,s.chapter_id,ch.order_no AS chapter,ch.title,s.outline_text, s.entities,s.pattern_hints FROM example_parse_scaffold s JOIN muse_content_chapter ch ON ch.id=s.chapter_id WHERE s.tenant_id=%s AND s.work_id=%s AND s.deleted=FALSE AND ch.tenant_id=%s AND ch.deleted=FALSE AND ch.order_no<=%s ORDER BY ch.order_no,s.id """, (tenant_id, work_id, tenant_id, normalized_as_of), ).fetchall() target_scaffold = conn.execute( """ SELECT s.id,s.chapter_id,ch.order_no AS chapter,ch.title,s.outline_text, s.entities,s.pattern_hints FROM example_parse_scaffold s JOIN muse_content_chapter ch ON ch.id=s.chapter_id WHERE s.tenant_id=%s AND s.work_id=%s AND s.deleted=FALSE AND ch.tenant_id=%s AND ch.deleted=FALSE AND ch.order_no=%s ORDER BY s.id LIMIT 1 """, (tenant_id, work_id, tenant_id, normalized_target), ).fetchone() if target_scaffold is None: raise AdapterError("目标章没有 reference scaffold proxy") card_rows = conn.execute( """ SELECT id,status,source_type,source_id,revision,draft_payload FROM muse_knowledge_draft WHERE tenant_id=%s AND work_id=%s AND deleted=FALSE AND source_type='upgrade_book' AND id=ANY(%s) ORDER BY id """, (tenant_id, work_id, selected_ids), ).fetchall() return { "work": work, "reference": reference, "outline_rows": outline_rows, "scaffold_rows": scaffold_rows, "target_scaffold": target_scaffold, "card_rows": card_rows, } def _parse_args() -> argparse.Namespace: """解析只读适配器命令行参数。""" parser = argparse.ArgumentParser(description="从实验库只读组装回放配置") parser.add_argument("--dsn", default=DSN) parser.add_argument("--tenant-id", type=int, default=TENANT_ID) parser.add_argument("--work-id", type=int, required=True) parser.add_argument("--as-of", type=int, required=True, dest="as_of") parser.add_argument("--target-chapter", type=int, required=True, dest="target") parser.add_argument("--card-selection", type=Path, required=True) parser.add_argument("--output-dir", type=Path, required=True) parser.add_argument("--evaluation-set-version", default="deep-space-v0") parser.add_argument("--strategy-version", default="card-index-outline-v0") parser.add_argument("--run-id") parser.add_argument("--history-chapter-limit", type=int, default=6) return parser.parse_args() def main() -> int: """执行只读查询并把配置写到仓库外临时目录。""" args = _parse_args() output_dir = args.output_dir.resolve() if output_dir.is_relative_to(REPO_ROOT.resolve()): raise AdapterError("适配器输出目录必须位于仓库外") selection = json.loads(args.card_selection.read_text(encoding="utf-8")) rows = load_reference_rows( dsn=args.dsn, tenant_id=args.tenant_id, work_id=args.work_id, as_of=args.as_of, target=args.target, card_selection=selection, ) config = build_replay_config( **rows, card_selection=selection, as_of=args.as_of, target=args.target, evaluation_set_version=args.evaluation_set_version, strategy_version=args.strategy_version, run_id=args.run_id, history_chapter_limit=args.history_chapter_limit, ) output_dir.mkdir(parents=True, exist_ok=True) (output_dir / "config.json").write_text(_safe_json(config) + "\n", encoding="utf-8") (output_dir / "target_proxy.json").write_text( _safe_json(rows["target_scaffold"]) + "\n", encoding="utf-8" ) summary = { "runId": config["runId"], "workId": args.work_id, "asOfChapter": args.as_of, "targetChapter": args.target, "outlineWindowCount": len(config["snapshot"]["data"]["outlineWindows"]), "historyChapterCount": len(config["snapshot"]["data"]["chapters"]), "correctCardCount": len(config["arms"]["outline_plus_cards"]["cards"]), "placeboCardCount": len(config["arms"]["outline_plus_placebo_cards"]["cards"]), "cardSourceMode": "eval_draft", "authorizationStatus": "missing_authorization_snapshot", "configPath": str(output_dir / "config.json"), } (output_dir / "adapter_summary.json").write_text(_safe_json(summary) + "\n", encoding="utf-8") print(json.dumps(summary, ensure_ascii=False, sort_keys=True)) return 0 if __name__ == "__main__": try: raise SystemExit(main()) except (AdapterError, OSError, psycopg.Error) as error: print(json.dumps({"status": "blocked_adapter", "error": str(error)}, ensure_ascii=False)) raise SystemExit(2)