opencode-skills-collection 4.0.69 → 4.0.70

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (185) hide show
  1. package/bundled-skills/.antigravity-install-manifest.json +39 -1
  2. package/bundled-skills/api-integration-architect/SKILL.md +241 -0
  3. package/bundled-skills/apify-generate-output-schema/SKILL.md +438 -0
  4. package/bundled-skills/apify-integration-development/SKILL.md +168 -0
  5. package/bundled-skills/apify-integration-development/references/ai-framework-package.md +158 -0
  6. package/bundled-skills/apify-integration-development/references/ai-harness-plugin.md +192 -0
  7. package/bundled-skills/apify-integration-development/references/sdk-integration.md +236 -0
  8. package/bundled-skills/apify-integration-development/references/workflow-automation.md +163 -0
  9. package/bundled-skills/architecture-review/README.md +42 -0
  10. package/bundled-skills/architecture-review/SKILL.md +77 -0
  11. package/bundled-skills/architecture-review/examples.md +11 -0
  12. package/bundled-skills/architecture-review/reference/best-practices.md +7 -0
  13. package/bundled-skills/architecture-review/reference/capabilities.md +20 -0
  14. package/bundled-skills/architecture-review/reference/fallbacks.md +11 -0
  15. package/bundled-skills/architecture-review/reference/graph.md +15 -0
  16. package/bundled-skills/architecture-review/reference/mcp.md +14 -0
  17. package/bundled-skills/architecture-review/reference/workflow.md +15 -0
  18. package/bundled-skills/architecture-review/templates/architecture-review.md +21 -0
  19. package/bundled-skills/code-review-sensei/SKILL.md +177 -0
  20. package/bundled-skills/codebase-onboarding/README.md +42 -0
  21. package/bundled-skills/codebase-onboarding/SKILL.md +77 -0
  22. package/bundled-skills/codebase-onboarding/examples.md +11 -0
  23. package/bundled-skills/codebase-onboarding/reference/best-practices.md +7 -0
  24. package/bundled-skills/codebase-onboarding/reference/capabilities.md +20 -0
  25. package/bundled-skills/codebase-onboarding/reference/fallbacks.md +11 -0
  26. package/bundled-skills/codebase-onboarding/reference/graph.md +15 -0
  27. package/bundled-skills/codebase-onboarding/reference/mcp.md +14 -0
  28. package/bundled-skills/codebase-onboarding/reference/workflow.md +15 -0
  29. package/bundled-skills/codebase-onboarding/templates/repository-onboarding.md +21 -0
  30. package/bundled-skills/connection-auth-rules/SKILL.md +199 -0
  31. package/bundled-skills/connection-auth-rules/fetch_schema.py +320 -0
  32. package/bundled-skills/dependency-analysis/README.md +42 -0
  33. package/bundled-skills/dependency-analysis/SKILL.md +76 -0
  34. package/bundled-skills/dependency-analysis/examples.md +11 -0
  35. package/bundled-skills/dependency-analysis/reference/best-practices.md +7 -0
  36. package/bundled-skills/dependency-analysis/reference/capabilities.md +20 -0
  37. package/bundled-skills/dependency-analysis/reference/fallbacks.md +11 -0
  38. package/bundled-skills/dependency-analysis/reference/graph.md +15 -0
  39. package/bundled-skills/dependency-analysis/reference/mcp.md +14 -0
  40. package/bundled-skills/dependency-analysis/reference/workflow.md +15 -0
  41. package/bundled-skills/dependency-analysis/templates/dependency-review.md +21 -0
  42. package/bundled-skills/devops-pipeline-builder/SKILL.md +200 -0
  43. package/bundled-skills/eas-app-stores/SKILL.md +197 -0
  44. package/bundled-skills/eas-app-stores/agents/openai.yaml +4 -0
  45. package/bundled-skills/eas-app-stores/references/app-store-metadata.md +497 -0
  46. package/bundled-skills/eas-app-stores/references/ios-app-store.md +376 -0
  47. package/bundled-skills/eas-app-stores/references/native-ios.md +167 -0
  48. package/bundled-skills/eas-app-stores/references/play-store.md +244 -0
  49. package/bundled-skills/eas-app-stores/references/testflight.md +62 -0
  50. package/bundled-skills/eas-app-stores/references/workflows.md +120 -0
  51. package/bundled-skills/eas-hosting/SKILL.md +448 -0
  52. package/bundled-skills/eas-hosting/agents/openai.yaml +4 -0
  53. package/bundled-skills/eas-observe/SKILL.md +75 -0
  54. package/bundled-skills/eas-observe/agents/openai.yaml +4 -0
  55. package/bundled-skills/eas-observe/references/metrics.md +98 -0
  56. package/bundled-skills/eas-observe/references/queries.md +403 -0
  57. package/bundled-skills/eas-observe/references/setup.md +476 -0
  58. package/bundled-skills/eas-observe/references/third-party.md +136 -0
  59. package/bundled-skills/eas-simulator/SKILL.md +251 -0
  60. package/bundled-skills/eas-simulator/agents/openai.yaml +4 -0
  61. package/bundled-skills/eas-simulator/references/controllers.md +135 -0
  62. package/bundled-skills/eas-simulator/references/run-your-app.md +240 -0
  63. package/bundled-skills/eas-simulator/references/troubleshooting.md +47 -0
  64. package/bundled-skills/eas-workflows/SKILL.md +119 -0
  65. package/bundled-skills/eas-workflows/agents/openai.yaml +4 -0
  66. package/bundled-skills/eas-workflows/scripts/fetch.js +109 -0
  67. package/bundled-skills/expo-animation/LICENSE +21 -0
  68. package/bundled-skills/expo-animation/RECIPES.md +385 -0
  69. package/bundled-skills/expo-animation/SKILL.md +295 -0
  70. package/bundled-skills/expo-animation/agents/openai.yaml +4 -0
  71. package/bundled-skills/fact-check-x-unified/SKILL.md +178 -0
  72. package/bundled-skills/fact-check-x-unified/agents/openai.yaml +4 -0
  73. package/bundled-skills/fact-check-x-unified/references/acceptance-criteria.md +44 -0
  74. package/bundled-skills/fact-check-x-unified/references/contracts.md +39 -0
  75. package/bundled-skills/fact-check-x-unified/scripts/common.py +31 -0
  76. package/bundled-skills/fact-check-x-unified/scripts/fact_check_x.py +1832 -0
  77. package/bundled-skills/fact-check-x-unified/scripts/trusted_search_config.py +324 -0
  78. package/bundled-skills/fact-check-x-unified/tests/anchor_downgrade_test.py +90 -0
  79. package/bundled-skills/fact-check-x-unified/tests/multi_platform_test.py +369 -0
  80. package/bundled-skills/fact-check-x-unified/tests/smoke_test.py +740 -0
  81. package/bundled-skills/fact-check-x-unified/tests/stage_checkpoint_test.py +103 -0
  82. package/bundled-skills/fact-check-x-unified/tests/trusted_search_config_test.py +156 -0
  83. package/bundled-skills/gpt-taste/SKILL.md +8 -1
  84. package/bundled-skills/hf-cli/SKILL.md +263 -0
  85. package/bundled-skills/huggingface-community-evals/SKILL.md +228 -0
  86. package/bundled-skills/huggingface-community-evals/examples/.env.example +3 -0
  87. package/bundled-skills/huggingface-community-evals/examples/USAGE_EXAMPLES.md +101 -0
  88. package/bundled-skills/huggingface-community-evals/scripts/inspect_eval_uv.py +104 -0
  89. package/bundled-skills/huggingface-community-evals/scripts/inspect_vllm_uv.py +306 -0
  90. package/bundled-skills/huggingface-community-evals/scripts/lighteval_vllm_uv.py +297 -0
  91. package/bundled-skills/huggingface-datasets/SKILL.md +130 -0
  92. package/bundled-skills/jev-social/SKILL.md +182 -0
  93. package/bundled-skills/longbridge-derivatives/SKILL.md +117 -0
  94. package/bundled-skills/longbridge-derivatives/references/option.md +36 -0
  95. package/bundled-skills/longbridge-derivatives/references/options-advanced.md +101 -0
  96. package/bundled-skills/longbridge-derivatives/references/options-pnl.md +74 -0
  97. package/bundled-skills/longbridge-derivatives/references/options-strategy.md +82 -0
  98. package/bundled-skills/longbridge-derivatives/references/options-volatility.md +70 -0
  99. package/bundled-skills/longbridge-derivatives/references/warrant.md +12 -0
  100. package/bundled-skills/longbridge-quant/SKILL.md +151 -0
  101. package/bundled-skills/longbridge-quant/references/correlation.md +51 -0
  102. package/bundled-skills/longbridge-quant/references/execution-model.md +68 -0
  103. package/bundled-skills/longbridge-quant/references/factor-research.md +95 -0
  104. package/bundled-skills/longbridge-quant/references/factor-screen.md +101 -0
  105. package/bundled-skills/longbridge-quant/references/hedging.md +136 -0
  106. package/bundled-skills/longbridge-quant/references/ml-strategy.md +77 -0
  107. package/bundled-skills/longbridge-quant/references/multifactor.md +68 -0
  108. package/bundled-skills/longbridge-quant/references/pairs-trading.md +61 -0
  109. package/bundled-skills/longbridge-quant/references/quant-cli.md +133 -0
  110. package/bundled-skills/longbridge-quant/references/quant-stats.md +150 -0
  111. package/bundled-skills/longbridge-quant/references/seasonality.md +50 -0
  112. package/bundled-skills/longbridge-quant/references/strategy-optimizer.md +68 -0
  113. package/bundled-skills/longbridge-quant/references/volatility-strategy.md +52 -0
  114. package/bundled-skills/longbridge-research/SKILL.md +187 -0
  115. package/bundled-skills/longbridge-research/references/company-profile.md +96 -0
  116. package/bundled-skills/longbridge-research/references/company-tearsheet.md +82 -0
  117. package/bundled-skills/longbridge-research/references/competitive-analysis.md +81 -0
  118. package/bundled-skills/longbridge-research/references/consensus.md +92 -0
  119. package/bundled-skills/longbridge-research/references/coverage-initiation.md +76 -0
  120. package/bundled-skills/longbridge-research/references/defi-yield.md +60 -0
  121. package/bundled-skills/longbridge-research/references/finance-calendar.md +165 -0
  122. package/bundled-skills/longbridge-research/references/financial-planning.md +77 -0
  123. package/bundled-skills/longbridge-research/references/forecast-eps.md +39 -0
  124. package/bundled-skills/longbridge-research/references/fund-holder.md +44 -0
  125. package/bundled-skills/longbridge-research/references/hkipo-analysis.md +101 -0
  126. package/bundled-skills/longbridge-research/references/industry-peers.md +46 -0
  127. package/bundled-skills/longbridge-research/references/industry-rank.md +62 -0
  128. package/bundled-skills/longbridge-research/references/insider-trades.md +48 -0
  129. package/bundled-skills/longbridge-research/references/institution-rating.md +62 -0
  130. package/bundled-skills/longbridge-research/references/investment-ideas.md +69 -0
  131. package/bundled-skills/longbridge-research/references/investment-proposal.md +95 -0
  132. package/bundled-skills/longbridge-research/references/investors.md +87 -0
  133. package/bundled-skills/longbridge-research/references/onchain.md +70 -0
  134. package/bundled-skills/longbridge-research/references/post-investment.md +76 -0
  135. package/bundled-skills/longbridge-research/references/shareholder.md +72 -0
  136. package/bundled-skills/longbridge-research/references/short-positions.md +50 -0
  137. package/bundled-skills/longbridge-research/references/short-trades.md +50 -0
  138. package/bundled-skills/longbridge-research/references/stock-research.md +61 -0
  139. package/bundled-skills/longbridge-research/references/thesis-tracker.md +64 -0
  140. package/bundled-skills/makepad-2-0-animation/SKILL.md +318 -0
  141. package/bundled-skills/makepad-2-0-animation/references/animator-reference.md +433 -0
  142. package/bundled-skills/makepad-2-0-dsl/SKILL.md +492 -0
  143. package/bundled-skills/makepad-2-0-dsl/references/dsl-syntax-reference.md +511 -0
  144. package/bundled-skills/makepad-2-0-dsl/references/extended-guide.md +56 -0
  145. package/bundled-skills/makepad-2-0-dsl/references/property-system.md +757 -0
  146. package/bundled-skills/makepad-2-0-events/SKILL.md +497 -0
  147. package/bundled-skills/makepad-2-0-events/references/event-patterns.md +802 -0
  148. package/bundled-skills/makepad-2-0-events/references/extended-guide.md +590 -0
  149. package/bundled-skills/makepad-2-0-layout/SKILL.md +499 -0
  150. package/bundled-skills/makepad-2-0-layout/references/extended-guide.md +243 -0
  151. package/bundled-skills/makepad-2-0-layout/references/layout-patterns.md +881 -0
  152. package/bundled-skills/makepad-2-0-widgets/SKILL.md +261 -0
  153. package/bundled-skills/makepad-2-0-widgets/references/widget-advanced.md +648 -0
  154. package/bundled-skills/makepad-2-0-widgets/references/widget-catalog.md +547 -0
  155. package/bundled-skills/meeting-distiller-pro/SKILL.md +120 -0
  156. package/bundled-skills/monte-carlo-analyze-root-cause/SKILL.md +12 -1
  157. package/bundled-skills/monte-carlo-asset-health/SKILL.md +12 -1
  158. package/bundled-skills/monte-carlo-context-detection/SKILL.md +170 -0
  159. package/bundled-skills/monte-carlo-context-detection/references/signal-definitions.md +46 -0
  160. package/bundled-skills/remotion-captions/SKILL.md +57 -0
  161. package/bundled-skills/remotion-captions/agents/openai.yaml +7 -0
  162. package/bundled-skills/remotion-captions/assets/remotion-icon.svg +4 -0
  163. package/bundled-skills/remotion-captions/display-captions.md +190 -0
  164. package/bundled-skills/remotion-captions/import-srt-captions.md +73 -0
  165. package/bundled-skills/remotion-captions/transcribe-captions.md +70 -0
  166. package/bundled-skills/remotion-create/SKILL.md +106 -0
  167. package/bundled-skills/remotion-create/agents/openai.yaml +7 -0
  168. package/bundled-skills/remotion-create/assets/remotion-icon.svg +4 -0
  169. package/bundled-skills/remotion-create/tailwind.md +11 -0
  170. package/bundled-skills/remotion-create/video-layout.md +9 -0
  171. package/bundled-skills/remotion-docs/SKILL.md +67 -0
  172. package/bundled-skills/remotion-docs/agents/openai.yaml +7 -0
  173. package/bundled-skills/remotion-docs/assets/remotion-icon.svg +4 -0
  174. package/bundled-skills/remotion-interactivity/SKILL.md +270 -0
  175. package/bundled-skills/remotion-interactivity/agents/openai.yaml +7 -0
  176. package/bundled-skills/remotion-interactivity/assets/remotion-icon.svg +4 -0
  177. package/bundled-skills/remotion-render/SKILL.md +48 -0
  178. package/bundled-skills/remotion-render/agents/openai.yaml +7 -0
  179. package/bundled-skills/remotion-render/assets/remotion-icon.svg +4 -0
  180. package/bundled-skills/remotion-render/transparent-videos.md +106 -0
  181. package/bundled-skills/saas-pricing-strategist/SKILL.md +169 -0
  182. package/bundled-skills/score-eval/SKILL.md +35 -0
  183. package/bundled-skills/writing-guidelines/SKILL.md +60 -0
  184. package/package.json +1 -1
  185. package/skills_index.json +980 -3
@@ -0,0 +1,740 @@
1
+ #!/usr/bin/env python3
2
+ from __future__ import annotations
3
+
4
+ import json
5
+ import os
6
+ import shutil
7
+ import subprocess
8
+ import sys
9
+ import tempfile
10
+ import zipfile
11
+ from pathlib import Path
12
+ from unittest.mock import patch
13
+
14
+
15
+ ROOT = Path(__file__).resolve().parents[1]
16
+ SKILLS = ROOT.parent
17
+ if (ROOT / "modules").exists():
18
+ SKILLS = ROOT / "modules"
19
+ COMPARE_FIXTURES = SKILLS / "fact-check-x-knowledge-compare" / "tests" / "fixtures"
20
+ AUTHORITY_FIXTURES = SKILLS / "fact-check-x-authoritative-verify" / "tests" / "fixtures"
21
+
22
+
23
+ def run(arguments: list[str], environment: dict[str, str] | None = None) -> dict:
24
+ process = subprocess.run(arguments, text=True, capture_output=True, check=False, env=environment)
25
+ if process.returncode:
26
+ raise AssertionError(process.stdout or process.stderr)
27
+ payload = json.loads([line for line in process.stdout.splitlines() if line.strip()][-1])
28
+ checkpoint = payload.get("checkpoint") or {}
29
+ acknowledgement = checkpoint.get("acknowledgement") or {}
30
+ if checkpoint.get("status") == "awaiting_user" and acknowledgement:
31
+ acknowledged = subprocess.run(
32
+ [sys.executable, str(ROOT / "scripts" / "fact_check_x.py"),
33
+ "acknowledge-stage", "--run-dir", str(Path(checkpoint["path"]).parent),
34
+ "--stage", checkpoint["stage"], "--token", acknowledgement["token"],
35
+ "--decision", "continue"],
36
+ text=True, capture_output=True, check=False, env=environment,
37
+ )
38
+ if acknowledged.returncode:
39
+ raise AssertionError(acknowledged.stdout or acknowledged.stderr)
40
+ return payload
41
+
42
+
43
+ def run_failed(arguments: list[str], environment: dict[str, str] | None = None) -> dict:
44
+ process = subprocess.run(arguments, text=True, capture_output=True, check=False, env=environment)
45
+ if process.returncode == 0:
46
+ raise AssertionError("命令应以非零状态结束")
47
+ return json.loads([line for line in process.stdout.splitlines() if line.strip()][-1])
48
+
49
+
50
+ def command(*arguments: str) -> list[str]:
51
+ return [sys.executable, str(ROOT / "scripts" / "fact_check_x.py"), *arguments]
52
+
53
+
54
+ def main() -> int:
55
+ with tempfile.TemporaryDirectory(prefix="fact-check-x-unified-") as temp:
56
+ sys.path.insert(0, str(ROOT / "scripts"))
57
+ import fact_check_x
58
+ from fact_check_x import (
59
+ collect_technical_notices,
60
+ merge_verification,
61
+ normalize_report_navigation,
62
+ )
63
+ assert fact_check_x.file_uri_for_path(
64
+ r"C:\WorkBuddy\fact-check-x\runs\sample run\02-comparison-report.html"
65
+ ) == "file:///C:/WorkBuddy/fact-check-x/runs/sample%20run/02-comparison-report.html"
66
+ assert fact_check_x.normalize_platform_path_text(
67
+ "/c/WorkBuddy/fact-check-x/runs/sample run/02-comparison-report.html",
68
+ "nt",
69
+ ) == "C:/WorkBuddy/fact-check-x/runs/sample run/02-comparison-report.html"
70
+ assert fact_check_x.file_uri_for_path(
71
+ "/c/WorkBuddy/fact-check-x/runs/sample run/02-comparison-report.html",
72
+ "nt",
73
+ ) == "file:///C:/WorkBuddy/fact-check-x/runs/sample%20run/02-comparison-report.html"
74
+
75
+ def assert_deliverable(item: dict, label: str, path: Path) -> None:
76
+ uri = path.resolve().as_uri()
77
+ assert item["label"] == label
78
+ assert item["path"] == str(path.resolve())
79
+ assert item["fileUri"] == uri
80
+ assert item["markdownLink"] == f"[打开{label}](<{uri}>)"
81
+
82
+ notice_run = Path(temp) / "technical-notice"
83
+ (notice_run / "capture").mkdir(parents=True)
84
+ (notice_run / "capture" / "results.json").write_text(
85
+ json.dumps({
86
+ "platforms": [{
87
+ "platform": "doubao",
88
+ "label": "豆包",
89
+ "references": [
90
+ {"sourceAcquisitionStatus": "blocked"},
91
+ {"contentAcquisition": "failed"},
92
+ ],
93
+ }],
94
+ }, ensure_ascii=False),
95
+ encoding="utf-8",
96
+ )
97
+ notices = collect_technical_notices(notice_run)
98
+ assert len(notices) == 1 and "豆包 2 条" in notices[0]
99
+ notice_html = normalize_report_navigation(
100
+ b"<html><head><style></style></head><body><main>test</main></body></html>",
101
+ "01-capture-report.html",
102
+ notices,
103
+ ).decode("utf-8")
104
+ assert 'data-fcx-run-notice="1"' in notice_html
105
+ assert "受影响且无法核验的主张按“疑似误导”呈现" in notice_html
106
+
107
+ merge_results = Path(temp) / "merge-results"
108
+ merge_results.mkdir()
109
+ (merge_results / "K1.json").write_text(json.dumps({
110
+ "schemaVersion": "fact-check-x/authority-result@1",
111
+ "requestId": "K1",
112
+ "searchMode": "trusted_search",
113
+ "requestCount": 1,
114
+ "authoritativeFinding": "直接问题权威结论。",
115
+ "claims": {
116
+ "doubao": {"covered": True, "claim": "直接问题平台主张。"},
117
+ },
118
+ "verdicts": {
119
+ "doubao": {"verdict": "contradicted"},
120
+ },
121
+ "evidenceGaps": [],
122
+ }), encoding="utf-8")
123
+ (merge_results / "K2.json").write_text(json.dumps({
124
+ "schemaVersion": "fact-check-x/authority-result@1",
125
+ "requestId": "K2",
126
+ "searchMode": "trusted_search",
127
+ "requestCount": 1,
128
+ "authoritativeFinding": "补充参考权威结论。",
129
+ "claims": {
130
+ "doubao": {"covered": True, "claim": "补充参考平台主张。"},
131
+ },
132
+ "verdicts": {
133
+ "doubao": {"verdict": "supported"},
134
+ },
135
+ "evidenceGaps": [],
136
+ }), encoding="utf-8")
137
+ merged = merge_verification({
138
+ "question": "测试",
139
+ "platforms": [{"platform": "doubao", "label": "豆包"}],
140
+ "knowledgePoints": [{
141
+ "id": "K1",
142
+ "description": "直接问题",
143
+ "role": "direct",
144
+ "trustedAnchor": {"eligible": False},
145
+ }, {
146
+ "id": "K2",
147
+ "description": "补充参考",
148
+ "role": "reference",
149
+ "trustedAnchor": {"eligible": False},
150
+ }],
151
+ "analysisGaps": [{
152
+ "stage": "comparison",
153
+ "knowledgePointId": "K1",
154
+ "platform": "doubao",
155
+ "reason": "原回答引用不足",
156
+ }],
157
+ }, merge_results)
158
+ assert merged["status"] == "completed"
159
+ assert merged["evidenceGaps"] == []
160
+ assert merged["finalAnswer"]["knowledgePointIds"] == ["K1"]
161
+ assert merged["supplementalFindings"]["knowledgePointIds"] == ["K2"]
162
+ assert merged["finalAnswer"]["answer"] == "直接问题权威结论。"
163
+ assert "补充参考权威结论" not in merged["finalAnswer"]["answer"]
164
+
165
+ run_dir = Path(temp) / "run"
166
+ results = COMPARE_FIXTURES / "results.json"
167
+ keyless_environment = {
168
+ **os.environ,
169
+ "TRUSTED_SEARCH_KEY": "",
170
+ "FACT_CHECK_X_TRUSTED_SEARCH_KEY_FILE": str(
171
+ Path(temp) / "missing-trusted-search-key"
172
+ ),
173
+ }
174
+ located = run(command("locate"))
175
+ assert set(located["skills"]) == {"collector", "comparison", "authority"}
176
+ capture_stage = run(command("prepare-comparison", "--results", str(results), "--run-dir", str(run_dir)))
177
+ assert capture_stage["stage"] == "capture_completed"
178
+ assert capture_stage["analysisExecution"]["mode"] == "single_pass"
179
+ assert capture_stage["analysisExecution"]["maxToolCallsAfterStageAcknowledgement"] == 3
180
+ assert len(capture_stage["analysisExecution"]["instructions"]) == 3
181
+ assert capture_stage["artifacts"]["answerReferenceReport"] == str((run_dir / "capture" / "report.html").resolve())
182
+ assert capture_stage["deliverables"][0]["path"] == str((run_dir / "01-capture-report.html").resolve())
183
+ assert_deliverable(
184
+ capture_stage["deliverables"][0],
185
+ "各方答案汇总",
186
+ run_dir / "01-capture-report.html",
187
+ )
188
+ assert capture_stage["checkpoint"]["mustPresentBeforeNextStage"] is True
189
+ assert capture_stage["checkpoint"]["path"] == str(
190
+ (run_dir / "01-capture-report.html").resolve()
191
+ )
192
+ assert "打开各方答案汇总" in capture_stage["checkpoint"]["message"]
193
+ assert (run_dir / "01-capture-report.html").exists()
194
+ assert len(capture_stage["platforms"]) == 2
195
+ comparison_stage = run(command("complete-comparison", "--results", str(results), "--analysis", str(COMPARE_FIXTURES / "comparison-analysis.json"), "--run-dir", str(run_dir)))
196
+ assert comparison_stage["stage"] == "comparison_completed"
197
+ assert comparison_stage["knowledgePointCount"] == 1
198
+ assert comparison_stage["artifacts"]["comparisonAnalysis"] == str(
199
+ (run_dir / "comparison-analysis.json").resolve()
200
+ )
201
+ assert (run_dir / "comparison-analysis.json").is_file()
202
+ assert comparison_stage["artifacts"]["comparisonReport"] == str((run_dir / "comparison.html").resolve())
203
+ assert comparison_stage["deliverables"][0]["path"] == str((run_dir / "02-comparison-report.html").resolve())
204
+ assert_deliverable(
205
+ comparison_stage["deliverables"][0],
206
+ "各方答案聚合(未核验)",
207
+ run_dir / "02-comparison-report.html",
208
+ )
209
+ assert comparison_stage["checkpoint"]["mustPresentBeforeNextStage"] is True
210
+ assert comparison_stage["checkpoint"]["path"] == str(
211
+ (run_dir / "02-comparison-report.html").resolve()
212
+ )
213
+ overwide_analysis = json.loads(
214
+ (COMPARE_FIXTURES / "comparison-analysis.json").read_text(encoding="utf-8")
215
+ )
216
+ overwide_analysis["knowledgePoints"][0]["claims"]["dknowc-chat"]["claim"] = (
217
+ "每人每月最高提取1400元,并另有最高300万元研发资助"
218
+ )
219
+ overwide_path = Path(temp) / "overwide-comparison-analysis.json"
220
+ overwide_path.write_text(
221
+ json.dumps(overwide_analysis, ensure_ascii=False), encoding="utf-8"
222
+ )
223
+ overwide_run = Path(temp) / "overwide-run"
224
+ run(command(
225
+ "prepare-comparison",
226
+ "--results", str(results),
227
+ "--run-dir", str(overwide_run),
228
+ ))
229
+ overwide_failure = run_failed(command(
230
+ "complete-comparison",
231
+ "--results", str(results),
232
+ "--analysis", str(overwide_path),
233
+ "--run-dir", str(overwide_run),
234
+ ))
235
+ assert "原子性门禁" in json.dumps(overwide_failure, ensure_ascii=False)
236
+ assert not (overwide_run / "comparison-gate.json").exists()
237
+ assert "打开各方答案聚合(未核验)" in comparison_stage["checkpoint"]["message"]
238
+ assert (run_dir / "02-comparison-report.html").exists()
239
+ prepared = run(command("prepare-authority", "--run-dir", str(run_dir)), keyless_environment)
240
+ assert prepared["taskCount"] == 1
241
+ assert prepared["status"] == "prepared" and prepared["trustedSearchRequiredCount"] == 0
242
+ request = json.loads((run_dir / "authority" / "requests" / "K1.json").read_text(encoding="utf-8"))
243
+ assert set(request["cloudPayload"]) == {"title", "knowledgePoint", "differingClaims"}
244
+ assert "answerMarkdown" not in json.dumps(request["cloudPayload"], ensure_ascii=False)
245
+ searched = run(command("search-authority", "--run-dir", str(run_dir), "--max-workers", "12"), keyless_environment)
246
+ assert searched["trustedSearchRequestCount"] == 0 and searched["dknowExemptCount"] == 1
247
+ assessments = run_dir / "authority" / "assessments"
248
+ assessments.mkdir(parents=True)
249
+ assessment = json.loads((AUTHORITY_FIXTURES / "K1-assessment.json").read_text(encoding="utf-8"))
250
+ (assessments / "K1.json").write_text(json.dumps(assessment, ensure_ascii=False), encoding="utf-8")
251
+ original_pipeline_run = fact_check_x.run
252
+
253
+ def fail_authority_report(arguments, environment=None):
254
+ if any(Path(str(item)).name == "render_authority_report.py" for item in arguments):
255
+ raise fact_check_x.PipelineError("强制报告渲染失败")
256
+ return original_pipeline_run(arguments, environment)
257
+
258
+ with patch.object(fact_check_x, "run", side_effect=fail_authority_report):
259
+ try:
260
+ fact_check_x.finalize_authority(
261
+ type("Args", (), {"run_dir": str(run_dir), "assessments_dir": None})(),
262
+ {key: Path(value) for key, value in located["skills"].items()},
263
+ )
264
+ except fact_check_x.PipelineError as exc:
265
+ assert "强制报告渲染失败" in str(exc)
266
+ else:
267
+ raise AssertionError("报告渲染失败应回滚权威核验事务")
268
+ assert json.loads(
269
+ (run_dir / "authority-gate.json").read_text(encoding="utf-8")
270
+ )["status"] == "searched"
271
+ assert not list((run_dir / "authority" / "results").glob("*.json"))
272
+ assert not (run_dir / "verification.json").exists()
273
+ assert not (run_dir / "03-authority-report.html").exists()
274
+ finalized = run(command("finalize-authority", "--run-dir", str(run_dir)))
275
+ assert finalized["status"] == "completed"
276
+ assert finalized["stage"] == "authority_completed"
277
+ assert finalized["deliverables"][0]["path"] == str(
278
+ (run_dir / "03-authority-report.html").resolve()
279
+ )
280
+ assert_deliverable(
281
+ finalized["deliverables"][0],
282
+ "权威核验后的最终答案",
283
+ run_dir / "03-authority-report.html",
284
+ )
285
+ assert finalized["checkpoint"]["mustPresentBeforeNextStage"] is True
286
+ assert finalized["checkpoint"]["path"] == str(
287
+ (run_dir / "03-authority-report.html").resolve()
288
+ )
289
+ assert "打开权威核验后的最终答案" in finalized["checkpoint"]["message"]
290
+ assert (run_dir / "03-authority-report.html").exists()
291
+ assert json.loads((run_dir / "authority-gate.json").read_text(encoding="utf-8"))["status"] == "finalized"
292
+ reopened = run(command(
293
+ "reopen-authority",
294
+ "--run-dir", str(run_dir),
295
+ "--reason", "修正被截断的权威结论",
296
+ ))
297
+ assert reopened["stage"] == "authority_reopened"
298
+ assert reopened["revision"] == 1
299
+ revision_dir = run_dir / "authority" / "revisions" / "revision-001"
300
+ assert (revision_dir / "revision.json").is_file()
301
+ assert (revision_dir / "verification.json").is_file()
302
+ assert (revision_dir / "authority" / "results" / "K1.json").is_file()
303
+ assert not list((run_dir / "authority" / "results").glob("*.json"))
304
+ assert not (run_dir / "verification.json").exists()
305
+ assert not (run_dir / "03-authority-report.html").exists()
306
+ reopened_gate = json.loads((run_dir / "authority-gate.json").read_text(encoding="utf-8"))
307
+ assert reopened_gate["status"] == "searched"
308
+ comparison_data = json.loads((run_dir / "comparison.json").read_text(encoding="utf-8"))
309
+ exact_title = comparison_data["knowledgePoints"][0]["description"]
310
+ assessment["authoritativeFinding"] = f"{exact_title}:每人每月最高提取1400元。"
311
+ (assessments / "K1.json").write_text(
312
+ json.dumps(assessment, ensure_ascii=False), encoding="utf-8"
313
+ )
314
+ refinalized = run(command("finalize-authority", "--run-dir", str(run_dir)))
315
+ assert refinalized["status"] == "completed"
316
+ reopened_verification = json.loads(
317
+ (run_dir / "verification.json").read_text(encoding="utf-8")
318
+ )
319
+ assert reopened_verification["finalAnswer"]["items"][0]["answer"] == assessment["authoritativeFinding"]
320
+ locked_verification = (run_dir / "verification.json").read_bytes()
321
+ locked_authority_report = (run_dir / "03-authority-report.html").read_bytes()
322
+ (run_dir / "verification.json").write_bytes(locked_verification + b"\n")
323
+ immutable_rejection = run_failed(command(
324
+ "deliver", "--results", str(results), "--run-dir", str(run_dir)
325
+ ))
326
+ assert "第三步权威核验结果已被修改" in immutable_rejection["error"]
327
+ (run_dir / "verification.json").write_bytes(locked_verification)
328
+ delivered = run(command("deliver", "--results", str(results), "--run-dir", str(run_dir)))
329
+ assert delivered["status"] == "completed"
330
+ assert delivered["trustedSearchRequestCount"] == 0 and delivered["dknowExemptCount"] == 1
331
+ assert delivered["answerReferenceReport"] == str((run_dir / "capture" / "report.html").resolve())
332
+ assert delivered["comparisonReport"] == str((run_dir / "comparison.html").resolve())
333
+ assert delivered["artifacts"]["report"] == str((run_dir / "report.html").resolve())
334
+ deliverable_paths = [item["path"] for item in delivered["deliverables"]]
335
+ assert deliverable_paths[:4] == [
336
+ str((run_dir / "01-capture-report.html").resolve()),
337
+ str((run_dir / "02-comparison-report.html").resolve()),
338
+ str((run_dir / "03-authority-report.html").resolve()),
339
+ str((run_dir / "04-final-report.html").resolve()),
340
+ ]
341
+ assert deliverable_paths[4] == delivered["artifacts"]["reportPackage"]
342
+ with zipfile.ZipFile(deliverable_paths[4]) as report_archive:
343
+ packaged_checkpoints = json.loads(
344
+ report_archive.read(
345
+ "fact-check-x-report/data/stage-checkpoints.json"
346
+ ).decode("utf-8")
347
+ )
348
+ assert list(packaged_checkpoints["stages"]) == [
349
+ "capture", "comparison", "authority", "evaluation"
350
+ ]
351
+ assert packaged_checkpoints["stages"]["evaluation"]["status"] == "completed"
352
+ for packaged_stage in packaged_checkpoints["stages"].values():
353
+ for packaged_item in packaged_stage.get("deliverables") or []:
354
+ assert packaged_item["fileUri"].startswith("../")
355
+ assert "file://" not in packaged_item["markdownLink"]
356
+ assert "file://" not in packaged_item["message"]
357
+ assert delivered["checkpoint"]["mustPresentBeforeNextStage"] is True
358
+ assert delivered["checkpoint"]["path"] == str(
359
+ (run_dir / "04-final-report.html").resolve()
360
+ )
361
+ assert "打开各方答案测评报告" in delivered["checkpoint"]["message"]
362
+ assert (run_dir / "verification.json").read_bytes() == locked_verification
363
+ assert (run_dir / "03-authority-report.html").read_bytes() == locked_authority_report
364
+ assert all(Path(item["path"]).exists() for item in delivered["deliverables"])
365
+ for item, label, path in (
366
+ (delivered["deliverables"][0], "各方答案汇总", run_dir / "01-capture-report.html"),
367
+ (delivered["deliverables"][1], "各方答案聚合(未核验)", run_dir / "02-comparison-report.html"),
368
+ (delivered["deliverables"][2], "权威核验后的最终答案", run_dir / "03-authority-report.html"),
369
+ (delivered["deliverables"][3], "各方答案测评报告", run_dir / "04-final-report.html"),
370
+ (delivered["deliverables"][4], "完整可分发报告包", Path(deliverable_paths[4])),
371
+ ):
372
+ assert_deliverable(item, label, path)
373
+ manifest = json.loads((run_dir / "pipeline.json").read_text(encoding="utf-8"))
374
+ verification = json.loads((run_dir / "verification.json").read_text(encoding="utf-8"))
375
+ report = (run_dir / "report.html").read_text(encoding="utf-8")
376
+ authority_report = (run_dir / "03-authority-report.html").read_text(
377
+ encoding="utf-8"
378
+ )
379
+ comparison_deliverable = (run_dir / "02-comparison-report.html").read_text(
380
+ encoding="utf-8"
381
+ )
382
+ assert manifest["schemaVersion"] == "fact-check-x/pipeline@2"
383
+ assert verification["finalAnswer"]["status"] == "verified"
384
+ assert verification["finalAnswer"]["knowledgePointIds"] == ["K1"]
385
+ assert verification["knowledgePoints"][0]["authority"]["verdicts"]["doubao"]["category"] == "misleading"
386
+ assert "权威核验报告(问题:" in authority_report
387
+ assert "权威核验后的最终答案" in authority_report
388
+ assert "各平台裁决" not in authority_report
389
+ assert "data-fcx-authority-binding-sha256" not in authority_report
390
+ report_names = [
391
+ "01-capture-report.html",
392
+ "02-comparison-report.html",
393
+ "03-authority-report.html",
394
+ "04-final-report.html",
395
+ ]
396
+ for report_name in report_names:
397
+ report_html = (run_dir / report_name).read_text(encoding="utf-8")
398
+ assert report_html.count('data-fcx-report-nav="1"') == 1
399
+ assert 'aria-current="page"' in report_html
400
+ assert "来源链接待补" not in report_html
401
+ assert all(
402
+ f'href="{target}"' in report_html
403
+ for target in report_names
404
+ if target != report_name
405
+ )
406
+ assert all(
407
+ f'href="{target}"' in comparison_deliverable
408
+ for target in (
409
+ "01-capture-report.html",
410
+ "03-authority-report.html",
411
+ "04-final-report.html",
412
+ )
413
+ )
414
+ assert "① 平台表现概览" in report
415
+ assert "② 逐知识点核验明细" in report
416
+ assert "③ 原始答案与参考文献(存证)" in report
417
+ assert "④ 指标口径速查" in report
418
+ assert "⑤ 评测元信息" in report
419
+ assert report.count("② 逐知识点核验明细") == 1
420
+ assert 'data-fcx-locked-role="direct"' in report
421
+ assert 'data-fcx-locked-role="reference"' in report
422
+ assert "② 直接答案逐知识点测评" not in report
423
+ assert "③ 补充参考逐知识点测评" not in report
424
+ assert "② 直接答案逐条判定" not in report
425
+ assert "②-补 补充参考分析" not in report
426
+ assert (run_dir / "comparison.html").exists()
427
+ capture_report = (run_dir / "capture" / "report.html").read_text(encoding="utf-8")
428
+ assert "各方答案汇总(问题:" in capture_report
429
+ assert "每人每月最高提取 1400 元" in capture_report
430
+ assert (run_dir / "capture" / "report.md").exists()
431
+ assert manifest["artifacts"]["answerReferenceReport"] == str((run_dir / "capture" / "report.html").resolve())
432
+ assert manifest["artifacts"]["captureGate"] == str((run_dir / "capture-gate.json").resolve())
433
+ assert manifest["artifacts"]["comparisonAnalysis"] == str(
434
+ (run_dir / "comparison-analysis.json").resolve()
435
+ )
436
+ assert manifest["artifacts"]["authorityGate"] == str((run_dir / "authority-gate.json").resolve())
437
+
438
+ artifact_source = Path(temp) / "artifact-source"
439
+ artifact_source.mkdir()
440
+ artifact_results = json.loads(results.read_text(encoding="utf-8"))
441
+ for platform in artifact_results["platforms"]:
442
+ platform_id = platform["platform"]
443
+ platform["artifacts"] = {
444
+ "screenshot": f"artifacts/{platform_id}/screenshot.png",
445
+ "html": f"artifacts/{platform_id}/page.html",
446
+ }
447
+ artifact_dir = artifact_source / "artifacts" / platform_id
448
+ artifact_dir.mkdir(parents=True)
449
+ (artifact_dir / "screenshot.png").write_bytes(f"png:{platform_id}".encode())
450
+ (artifact_dir / "page.html").write_text(
451
+ f"<html>{platform_id}</html>", encoding="utf-8"
452
+ )
453
+ artifact_results_path = artifact_source / "results.json"
454
+ artifact_results_path.write_text(
455
+ json.dumps(artifact_results, ensure_ascii=False), encoding="utf-8"
456
+ )
457
+ (artifact_source / "capture-recovery.json").write_text(
458
+ json.dumps({
459
+ "schemaVersion": "fact-check-x/capture-recovery@2",
460
+ "status": "completed",
461
+ }, ensure_ascii=False),
462
+ encoding="utf-8",
463
+ )
464
+ artifact_run = Path(temp) / "artifact-run"
465
+ run(command(
466
+ "prepare-comparison",
467
+ "--results",
468
+ str(artifact_results_path),
469
+ "--run-dir",
470
+ str(artifact_run),
471
+ ))
472
+ artifact_gate = json.loads(
473
+ (artifact_run / "capture-gate.json").read_text(encoding="utf-8")
474
+ )
475
+ assert len(artifact_gate["artifactSha256"]) == 4
476
+ assert artifact_gate["recoverySha256"]
477
+ assert (artifact_run / "capture/capture-recovery.json").is_file()
478
+ for platform in artifact_results["platforms"]:
479
+ platform_id = platform["platform"]
480
+ assert (
481
+ artifact_run / f"capture/artifacts/{platform_id}/screenshot.png"
482
+ ).is_file()
483
+ assert (
484
+ artifact_run / f"capture/artifacts/{platform_id}/page.html"
485
+ ).is_file()
486
+ assert (
487
+ artifact_run / f"artifacts/{platform_id}/screenshot.png"
488
+ ).is_file()
489
+ assert (
490
+ artifact_run / f"artifacts/{platform_id}/page.html"
491
+ ).is_file()
492
+ missing_artifact_results = json.loads(
493
+ artifact_results_path.read_text(encoding="utf-8")
494
+ )
495
+ (
496
+ artifact_source
497
+ / missing_artifact_results["platforms"][0]["artifacts"]["screenshot"]
498
+ ).unlink()
499
+ missing_artifact_run = Path(temp) / "missing-artifact-run"
500
+ missing_artifact = run_failed(command(
501
+ "prepare-comparison",
502
+ "--results",
503
+ str(artifact_results_path),
504
+ "--run-dir",
505
+ str(missing_artifact_run),
506
+ ))
507
+ assert "存证文件不存在" in missing_artifact["error"]
508
+
509
+ configuration_run = Path(temp) / "configuration-run"
510
+ run(command("prepare-comparison", "--results", str(results), "--run-dir", str(configuration_run)))
511
+ configuration_analysis = json.loads((COMPARE_FIXTURES / "comparison-analysis.json").read_text(encoding="utf-8"))
512
+ configuration_analysis["knowledgePoints"][0]["trustedAnchor"] = {"eligible": False}
513
+ configuration_analysis["knowledgePoints"][0]["claims"]["dknowc-chat"] = {
514
+ "covered": False,
515
+ "claim": "",
516
+ "answerExcerpt": "",
517
+ "citedReferenceIndexes": [],
518
+ "answerLevelReferenceIndexes": [],
519
+ "faithfulness": "insufficient",
520
+ "reason": "",
521
+ "evidence": [],
522
+ }
523
+ configuration_analysis_path = configuration_run / "comparison-analysis.json"
524
+ configuration_analysis_path.write_text(json.dumps(configuration_analysis, ensure_ascii=False), encoding="utf-8")
525
+ run(command("complete-comparison", "--results", str(results), "--analysis", str(configuration_analysis_path), "--run-dir", str(configuration_run)))
526
+ configuration_required = run_failed(command(
527
+ "prepare-authority",
528
+ "--run-dir",
529
+ str(configuration_run),
530
+ ), keyless_environment)
531
+ assert configuration_required["status"] == "configuration_required"
532
+ assert configuration_required["action"] == "configure_trusted_search"
533
+ assert configuration_required["trustedSearchRequiredCount"] == 1
534
+ assert "您只需完成登录" in configuration_required["userPrompt"]
535
+ assert "自动读取已有的可用 Key" in configuration_required["userPrompt"]
536
+ assert configuration_required["configuration"]["providerUrl"] == "https://platform.dknowc.cn/auth/#/login"
537
+ assert configuration_required["configuration"]["interaction"] == "browser_login_only"
538
+ assert configuration_required["configuration"]["requiresChatSecret"] is False
539
+ assert configuration_required["configuration"]["autoResume"] is True
540
+ assert configuration_required["configuration"]["sharedAcrossCarriers"] is True
541
+ assert configuration_required["configuration"]["command"][-1] == "configure"
542
+ assert configuration_required["configuration"]["command"][-2].endswith(
543
+ "trusted_search_config.py"
544
+ )
545
+ assert json.loads((configuration_run / "authority-gate.json").read_text(encoding="utf-8"))["status"] == "configuration_required"
546
+ search_blocked = run_failed(command(
547
+ "search-authority",
548
+ "--run-dir",
549
+ str(configuration_run),
550
+ "--max-workers",
551
+ "12",
552
+ ), keyless_environment)
553
+ assert search_blocked["status"] == "configuration_required"
554
+ assert search_blocked["action"] == "configure_trusted_search"
555
+ assert not (configuration_run / "authority" / "evidence" / "batch.json").exists()
556
+ bypass_attempt = run_failed(command(
557
+ "finalize-authority",
558
+ "--run-dir",
559
+ str(configuration_run),
560
+ ), keyless_environment)
561
+ assert bypass_attempt["status"] == "failed"
562
+ assert "禁止写入裁决" in bypass_attempt["error"]
563
+ bypass_delivery = run_failed(command(
564
+ "deliver",
565
+ "--results",
566
+ str(results),
567
+ "--run-dir",
568
+ str(configuration_run),
569
+ ), keyless_environment)
570
+ assert bypass_delivery["status"] == "failed"
571
+ assert "缺少 authority 阶段产物交付记录" in bypass_delivery["error"]
572
+
573
+ tamper_run = Path(temp) / "tamper-run"
574
+ run(command("prepare-comparison", "--results", str(results), "--run-dir", str(tamper_run)))
575
+ run(command("complete-comparison", "--results", str(results), "--analysis", str(COMPARE_FIXTURES / "comparison-analysis.json"), "--run-dir", str(tamper_run)))
576
+ tampered = json.loads((tamper_run / "comparison.json").read_text(encoding="utf-8"))
577
+ tampered["knowledgePoints"][0]["claims"]["doubao"]["sourceLevel"] = "official"
578
+ (tamper_run / "comparison.json").write_text(json.dumps(tampered, ensure_ascii=False), encoding="utf-8")
579
+ tamper_rejected = run_failed(command("prepare-authority", "--run-dir", str(tamper_run)))
580
+ assert "被修改" in tamper_rejected["error"]
581
+
582
+ pollution_run = Path(temp) / "pollution-run"
583
+ run(command("prepare-comparison", "--results", str(results), "--run-dir", str(pollution_run)))
584
+ run(command("complete-comparison", "--results", str(results), "--analysis", str(COMPARE_FIXTURES / "comparison-analysis.json"), "--run-dir", str(pollution_run)))
585
+ run(command("prepare-authority", "--run-dir", str(pollution_run)))
586
+ run(command("search-authority", "--run-dir", str(pollution_run), "--max-workers", "12"))
587
+ pollution_assessments = pollution_run / "authority" / "assessments"
588
+ pollution_assessments.mkdir(parents=True)
589
+ shutil.copyfile(AUTHORITY_FIXTURES / "K1-assessment.json", pollution_assessments / "K1.json")
590
+ pollution_results = pollution_run / "authority" / "results"
591
+ pollution_results.mkdir(parents=True)
592
+ (pollution_results / "KP-001.json").write_text("{}\n", encoding="utf-8")
593
+ pollution_rejected = run_failed(command("finalize-authority", "--run-dir", str(pollution_run)))
594
+ assert "results 目录必须为空" in pollution_rejected["error"]
595
+
596
+ invalid_assessments = Path(temp) / "invalid-assessments"
597
+ invalid_assessments.mkdir()
598
+ (invalid_assessments / "K1.json").write_text(json.dumps({
599
+ "requestId": "K1",
600
+ "verdict": "supported",
601
+ "officialAnswer": "旧结构结论",
602
+ "platformAssessment": {
603
+ "dknowc-chat": {"verdict": "supported", "reason": "旧结构"},
604
+ "doubao": {"verdict": "supported", "reason": "旧结构"},
605
+ },
606
+ }, ensure_ascii=False), encoding="utf-8")
607
+ invalid_run = Path(temp) / "invalid-run"
608
+ run(command("prepare-comparison", "--results", str(results), "--run-dir", str(invalid_run)))
609
+ run(command("complete-comparison", "--results", str(results), "--analysis", str(COMPARE_FIXTURES / "comparison-analysis.json"), "--run-dir", str(invalid_run)))
610
+ run(command("prepare-authority", "--run-dir", str(invalid_run)))
611
+ run(command("search-authority", "--run-dir", str(invalid_run), "--max-workers", "12"))
612
+ invalid_final = run_failed(command(
613
+ "finalize-authority",
614
+ "--run-dir",
615
+ str(invalid_run),
616
+ "--assessments-dir",
617
+ str(invalid_assessments),
618
+ ))
619
+ assert invalid_final["status"] == "failed"
620
+ assert "裁决文件结构错误" in invalid_final["error"]
621
+ assert not (invalid_run / "authority" / "results" / "K1.json").exists()
622
+
623
+ review_run = Path(temp) / "review-run"
624
+ run(command("prepare-comparison", "--results", str(results), "--run-dir", str(review_run)))
625
+ run(command("complete-comparison", "--results", str(results), "--analysis", str(COMPARE_FIXTURES / "comparison-analysis.json"), "--run-dir", str(review_run)))
626
+ run(command("prepare-authority", "--run-dir", str(review_run)))
627
+ run(command("search-authority", "--run-dir", str(review_run), "--max-workers", "12"))
628
+ review_assessments = review_run / "authority" / "assessments"
629
+ review_assessments.mkdir(parents=True)
630
+ review_assessment = json.loads((AUTHORITY_FIXTURES / "K1-assessment.json").read_text(encoding="utf-8"))
631
+ review_assessment["verdicts"]["doubao"] = {
632
+ "verdict": "insufficient",
633
+ "reason": "当前证据不足以完成裁决",
634
+ "evidenceIds": [],
635
+ }
636
+ (review_assessments / "K1.json").write_text(json.dumps(review_assessment, ensure_ascii=False), encoding="utf-8")
637
+ review_final = run(command("finalize-authority", "--run-dir", str(review_run)))
638
+ assert review_final["status"] == "completed"
639
+ assert review_final["stage"] == "authority_completed"
640
+ assert review_final["evidenceGapCount"] == 1
641
+ assert review_final["deliverables"][0]["path"] == str(
642
+ (review_run / "03-authority-report.html").resolve()
643
+ )
644
+ review_authority_report = (
645
+ review_run / "03-authority-report.html"
646
+ ).read_text(encoding="utf-8")
647
+ assert "证据不足项" in review_authority_report
648
+ assert 'href="04-final-report.html"' in review_authority_report
649
+ assert json.loads((review_run / "authority-gate.json").read_text(encoding="utf-8"))["status"] == "finalized"
650
+ review_delivery = run(command("deliver", "--results", str(results), "--run-dir", str(review_run)))
651
+ assert review_delivery["status"] == "completed"
652
+ assert review_delivery["evidenceGapCount"] == 1
653
+ assert (review_run / "04-final-report.html").is_file()
654
+ review_verification = json.loads(
655
+ (review_run / "verification.json").read_text(encoding="utf-8")
656
+ )
657
+ assert review_verification["status"] == "completed"
658
+ assert review_verification["evidenceGapCount"] == 1
659
+ assert review_verification["evidenceGaps"][0]["platform"] == "doubao"
660
+
661
+ failed_results = json.loads(results.read_text(encoding="utf-8"))
662
+ failed_results["platforms"][0]["answerMarkdown"] = "为您智能匹配到当前所在区域为“北京市”,如想咨询其他区域可点击修改"
663
+ failed_results["platforms"][1]["status"] = "failed"
664
+ failed_results["platforms"][1]["answerMarkdown"] = ""
665
+ failed_results["platforms"][1]["error"] = "No answer text detected."
666
+ failed_path = Path(temp) / "failed-results.json"
667
+ failed_path.write_text(json.dumps(failed_results, ensure_ascii=False), encoding="utf-8")
668
+ failed_run = Path(temp) / "failed-run"
669
+ rejected = run_failed(command("prepare-comparison", "--results", str(failed_path), "--run-dir", str(failed_run)))
670
+ assert rejected["status"] == "failed"
671
+ assert "禁止进入知识点对比" in rejected["error"]
672
+ assert not (failed_run / "comparison-task.json").exists()
673
+
674
+ answered_with_region_footer = json.loads(results.read_text(encoding="utf-8"))
675
+ answered_with_region_footer["platforms"][0]["answerMarkdown"] = (
676
+ "北京市高考报名资格、网上申请、填报缴费和现场确认的完整政策回答。" * 10
677
+ + "页面底部:为您智能匹配到当前所在区域为“北京市”,如想咨询其他区域可点击修改"
678
+ )
679
+ answered_with_region_footer_path = Path(temp) / "answered-with-region-footer.json"
680
+ answered_with_region_footer_path.write_text(
681
+ json.dumps(answered_with_region_footer, ensure_ascii=False),
682
+ encoding="utf-8",
683
+ )
684
+ answered_with_region_footer_run = Path(temp) / "answered-with-region-footer-run"
685
+ accepted_with_footer = run(command(
686
+ "prepare-comparison",
687
+ "--results",
688
+ str(answered_with_region_footer_path),
689
+ "--run-dir",
690
+ str(answered_with_region_footer_run),
691
+ ))
692
+ assert accepted_with_footer["stage"] == "capture_completed"
693
+ assert (answered_with_region_footer_run / "comparison-task.json").exists()
694
+
695
+ recovery_capture = Path(temp) / "recovery-capture"
696
+ recovery_capture.mkdir()
697
+ recovery_results = recovery_capture / "results.json"
698
+ recovery_results.write_text(results.read_text(encoding="utf-8"), encoding="utf-8")
699
+ (recovery_capture / "capture-recovery.json").write_text(
700
+ json.dumps({
701
+ "schemaVersion": "fact-check-x/capture-recovery@2",
702
+ "status": "required",
703
+ "action": "computer_use",
704
+ }, ensure_ascii=False),
705
+ encoding="utf-8",
706
+ )
707
+ recovery_run = Path(temp) / "recovery-run"
708
+ recovery_rejected = run_failed(command(
709
+ "prepare-comparison",
710
+ "--results",
711
+ str(recovery_results),
712
+ "--run-dir",
713
+ str(recovery_run),
714
+ ))
715
+ assert "Computer Use" in recovery_rejected["error"]
716
+ assert not (recovery_run / "comparison-task.json").exists()
717
+ assert not (ROOT / "assets").exists()
718
+ if os.getenv("FACT_CHECK_X_ASSERTIONS_OUTPUT"):
719
+ Path(os.environ["FACT_CHECK_X_ASSERTIONS_OUTPUT"]).write_text(json.dumps({
720
+ "schemaVersion": "fact-check-x/test-assertions@1",
721
+ "actualAssertionIds": [
722
+ "report.stage_artifacts_visible",
723
+ "report.checkpoints_indexed",
724
+ "report.unverified_draft_visible",
725
+ "report.verified_final_answer_visible",
726
+ "report.stage2_role_sections",
727
+ "report.stage4_single_locked_detail",
728
+ "report.technical_failure_notice",
729
+ "report.renamed_four_stages",
730
+ "authority.finalize_transaction_rollback",
731
+ "comparison.overwide_claim_blocks_authority",
732
+ "path.windows_drive_normalized",
733
+ ],
734
+ }), encoding="utf-8")
735
+ print("PASS Fact-Check-X 统一入口")
736
+ return 0
737
+
738
+
739
+ if __name__ == "__main__":
740
+ raise SystemExit(main())