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.
- playbook_engine-2.0.0/.claude/skills/playbook-from-corpus/REFERENCE.md +839 -0
- playbook_engine-2.0.0/.claude/skills/playbook-from-corpus/SKILL.md +1817 -0
- playbook_engine-2.0.0/.claude/skills/playbook-from-corpus/estimate_runtime.py +423 -0
- playbook_engine-2.0.0/.dockerignore +41 -0
- playbook_engine-2.0.0/.github/ISSUE_TEMPLATE/bug.yml +32 -0
- playbook_engine-2.0.0/.github/ISSUE_TEMPLATE/new-agreement-type.yml +33 -0
- playbook_engine-2.0.0/.github/ISSUE_TEMPLATE/spec-question.yml +25 -0
- playbook_engine-2.0.0/.github/pull_request_template.md +21 -0
- playbook_engine-2.0.0/.github/workflows/ci.yml +76 -0
- playbook_engine-2.0.0/.github/workflows/docker-publish.yml +70 -0
- playbook_engine-2.0.0/.github/workflows/docker-smoke.yml +150 -0
- playbook_engine-2.0.0/.github/workflows/pages.yml +51 -0
- playbook_engine-2.0.0/.github/workflows/pypi-publish.yml +122 -0
- playbook_engine-2.0.0/.gitignore +41 -0
- playbook_engine-2.0.0/CHANGELOG.md +1590 -0
- playbook_engine-2.0.0/CODE_OF_CONDUCT.md +135 -0
- playbook_engine-2.0.0/CONTRIBUTING.md +208 -0
- playbook_engine-2.0.0/Dockerfile +136 -0
- playbook_engine-2.0.0/LICENSE +224 -0
- playbook_engine-2.0.0/Makefile +210 -0
- playbook_engine-2.0.0/NOTICE +15 -0
- playbook_engine-2.0.0/PKG-INFO +322 -0
- playbook_engine-2.0.0/README.md +280 -0
- playbook_engine-2.0.0/SECURITY.md +51 -0
- playbook_engine-2.0.0/docker/constraints.txt +35 -0
- playbook_engine-2.0.0/docs/ADOPTING.md +372 -0
- playbook_engine-2.0.0/docs/ARCHITECTURE.md +142 -0
- playbook_engine-2.0.0/docs/CORPUS-LAYOUT.md +75 -0
- playbook_engine-2.0.0/docs/OPF-BUNDLE-BOUNDARY.md +96 -0
- playbook_engine-2.0.0/docs/OPF-SPEC-v0.1.md +221 -0
- playbook_engine-2.0.0/docs/OPF-SPEC.md +1771 -0
- playbook_engine-2.0.0/docs/PLAN-FIRST.md +44 -0
- playbook_engine-2.0.0/docs/QUICK-COMPILE.md +248 -0
- playbook_engine-2.0.0/docs/REAL-CORPUS-DERIVATION.md +101 -0
- playbook_engine-2.0.0/docs/prompts/create-playbook.md +12 -0
- playbook_engine-2.0.0/docs/reviews/2026-08-24-skill-qa-audit/README.md +33 -0
- playbook_engine-2.0.0/docs/reviews/2026-08-24-skill-qa-audit/findings.md +1232 -0
- playbook_engine-2.0.0/docs/reviews/2026-08-24-skill-qa-audit/report.html +163 -0
- playbook_engine-2.0.0/examples/.gitkeep +0 -0
- playbook_engine-2.0.0/examples/README.md +242 -0
- playbook_engine-2.0.0/examples/affiliation-config/playbook.config.yaml +41 -0
- playbook_engine-2.0.0/examples/canary/README.md +128 -0
- playbook_engine-2.0.0/examples/canary/build_corpus.py +373 -0
- playbook_engine-2.0.0/examples/canary/build_expected.py +52 -0
- playbook_engine-2.0.0/examples/canary/build_verdicts.py +190 -0
- playbook_engine-2.0.0/examples/canary/config.yaml +67 -0
- playbook_engine-2.0.0/examples/canary/corpus/halcyon-freight/v1.docx +0 -0
- playbook_engine-2.0.0/examples/canary/corpus/halcyon-freight/v2.docx +0 -0
- playbook_engine-2.0.0/examples/canary/corpus/meridian-assay/v1.docx +0 -0
- playbook_engine-2.0.0/examples/canary/corpus/meridian-assay/v2.docx +0 -0
- playbook_engine-2.0.0/examples/canary/expected.json +29 -0
- playbook_engine-2.0.0/examples/canary/segment-verdicts.jsonl +4 -0
- playbook_engine-2.0.0/examples/fixtures/invalid_missing_opf_version.json +8 -0
- playbook_engine-2.0.0/examples/fixtures/invalid_unknown_opf_version.json +27 -0
- playbook_engine-2.0.0/examples/judge-fixture/canned-verdicts.jsonl +8 -0
- playbook_engine-2.0.0/examples/judge-fixture/config.yaml +25 -0
- playbook_engine-2.0.0/examples/judge-fixture/corpus/deal-alpha/hints.yaml +9 -0
- playbook_engine-2.0.0/examples/judge-fixture/corpus/deal-alpha/v1.rtf +1 -0
- playbook_engine-2.0.0/examples/judge-fixture/corpus/deal-alpha/v2.rtf +1 -0
- playbook_engine-2.0.0/examples/judge-fixture/corpus/deal-beta/hints.yaml +3 -0
- playbook_engine-2.0.0/examples/judge-fixture/corpus/deal-beta/v1.rtf +1 -0
- playbook_engine-2.0.0/examples/judge-fixture/posture-answers.json +8 -0
- playbook_engine-2.0.0/examples/nda/README.md +311 -0
- playbook_engine-2.0.0/examples/nda/canned-verdicts.jsonl +48 -0
- playbook_engine-2.0.0/examples/nda/config.smoke.yaml +52 -0
- playbook_engine-2.0.0/examples/nda/config.yaml +53 -0
- playbook_engine-2.0.0/examples/nda/corpus/beta-industries/v1.rtf +1 -0
- playbook_engine-2.0.0/examples/nda/corpus/beta-industries/v2.rtf +1 -0
- playbook_engine-2.0.0/examples/nda/corpus/beta-industries/v3.rtf +1 -0
- playbook_engine-2.0.0/examples/nda/corpus/beta-industries/v4.rtf +1 -0
- playbook_engine-2.0.0/examples/nda/corpus/delta-ventures/v1.rtf +1 -0
- playbook_engine-2.0.0/examples/nda/corpus/delta-ventures/v2.rtf +1 -0
- playbook_engine-2.0.0/examples/nda/corpus/epsilon-systems/v1.rtf +1 -0
- playbook_engine-2.0.0/examples/nda/corpus/epsilon-systems/v2.rtf +1 -0
- playbook_engine-2.0.0/examples/nda/corpus/epsilon-systems/v3.rtf +1 -0
- playbook_engine-2.0.0/examples/nda/corpus/gamma-holdings/v1.rtf +1 -0
- playbook_engine-2.0.0/examples/nda/corpus/gamma-holdings/v2.rtf +1 -0
- playbook_engine-2.0.0/examples/nda/corpus/gamma-holdings/v3.rtf +1 -0
- playbook_engine-2.0.0/examples/nda/corpus/theta-logistics/v1.rtf +1 -0
- playbook_engine-2.0.0/examples/nda/corpus/theta-logistics/v2.rtf +1 -0
- playbook_engine-2.0.0/examples/nda/corpus/zeta-diagnostics/v1.rtf +1 -0
- playbook_engine-2.0.0/examples/nda/corpus/zeta-diagnostics/v2.rtf +1 -0
- playbook_engine-2.0.0/examples/nda/corpus/zeta-diagnostics/v3.rtf +1 -0
- playbook_engine-2.0.0/examples/nda/playbook.opf.json +10864 -0
- playbook_engine-2.0.0/examples/nda/posture-answers.json +8 -0
- playbook_engine-2.0.0/examples/nda/precedent.jsonl +128 -0
- playbook_engine-2.0.0/examples/nda/standard-form.rtf +1 -0
- playbook_engine-2.0.0/examples/staging-fixtures/clm-nested-corpus/deal-delta/EXECUTED_deal-delta.pdf +1 -0
- playbook_engine-2.0.0/examples/staging-fixtures/clm-nested-corpus/deal-delta/Versions/01_draft.rtf +1 -0
- playbook_engine-2.0.0/examples/staging-fixtures/clm-nested-corpus/deal-delta/Versions/02_our_redline.rtf +1 -0
- playbook_engine-2.0.0/examples/staging-fixtures/clm-nested-corpus/deal-gamma/EXECUTED_deal-gamma.pdf +1 -0
- playbook_engine-2.0.0/examples/staging-fixtures/clm-nested-corpus/deal-gamma/Versions/01_draft.rtf +1 -0
- playbook_engine-2.0.0/examples/staging-fixtures/clm-nested-corpus/deal-gamma/Versions/02_counterparty_redline.rtf +1 -0
- playbook_engine-2.0.0/examples/staging-fixtures/flat-corpus/deal-alpha/01_draft.rtf +1 -0
- playbook_engine-2.0.0/examples/staging-fixtures/flat-corpus/deal-alpha/02_signed_final.rtf +1 -0
- playbook_engine-2.0.0/examples/staging-fixtures/flat-corpus/deal-beta/01_draft.rtf +1 -0
- playbook_engine-2.0.0/examples/staging-fixtures/flat-corpus/deal-beta/02_counterparty_redline.rtf +1 -0
- playbook_engine-2.0.0/examples/staging-fixtures/manifest-corpus/docs/deal-epsilon/01_initial_draft.rtf +1 -0
- playbook_engine-2.0.0/examples/staging-fixtures/manifest-corpus/docs/deal-epsilon/02_our_redline.rtf +1 -0
- playbook_engine-2.0.0/examples/staging-fixtures/manifest-corpus/docs/deal-epsilon/03_executed_final.rtf +1 -0
- playbook_engine-2.0.0/examples/staging-fixtures/manifest-corpus/docs/deal-zeta/01_draft.rtf +1 -0
- playbook_engine-2.0.0/examples/staging-fixtures/manifest-corpus/docs/deal-zeta/02_signed.rtf +1 -0
- playbook_engine-2.0.0/examples/staging-fixtures/manifest-corpus/manifest.jsonl +5 -0
- playbook_engine-2.0.0/playbook_engine/__init__.py +3 -0
- playbook_engine-2.0.0/playbook_engine/absence.py +538 -0
- playbook_engine-2.0.0/playbook_engine/agent_judge.py +1325 -0
- playbook_engine-2.0.0/playbook_engine/agent_segmenter.py +140 -0
- playbook_engine-2.0.0/playbook_engine/artifact_store.py +314 -0
- playbook_engine-2.0.0/playbook_engine/cache_format.py +266 -0
- playbook_engine-2.0.0/playbook_engine/canonicalize.py +142 -0
- playbook_engine-2.0.0/playbook_engine/citation_resolver.py +274 -0
- playbook_engine-2.0.0/playbook_engine/clause_aligner.py +860 -0
- playbook_engine-2.0.0/playbook_engine/clause_classifier.py +900 -0
- playbook_engine-2.0.0/playbook_engine/clause_differ.py +619 -0
- playbook_engine-2.0.0/playbook_engine/clause_position_compiler.py +382 -0
- playbook_engine-2.0.0/playbook_engine/clause_tree.py +428 -0
- playbook_engine-2.0.0/playbook_engine/cli.py +4266 -0
- playbook_engine-2.0.0/playbook_engine/config.py +802 -0
- playbook_engine-2.0.0/playbook_engine/corpus_linter.py +969 -0
- playbook_engine-2.0.0/playbook_engine/deal_dates.py +814 -0
- playbook_engine-2.0.0/playbook_engine/deviation_classifier.py +210 -0
- playbook_engine-2.0.0/playbook_engine/digest.py +854 -0
- playbook_engine-2.0.0/playbook_engine/document_renderer.py +1134 -0
- playbook_engine-2.0.0/playbook_engine/docx_ingester.py +659 -0
- playbook_engine-2.0.0/playbook_engine/docx_normalizer.py +279 -0
- playbook_engine-2.0.0/playbook_engine/dossiers.py +742 -0
- playbook_engine-2.0.0/playbook_engine/entity_registry.py +461 -0
- playbook_engine-2.0.0/playbook_engine/environment.py +226 -0
- playbook_engine-2.0.0/playbook_engine/equivalence.py +1013 -0
- playbook_engine-2.0.0/playbook_engine/equivalence_check.py +591 -0
- playbook_engine-2.0.0/playbook_engine/extraction.py +2561 -0
- playbook_engine-2.0.0/playbook_engine/floor_candidates.py +1832 -0
- playbook_engine-2.0.0/playbook_engine/floor_judge.py +328 -0
- playbook_engine-2.0.0/playbook_engine/index_page_assets.py +695 -0
- playbook_engine-2.0.0/playbook_engine/induction_version_selector.py +163 -0
- playbook_engine-2.0.0/playbook_engine/inspection_report.py +483 -0
- playbook_engine-2.0.0/playbook_engine/instrument.py +178 -0
- playbook_engine-2.0.0/playbook_engine/intake_plan.py +769 -0
- playbook_engine-2.0.0/playbook_engine/judgment.py +387 -0
- playbook_engine-2.0.0/playbook_engine/llm_segmentation_stage.py +296 -0
- playbook_engine-2.0.0/playbook_engine/llm_segmenter.py +367 -0
- playbook_engine-2.0.0/playbook_engine/llm_segmenter_batch.py +762 -0
- playbook_engine-2.0.0/playbook_engine/natural_sort.py +22 -0
- playbook_engine-2.0.0/playbook_engine/observation_builder.py +2009 -0
- playbook_engine-2.0.0/playbook_engine/opf_accessors.py +459 -0
- playbook_engine-2.0.0/playbook_engine/overrides.py +805 -0
- playbook_engine-2.0.0/playbook_engine/pdf_ingester.py +385 -0
- playbook_engine-2.0.0/playbook_engine/pipeline.py +6190 -0
- playbook_engine-2.0.0/playbook_engine/playbook_assembler.py +633 -0
- playbook_engine-2.0.0/playbook_engine/posture.py +837 -0
- playbook_engine-2.0.0/playbook_engine/precedent.py +669 -0
- playbook_engine-2.0.0/playbook_engine/prior_form_origin_check.py +270 -0
- playbook_engine-2.0.0/playbook_engine/prior_reconstruction.py +1015 -0
- playbook_engine-2.0.0/playbook_engine/prior_standards.py +218 -0
- playbook_engine-2.0.0/playbook_engine/provenance_detector.py +523 -0
- playbook_engine-2.0.0/playbook_engine/resegmentation.py +1312 -0
- playbook_engine-2.0.0/playbook_engine/reversal_detector.py +304 -0
- playbook_engine-2.0.0/playbook_engine/review_rows.py +320 -0
- playbook_engine-2.0.0/playbook_engine/rtf_ingester.py +248 -0
- playbook_engine-2.0.0/playbook_engine/rubric.py +532 -0
- playbook_engine-2.0.0/playbook_engine/run_manifest.py +1262 -0
- playbook_engine-2.0.0/playbook_engine/scope_gate.py +392 -0
- playbook_engine-2.0.0/playbook_engine/scorecard.py +1444 -0
- playbook_engine-2.0.0/playbook_engine/segmentation_grounding.py +258 -0
- playbook_engine-2.0.0/playbook_engine/segmentation_qa.py +581 -0
- playbook_engine-2.0.0/playbook_engine/segmenter.py +330 -0
- playbook_engine-2.0.0/playbook_engine/signed_detector.py +778 -0
- playbook_engine-2.0.0/playbook_engine/staging.py +933 -0
- playbook_engine-2.0.0/playbook_engine/subsplit.py +603 -0
- playbook_engine-2.0.0/playbook_engine/subsplit_check.py +323 -0
- playbook_engine-2.0.0/playbook_engine/taxonomy.py +208 -0
- playbook_engine-2.0.0/playbook_engine/taxonomy_inductor.py +529 -0
- playbook_engine-2.0.0/playbook_engine/template_standards.py +221 -0
- playbook_engine-2.0.0/playbook_engine/toaster_install.py +40 -0
- playbook_engine-2.0.0/playbook_engine/tracked_changes_overlay.py +363 -0
- playbook_engine-2.0.0/playbook_engine/validator.py +2057 -0
- playbook_engine-2.0.0/playbook_engine/version_orderer.py +874 -0
- playbook_engine-2.0.0/pyproject.toml +116 -0
- playbook_engine-2.0.0/scripts/generate_conformance_vectors.py +2716 -0
- playbook_engine-2.0.0/scripts/pre-push-confidentiality.py +269 -0
- playbook_engine-2.0.0/scripts/pre-push-lint.sh +99 -0
- playbook_engine-2.0.0/spec/CHANGELOG.md +755 -0
- playbook_engine-2.0.0/spec/clause-tree.schema.json +86 -0
- playbook_engine-2.0.0/spec/conformance/0.6/README.md +44 -0
- playbook_engine-2.0.0/spec/conformance/0.6/manifest.json +85 -0
- playbook_engine-2.0.0/spec/conformance/0.6/vectors/001-minimal-no-perspective.json +104 -0
- playbook_engine-2.0.0/spec/conformance/0.6/vectors/002-variants-refused-and-exclusions.json +682 -0
- playbook_engine-2.0.0/spec/conformance/0.6/vectors/003-cap-totals-and-summary.json +638 -0
- playbook_engine-2.0.0/spec/conformance/0.6/vectors/004-party-alias-grouping.json +542 -0
- playbook_engine-2.0.0/spec/conformance/0.6/vectors/005-opening-evidence.json +621 -0
- playbook_engine-2.0.0/spec/conformance/0.6/vectors/006-opening-rules.json +849 -0
- playbook_engine-2.0.0/spec/conformance/0.6/vectors/007-equivalence-label-and-coverage.json +1035 -0
- playbook_engine-2.0.0/spec/conformance/0.6/vectors/008-hard-rules-and-dossiers.json +1791 -0
- playbook_engine-2.0.0/spec/conformance/0.6/vectors/009-standard-parts-and-owner-label.json +788 -0
- playbook_engine-2.0.0/spec/conformance/0.6/vectors/010-signing-dates.json +646 -0
- playbook_engine-2.0.0/spec/conformance/0.6/vectors/011-settled-outcomes.json +1117 -0
- playbook_engine-2.0.0/spec/conformance/0.6/vectors/012-prior-standards.json +943 -0
- playbook_engine-2.0.0/spec/conformance/0.6/vectors/013-reconstructed-prior-form.json +568 -0
- playbook_engine-2.0.0/spec/conformance/0.6/vectors/014-verified-absence.json +763 -0
- playbook_engine-2.0.0/spec/conformance/0.6/vectors/015-number-and-key-rules.json +114 -0
- playbook_engine-2.0.0/spec/conformance/README.md +64 -0
- playbook_engine-2.0.0/spec/playbook.schema-0.6.json +3208 -0
- playbook_engine-2.0.0/spec/taxonomy/affiliation-agreement.yaml +62 -0
- playbook_engine-2.0.0/spec/taxonomy/cuad-base.yaml +67 -0
- playbook_engine-2.0.0/spec/taxonomy/general-commercial.yaml +32 -0
- playbook_engine-2.0.0/spec/taxonomy/nda.yaml +93 -0
- playbook_engine-2.0.0/tests/__init__.py +0 -0
- playbook_engine-2.0.0/tests/chrome_cdp.py +268 -0
- playbook_engine-2.0.0/tests/entity_scan.py +61 -0
- playbook_engine-2.0.0/tests/test_absence_counts.py +635 -0
- playbook_engine-2.0.0/tests/test_agent_judge.py +1083 -0
- playbook_engine-2.0.0/tests/test_agent_segmenter.py +434 -0
- playbook_engine-2.0.0/tests/test_alias_sanity_check.py +136 -0
- playbook_engine-2.0.0/tests/test_artifact_store.py +886 -0
- playbook_engine-2.0.0/tests/test_born_safe_holistic.py +622 -0
- playbook_engine-2.0.0/tests/test_cache_format.py +226 -0
- playbook_engine-2.0.0/tests/test_canary_corpus.py +584 -0
- playbook_engine-2.0.0/tests/test_canonicalize.py +315 -0
- playbook_engine-2.0.0/tests/test_citation_resolution.py +365 -0
- playbook_engine-2.0.0/tests/test_classification_config.py +245 -0
- playbook_engine-2.0.0/tests/test_clause_aligner.py +1501 -0
- playbook_engine-2.0.0/tests/test_clause_classifier.py +1356 -0
- playbook_engine-2.0.0/tests/test_clause_differ.py +711 -0
- playbook_engine-2.0.0/tests/test_clause_position_compiler.py +504 -0
- playbook_engine-2.0.0/tests/test_clause_tree.py +843 -0
- playbook_engine-2.0.0/tests/test_cli.py +1536 -0
- playbook_engine-2.0.0/tests/test_cli_help_hygiene.py +144 -0
- playbook_engine-2.0.0/tests/test_cli_judge.py +1279 -0
- playbook_engine-2.0.0/tests/test_cli_precedent.py +267 -0
- playbook_engine-2.0.0/tests/test_cli_scorecard.py +1186 -0
- playbook_engine-2.0.0/tests/test_config.py +843 -0
- playbook_engine-2.0.0/tests/test_conformance_vectors.py +406 -0
- playbook_engine-2.0.0/tests/test_consumer_e2e.py +153 -0
- playbook_engine-2.0.0/tests/test_corpus_linter.py +1252 -0
- playbook_engine-2.0.0/tests/test_cuad_base_taxonomy.py +115 -0
- playbook_engine-2.0.0/tests/test_deal_dates.py +1270 -0
- playbook_engine-2.0.0/tests/test_deviation_classifier.py +212 -0
- playbook_engine-2.0.0/tests/test_digest.py +1000 -0
- playbook_engine-2.0.0/tests/test_docx_ingester.py +853 -0
- playbook_engine-2.0.0/tests/test_docx_normalizer.py +446 -0
- playbook_engine-2.0.0/tests/test_dynamics_fields.py +494 -0
- playbook_engine-2.0.0/tests/test_entity_registry.py +1307 -0
- playbook_engine-2.0.0/tests/test_entity_registry_residue_scan.py +100 -0
- playbook_engine-2.0.0/tests/test_environment_drift.py +517 -0
- playbook_engine-2.0.0/tests/test_equivalence_label.py +1195 -0
- playbook_engine-2.0.0/tests/test_equivalence_parts.py +828 -0
- playbook_engine-2.0.0/tests/test_estimate_runtime_cache.py +551 -0
- playbook_engine-2.0.0/tests/test_examples_validate.py +174 -0
- playbook_engine-2.0.0/tests/test_extraction.py +2777 -0
- playbook_engine-2.0.0/tests/test_extraction_reliability.py +753 -0
- playbook_engine-2.0.0/tests/test_floor_candidates.py +1847 -0
- playbook_engine-2.0.0/tests/test_floor_judge.py +242 -0
- playbook_engine-2.0.0/tests/test_golden_affiliation.py +497 -0
- playbook_engine-2.0.0/tests/test_hooks_install.py +395 -0
- playbook_engine-2.0.0/tests/test_index_browser.py +957 -0
- playbook_engine-2.0.0/tests/test_index_html.py +612 -0
- playbook_engine-2.0.0/tests/test_induction_version_selection.py +162 -0
- playbook_engine-2.0.0/tests/test_inspection_report.py +890 -0
- playbook_engine-2.0.0/tests/test_instrument.py +205 -0
- playbook_engine-2.0.0/tests/test_intake_plan.py +836 -0
- playbook_engine-2.0.0/tests/test_judgment.py +887 -0
- playbook_engine-2.0.0/tests/test_known_entity_zero_match_warning.py +137 -0
- playbook_engine-2.0.0/tests/test_license.py +67 -0
- playbook_engine-2.0.0/tests/test_llm_segmentation_stage.py +309 -0
- playbook_engine-2.0.0/tests/test_llm_segmenter.py +380 -0
- playbook_engine-2.0.0/tests/test_llm_segmenter_batch.py +713 -0
- playbook_engine-2.0.0/tests/test_manifest_and_dossiers.py +1380 -0
- playbook_engine-2.0.0/tests/test_nda_content_similarity.py +244 -0
- playbook_engine-2.0.0/tests/test_nda_derive_reproducible.py +194 -0
- playbook_engine-2.0.0/tests/test_nda_smoke.py +219 -0
- playbook_engine-2.0.0/tests/test_notice_attribution.py +48 -0
- playbook_engine-2.0.0/tests/test_observation_builder.py +2628 -0
- playbook_engine-2.0.0/tests/test_opf_accessors.py +347 -0
- playbook_engine-2.0.0/tests/test_overrides.py +882 -0
- playbook_engine-2.0.0/tests/test_ownership_boundary_doc.py +65 -0
- playbook_engine-2.0.0/tests/test_packaging.py +271 -0
- playbook_engine-2.0.0/tests/test_pdf_ingester.py +672 -0
- playbook_engine-2.0.0/tests/test_perspective_config.py +238 -0
- playbook_engine-2.0.0/tests/test_pipeline_cache.py +593 -0
- playbook_engine-2.0.0/tests/test_pipeline_deterministic_deviations.py +269 -0
- playbook_engine-2.0.0/tests/test_pipeline_discover_versions.py +70 -0
- playbook_engine-2.0.0/tests/test_pipeline_judgment_cache.py +808 -0
- playbook_engine-2.0.0/tests/test_pipeline_llm_seg.py +4061 -0
- playbook_engine-2.0.0/tests/test_pipeline_mine_empty_corpus.py +203 -0
- playbook_engine-2.0.0/tests/test_pipeline_normalized_trees.py +351 -0
- playbook_engine-2.0.0/tests/test_pipeline_project.py +2259 -0
- playbook_engine-2.0.0/tests/test_pipeline_provenance.py +1084 -0
- playbook_engine-2.0.0/tests/test_pipeline_reclassify.py +226 -0
- playbook_engine-2.0.0/tests/test_playbook_assembler.py +1015 -0
- playbook_engine-2.0.0/tests/test_posture.py +1256 -0
- playbook_engine-2.0.0/tests/test_pre_push_confidentiality.py +262 -0
- playbook_engine-2.0.0/tests/test_prior_reconstruction.py +1191 -0
- playbook_engine-2.0.0/tests/test_prior_standards.py +1504 -0
- playbook_engine-2.0.0/tests/test_prior_templates_intake.py +621 -0
- playbook_engine-2.0.0/tests/test_provenance_detector.py +727 -0
- playbook_engine-2.0.0/tests/test_q4_floor_qa.py +477 -0
- playbook_engine-2.0.0/tests/test_qa_onboarding_pages.py +141 -0
- playbook_engine-2.0.0/tests/test_quickstart.py +252 -0
- playbook_engine-2.0.0/tests/test_resegmentation.py +1892 -0
- playbook_engine-2.0.0/tests/test_reversal_detector.py +622 -0
- playbook_engine-2.0.0/tests/test_rtf_ingester.py +545 -0
- playbook_engine-2.0.0/tests/test_rubric_versioning.py +693 -0
- playbook_engine-2.0.0/tests/test_run_manifest.py +550 -0
- playbook_engine-2.0.0/tests/test_schema_extensions.py +106 -0
- playbook_engine-2.0.0/tests/test_schema_ids.py +69 -0
- playbook_engine-2.0.0/tests/test_scope_gate.py +628 -0
- playbook_engine-2.0.0/tests/test_segmentation_grounding.py +228 -0
- playbook_engine-2.0.0/tests/test_segmentation_qa.py +523 -0
- playbook_engine-2.0.0/tests/test_segmenter.py +909 -0
- playbook_engine-2.0.0/tests/test_settled.py +449 -0
- playbook_engine-2.0.0/tests/test_signature_block_pipeline.py +167 -0
- playbook_engine-2.0.0/tests/test_signed_detector.py +1559 -0
- playbook_engine-2.0.0/tests/test_skill_plan_examples.py +112 -0
- playbook_engine-2.0.0/tests/test_skill_playbook_from_corpus.py +788 -0
- playbook_engine-2.0.0/tests/test_span_bridge.py +381 -0
- playbook_engine-2.0.0/tests/test_spec_consistency.py +503 -0
- playbook_engine-2.0.0/tests/test_staging.py +1143 -0
- playbook_engine-2.0.0/tests/test_subsplit.py +777 -0
- playbook_engine-2.0.0/tests/test_taxonomy.py +979 -0
- playbook_engine-2.0.0/tests/test_taxonomy_inductor.py +879 -0
- playbook_engine-2.0.0/tests/test_template_agent_segmentation.py +387 -0
- playbook_engine-2.0.0/tests/test_template_standards.py +803 -0
- playbook_engine-2.0.0/tests/test_tracked_changes_overlay.py +496 -0
- playbook_engine-2.0.0/tests/test_validator.py +1070 -0
- playbook_engine-2.0.0/tests/test_version_orderer.py +1371 -0
- 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` |
|