playbook-engine 2.0.0__tar.gz

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 (326) hide show
  1. playbook_engine-2.0.0/.claude/skills/playbook-from-corpus/REFERENCE.md +839 -0
  2. playbook_engine-2.0.0/.claude/skills/playbook-from-corpus/SKILL.md +1817 -0
  3. playbook_engine-2.0.0/.claude/skills/playbook-from-corpus/estimate_runtime.py +423 -0
  4. playbook_engine-2.0.0/.dockerignore +41 -0
  5. playbook_engine-2.0.0/.github/ISSUE_TEMPLATE/bug.yml +32 -0
  6. playbook_engine-2.0.0/.github/ISSUE_TEMPLATE/new-agreement-type.yml +33 -0
  7. playbook_engine-2.0.0/.github/ISSUE_TEMPLATE/spec-question.yml +25 -0
  8. playbook_engine-2.0.0/.github/pull_request_template.md +21 -0
  9. playbook_engine-2.0.0/.github/workflows/ci.yml +76 -0
  10. playbook_engine-2.0.0/.github/workflows/docker-publish.yml +70 -0
  11. playbook_engine-2.0.0/.github/workflows/docker-smoke.yml +150 -0
  12. playbook_engine-2.0.0/.github/workflows/pages.yml +51 -0
  13. playbook_engine-2.0.0/.github/workflows/pypi-publish.yml +122 -0
  14. playbook_engine-2.0.0/.gitignore +41 -0
  15. playbook_engine-2.0.0/CHANGELOG.md +1590 -0
  16. playbook_engine-2.0.0/CODE_OF_CONDUCT.md +135 -0
  17. playbook_engine-2.0.0/CONTRIBUTING.md +208 -0
  18. playbook_engine-2.0.0/Dockerfile +136 -0
  19. playbook_engine-2.0.0/LICENSE +224 -0
  20. playbook_engine-2.0.0/Makefile +210 -0
  21. playbook_engine-2.0.0/NOTICE +15 -0
  22. playbook_engine-2.0.0/PKG-INFO +322 -0
  23. playbook_engine-2.0.0/README.md +280 -0
  24. playbook_engine-2.0.0/SECURITY.md +51 -0
  25. playbook_engine-2.0.0/docker/constraints.txt +35 -0
  26. playbook_engine-2.0.0/docs/ADOPTING.md +372 -0
  27. playbook_engine-2.0.0/docs/ARCHITECTURE.md +142 -0
  28. playbook_engine-2.0.0/docs/CORPUS-LAYOUT.md +75 -0
  29. playbook_engine-2.0.0/docs/OPF-BUNDLE-BOUNDARY.md +96 -0
  30. playbook_engine-2.0.0/docs/OPF-SPEC-v0.1.md +221 -0
  31. playbook_engine-2.0.0/docs/OPF-SPEC.md +1771 -0
  32. playbook_engine-2.0.0/docs/PLAN-FIRST.md +44 -0
  33. playbook_engine-2.0.0/docs/QUICK-COMPILE.md +248 -0
  34. playbook_engine-2.0.0/docs/REAL-CORPUS-DERIVATION.md +101 -0
  35. playbook_engine-2.0.0/docs/prompts/create-playbook.md +12 -0
  36. playbook_engine-2.0.0/docs/reviews/2026-08-24-skill-qa-audit/README.md +33 -0
  37. playbook_engine-2.0.0/docs/reviews/2026-08-24-skill-qa-audit/findings.md +1232 -0
  38. playbook_engine-2.0.0/docs/reviews/2026-08-24-skill-qa-audit/report.html +163 -0
  39. playbook_engine-2.0.0/examples/.gitkeep +0 -0
  40. playbook_engine-2.0.0/examples/README.md +242 -0
  41. playbook_engine-2.0.0/examples/affiliation-config/playbook.config.yaml +41 -0
  42. playbook_engine-2.0.0/examples/canary/README.md +128 -0
  43. playbook_engine-2.0.0/examples/canary/build_corpus.py +373 -0
  44. playbook_engine-2.0.0/examples/canary/build_expected.py +52 -0
  45. playbook_engine-2.0.0/examples/canary/build_verdicts.py +190 -0
  46. playbook_engine-2.0.0/examples/canary/config.yaml +67 -0
  47. playbook_engine-2.0.0/examples/canary/corpus/halcyon-freight/v1.docx +0 -0
  48. playbook_engine-2.0.0/examples/canary/corpus/halcyon-freight/v2.docx +0 -0
  49. playbook_engine-2.0.0/examples/canary/corpus/meridian-assay/v1.docx +0 -0
  50. playbook_engine-2.0.0/examples/canary/corpus/meridian-assay/v2.docx +0 -0
  51. playbook_engine-2.0.0/examples/canary/expected.json +29 -0
  52. playbook_engine-2.0.0/examples/canary/segment-verdicts.jsonl +4 -0
  53. playbook_engine-2.0.0/examples/fixtures/invalid_missing_opf_version.json +8 -0
  54. playbook_engine-2.0.0/examples/fixtures/invalid_unknown_opf_version.json +27 -0
  55. playbook_engine-2.0.0/examples/judge-fixture/canned-verdicts.jsonl +8 -0
  56. playbook_engine-2.0.0/examples/judge-fixture/config.yaml +25 -0
  57. playbook_engine-2.0.0/examples/judge-fixture/corpus/deal-alpha/hints.yaml +9 -0
  58. playbook_engine-2.0.0/examples/judge-fixture/corpus/deal-alpha/v1.rtf +1 -0
  59. playbook_engine-2.0.0/examples/judge-fixture/corpus/deal-alpha/v2.rtf +1 -0
  60. playbook_engine-2.0.0/examples/judge-fixture/corpus/deal-beta/hints.yaml +3 -0
  61. playbook_engine-2.0.0/examples/judge-fixture/corpus/deal-beta/v1.rtf +1 -0
  62. playbook_engine-2.0.0/examples/judge-fixture/posture-answers.json +8 -0
  63. playbook_engine-2.0.0/examples/nda/README.md +311 -0
  64. playbook_engine-2.0.0/examples/nda/canned-verdicts.jsonl +48 -0
  65. playbook_engine-2.0.0/examples/nda/config.smoke.yaml +52 -0
  66. playbook_engine-2.0.0/examples/nda/config.yaml +53 -0
  67. playbook_engine-2.0.0/examples/nda/corpus/beta-industries/v1.rtf +1 -0
  68. playbook_engine-2.0.0/examples/nda/corpus/beta-industries/v2.rtf +1 -0
  69. playbook_engine-2.0.0/examples/nda/corpus/beta-industries/v3.rtf +1 -0
  70. playbook_engine-2.0.0/examples/nda/corpus/beta-industries/v4.rtf +1 -0
  71. playbook_engine-2.0.0/examples/nda/corpus/delta-ventures/v1.rtf +1 -0
  72. playbook_engine-2.0.0/examples/nda/corpus/delta-ventures/v2.rtf +1 -0
  73. playbook_engine-2.0.0/examples/nda/corpus/epsilon-systems/v1.rtf +1 -0
  74. playbook_engine-2.0.0/examples/nda/corpus/epsilon-systems/v2.rtf +1 -0
  75. playbook_engine-2.0.0/examples/nda/corpus/epsilon-systems/v3.rtf +1 -0
  76. playbook_engine-2.0.0/examples/nda/corpus/gamma-holdings/v1.rtf +1 -0
  77. playbook_engine-2.0.0/examples/nda/corpus/gamma-holdings/v2.rtf +1 -0
  78. playbook_engine-2.0.0/examples/nda/corpus/gamma-holdings/v3.rtf +1 -0
  79. playbook_engine-2.0.0/examples/nda/corpus/theta-logistics/v1.rtf +1 -0
  80. playbook_engine-2.0.0/examples/nda/corpus/theta-logistics/v2.rtf +1 -0
  81. playbook_engine-2.0.0/examples/nda/corpus/zeta-diagnostics/v1.rtf +1 -0
  82. playbook_engine-2.0.0/examples/nda/corpus/zeta-diagnostics/v2.rtf +1 -0
  83. playbook_engine-2.0.0/examples/nda/corpus/zeta-diagnostics/v3.rtf +1 -0
  84. playbook_engine-2.0.0/examples/nda/playbook.opf.json +10864 -0
  85. playbook_engine-2.0.0/examples/nda/posture-answers.json +8 -0
  86. playbook_engine-2.0.0/examples/nda/precedent.jsonl +128 -0
  87. playbook_engine-2.0.0/examples/nda/standard-form.rtf +1 -0
  88. playbook_engine-2.0.0/examples/staging-fixtures/clm-nested-corpus/deal-delta/EXECUTED_deal-delta.pdf +1 -0
  89. playbook_engine-2.0.0/examples/staging-fixtures/clm-nested-corpus/deal-delta/Versions/01_draft.rtf +1 -0
  90. playbook_engine-2.0.0/examples/staging-fixtures/clm-nested-corpus/deal-delta/Versions/02_our_redline.rtf +1 -0
  91. playbook_engine-2.0.0/examples/staging-fixtures/clm-nested-corpus/deal-gamma/EXECUTED_deal-gamma.pdf +1 -0
  92. playbook_engine-2.0.0/examples/staging-fixtures/clm-nested-corpus/deal-gamma/Versions/01_draft.rtf +1 -0
  93. playbook_engine-2.0.0/examples/staging-fixtures/clm-nested-corpus/deal-gamma/Versions/02_counterparty_redline.rtf +1 -0
  94. playbook_engine-2.0.0/examples/staging-fixtures/flat-corpus/deal-alpha/01_draft.rtf +1 -0
  95. playbook_engine-2.0.0/examples/staging-fixtures/flat-corpus/deal-alpha/02_signed_final.rtf +1 -0
  96. playbook_engine-2.0.0/examples/staging-fixtures/flat-corpus/deal-beta/01_draft.rtf +1 -0
  97. playbook_engine-2.0.0/examples/staging-fixtures/flat-corpus/deal-beta/02_counterparty_redline.rtf +1 -0
  98. playbook_engine-2.0.0/examples/staging-fixtures/manifest-corpus/docs/deal-epsilon/01_initial_draft.rtf +1 -0
  99. playbook_engine-2.0.0/examples/staging-fixtures/manifest-corpus/docs/deal-epsilon/02_our_redline.rtf +1 -0
  100. playbook_engine-2.0.0/examples/staging-fixtures/manifest-corpus/docs/deal-epsilon/03_executed_final.rtf +1 -0
  101. playbook_engine-2.0.0/examples/staging-fixtures/manifest-corpus/docs/deal-zeta/01_draft.rtf +1 -0
  102. playbook_engine-2.0.0/examples/staging-fixtures/manifest-corpus/docs/deal-zeta/02_signed.rtf +1 -0
  103. playbook_engine-2.0.0/examples/staging-fixtures/manifest-corpus/manifest.jsonl +5 -0
  104. playbook_engine-2.0.0/playbook_engine/__init__.py +3 -0
  105. playbook_engine-2.0.0/playbook_engine/absence.py +538 -0
  106. playbook_engine-2.0.0/playbook_engine/agent_judge.py +1325 -0
  107. playbook_engine-2.0.0/playbook_engine/agent_segmenter.py +140 -0
  108. playbook_engine-2.0.0/playbook_engine/artifact_store.py +314 -0
  109. playbook_engine-2.0.0/playbook_engine/cache_format.py +266 -0
  110. playbook_engine-2.0.0/playbook_engine/canonicalize.py +142 -0
  111. playbook_engine-2.0.0/playbook_engine/citation_resolver.py +274 -0
  112. playbook_engine-2.0.0/playbook_engine/clause_aligner.py +860 -0
  113. playbook_engine-2.0.0/playbook_engine/clause_classifier.py +900 -0
  114. playbook_engine-2.0.0/playbook_engine/clause_differ.py +619 -0
  115. playbook_engine-2.0.0/playbook_engine/clause_position_compiler.py +382 -0
  116. playbook_engine-2.0.0/playbook_engine/clause_tree.py +428 -0
  117. playbook_engine-2.0.0/playbook_engine/cli.py +4266 -0
  118. playbook_engine-2.0.0/playbook_engine/config.py +802 -0
  119. playbook_engine-2.0.0/playbook_engine/corpus_linter.py +969 -0
  120. playbook_engine-2.0.0/playbook_engine/deal_dates.py +814 -0
  121. playbook_engine-2.0.0/playbook_engine/deviation_classifier.py +210 -0
  122. playbook_engine-2.0.0/playbook_engine/digest.py +854 -0
  123. playbook_engine-2.0.0/playbook_engine/document_renderer.py +1134 -0
  124. playbook_engine-2.0.0/playbook_engine/docx_ingester.py +659 -0
  125. playbook_engine-2.0.0/playbook_engine/docx_normalizer.py +279 -0
  126. playbook_engine-2.0.0/playbook_engine/dossiers.py +742 -0
  127. playbook_engine-2.0.0/playbook_engine/entity_registry.py +461 -0
  128. playbook_engine-2.0.0/playbook_engine/environment.py +226 -0
  129. playbook_engine-2.0.0/playbook_engine/equivalence.py +1013 -0
  130. playbook_engine-2.0.0/playbook_engine/equivalence_check.py +591 -0
  131. playbook_engine-2.0.0/playbook_engine/extraction.py +2561 -0
  132. playbook_engine-2.0.0/playbook_engine/floor_candidates.py +1832 -0
  133. playbook_engine-2.0.0/playbook_engine/floor_judge.py +328 -0
  134. playbook_engine-2.0.0/playbook_engine/index_page_assets.py +695 -0
  135. playbook_engine-2.0.0/playbook_engine/induction_version_selector.py +163 -0
  136. playbook_engine-2.0.0/playbook_engine/inspection_report.py +483 -0
  137. playbook_engine-2.0.0/playbook_engine/instrument.py +178 -0
  138. playbook_engine-2.0.0/playbook_engine/intake_plan.py +769 -0
  139. playbook_engine-2.0.0/playbook_engine/judgment.py +387 -0
  140. playbook_engine-2.0.0/playbook_engine/llm_segmentation_stage.py +296 -0
  141. playbook_engine-2.0.0/playbook_engine/llm_segmenter.py +367 -0
  142. playbook_engine-2.0.0/playbook_engine/llm_segmenter_batch.py +762 -0
  143. playbook_engine-2.0.0/playbook_engine/natural_sort.py +22 -0
  144. playbook_engine-2.0.0/playbook_engine/observation_builder.py +2009 -0
  145. playbook_engine-2.0.0/playbook_engine/opf_accessors.py +459 -0
  146. playbook_engine-2.0.0/playbook_engine/overrides.py +805 -0
  147. playbook_engine-2.0.0/playbook_engine/pdf_ingester.py +385 -0
  148. playbook_engine-2.0.0/playbook_engine/pipeline.py +6190 -0
  149. playbook_engine-2.0.0/playbook_engine/playbook_assembler.py +633 -0
  150. playbook_engine-2.0.0/playbook_engine/posture.py +837 -0
  151. playbook_engine-2.0.0/playbook_engine/precedent.py +669 -0
  152. playbook_engine-2.0.0/playbook_engine/prior_form_origin_check.py +270 -0
  153. playbook_engine-2.0.0/playbook_engine/prior_reconstruction.py +1015 -0
  154. playbook_engine-2.0.0/playbook_engine/prior_standards.py +218 -0
  155. playbook_engine-2.0.0/playbook_engine/provenance_detector.py +523 -0
  156. playbook_engine-2.0.0/playbook_engine/resegmentation.py +1312 -0
  157. playbook_engine-2.0.0/playbook_engine/reversal_detector.py +304 -0
  158. playbook_engine-2.0.0/playbook_engine/review_rows.py +320 -0
  159. playbook_engine-2.0.0/playbook_engine/rtf_ingester.py +248 -0
  160. playbook_engine-2.0.0/playbook_engine/rubric.py +532 -0
  161. playbook_engine-2.0.0/playbook_engine/run_manifest.py +1262 -0
  162. playbook_engine-2.0.0/playbook_engine/scope_gate.py +392 -0
  163. playbook_engine-2.0.0/playbook_engine/scorecard.py +1444 -0
  164. playbook_engine-2.0.0/playbook_engine/segmentation_grounding.py +258 -0
  165. playbook_engine-2.0.0/playbook_engine/segmentation_qa.py +581 -0
  166. playbook_engine-2.0.0/playbook_engine/segmenter.py +330 -0
  167. playbook_engine-2.0.0/playbook_engine/signed_detector.py +778 -0
  168. playbook_engine-2.0.0/playbook_engine/staging.py +933 -0
  169. playbook_engine-2.0.0/playbook_engine/subsplit.py +603 -0
  170. playbook_engine-2.0.0/playbook_engine/subsplit_check.py +323 -0
  171. playbook_engine-2.0.0/playbook_engine/taxonomy.py +208 -0
  172. playbook_engine-2.0.0/playbook_engine/taxonomy_inductor.py +529 -0
  173. playbook_engine-2.0.0/playbook_engine/template_standards.py +221 -0
  174. playbook_engine-2.0.0/playbook_engine/toaster_install.py +40 -0
  175. playbook_engine-2.0.0/playbook_engine/tracked_changes_overlay.py +363 -0
  176. playbook_engine-2.0.0/playbook_engine/validator.py +2057 -0
  177. playbook_engine-2.0.0/playbook_engine/version_orderer.py +874 -0
  178. playbook_engine-2.0.0/pyproject.toml +116 -0
  179. playbook_engine-2.0.0/scripts/generate_conformance_vectors.py +2716 -0
  180. playbook_engine-2.0.0/scripts/pre-push-confidentiality.py +269 -0
  181. playbook_engine-2.0.0/scripts/pre-push-lint.sh +99 -0
  182. playbook_engine-2.0.0/spec/CHANGELOG.md +755 -0
  183. playbook_engine-2.0.0/spec/clause-tree.schema.json +86 -0
  184. playbook_engine-2.0.0/spec/conformance/0.6/README.md +44 -0
  185. playbook_engine-2.0.0/spec/conformance/0.6/manifest.json +85 -0
  186. playbook_engine-2.0.0/spec/conformance/0.6/vectors/001-minimal-no-perspective.json +104 -0
  187. playbook_engine-2.0.0/spec/conformance/0.6/vectors/002-variants-refused-and-exclusions.json +682 -0
  188. playbook_engine-2.0.0/spec/conformance/0.6/vectors/003-cap-totals-and-summary.json +638 -0
  189. playbook_engine-2.0.0/spec/conformance/0.6/vectors/004-party-alias-grouping.json +542 -0
  190. playbook_engine-2.0.0/spec/conformance/0.6/vectors/005-opening-evidence.json +621 -0
  191. playbook_engine-2.0.0/spec/conformance/0.6/vectors/006-opening-rules.json +849 -0
  192. playbook_engine-2.0.0/spec/conformance/0.6/vectors/007-equivalence-label-and-coverage.json +1035 -0
  193. playbook_engine-2.0.0/spec/conformance/0.6/vectors/008-hard-rules-and-dossiers.json +1791 -0
  194. playbook_engine-2.0.0/spec/conformance/0.6/vectors/009-standard-parts-and-owner-label.json +788 -0
  195. playbook_engine-2.0.0/spec/conformance/0.6/vectors/010-signing-dates.json +646 -0
  196. playbook_engine-2.0.0/spec/conformance/0.6/vectors/011-settled-outcomes.json +1117 -0
  197. playbook_engine-2.0.0/spec/conformance/0.6/vectors/012-prior-standards.json +943 -0
  198. playbook_engine-2.0.0/spec/conformance/0.6/vectors/013-reconstructed-prior-form.json +568 -0
  199. playbook_engine-2.0.0/spec/conformance/0.6/vectors/014-verified-absence.json +763 -0
  200. playbook_engine-2.0.0/spec/conformance/0.6/vectors/015-number-and-key-rules.json +114 -0
  201. playbook_engine-2.0.0/spec/conformance/README.md +64 -0
  202. playbook_engine-2.0.0/spec/playbook.schema-0.6.json +3208 -0
  203. playbook_engine-2.0.0/spec/taxonomy/affiliation-agreement.yaml +62 -0
  204. playbook_engine-2.0.0/spec/taxonomy/cuad-base.yaml +67 -0
  205. playbook_engine-2.0.0/spec/taxonomy/general-commercial.yaml +32 -0
  206. playbook_engine-2.0.0/spec/taxonomy/nda.yaml +93 -0
  207. playbook_engine-2.0.0/tests/__init__.py +0 -0
  208. playbook_engine-2.0.0/tests/chrome_cdp.py +268 -0
  209. playbook_engine-2.0.0/tests/entity_scan.py +61 -0
  210. playbook_engine-2.0.0/tests/test_absence_counts.py +635 -0
  211. playbook_engine-2.0.0/tests/test_agent_judge.py +1083 -0
  212. playbook_engine-2.0.0/tests/test_agent_segmenter.py +434 -0
  213. playbook_engine-2.0.0/tests/test_alias_sanity_check.py +136 -0
  214. playbook_engine-2.0.0/tests/test_artifact_store.py +886 -0
  215. playbook_engine-2.0.0/tests/test_born_safe_holistic.py +622 -0
  216. playbook_engine-2.0.0/tests/test_cache_format.py +226 -0
  217. playbook_engine-2.0.0/tests/test_canary_corpus.py +584 -0
  218. playbook_engine-2.0.0/tests/test_canonicalize.py +315 -0
  219. playbook_engine-2.0.0/tests/test_citation_resolution.py +365 -0
  220. playbook_engine-2.0.0/tests/test_classification_config.py +245 -0
  221. playbook_engine-2.0.0/tests/test_clause_aligner.py +1501 -0
  222. playbook_engine-2.0.0/tests/test_clause_classifier.py +1356 -0
  223. playbook_engine-2.0.0/tests/test_clause_differ.py +711 -0
  224. playbook_engine-2.0.0/tests/test_clause_position_compiler.py +504 -0
  225. playbook_engine-2.0.0/tests/test_clause_tree.py +843 -0
  226. playbook_engine-2.0.0/tests/test_cli.py +1536 -0
  227. playbook_engine-2.0.0/tests/test_cli_help_hygiene.py +144 -0
  228. playbook_engine-2.0.0/tests/test_cli_judge.py +1279 -0
  229. playbook_engine-2.0.0/tests/test_cli_precedent.py +267 -0
  230. playbook_engine-2.0.0/tests/test_cli_scorecard.py +1186 -0
  231. playbook_engine-2.0.0/tests/test_config.py +843 -0
  232. playbook_engine-2.0.0/tests/test_conformance_vectors.py +406 -0
  233. playbook_engine-2.0.0/tests/test_consumer_e2e.py +153 -0
  234. playbook_engine-2.0.0/tests/test_corpus_linter.py +1252 -0
  235. playbook_engine-2.0.0/tests/test_cuad_base_taxonomy.py +115 -0
  236. playbook_engine-2.0.0/tests/test_deal_dates.py +1270 -0
  237. playbook_engine-2.0.0/tests/test_deviation_classifier.py +212 -0
  238. playbook_engine-2.0.0/tests/test_digest.py +1000 -0
  239. playbook_engine-2.0.0/tests/test_docx_ingester.py +853 -0
  240. playbook_engine-2.0.0/tests/test_docx_normalizer.py +446 -0
  241. playbook_engine-2.0.0/tests/test_dynamics_fields.py +494 -0
  242. playbook_engine-2.0.0/tests/test_entity_registry.py +1307 -0
  243. playbook_engine-2.0.0/tests/test_entity_registry_residue_scan.py +100 -0
  244. playbook_engine-2.0.0/tests/test_environment_drift.py +517 -0
  245. playbook_engine-2.0.0/tests/test_equivalence_label.py +1195 -0
  246. playbook_engine-2.0.0/tests/test_equivalence_parts.py +828 -0
  247. playbook_engine-2.0.0/tests/test_estimate_runtime_cache.py +551 -0
  248. playbook_engine-2.0.0/tests/test_examples_validate.py +174 -0
  249. playbook_engine-2.0.0/tests/test_extraction.py +2777 -0
  250. playbook_engine-2.0.0/tests/test_extraction_reliability.py +753 -0
  251. playbook_engine-2.0.0/tests/test_floor_candidates.py +1847 -0
  252. playbook_engine-2.0.0/tests/test_floor_judge.py +242 -0
  253. playbook_engine-2.0.0/tests/test_golden_affiliation.py +497 -0
  254. playbook_engine-2.0.0/tests/test_hooks_install.py +395 -0
  255. playbook_engine-2.0.0/tests/test_index_browser.py +957 -0
  256. playbook_engine-2.0.0/tests/test_index_html.py +612 -0
  257. playbook_engine-2.0.0/tests/test_induction_version_selection.py +162 -0
  258. playbook_engine-2.0.0/tests/test_inspection_report.py +890 -0
  259. playbook_engine-2.0.0/tests/test_instrument.py +205 -0
  260. playbook_engine-2.0.0/tests/test_intake_plan.py +836 -0
  261. playbook_engine-2.0.0/tests/test_judgment.py +887 -0
  262. playbook_engine-2.0.0/tests/test_known_entity_zero_match_warning.py +137 -0
  263. playbook_engine-2.0.0/tests/test_license.py +67 -0
  264. playbook_engine-2.0.0/tests/test_llm_segmentation_stage.py +309 -0
  265. playbook_engine-2.0.0/tests/test_llm_segmenter.py +380 -0
  266. playbook_engine-2.0.0/tests/test_llm_segmenter_batch.py +713 -0
  267. playbook_engine-2.0.0/tests/test_manifest_and_dossiers.py +1380 -0
  268. playbook_engine-2.0.0/tests/test_nda_content_similarity.py +244 -0
  269. playbook_engine-2.0.0/tests/test_nda_derive_reproducible.py +194 -0
  270. playbook_engine-2.0.0/tests/test_nda_smoke.py +219 -0
  271. playbook_engine-2.0.0/tests/test_notice_attribution.py +48 -0
  272. playbook_engine-2.0.0/tests/test_observation_builder.py +2628 -0
  273. playbook_engine-2.0.0/tests/test_opf_accessors.py +347 -0
  274. playbook_engine-2.0.0/tests/test_overrides.py +882 -0
  275. playbook_engine-2.0.0/tests/test_ownership_boundary_doc.py +65 -0
  276. playbook_engine-2.0.0/tests/test_packaging.py +271 -0
  277. playbook_engine-2.0.0/tests/test_pdf_ingester.py +672 -0
  278. playbook_engine-2.0.0/tests/test_perspective_config.py +238 -0
  279. playbook_engine-2.0.0/tests/test_pipeline_cache.py +593 -0
  280. playbook_engine-2.0.0/tests/test_pipeline_deterministic_deviations.py +269 -0
  281. playbook_engine-2.0.0/tests/test_pipeline_discover_versions.py +70 -0
  282. playbook_engine-2.0.0/tests/test_pipeline_judgment_cache.py +808 -0
  283. playbook_engine-2.0.0/tests/test_pipeline_llm_seg.py +4061 -0
  284. playbook_engine-2.0.0/tests/test_pipeline_mine_empty_corpus.py +203 -0
  285. playbook_engine-2.0.0/tests/test_pipeline_normalized_trees.py +351 -0
  286. playbook_engine-2.0.0/tests/test_pipeline_project.py +2259 -0
  287. playbook_engine-2.0.0/tests/test_pipeline_provenance.py +1084 -0
  288. playbook_engine-2.0.0/tests/test_pipeline_reclassify.py +226 -0
  289. playbook_engine-2.0.0/tests/test_playbook_assembler.py +1015 -0
  290. playbook_engine-2.0.0/tests/test_posture.py +1256 -0
  291. playbook_engine-2.0.0/tests/test_pre_push_confidentiality.py +262 -0
  292. playbook_engine-2.0.0/tests/test_prior_reconstruction.py +1191 -0
  293. playbook_engine-2.0.0/tests/test_prior_standards.py +1504 -0
  294. playbook_engine-2.0.0/tests/test_prior_templates_intake.py +621 -0
  295. playbook_engine-2.0.0/tests/test_provenance_detector.py +727 -0
  296. playbook_engine-2.0.0/tests/test_q4_floor_qa.py +477 -0
  297. playbook_engine-2.0.0/tests/test_qa_onboarding_pages.py +141 -0
  298. playbook_engine-2.0.0/tests/test_quickstart.py +252 -0
  299. playbook_engine-2.0.0/tests/test_resegmentation.py +1892 -0
  300. playbook_engine-2.0.0/tests/test_reversal_detector.py +622 -0
  301. playbook_engine-2.0.0/tests/test_rtf_ingester.py +545 -0
  302. playbook_engine-2.0.0/tests/test_rubric_versioning.py +693 -0
  303. playbook_engine-2.0.0/tests/test_run_manifest.py +550 -0
  304. playbook_engine-2.0.0/tests/test_schema_extensions.py +106 -0
  305. playbook_engine-2.0.0/tests/test_schema_ids.py +69 -0
  306. playbook_engine-2.0.0/tests/test_scope_gate.py +628 -0
  307. playbook_engine-2.0.0/tests/test_segmentation_grounding.py +228 -0
  308. playbook_engine-2.0.0/tests/test_segmentation_qa.py +523 -0
  309. playbook_engine-2.0.0/tests/test_segmenter.py +909 -0
  310. playbook_engine-2.0.0/tests/test_settled.py +449 -0
  311. playbook_engine-2.0.0/tests/test_signature_block_pipeline.py +167 -0
  312. playbook_engine-2.0.0/tests/test_signed_detector.py +1559 -0
  313. playbook_engine-2.0.0/tests/test_skill_plan_examples.py +112 -0
  314. playbook_engine-2.0.0/tests/test_skill_playbook_from_corpus.py +788 -0
  315. playbook_engine-2.0.0/tests/test_span_bridge.py +381 -0
  316. playbook_engine-2.0.0/tests/test_spec_consistency.py +503 -0
  317. playbook_engine-2.0.0/tests/test_staging.py +1143 -0
  318. playbook_engine-2.0.0/tests/test_subsplit.py +777 -0
  319. playbook_engine-2.0.0/tests/test_taxonomy.py +979 -0
  320. playbook_engine-2.0.0/tests/test_taxonomy_inductor.py +879 -0
  321. playbook_engine-2.0.0/tests/test_template_agent_segmentation.py +387 -0
  322. playbook_engine-2.0.0/tests/test_template_standards.py +803 -0
  323. playbook_engine-2.0.0/tests/test_tracked_changes_overlay.py +496 -0
  324. playbook_engine-2.0.0/tests/test_validator.py +1070 -0
  325. playbook_engine-2.0.0/tests/test_version_orderer.py +1371 -0
  326. playbook_engine-2.0.0/tests/test_version_output.py +26 -0
@@ -0,0 +1,839 @@
1
+ # playbook-from-corpus — Judge reference
2
+
3
+ Agent reference for acting as the LLM judge in the `playbook-from-corpus`
4
+ derivation loop. Contains judge prompts, guardrails, and machine-checkable
5
+ done-criteria.
6
+
7
+ ---
8
+
9
+ ## Judge prompts
10
+
11
+ Each item in `out/judge/pending.jsonl` has a `kind` field that determines
12
+ the judgment task.
13
+
14
+ ### Classification (`kind: classify`)
15
+
16
+ **Input fields:** `text` (full clause text), `heading`, `taxonomy_ids`
17
+ (flat list of allowed ids — labels/descriptions live in the taxonomy YAML;
18
+ read it once and keep it at hand).
19
+
20
+ **Task:** Assign the best-fit `taxonomy_id` from `taxonomy_ids`. Return
21
+ `null` if no entry fits with reasonable confidence. Read each candidate's
22
+ description in the taxonomy YAML, including the "Not ..." boundary it names:
23
+ the boundaries exist to stop a lexical match (the word "assignment" in a clause
24
+ about placing students) from winning over the meaning.
25
+
26
+ On an agent-segmented corpus these items are the nodes the segmenter left
27
+ `null` (issue #253), asked again under the taxonomy in force; the answer is a
28
+ dedicated judge verdict (`basis: "judge"`), not the flat 0.45 segmenter
29
+ confidence.
30
+
31
+ **Prompt (adapt as needed):**
32
+
33
+ > You are classifying a clause from a legal agreement. The clause text is:
34
+ >
35
+ > ---
36
+ > {text}
37
+ > ---
38
+ >
39
+ > Allowed taxonomy ids (see the taxonomy YAML for labels/descriptions):
40
+ > {taxonomy_ids}
41
+ >
42
+ > Assign the single best-fit taxonomy ID from the list above. If no entry
43
+ > fits with reasonable confidence, return null.
44
+ >
45
+ > Respond with JSON: `{"taxonomy_id": "<id or null>", "confidence": 0.0–1.0, "rationale": "..."}`
46
+
47
+ **Rules:**
48
+ - Confidence < 0.70: set `needs_review: true` in the verdict. **This is
49
+ behavioral, not advisory:** 0.70 is `clause_classifier.AMBIGUITY_THRESHOLD`
50
+ — a stored classify confidence below it trips
51
+ `ClauseClassification.is_ambiguous` in the engine. There is no reason to
52
+ shade a genuine judgment below its true value — score honestly and let the
53
+ threshold do its job. (This `kind: classify` prompt is the drain-loop
54
+ judge path. Step 2a's agent-segmentation classification is a separate
55
+ path — no dedicated judge verdict — and always stamps a flat
56
+ `confidence: 0.45` regardless of your actual certainty, marking that whole
57
+ cohort as one sample to spot-check — see issue #181.)
58
+ - Never invent a `taxonomy_id` not in the provided list.
59
+ - Prefer specificity: if multiple entries match, pick the most specific.
60
+
61
+ ---
62
+
63
+ ### Deviation — not a judge kind
64
+
65
+ There is no deviation item to judge and no deviation verdict to write: every
66
+ clause's deviation is the deterministic standard check (its text matches our
67
+ template clause → `none`, otherwise `substantive`, `basis: "deterministic"`).
68
+ The consumer (the review model) does the judging; the playbook supplies
69
+ precedent. A `judge-apply` line shaped like a deviation verdict is rejected
70
+ ("cannot determine verdict kind").
71
+
72
+ ---
73
+
74
+ ### Provenance (`kind: provenance`)
75
+
76
+ **Input fields:** `preamble` (opening recital block), `letterhead`
77
+ (title/heading block), `agreement_type`. The payload does NOT carry the
78
+ known-alias list — read `provenance.our_party_aliases` from
79
+ `playbook.config.yaml` yourself before judging provenance items.
80
+
81
+ **Task:** Determine whether this document originated from our paper or the
82
+ counterparty's paper.
83
+
84
+ **Prompt (adapt as needed):**
85
+
86
+ > You are determining which party drafted the original version of this
87
+ > agreement by reading its recital/opening section.
88
+ >
89
+ > Our known names/aliases: {our_party_aliases from playbook.config.yaml}
90
+ >
91
+ > Agreement opening (recital):
92
+ > ---
93
+ > {preamble}
94
+ > ---
95
+ >
96
+ > Determine provenance:
97
+ > - `our_paper` — we drafted the original; our standard language is the base
98
+ > - `counterparty_paper` — the counterparty drafted the original; their language is the base
99
+ >
100
+ > Signals to look for:
101
+ > - Which party's name appears in the "agreement template" or "standard form" reference?
102
+ > - Which party's address block appears first (in US contracts, the drafting party often appears first)?
103
+ > - Does the recital say "our form", "standard agreement", "template provided by"?
104
+ > - Indemnification structure: our paper typically protects us first.
105
+ >
106
+ > If the recital does not provide enough signal, return `needs_review: true`
107
+ > rather than guessing.
108
+ >
109
+ > Respond with JSON: `{"provenance": "<our_paper|counterparty_paper>", "confidence": 0.0–1.0, "rationale": "...", "needs_review": false}`
110
+
111
+ **Rules:**
112
+ - Unknown entity name (a party name in the recital not in
113
+ `provenance.our_party_aliases`): record the alias in `rationale`, set
114
+ `needs_review: true`. Do not silently assume it is us.
115
+ - Confidence < 0.7: set `needs_review: true`. **Calibration matters:** the
116
+ engine treats any stored provenance verdict with confidence below 0.70 as
117
+ ambiguous at mine time (`pipeline.py` ambiguity rule) and records the
118
+ deal's paper side as `"unknown"` — the trail and every observation say
119
+ `"unknown"`, the OPF 0.6 precedent records say `paper: "unknown"`, and
120
+ `corpus.documents[]` flags it `provenance_is_ambiguous: true` (issue #225).
121
+ The side you named is not used and the verdict is not re-queued, so a
122
+ correct `our_paper` verdict at 0.55 leaves that deal's paper side unknown.
123
+ When the recital evidence is real, say so with confidence ≥ 0.70; reserve
124
+ sub-0.70 for genuine uncertainty.
125
+ - Conservative default when genuinely uncertain: `counterparty_paper` (the
126
+ safe choice — it attributes less favorable positions to the counterparty,
127
+ not to us).
128
+
129
+ ### Equivalence (`kind: equivalence`)
130
+
131
+ Queued by `playbook judge` AFTER mining, once a canonical template is
132
+ configured: one item per DISTINCT non-standard text of the precedent record (a
133
+ deal's signed text, a non-standard opening, a refused ask) that has no verdict
134
+ against our standard yet, and one per clause of each OLDER form of ours
135
+ (`baseline.prior_templates`, role `prior_standard`) that reads differently from
136
+ today's standard: the same judgment answers "how does our former wording compare
137
+ with our current wording?", once per distinct text. A former form's clause that
138
+ is word for word today's is never queued. The same words in ten deals, on either paper, in any
139
+ role, are one item and one shared verdict (the key is over the agreement type,
140
+ clause type, `perspective.party`, the text and the ORDERED parts of our
141
+ standard, never a deal or a paper side). A text that exactly matches our
142
+ standard (`standard: true`) is never queued, and neither is any text in
143
+ emergent mode (no template, no `our_standard`).
144
+
145
+ Our standard for a clause type is a list of **parts**: the template passages
146
+ of that type in template order (a lead-in sentence and each operative limb,
147
+ form front matter excluded). Each part has a stable `part_id` (a short content
148
+ hash of its text). You judge the candidate against EVERY part; the label is
149
+ then DERIVED by the engine, never chosen by you.
150
+
151
+ **Input fields:** `taxonomy_id`, `taxonomy_title`, `perspective_party`,
152
+ `our_standard_parts` (the numbered parts: `{n, part_id, text}`, in order),
153
+ `candidate` (the text to judge), `roles` (which of `signed` / `opening` /
154
+ `refused` / `prior_standard` it plays; a candidate that is one of our former
155
+ forms' clauses is judged exactly like any other text, against today's parts,
156
+ and nothing in the payload names the form). `agreement_type_id` and `stage` are context. There is no
157
+ deal name, counterparty, paper side or outcome in the payload; none of those
158
+ may enter your answer.
159
+
160
+ **Task:** For each part of our standard, say whether the candidate keeps it
161
+ and how it compares; list what the candidate adds that no part covers; name
162
+ the single most important difference in one sentence.
163
+
164
+ **Prompt (adapt as needed):**
165
+
166
+ > You are comparing a clause with our standard clause for the same clause
167
+ > type, part by part. Perspective: {perspective_party}. Every direction below
168
+ > is relative to {perspective_party}.
169
+ >
170
+ > Our standard, in parts:
171
+ > ---
172
+ > [1] (part_id {part_id}) {text}
173
+ > [2] (part_id {part_id}) {text}
174
+ > ...
175
+ > ---
176
+ >
177
+ > Candidate:
178
+ > ---
179
+ > {candidate}
180
+ > ---
181
+ >
182
+ > For EVERY part:
183
+ > - `status: "present"` when the candidate keeps that part's substance (even
184
+ > reworded or changed), with `relation`:
185
+ > - `equivalent` -- the same legal effect;
186
+ > - `more_protective` / `less_protective` -- better / worse than our part;
187
+ > - `status: "omitted"` when the candidate drops it, with `effect` (of the
188
+ > omission):
189
+ > - `more_protective` -- dropping it helps us (e.g. an obligation that
190
+ > falls on us);
191
+ > - `less_protective` -- dropping it hurts us;
192
+ > - `neutral` -- it makes no difference (e.g. a recital).
193
+ > - `reason`: ONE sentence, only for a part whose relation is not
194
+ > `equivalent` or whose effect is not `neutral`; omit it otherwise.
195
+ >
196
+ > `additions`: each thing the candidate adds that no part covers,
197
+ > `{"summary": "<one sentence>", "effect": "more_protective" |
198
+ > "less_protective" | "neutral"}` (`[]` when nothing is added).
199
+ >
200
+ > Respond with JSON: `{"parts": [{"part_id", "status", "relation" |
201
+ > "effect", "reason"?}, ...], "additions": [...], "reason": "<one sentence:
202
+ > the single most important difference, or that the effect is the same>",
203
+ > "basis": "agent"}`
204
+
205
+ **The derived label** (the engine computes it; a `label` you supply must
206
+ match it, so leave it out):
207
+
208
+ | label | when |
209
+ |---|---|
210
+ | `different_concept` | no part is present |
211
+ | `equivalent` | at least one part present, every present part equivalent, every omission and addition neutral |
212
+ | `more_protective` / `less_protective` | every non-equivalent, non-neutral effect points that way |
213
+ | `mixed` | effects point both ways (the parts say which) |
214
+
215
+ **Rules:**
216
+ - Judge EVERY part exactly once, by its `part_id`; never invent a part id.
217
+ - A text that matches some of our parts, changes others, drops one and adds
218
+ something is the normal case: say so part by part. Do not force a whole-text
219
+ verdict; `mixed` and `different_concept` follow from the parts.
220
+ - Reasons are ONE sentence naming the operative difference, never a
221
+ restatement of the relation. No deal, party or counterparty names.
222
+ - Judge the language, not the negotiation. Do not use how the deal ended, who
223
+ proposed the text, or whose paper it was on.
224
+ - `basis` is required: `"agent"` when you (the coder or agent) judge,
225
+ `"judge"` for the store-backed judge, `"owner"` only for an after-the-fact
226
+ correction by the owner (it wins over any agent or check answer and is never
227
+ checked).
228
+ - Do not write `check`: checks are recorded only by `judge-apply --check`.
229
+ - A text you leave out of the verdict file stays queued (and `null` in the
230
+ playbook), so the drain loop cannot finish until it is answered.
231
+
232
+ **The blind check.** Every drafted verdict is then checked by a SEPARATE
233
+ agent pinned to `claude-opus-5-5` at reasoning effort `xhigh`, which never
234
+ sees the draft:
235
+
236
+ ```bash
237
+ playbook judge --check equivalence $OUT --config <config>
238
+ # -> $OUT/judge/check-pending.jsonl: the drafter's payload WITHOUT the judgment
239
+ # -> $OUT/judge/adjudication-pending.jsonl: disputed items (both judgments)
240
+ ```
241
+
242
+ Answer each queue line with the same structured judgment,
243
+ `{"key", "parts", "additions", "reason", "model", "effort"}`, writing the
244
+ model id you actually ran on and the effort, then:
245
+
246
+ ```bash
247
+ playbook judge-apply $OUT --check $OUT/my-checks-<date>.jsonl
248
+ ```
249
+
250
+ `judge-apply --check` rejects a record whose `model` is not `claude-opus-5-5`
251
+ or whose `effort` is not `xhigh` unless `--allow-checker-model` is passed.
252
+ **Agreement** is the same derived label AND the same set of
253
+ `(part_id, status, direction)` over the parts that are not equivalent or not
254
+ a neutral omission; both halves are recorded (`label_agreed`,
255
+ `parts_agreed`) and the scorecard reports them separately. A match keeps the
256
+ verdict (`check: {agreed: true, ...}`); a mismatch keeps the draft flagged
257
+ `agreed: false` and queues it for adjudication, which a FRESH pinned agent
258
+ answers from both judgments (its answer replaces the verdict,
259
+ `adjudicated: true`).
260
+ Every line of a `--check` file is judged against the store as it stood before
261
+ that file, and a key repeated in one file is rejected: record the adjudication
262
+ answers in a later file, after re-running `judge --check equivalence`.
263
+ The drafter never answers its own check. `playbook judge --check equivalence
264
+ --api` answers both queues through the Message Batches API when
265
+ `ANTHROPIC_API_KEY` is set (a refusal leaves the item unchecked; there is no
266
+ fallback model). An unchecked draft still reaches the playbook (`check:
267
+ null`): nothing blocks.
268
+
269
+ ### Re-segmentation check (`kind: resegment_check`)
270
+
271
+ Queued by `playbook judge` AFTER mining, for a deterministic sample of the
272
+ splits and joins the engine's re-segmentation pass made (all of them when there
273
+ are 60 or fewer). The pass cuts a sentence or list item that belongs to another
274
+ clause type out of the node it sits in, and rejoins a node the extractor cut off
275
+ mid-sentence with the one that continues it. Nothing waits on your answer: it is
276
+ the measurement the pass's thresholds are tuned against, so answer what the text
277
+ says, not what the engine did. Two shapes of question:
278
+
279
+ ```json
280
+ {"stage": "resegment_check", "check": "split",
281
+ "question": "Is the text below really a provision of the clause type given, rather than of the clause it was cut out of?",
282
+ "clause_type": {"id": "insurance", "label": "Insurance"},
283
+ "text": "...", "context_before": "...the end of the passage it was cut out of..."}
284
+ {"stage": "resegment_check", "check": "join",
285
+ "question": "Do these consecutive passages form ONE clause, each continuing the one before?",
286
+ "passages": ["...", "..."]}
287
+ ```
288
+
289
+ Answer `{"resegment_ok": true}` when the split puts the text under the right
290
+ clause type (or the passages are one clause), `{"resegment_ok": false,
291
+ "reason": "<one short sentence>"}` when it does not: the text is its own
292
+ clause's, a different type's, or the passages are two clauses that merely sit
293
+ next to each other. A text that fits both the named type and the clause it came
294
+ from is `true`. Do not judge the type from the sentence's position: judge the
295
+ words. The count of `false` answers is the figure the scorecard reports
296
+ (`resegmentation.wrong`); the aim is none.
297
+
298
+ ### Sub-split (`kind: subsplit`)
299
+
300
+ Queued WHILE mining (by `playbook judge` and `playbook mine`), once per distinct
301
+ text of a classified clause one of whose sentences or list items reads like a
302
+ second clause type: a term clause that also states the renewal, an
303
+ entire-agreement clause with severability in the same block, termination for
304
+ cause and for convenience in one paragraph. The segmenter cannot cut inside one
305
+ block, so the engine asks you where the clause boundaries inside the text are.
306
+ The text comes cut into numbered units (sentences and list items; a leading
307
+ heading line is part of the first), with every allowed clause type:
308
+
309
+ ```json
310
+ {"stage": "subsplit",
311
+ "question": "Does this clause text state provisions of more than one clause type? ...",
312
+ "text": "...the whole text...",
313
+ "units": [{"id": "u0", "text": "..."}, {"id": "u1", "text": "..."}],
314
+ "taxonomy": [{"id": "renewal_term", "label": "Renewal / Auto-Renewal"}, ...]}
315
+ ```
316
+
317
+ Answer `{"split": false, "reason": "<one sentence>", "basis": "agent"}` when the
318
+ text states ONE clause (a sentence that merely mentions another topic, a
319
+ cross-reference, a definition used by the clause, or a consequence of it
320
+ belongs to it), or a partition into consecutive spans, each stating ONE clause
321
+ type:
322
+
323
+ ```json
324
+ {"split": true,
325
+ "spans": [{"start": "u0", "end": "u0", "taxonomy_id": "term"},
326
+ {"start": "u1", "end": "u2", "taxonomy_id": "renewal_term"}],
327
+ "reason": "The second and third sentences state the automatic renewal.",
328
+ "basis": "agent"}
329
+ ```
330
+
331
+ The spans must start at `u0`, follow each other with no gap and end at the
332
+ last unit; none may be empty; two neighbouring spans never share a type; each
333
+ `taxonomy_id` is one from the list or `null` (a span that states no clause type
334
+ on the list). You cannot cut inside a unit. Judge the words, not the heading:
335
+ the first span may be a different type from the one the clause was filed
336
+ under. **A false clause boundary is worse than a mixed clause**: when you are not
337
+ sure a passage is a separate provision, answer `"split": false`.
338
+ `judge-apply --verdicts` refuses an answer whose spans do not ground on the
339
+ question.
340
+
341
+ Nothing changes until the answer is blind-checked. `playbook judge --check
342
+ subsplit $OUT --config <config>` writes `judge/subsplit-check-pending.jsonl` (the
343
+ question, no draft) and `judge/subsplit-adjudication-pending.jsonl` (disputed
344
+ items, both answers); a separate `claude-opus-5-5` / `xhigh` agent answers
345
+ `{"key", "split", "spans", "reason", "model", "effort"}` per line (`spans` is
346
+ `[]` for no split), recorded with `playbook judge-apply $OUT --check <file>`.
347
+ Agreement is the same split decision and the same span boundaries and ids. Only
348
+ an agreed or adjudicated answer (or one with `basis: "owner"`) cuts the clause;
349
+ the next mine applies it. There is no `--api` path for this kind.
350
+
351
+ ### Absence check (`kind: absence_check`)
352
+
353
+ Queued by `playbook judge` AFTER mining, for a deterministic sample of the
354
+ engine's verified absences (all of them when there are 40 or fewer). The engine
355
+ counts "we signed N deals without clause X" (OPF-SPEC §3.5.8) and calls a deal
356
+ **verified absent** from a clause when no signed-text passage other than one
357
+ filed under the clause itself is even plausibly that clause (the passages the
358
+ classifier left unclassified and the ones it filed under other clause types
359
+ are all scored). Nothing waits on
360
+ your answer: it is the measurement the engine's guard is tuned against, so
361
+ answer what the text says. The question carries the clause type, our standard
362
+ for it and the deal's whole signed text as an ordered list of its passages, each
363
+ with the clause type the classifier filed it under (`null` when it filed none):
364
+
365
+ ```json
366
+ {"stage": "absence_check",
367
+ "question": "Is the clause type below really absent from this signed text, that is, does no passage of it state this clause?",
368
+ "clause_type": {"id": "assignment", "label": "Assignment"},
369
+ "our_standard": "...",
370
+ "signed_text": [ {"clause_type": "Term", "text": "..."},
371
+ {"clause_type": null, "text": "..."} ]}
372
+ ```
373
+
374
+ Answer `{"absent": true}` when no passage states the clause, however it is worded
375
+ and wherever it sits (including a sentence inside a longer passage, or a passage
376
+ filed under another clause type), and `{"absent": false, "reason": "<one short
377
+ sentence>"}` when a passage does. A clause stated inside a passage filed under another clause type makes it
378
+ present. A passage that merely touches the same
379
+ subject without stating the clause (a lead-in, a cross-reference, a defined
380
+ term) does not make it present. The count of `false` answers is the figure the
381
+ scorecard reports (`absence.wrong`); the aim is none, and any `false` means the
382
+ guard (`ABSENCE_GUARD`) is too weak and must be lowered.
383
+
384
+ ### Former form origin (`kind: prior_form_origin`)
385
+
386
+ Queued by `playbook judge` AFTER mining, when the owner supplied NO older form
387
+ files and the corpus' first drafts on our paper hold a wording that recurs. Our
388
+ former wording is what we SENT, so a non-standard first draft that is the first
389
+ draft of at least three signed deals and is close to identical across them
390
+ (same numbers, same negations) would be rebuilt as a former form of ours. This
391
+ item asks whether that wording really is OURS:
392
+
393
+ ```json
394
+ {"stage": "prior_form_origin", "agreement_type_id": "...",
395
+ "taxonomy_id": "indemnification", "taxonomy_title": "Indemnification",
396
+ "perspective_party": "Fixture Co",
397
+ "our_standard_parts": [{"n": 1, "part_id": "3f9a2c1d", "text": "..."}],
398
+ "candidate": "...the recurring first-draft wording...",
399
+ "recurs": {"n_deals": 7, "n_wordings": 2, "first_signed": "2021-03", "last_signed": "2022-11"}}
400
+ ```
401
+
402
+ Answer `{"ours": true, "reason": "<one sentence>", "basis": "agent"}` when the
403
+ candidate reads as an EARLIER VERSION OF OUR OWN DRAFTING of today's standard:
404
+ the same structure, defined terms, party naming and register, with different
405
+ terms. Answer `"ours": false` when it reads as a counterparty's form or
406
+ somebody else's paper (their entity or their typical vendor language, a
407
+ different drafting register, terms built around their business) or you cannot
408
+ tell: a wrong "ours" files a counterparty's wording as ours and hides their
409
+ changed openings, a wrong "not ours" only leaves the clause as it was. Judge
410
+ the words only; you are told nothing about any deal, counterparty or paper
411
+ side, and none of that may matter. Only a verdict that has been blind-checked
412
+ (and adjudicated on disagreement) makes the cluster a prior standard.
413
+
414
+ The check follows the equivalence one, with its own queue files:
415
+ `playbook judge --check prior_form_origin $OUT --config <config>` writes
416
+ `judge/origin-check-pending.jsonl` (no draft) and
417
+ `judge/origin-adjudication-pending.jsonl`; a separate `claude-opus-5-5` /
418
+ `xhigh` agent answers `{"key","ours","reason","model","effort"}` per line,
419
+ recorded with `playbook judge-apply $OUT --check <file>`. There is no `--api`
420
+ path for this kind.
421
+
422
+ ### Signing date (`kind: deal_date`)
423
+
424
+ Queued by `playbook judge` AFTER mining, for an executed copy whose signing
425
+ date no deterministic reader could establish: the document's preamble ("dated
426
+ as of …", "Effective Date: …") and its signature block were read first, and
427
+ either said nothing, said two different things, or gave a numeric date that
428
+ reads two ways (`03/04/2021`). Only such deals are queued, and only when the
429
+ text holds something date-like to read. The payload shows just the passages
430
+ holding a date, never the rest of the document or the signatories' names:
431
+
432
+ ```json
433
+ {"stage": "deal_date",
434
+ "document_text": ["... made as of the third of March, 2021 between ..."],
435
+ "signature_block": ["Date: 03/04/2021"]}
436
+ ```
437
+
438
+ Say when the deal was signed, from those passages alone. Answer with a date
439
+ ONLY when the passages state it: the document's own date or the date the
440
+ parties signed. Do not infer a date from anything else (the year of a statute, a
441
+ term length, a date a clause refers to). If the passages do not say, or say it
442
+ in a way you cannot read one way, answer `null`: a recorded `null` is a real
443
+ answer (the file name and file metadata are tried next), and an invented date
444
+ is the one wrong answer. Two shapes:
445
+
446
+ ```json
447
+ {"signed_on": "2021-03-03", "basis": "document_text",
448
+ "quote": "made as of the third of March, 2021"}
449
+ {"signed_on": null, "basis": null, "quote": null}
450
+ ```
451
+
452
+ - `signed_on` is `YYYY-MM-DD`, or `YYYY-MM` when only the month is stated;
453
+ a real date, in 1980-2099.
454
+ - `basis` is `document_text` or `signature_block`: the passages it came from.
455
+ - `quote` is the words you read the date from, copied EXACTLY from that
456
+ source's passages (whitespace aside). `playbook judge-apply` rejects an
457
+ answer whose quote does not occur in the passages the item showed, so a date
458
+ you cannot point to is never recorded.
459
+ - When signatories dated their signatures differently, the deal was signed on
460
+ the last of them.
461
+ - The same passages in several deals are one item and one shared verdict.
462
+
463
+ ---
464
+
465
+ ## Verdict format
466
+
467
+ Each line written to the verdicts JSONL file (for `playbook judge-apply`):
468
+
469
+ ```json
470
+ {"key": "<sha256-hex>", "verdict": { ... }}
471
+ ```
472
+
473
+ The `key` is the SHA-256 from the `pending.jsonl` item. The `verdict` schema
474
+ depends on kind.
475
+
476
+ **`basis` is NOT one shared enum — each judge type has its own, and they do
477
+ NOT all include `"llm"`.** Using the wrong value is rejected at
478
+ `playbook judge-apply`, with the offending line number, before it ever
479
+ reaches the store: `validate_verdict` reconstructs the exact dataclass the
480
+ store-backed judge would build on replay (`classify` accepts
481
+ `{"judge", "unclassified"}`, `provenance` builds a `ProvenanceResult` that
482
+ rejects `"judge"` — see `agent_judge.py`'s `_CLASSIFY_REPLAYABLE_BASES`) and raises on
483
+ the first mismatch. The silent-requeue-then-late-`project`/`validate`-failure
484
+ chain this section used to describe is exactly what that apply-time
485
+ validation (issue #182) exists to prevent; it can still happen for a verdict
486
+ written straight into the store by hand (bypassing `judge-apply`) or banked
487
+ before the #182 hardening — and even then `playbook judge` surfaces the
488
+ re-queue loudly, as a `WARNING`, rather than silently. Use exactly the value
489
+ shown for each kind below:
490
+ - Classification (`ClauseClassification.basis`, `clause_classifier.py`) — use
491
+ `"judge"` for an agent-produced verdict that found a taxonomy fit, or
492
+ `"unclassified"` (with `taxonomy_id: null`) for a producer-supplied
493
+ no-fit verdict — both are replayable (`_CLASSIFY_REPLAYABLE_BASES`).
494
+ `exact_match` / `heading_similarity` / `judge_error` / `needs_review` /
495
+ `llm_segmenter` / `inherited` are set by the engine itself, not by you, and
496
+ are rejected. A no-fit (`"unclassified"`) answer for a heading-less
497
+ sub-item (an `(a)`/`(b)` or `(i)`/`(ii)` child) whose parent clause is
498
+ classified is resolved to the parent's `taxonomy_id` with
499
+ `basis: "inherited"` (confidence capped at 0.6, issue #222); a specific
500
+ taxonomy fit you give the child is kept.
501
+ - Provenance (`ProvenanceResult.basis`, `provenance_detector.py`) — use
502
+ `"llm"` — this is the one kind where `"llm"` is correct; `validate_verdict`
503
+ rejects any other basis from a producer-supplied verdict (deterministic
504
+ bases like `template_similarity` / `alias_first_party` / `hint` are set by
505
+ the engine itself, not by you).
506
+ - Scope — no `basis` field; it is forced to `"judge"` on replay.
507
+ - Equivalence — `basis` is REQUIRED and is one of `"agent"` (you),
508
+ `"judge"` (the store-backed judge) or `"owner"` (an owner correction);
509
+ anything else is rejected.
510
+ - Signing date (`deal_date`) — `basis` is the SOURCE of the date
511
+ (`"document_text"` or `"signature_block"`, or `null` with a `null` date),
512
+ not a judge type.
513
+
514
+ **Classification verdict:**
515
+ ```json
516
+ {
517
+ "taxonomy_id": "indemnification",
518
+ "confidence": 0.88,
519
+ "basis": "judge",
520
+ "rationale": "Clause defines mutual indemnification obligations.",
521
+ "needs_review": false
522
+ }
523
+ ```
524
+
525
+ **Provenance verdict:**
526
+ ```json
527
+ {
528
+ "provenance": "counterparty_paper",
529
+ "confidence": 0.82,
530
+ "basis": "llm",
531
+ "rationale": "Recital references 'University Standard Agreement Form'.",
532
+ "needs_review": false
533
+ }
534
+ ```
535
+
536
+ **Scope verdict** (`kind: scope`) — one per document; decide whether the
537
+ document is an instance of this agreement type. `in_scope` is required;
538
+ `scope_rationale` and `scope_confidence` are optional (`basis` is forced to
539
+ `"judge"` on replay). Payload gives `agreement_type_id`, `document_id`, and the
540
+ document's `clause_heads`.
541
+ ```json
542
+ {
543
+ "in_scope": true,
544
+ "scope_confidence": 0.95,
545
+ "scope_rationale": "Educational-institution affiliation agreement for student internships."
546
+ }
547
+ ```
548
+ An out-of-scope document (`in_scope: false`) is retained but excluded from the
549
+ playbook.
550
+
551
+ **Equivalence verdict** (`kind: equivalence`) — one per distinct text; exactly
552
+ `parts`, `additions`, `reason`, `basis` (a `label`, if given, must equal the
553
+ derived one):
554
+ ```json
555
+ {
556
+ "parts": [
557
+ {"part_id": "3f9a2c1d", "status": "present", "relation": "equivalent"},
558
+ {"part_id": "77b0e4aa", "status": "present", "relation": "less_protective",
559
+ "reason": "Drops the recipient's burden of establishing that an exclusion applies."},
560
+ {"part_id": "c41e09b2", "status": "omitted", "effect": "neutral"}
561
+ ],
562
+ "additions": [
563
+ {"summary": "Adds a right to disclose to professional advisers.", "effect": "less_protective"}
564
+ ],
565
+ "reason": "Drops the recipient's burden of proof and lets it disclose to advisers.",
566
+ "basis": "agent"
567
+ }
568
+ ```
569
+ `relation` is `equivalent`, `more_protective` or `less_protective`; `effect`
570
+ is `more_protective`, `less_protective` or `neutral`; both relative to
571
+ `perspective_party`. The example derives `less_protective`.
572
+
573
+ **Re-segmentation check verdict** (`kind: resegment_check`) — one per sampled
574
+ split or join (see "Re-segmentation check" above):
575
+ ```json
576
+ {"resegment_ok": false, "reason": "This sentence is about notice, not insurance."}
577
+ ```
578
+
579
+ **Absence check verdict** (`kind: absence_check`) — one per sampled verified
580
+ absence (see "Absence check" above):
581
+ ```json
582
+ {"absent": false, "reason": "Section 7 states the clause in other words."}
583
+ ```
584
+
585
+ **Sub-split verdict** (`kind: subsplit`) — one per distinct multi-topic text
586
+ (see "Sub-split" above):
587
+ ```json
588
+ {"split": true, "spans": [{"start": "u0", "end": "u0", "taxonomy_id": "entire_agreement"},
589
+ {"start": "u1", "end": "u1", "taxonomy_id": "severability"}],
590
+ "reason": "The second sentence is a severability clause.", "basis": "agent"}
591
+ ```
592
+
593
+ **Former form origin verdict** (`kind: prior_form_origin`) — one per recurring
594
+ first-draft wording (see "Former form origin" above):
595
+ ```json
596
+ {"ours": true, "reason": "Same structure and defined terms as today's clause, with an older cap.", "basis": "agent"}
597
+ ```
598
+
599
+ **Signing-date verdict** (`kind: deal_date`) — one per distinct set of
600
+ passages; `signed_on`, `basis` and `quote` (see "Signing date" above):
601
+ ```json
602
+ {"signed_on": "2021-03-03", "basis": "signature_block", "quote": "Date: March 3rd, 2021"}
603
+ ```
604
+
605
+ ### SegNode (agent segmentation — `segment` / `segment-apply`, issue #191)
606
+
607
+ Each `segment/pending.jsonl` item is one *version* of one document (a
608
+ 5-version document queues 5 items; `document_id`/`version` identify which),
609
+ and gives that version's `canonical_text`, its `blocks`
610
+ (`{block_id, page, char_span, text}`), and the allowed `taxonomy_ids`.
611
+ Partition the blocks into contiguous clause ranges — one `SegNode` per clause —
612
+ and write one verdict line per pending item to the verdicts JSONL (identical
613
+ version texts across documents dedup by content hash, so you only need one
614
+ line per distinct `canonical_text`):
615
+
616
+ ```json
617
+ {
618
+ "canonical_text": "<echoed verbatim from the pending item>",
619
+ "nodes": [
620
+ {"node_id": "n1", "parent_id": null, "order": 1, "heading": "Recitals",
621
+ "taxonomy_id": "parties_and_recitals",
622
+ "start_block_id": "b0", "end_block_id": "b3",
623
+ "start_quote": "", "end_quote": ""},
624
+ {"node_id": "n2", "parent_id": null, "order": 2, "heading": "Indemnification",
625
+ "taxonomy_id": "indemnification",
626
+ "start_block_id": "b4", "end_block_id": "b9", "start_quote": "", "end_quote": ""}
627
+ ]
628
+ }
629
+ ```
630
+
631
+ **Rules:**
632
+ - Cover **every** block exactly once — clause ranges are contiguous and
633
+ partition `b0`..`b<last>` (the coverage/reconstruction gates enforce this).
634
+ - `parent_id: null` for top-level clauses; set it to a parent `node_id` to nest
635
+ (dotted clause paths are derived from the tree). `order` is 1-based within a
636
+ parent.
637
+ - `taxonomy_id` must be from the item's `taxonomy_ids`, or `null` if no entry
638
+ fits — this doubles as first-pass classification. The judge loop then has
639
+ `classify` items only for the nodes you left `null` that carry operative text
640
+ (issue #253: a clause type added after segmentation reaches that text only
641
+ through them); a node that is only its own heading, or has no letters, is
642
+ never queued.
643
+ - `start_quote`/`end_quote` may be `""` for block-aligned clauses; the range is
644
+ reconstructed from the block span. (Splitting *within* a block would require
645
+ exact boundary quotes — the live-LLM segmenter's job, not this path.)
646
+
647
+ ---
648
+
649
+ ## Rubric versions
650
+
651
+ Verdicts are cached by a hash of the clause **content**, so nothing about a
652
+ changed *rubric* moves the key. Without a version, edits to the criteria
653
+ below would replay every previously banked verdict forever, silently.
654
+
655
+ Each stored verdict therefore carries a stamp — `{"rubric": {"kind": ...,
656
+ "version": "<manual>+<derived>"}}` — recording the rubric it was produced
657
+ under. `playbook judge` compares it against the rubric in force:
658
+
659
+ | state | meaning | behaviour |
660
+ |-------|---------|-----------|
661
+ | current | stamp matches | replays |
662
+ | stale | stamp differs | **re-queued for re-judgement** (`--accept-stale` to replay) |
663
+ | legacy | no stamp (banked before versioning) | replays, reported every run (`--strict-rubric` to re-queue instead) |
664
+
665
+ **The manual half** is `RUBRIC_PROMPT_VERSIONS` in
666
+ `playbook_engine/rubric.py`, one entry per kind. Bump the entry for a kind
667
+ when the corresponding **Judge prompts** section above changes in a way that
668
+ could change a reasonable judge's answer: a new or removed answer category, a
669
+ reversed default, a changed definition of "material" or "substantive". Do
670
+ **not** bump for typo fixes, reworded examples, or added guardrails that only
671
+ restate existing rules — that would discard thousands of sound verdicts.
672
+
673
+ **The derived half** needs no discipline; it moves on its own when the
674
+ machine-readable rubric moves: the classifier-eligible taxonomy entries
675
+ (id + label + description) for `classify`, the agreement-type definition for
676
+ `scope`, the answer vocabulary for `provenance`, the label, status,
677
+ relation and effect vocabularies for `equivalence`, the source vocabulary
678
+ (`document_text`, `signature_block`) for `deal_date`, the two question shapes
679
+ (`split`, `join`) for `resegment_check`, the two answers (`absent`, `present`) for `absence_check`, the answers and bases (`agent`, `judge`,
680
+ `owner`) for `prior_form_origin`, the classifier-eligible taxonomy entries plus
681
+ the answers (`split`, `no_split`) and bases for `subsplit`. Editing
682
+ `spec/taxonomy/*.yaml` re-queues classify verdicts and nothing else.
683
+
684
+ A store banked before versioning (legacy verdicts) keeps replaying and is
685
+ reported on every run; if you have reason to distrust it, `--strict-rubric`
686
+ re-queues it for re-judgement. There is no command that re-stamps a store
687
+ in place: a verdict is re-asked, or kept as it is.
688
+
689
+ ---
690
+
691
+ ## Guardrails
692
+
693
+ 1. **No fabrication.** Every verdict must be grounded in the actual clause
694
+ text or recital. Do not invent legal interpretations.
695
+
696
+ 2. **Flag, don't guess.** When confidence is below threshold, set
697
+ `needs_review: true` and include the uncertainty in `rationale`. **This
698
+ flag is not read back anywhere today** — `ClauseClassification` and
699
+ `ProvenanceResult` have no `needs_review` field, and replay reconstruction
700
+ (`agent_judge.py`) discards the stored boolean when it rebuilds a verdict
701
+ from the store. Setting it is still worth doing — it is a durable,
702
+ human-auditable record in `verdicts.jsonl` for a future manual pass over
703
+ the store — but do not tell a reviewer, or assume yourself, that flagging
704
+ a doubtful verdict here causes it to surface anywhere. If a low-confidence
705
+ call genuinely needs a human's eyes before this round ships, say so
706
+ directly — in the round summary you give the human running this skill —
707
+ rather than relying on `needs_review: true` to do that for you.
708
+
709
+ 3. **Unknown aliases.** If a party name in the corpus is not in
710
+ `provenance.known_entities`, record it explicitly in `rationale` and set
711
+ `needs_review: true`. Supply a curated alias list to the human before the
712
+ next round.
713
+
714
+ 4. **`needs_review` is an internal flag only.** It must be resolved (by human
715
+ review or re-judgment) before `project`. It is not a valid value of any
716
+ OPF field.
717
+
718
+ 5. **Deduplication.** `playbook judge` deduplicates by content hash. Judge
719
+ each unique clause payload once; verdicts propagate automatically to all
720
+ documents sharing that clause.
721
+
722
+ 6. **Corpus confidentiality.** Real agreement text is private. Do not log,
723
+ echo, or store clause text outside the local `out/` directory.
724
+
725
+ 7. **Posture / Floor fields.** `posture.system_prompt` (Posture section) and the
726
+ walk-away floor (`floor.invariants`, Floor section) require the GC interview
727
+ (OPF-SPEC.md §7) and cannot be derived from the corpus — never invent them.
728
+ (The Evidence section is different: it is purely descriptive — per-deal
729
+ precedent facts and counts, OPF-SPEC.md §3.5.4 — and compiles straight
730
+ from the corpus; do not treat it as interview-gated.) With the human
731
+ present, run SKILL.md Step 7a (interview) and Step 7b (floor propose → sign);
732
+ without them, list both as pending human input in your round summary and say that an
733
+ evidence-only playbook is a complete document (Rung 0), not an unfinished one.
734
+ Q4 `sacred_clauses` is written straight into signed `floor.invariants` (the
735
+ human authored it) **only when the item is a bare clause-type name**;
736
+ compiler-derived candidates become hard lines only when the human signs
737
+ them with `playbook floor sign`. A sentence-shaped or conditional item
738
+ either of those templates would garble ("X, if present, must not be Y";
739
+ more than 7 words; or containing "if"/"unless"/"must"/"shall"/"provided",
740
+ issue #104) is skipped from promotion — the interview prints a WARN
741
+ instead of writing it — and goes through
742
+ `playbook floor sign --statement "..." --signed-by "<name>"`
743
+ instead, verbatim — never by hand-editing `floor.invariants`. `--signed-by`
744
+ is required: it names the human legal owner signing off, recorded as a
745
+ structural `x_signed_by` field the command refuses to omit — get an
746
+ explicit in-chat confirmation of the exact statement before running it.
747
+
748
+ ---
749
+
750
+ ## Done-criteria (machine-checkable)
751
+
752
+ The derivation is **done** when all four conditions hold:
753
+
754
+ 1. **`out/judge/pending.jsonl` exists and is empty.**
755
+
756
+ Absence does **not** count as done — `playbook judge` unlinks
757
+ `pending.jsonl` at the start of every round and only writes it lazily as
758
+ items are queued, so a round killed mid-`mine` (e.g. an overnight run that
759
+ died) also leaves the file absent. A completed round with 0 new pending
760
+ items always writes an explicit empty file, so `-f` (must exist) is what
761
+ distinguishes "finished, nothing pending" from "interrupted before
762
+ finishing" (issue #170).
763
+
764
+ ```bash
765
+ # Confirm: file exists AND is empty
766
+ [ -f ./out/judge/pending.jsonl ] && [ ! -s ./out/judge/pending.jsonl ]
767
+ echo "Exit: $?" # must be 0
768
+ ```
769
+
770
+ 2. **`playbook validate` exits 0.**
771
+
772
+ ```bash
773
+ playbook validate ./out/playbook.opf.json
774
+ echo "Exit: $?" # must be 0
775
+ ```
776
+
777
+ 3. **The packaged artifact is generated** (`index.html` exists in `out/`).
778
+
779
+ SKILL.md Step 9 names `index.html` as the **one human-readable artifact**
780
+ (the playbook and an optional editor, five tabs); it is produced on every
781
+ route (A, B and C all reach Step 9). A run that stops after `validate`
782
+ without `view bundle` is not done: the GC has the canonical JSON but not the
783
+ page, and the closing (open the page, print both paths, print the toaster
784
+ install steps, one line saying review is optional) has not happened. The
785
+ digest is not a separate file: it is the `digest` section of
786
+ `playbook.opf.json`. `index.html` is **not** a guarantee of pseudonymization
787
+ on its own — SKILL.md Step 9's mandatory residue check must be run before
788
+ treating it as shareable, and nothing in the engine anonymizes a playbook
789
+ for public release. If `overrides.json` exists, run
790
+ `apply-overrides` (always, whatever the file times; it is a no-op once the
791
+ edits are in effect), re-validate, rebuild the page.
792
+
793
+ ```bash
794
+ test -f ./out/index.html
795
+ echo "Exit: $?" # must be 0
796
+ ```
797
+
798
+ 4. **`out/quarantine.json` is empty, or every entry has been explicitly
799
+ triaged.**
800
+
801
+ A document quarantined during `mine` (SegmentationQAError, HintsError,
802
+ all-versions-failed-ingest) contributes no `pending.jsonl` items, so
803
+ criteria 1–3 above can all hold while a whole agreement's negotiation
804
+ history is silently absent from the evidence — the only trace is
805
+ `quarantine.json` itself, which is a file, not a gate. `quarantine.json` is
806
+ rewritten (even when empty) every `mine` run, so it always reflects the
807
+ current state, not a stale prior round.
808
+
809
+ ```bash
810
+ PATH="$PWD/.venv/bin:$PATH" .venv/bin/python -c \
811
+ 'import json,sys; q=json.load(open("./out/quarantine.json")); sys.exit(1 if q else 0)'
812
+ echo "Exit: $?" # 0 = no quarantined documents
813
+ ```
814
+
815
+ A non-zero exit does **not** by itself mean the derivation is unfinished
816
+ — it means every entry must be triaged before you call it done: re-segment
817
+ the document (fix `hints.yaml`, adjust extraction) and re-run `mine`, or
818
+ explicitly accept the exclusion by naming the `document_id` and reason in
819
+ your round summary to the human running this skill (`quarantine.json` is
820
+ tool-generated and cannot itself record a human decision). Never treat an untriaged
821
+ `quarantine.json` entry as background noise the loop can converge past.
822
+
823
+ Items that remain for human review (unknown aliases, a doubtful
824
+ low-confidence call, a thin `floor.candidates.json`) do not block the
825
+ done-criteria above, but they must not be silently suppressed: name them in the
826
+ round summary. Setting `needs_review: true` on a verdict is **not** a way to
827
+ report them — it is not copied onto anything a reader sees.
828
+
829
+ ---
830
+
831
+ ## Common failure modes
832
+
833
+ | Symptom | Cause | Fix |
834
+ |---------|-------|-----|
835
+ | `validate` exits non-zero after `project` | Residual `needs_review` or a malformed verdict in the store | Drain the judge loop; fix malformed verdicts |
836
+ | `pending.jsonl` does not shrink between rounds | Verdicts not applied or wrong `key` | Check `judge-apply` output; confirm keys match |
837
+ | All provenance = `counterparty_paper` | Recitals not loaded or entity alias list empty | Supply `provenance.our_party_aliases`; re-run provenance round |
838
+ | All clauses unclassified (`taxonomy_id: null`) | Taxonomy mismatch with document content | Check taxonomy covers the agreement type; refine taxonomy entries |
839
+ | Trail ordering wrong | No `order:` hint; version-orderer used greedy fallback | Add explicit `order:` list to `hints.yaml`; re-run `mine` |