causal-continuity-engine 0.2.0__tar.gz → 0.3.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.
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/.github/CONTRIBUTING.md +5 -1
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/.github/scripts/check_attribution.py +12 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/AGENTS.md +12 -5
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/CHANGELOG.md +34 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/CITATION.cff +2 -2
- {causal_continuity_engine-0.2.0/causal_continuity_engine.egg-info → causal_continuity_engine-0.3.0}/PKG-INFO +25 -7
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/README.md +24 -6
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/causal_continuity_engine/__init__.py +1 -1
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/causal_continuity_engine/engine.py +121 -6
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/causal_continuity_engine/extraction.py +100 -1
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/causal_continuity_engine/invalidation.py +28 -5
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0/causal_continuity_engine.egg-info}/PKG-INFO +25 -7
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/causal_continuity_engine.egg-info/SOURCES.txt +4 -0
- causal_continuity_engine-0.3.0/docs/MCP.md +270 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/docs/RELEASE.md +3 -3
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/docs/REQUIREMENTS_COVERAGE.md +6 -6
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/docs/adr/ADR-INDEX.md +134 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/tests/test_attribution_controls.py +39 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/tests/test_authority_extraction.py +3 -3
- causal_continuity_engine-0.3.0/tests/test_contradiction_notice.py +346 -0
- causal_continuity_engine-0.3.0/tests/test_extraction_negated_cue.py +93 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/tests/test_isolated_review.py +6 -0
- causal_continuity_engine-0.3.0/tests/test_proof_rejection_reason.py +45 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/tests/test_release_controls.py +25 -4
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/.editorconfig +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/.gitattributes +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/.githooks/commit-msg +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/.githooks/pre-commit +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/.github/CODEOWNERS +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/.github/ISSUE_TEMPLATE/bug.yml +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/.github/ISSUE_TEMPLATE/config.yml +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/.github/ISSUE_TEMPLATE/feature.yml +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/.github/SECURITY.md +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/.github/SUPPORT.md +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/.github/dependabot.yml +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/.github/pull_request_template.md +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/.github/release.yml +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/.github/ruleset.README.md +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/.github/ruleset.json +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/.github/scripts/bootstrap_tools.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/.github/scripts/build_distributions.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/.github/scripts/check_clean.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/.github/scripts/check_content_marks.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/.github/scripts/check_release_metadata.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/.github/scripts/check_release_tag.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/.github/scripts/check_stdlib.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/.github/scripts/prepare_release_tag.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/.github/scripts/render_api_docs.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/.github/scripts/run_gates.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/.github/scripts/run_isolated_review.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/.github/scripts/verify_distributions.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/.github/scripts/verify_public_schemas.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/.github/tag-ruleset.json +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/.github/workflows/ci.yml +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/.github/workflows/commit-signature-audit.yml +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/.github/workflows/content-integrity-pr.yml +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/.github/workflows/content-integrity.yml +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/.github/workflows/dependency-review.yml +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/.github/workflows/no-ai-attribution.yml +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/.github/workflows/release.yml +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/.github/workflows/secret-scan.yml +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/.gitignore +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/.gitleaks.toml +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/.pre-commit-config.yaml +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/LICENSE.txt +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/MANIFEST.in +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/NOTICE +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/SPEC.md +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/benchmarks/__init__.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/benchmarks/continuitybench/__init__.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/benchmarks/continuitybench/run.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/benchmarks/continuitybench/scenarios.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/causal_continuity_engine/api.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/causal_continuity_engine/capabilities.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/causal_continuity_engine/capsule.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/causal_continuity_engine/cli.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/causal_continuity_engine/core.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/causal_continuity_engine/evidence.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/causal_continuity_engine/github.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/causal_continuity_engine/graph.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/causal_continuity_engine/lamport.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/causal_continuity_engine/learning.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/causal_continuity_engine/mcp.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/causal_continuity_engine/memory.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/causal_continuity_engine/ontology.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/causal_continuity_engine/partial.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/causal_continuity_engine/policy.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/causal_continuity_engine/proof.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/causal_continuity_engine/py.typed +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/causal_continuity_engine/redaction.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/causal_continuity_engine/resume.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/causal_continuity_engine/store.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/causal_continuity_engine/verifiers.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/causal_continuity_engine.egg-info/dependency_links.txt +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/causal_continuity_engine.egg-info/entry_points.txt +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/causal_continuity_engine.egg-info/requires.txt +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/causal_continuity_engine.egg-info/top_level.txt +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/docs/API.md +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/docs/CAPABILITIES.md +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/docs/CONTENT-INGRESS-FIREWALL.md +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/docs/PUBLIC-FLIP.md +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/docs/REQUIREMENTS.md +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/docs/RESEARCH-ROADMAP.md +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/docs/TESTING.md +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/docs/V0.1.5-TRUST-CLOSURE.md +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/docs/assets/demo.svg +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/docs/assets/social-preview.png +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/examples/backfill_github.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/examples/quickstart.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/justfile +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/pyproject.toml +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/requirements-dev.in +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/requirements-dev.lock +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/schemas/cce.anchor.v1.json +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/schemas/cce.capsule.v1.json +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/schemas/cce.capsule.v2.json +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/schemas/cce.continuity-receipt.v1.json +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/schemas/cce.continuity-receipt.v2.json +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/schemas/cce.event.v1.json +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/schemas/cce.proof-predicate.v1.json +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/schemas/cce.proof-predicate.v2.json +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/schemas/cce.proof.v1.json +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/schemas/cce.proof.v2.json +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/schemas/cce.recovery.v1.json +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/schemas/cce.resume.v1.json +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/schemas/cce.resume.v2.json +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/setup.cfg +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/tests/__init__.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/tests/authority_helpers.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/tests/schema_validation.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/tests/test_api_contract.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/tests/test_authority_atomic_append.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/tests/test_authority_cli.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/tests/test_authority_consumers.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/tests/test_authority_criticality.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/tests/test_authority_producer.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/tests/test_authority_read_reuse.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/tests/test_authority_redteam.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/tests/test_authority_withdrawal_classification.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/tests/test_benchmark_authority.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/tests/test_benchmark_cleanup.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/tests/test_benchmark_runner.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/tests/test_capsule_packet_clock.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/tests/test_cli.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/tests/test_conformance.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/tests/test_content_marks.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/tests/test_distribution_environment.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/tests/test_engine_e2e.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/tests/test_github_redaction.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/tests/test_identity_compatibility.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/tests/test_installed_contracts.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/tests/test_installed_packet_bounds.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/tests/test_instrument_validation.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/tests/test_invalidation_resume.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/tests/test_learning_partial_api.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/tests/test_mcp_server.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/tests/test_memory_extraction.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/tests/test_obligation_boundaries.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/tests/test_obligation_completeness.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/tests/test_obligation_contract_repairs.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/tests/test_packet_byte_bounds.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/tests/test_packet_byte_transports.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/tests/test_packet_complete_trust.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/tests/test_packet_failure_boundaries.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/tests/test_packet_scope_receipts.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/tests/test_packet_scope_wire.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/tests/test_packet_validity_frontier.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/tests/test_processor_compatibility.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/tests/test_proof_obligation_wire.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/tests/test_readme_contract.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/tests/test_receipt_spec_contract.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/tests/test_regressions.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/tests/test_regressions_round10_release.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/tests/test_regressions_round11_deep_integrity.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/tests/test_regressions_round12_release_blockers.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/tests/test_regressions_round13_jcs.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/tests/test_regressions_round14_state_integrity.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/tests/test_regressions_round15_capsule_invalidation_github.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/tests/test_regressions_round15_public_boundaries.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/tests/test_regressions_round15_storage_boundaries.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/tests/test_regressions_round16_cli_api_core_capabilities.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/tests/test_regressions_round17_backfill_layout.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/tests/test_regressions_round18_assumption_edits.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/tests/test_regressions_round18_co_assertion.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/tests/test_regressions_round18_journal.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/tests/test_regressions_round18_negation.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/tests/test_regressions_round18_source_order.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/tests/test_regressions_round2.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/tests/test_regressions_round3.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/tests/test_regressions_round4.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/tests/test_regressions_round5.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/tests/test_regressions_round6.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/tests/test_regressions_round7.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/tests/test_regressions_round8_boundaries.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/tests/test_regressions_round8_engine.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/tests/test_regressions_round8_portability.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/tests/test_regressions_round8_state.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/tests/test_regressions_round9_integrity.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/tests/test_regressions_round9_scope_freshness.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/tests/test_release_boundary_validation.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/tests/test_resume_prose_origin.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/tests/test_schema_release_identities.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/tests/test_sqlite_lifecycle.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/tests/test_store_graph.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/tests/test_task_packet_review.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/tests/test_task_packets.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/tests/test_trust.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/vectors/action_intent_wrong_type.json +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/vectors/actor_wrong_type.json +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/vectors/created_at_invalid_calendar_date.json +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/vectors/created_at_leap_second.json +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/vectors/created_at_lowercase_designators.json +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/vectors/created_at_missing_timezone.json +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/vectors/created_at_noncanonical_offset.json +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/vectors/created_at_space_separator.json +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/vectors/empty_required_authoritative_failure.json +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/vectors/empty_required_authoritative_pass.json +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/vectors/empty_required_no_results.json +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/vectors/empty_required_self_asserted_pass.json +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/vectors/generate.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/vectors/historical_proof_version.json +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/vectors/incomplete_failed_check.json +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/vectors/index.json +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/vectors/input_item_wrong_type.json +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/vectors/invalid_result_value.json +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/vectors/lamport_fingerprint_spoof.json +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/vectors/lamport_no_registry.json +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/vectors/lamport_truncated_key.json +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/vectors/missing_required_field.json +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/vectors/non_binary64_integer.json +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/vectors/obligation_duplicate.json +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/vectors/obligation_duplicate_task.json +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/vectors/obligation_missing.json +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/vectors/obligation_wrong_kind.json +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/vectors/obligation_wrong_task.json +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/vectors/policy_decision_wrong_type.json +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/vectors/retry_cannot_launder_a_failure.json +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/vectors/self_asserted_cannot_satisfy.json +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/vectors/self_assertion_cannot_poison_authoritative_pass.json +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/vectors/status_contradicts_contents.json +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/vectors/summary_contradicts_contents.json +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/vectors/tamper_statement.json +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/vectors/tamper_statement_resealed.json +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/vectors/task_id_under_unrelated_field.json +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/vectors/unbound_task.json +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/vectors/unknown_action_intent_field.json +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/vectors/unknown_algorithm.json +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/vectors/unknown_evidence_context_field.json +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/vectors/unknown_execution_field.json +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/vectors/unknown_schema_version.json +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/vectors/unknown_top_level_field.json +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/vectors/unknown_verification_field.json +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/vectors/unverified_forged_body_no_key.json +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/vectors/unverified_hmac_no_key.json +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/vectors/valid_hmac.json +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/vectors/valid_hmac_jcs_edges.json +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/vectors/valid_hmac_unicode.json +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/vectors/valid_lamport.json +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/vectors/wrong_project.json +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/verifiers/__init__.py +0 -0
- {causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/verifiers/verify_proof.py +0 -0
|
@@ -258,7 +258,11 @@ outcome, say what distinguishes it from "nothing happened".
|
|
|
258
258
|
|
|
259
259
|
**No authorship attribution to tooling.** The `.githooks/` hooks and the CI
|
|
260
260
|
attribution guard both reject it, in commit messages and in committed files.
|
|
261
|
-
Do not work around them.
|
|
261
|
+
Do not work around them. This is a rule about credit, not secrecy: AI coding
|
|
262
|
+
assistants are used in this project's development, and every change is
|
|
263
|
+
reviewed, tested, signed off, and committed by the maintainer, who is
|
|
264
|
+
responsible for it. If AI tools did significant work on your patch, say so in
|
|
265
|
+
plain words in the pull request; never list a tool as an author or co-author.
|
|
262
266
|
|
|
263
267
|
## Security
|
|
264
268
|
|
|
@@ -76,6 +76,13 @@ _ARTIFACT_AI_CREDIT = re.compile(
|
|
|
76
76
|
_TOOL_BYLINE = re.compile(
|
|
77
77
|
rf"{_AI_ALIAS.pattern}\s*\((?:anthropic|openai)\)[.!]?"
|
|
78
78
|
)
|
|
79
|
+
# An AI coding session appends provenance rather than credit: a trailer whose key
|
|
80
|
+
# names the tool ("<tool>-Session: <link>") and a bare link to the session. Neither
|
|
81
|
+
# names an actor, so the credit grammar above never reached them.
|
|
82
|
+
_AI_SESSION_KEY = re.compile(
|
|
83
|
+
rf"{_AI_ALIAS.pattern}[- ]session(?:[- ](?:id|link|url))?"
|
|
84
|
+
)
|
|
85
|
+
_AI_SESSION_LINK = re.compile(r"<?https?://claude\.ai/code/session_[a-z0-9_-]+/?>?")
|
|
79
86
|
_TRAILER_KEYS = frozenset({
|
|
80
87
|
"assisted-by",
|
|
81
88
|
"authored-by",
|
|
@@ -233,6 +240,11 @@ def scan_prose(
|
|
|
233
240
|
key, separator, value = line.partition(":")
|
|
234
241
|
if separator and key.replace("_", "-") in _TRAILER_KEYS:
|
|
235
242
|
reason = _actor_reason(value)
|
|
243
|
+
if reason is None and separator and _AI_SESSION_KEY.fullmatch(
|
|
244
|
+
key.replace("_", "-")):
|
|
245
|
+
reason = "tool/model session trailer"
|
|
246
|
+
if reason is None and _AI_SESSION_LINK.fullmatch(line):
|
|
247
|
+
reason = "tool/model session link"
|
|
236
248
|
if reason is None:
|
|
237
249
|
match = _CREDIT.fullmatch(line) or _ROLE.fullmatch(line)
|
|
238
250
|
if match is not None:
|
|
@@ -14,9 +14,13 @@ history and a bi-temporal causal graph. Event-derived projections are
|
|
|
14
14
|
rebuildable only while source payloads remain inside the retention window;
|
|
15
15
|
runtime records are authenticated/audited rather than replay-derived. Memory
|
|
16
16
|
tiers L0–L4 and token-budgeted Resume Packets are what
|
|
17
|
-
an agent actually receives on resume. When a requirement changes
|
|
18
|
-
|
|
19
|
-
|
|
17
|
+
an agent actually receives on resume. When a requirement changes, when the
|
|
18
|
+
source that stated an assumption withdraws it, or when an integration records
|
|
19
|
+
contradicting evidence through `engine.invalidation.fire(...)`, causal
|
|
20
|
+
invalidation computes the blast radius over typed edges, bounds it, and
|
|
21
|
+
classifies it deterministically. It does not read prose for meaning; a trusted
|
|
22
|
+
explicit negation of an active assumption's own wording raises a human-gated
|
|
23
|
+
notice (ADR-133). When policy
|
|
20
24
|
requires proof, a task cannot be marked complete without a signed proof envelope
|
|
21
25
|
whose required verifiers actually ran — `Engine.complete_task` has twenty-two
|
|
22
26
|
independently instrumented rejection gates, including current confirmed authority,
|
|
@@ -325,8 +329,11 @@ below what it is about to do (ADR-048).
|
|
|
325
329
|
|
|
326
330
|
Never add AI authorship credit to a commit message, PR title or body, code
|
|
327
331
|
comment, documentation file, changelog, or example. No co-author trailers,
|
|
328
|
-
no "generated with" lines, no tool bylines
|
|
329
|
-
|
|
332
|
+
no "generated with" lines, no tool bylines. This is a rule about credit, not
|
|
333
|
+
secrecy: `.github/CONTRIBUTING.md` states plainly that AI coding assistants
|
|
334
|
+
are used in development and that the maintainer reviews, tests, and commits
|
|
335
|
+
every change. Disclosure in plain words is allowed; crediting a tool as an
|
|
336
|
+
author is not.
|
|
330
337
|
|
|
331
338
|
This is enforced, not requested. `.githooks/commit-msg` and
|
|
332
339
|
`.githooks/pre-commit` block it locally (`core.hooksPath` is set to
|
|
@@ -9,6 +9,40 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|
|
9
9
|
|
|
10
10
|
No unreleased changes.
|
|
11
11
|
|
|
12
|
+
## 0.3.0 — 2026-10-16
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
|
|
16
|
+
- **A trusted explicit negation of an active assumption raises a human-gated
|
|
17
|
+
notice.** An OWNER, MEMBER or COLLABORATOR sentence that restates an active
|
|
18
|
+
assumption with one negator ("the feed is NOT ordered by timestamp"), negates
|
|
19
|
+
its cue ("we no longer assume …") or calls it wrong fires a
|
|
20
|
+
`contradictory_evidence` invalidation in `pending_confirmation`: nothing
|
|
21
|
+
changes until the owner records `confirm_invalidation` or
|
|
22
|
+
`reject_invalidation` through `authority --request`, with operands from
|
|
23
|
+
`Engine.authority_invalidation`. The notice appears in the resume packet and
|
|
24
|
+
makes `check` report `action_required`. The rule is closed and lexical; other
|
|
25
|
+
wordings are outside it (ADR-133).
|
|
26
|
+
|
|
27
|
+
### Changed
|
|
28
|
+
|
|
29
|
+
- **Breaking: processor compatibility advances to `cce-processor/1.10.0` and
|
|
30
|
+
extraction to `cce-deterministic/1.5.0`.** Preserve older projected stores and
|
|
31
|
+
their SQLite sidecars after stopping writers. Re-ingest retained sources into
|
|
32
|
+
a separate new store/project and review its proposals before confirming them.
|
|
33
|
+
Older projections do not upgrade in place; erased source payloads cannot be
|
|
34
|
+
reconstructed by replay (ADR-114, ADR-132).
|
|
35
|
+
|
|
36
|
+
### Fixed
|
|
37
|
+
|
|
38
|
+
- **A negated assumption cue no longer proposes the assumption it denies.**
|
|
39
|
+
"We no longer assume X", "We do not assume X" and "nothing relies on X"
|
|
40
|
+
extracted the same positive proposal as "We assume X", so a sentence written
|
|
41
|
+
to retire an assumption offered it for confirmation. The extractor now
|
|
42
|
+
abstains when a negator stands immediately before the cue. A prohibition or
|
|
43
|
+
decision form still proposes the constraint or decision the author wrote;
|
|
44
|
+
other wordings of a denial are outside this closed rule (ADR-132).
|
|
45
|
+
|
|
12
46
|
## 0.2.0 — 2026-09-23
|
|
13
47
|
|
|
14
48
|
### Changed
|
|
@@ -8,8 +8,8 @@ authors:
|
|
|
8
8
|
- given-names: Thomas
|
|
9
9
|
family-names: Albrecht
|
|
10
10
|
alias: The Quantum Falcon
|
|
11
|
-
version: 0.
|
|
12
|
-
date-released: 2026-
|
|
11
|
+
version: 0.3.0
|
|
12
|
+
date-released: 2026-10-16
|
|
13
13
|
license: Apache-2.0
|
|
14
14
|
repository-code: https://github.com/thequantumfalcon/causal-continuity-engine
|
|
15
15
|
url: https://github.com/thequantumfalcon/causal-continuity-engine
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: causal-continuity-engine
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 0.3.0
|
|
4
4
|
Summary: Causal Continuity Engine - continuity, causal invalidation, and proof for long-running coding agents
|
|
5
5
|
Author: Thomas Albrecht
|
|
6
6
|
License-Expression: Apache-2.0
|
|
@@ -71,9 +71,16 @@ and neither is an unexamined green check.
|
|
|
71
71
|
CCE addresses each with a mechanism rather than a better prompt. Decisions,
|
|
72
72
|
assumptions, requirements and evidence become nodes in a bi-temporal causal
|
|
73
73
|
graph over an application-enforced append-only, hash-chained event log, so
|
|
74
|
-
*why* is a queryable edge and not a memory. When a requirement changes
|
|
74
|
+
*why* is a queryable edge and not a memory. When a requirement changes, when the
|
|
75
|
+
source that stated an assumption withdraws it, or when an integration records
|
|
76
|
+
contradicting evidence through `engine.invalidation.fire(...)`, the
|
|
75
77
|
blast radius is computed over typed edges, bounded, and classified
|
|
76
|
-
deterministically.
|
|
78
|
+
deterministically. CCE does not read prose for meaning. It does recognize one
|
|
79
|
+
closed form: a trusted comment that restates an active assumption with a
|
|
80
|
+
negator, like the one above, raises a human-gated notice that changes nothing
|
|
81
|
+
until the owner confirms or rejects it; a differently worded dispute proposes
|
|
82
|
+
nothing and fires nothing.
|
|
83
|
+
When policy requires proof, a task cannot be marked complete
|
|
77
84
|
without a signed envelope whose required verifiers actually ran — the rejection gates are
|
|
78
85
|
mechanically enumerated and mutation-tested below.
|
|
79
86
|
|
|
@@ -97,7 +104,9 @@ command are already used by an unrelated published project.
|
|
|
97
104
|
## Use it from an editor
|
|
98
105
|
|
|
99
106
|
`cce-engine mcp` speaks the Model Context Protocol over stdio, so an MCP client
|
|
100
|
-
can read a project's control state directly.
|
|
107
|
+
can read a project's control state directly. [docs/MCP.md](docs/MCP.md) walks the
|
|
108
|
+
whole path on this repository, from install to a connected agent, and shows how
|
|
109
|
+
the continuity check runs in CI on a runner that holds the store. Four read-only tools:
|
|
101
110
|
`resume_packet`, `list_assumptions`, `list_invalidations`, `continuity_check`.
|
|
102
111
|
|
|
103
112
|
The subcommand first ships in 0.1.6. The published 0.1.0, 0.1.2 and 0.1.3
|
|
@@ -165,15 +174,15 @@ with `.\.venv\Scripts\python.exe -m pip install causal-continuity-engine`
|
|
|
165
174
|
and invoke the environment's `cce-engine.exe` by its absolute path in the
|
|
166
175
|
commands below. After activation, `cce-engine --help` checks the installation.
|
|
167
176
|
|
|
168
|
-
**Upgrading an existing project:** this release uses processor 1.
|
|
169
|
-
extractor 1.
|
|
177
|
+
**Upgrading an existing project:** this release uses processor 1.10.0 and
|
|
178
|
+
extractor 1.5.0; projections written by 0.2.0's processor 1.9.0 or earlier
|
|
170
179
|
processors are refused. There is no in-place projection or authority upgrade. Stop writers,
|
|
171
180
|
preserve the complete old project state (including its database and any SQLite
|
|
172
181
|
sidecars), and re-ingest retained sources into a separate new project/store.
|
|
173
182
|
Review and explicitly confirm the new proposals there; old prose is not approval.
|
|
174
183
|
Do not remove markers or sidecars to bypass refusal. Payloads already removed
|
|
175
184
|
by retention cannot be reconstructed from their digests. New users can simply
|
|
176
|
-
start a fresh project. See [the upgrade notes for this release](CHANGELOG.md#
|
|
185
|
+
start a fresh project. See [the upgrade notes for this release](CHANGELOG.md#030--2026-10-16);
|
|
177
186
|
the [0.1.6 notes](CHANGELOG.md#016--2026-09-20) remain historical.
|
|
178
187
|
|
|
179
188
|
To run this exact checkout instead of the published package, or to work on the
|
|
@@ -191,6 +200,12 @@ python -m pip install -e .
|
|
|
191
200
|
On Windows, use the PowerShell environment-creation and activation commands
|
|
192
201
|
above, then `python -m pip install -e .` from the checkout.
|
|
193
202
|
|
|
203
|
+
The editable install is enough for the engine but not for the test suite,
|
|
204
|
+
which fails at collection without the hash-locked development tools. Install
|
|
205
|
+
them with `python -m pip install -r requirements-dev.lock` (or `just setup`),
|
|
206
|
+
then run `python -m pytest tests -q`. The full suite can take around fifteen
|
|
207
|
+
minutes.
|
|
208
|
+
|
|
194
209
|
Global flags come **before** the subcommand: `cce-engine --dir <path> <subcommand>`.
|
|
195
210
|
|
|
196
211
|
```bash
|
|
@@ -899,6 +914,9 @@ condition.
|
|
|
899
914
|
- [docs/PUBLIC-FLIP.md](docs/PUBLIC-FLIP.md) — the register of every control
|
|
900
915
|
that could not be enabled while this repository was private, with the exact
|
|
901
916
|
command that turned each one on at the flip.
|
|
917
|
+
- [docs/MCP.md](docs/MCP.md) — connecting an agent over MCP, walked end to end on
|
|
918
|
+
this repository: install, init, ingest real events, confirm, verify, packet, check,
|
|
919
|
+
client configuration, and the freshness rule.
|
|
902
920
|
- [docs/RELEASE.md](docs/RELEASE.md) — the reproducible, signed, immutable-ready
|
|
903
921
|
release procedure and its continuity-bound attestation roadmap.
|
|
904
922
|
- [docs/RESEARCH-ROADMAP.md](docs/RESEARCH-ROADMAP.md) — standards-backed
|
|
@@ -36,9 +36,16 @@ and neither is an unexamined green check.
|
|
|
36
36
|
CCE addresses each with a mechanism rather than a better prompt. Decisions,
|
|
37
37
|
assumptions, requirements and evidence become nodes in a bi-temporal causal
|
|
38
38
|
graph over an application-enforced append-only, hash-chained event log, so
|
|
39
|
-
*why* is a queryable edge and not a memory. When a requirement changes
|
|
39
|
+
*why* is a queryable edge and not a memory. When a requirement changes, when the
|
|
40
|
+
source that stated an assumption withdraws it, or when an integration records
|
|
41
|
+
contradicting evidence through `engine.invalidation.fire(...)`, the
|
|
40
42
|
blast radius is computed over typed edges, bounded, and classified
|
|
41
|
-
deterministically.
|
|
43
|
+
deterministically. CCE does not read prose for meaning. It does recognize one
|
|
44
|
+
closed form: a trusted comment that restates an active assumption with a
|
|
45
|
+
negator, like the one above, raises a human-gated notice that changes nothing
|
|
46
|
+
until the owner confirms or rejects it; a differently worded dispute proposes
|
|
47
|
+
nothing and fires nothing.
|
|
48
|
+
When policy requires proof, a task cannot be marked complete
|
|
42
49
|
without a signed envelope whose required verifiers actually ran — the rejection gates are
|
|
43
50
|
mechanically enumerated and mutation-tested below.
|
|
44
51
|
|
|
@@ -62,7 +69,9 @@ command are already used by an unrelated published project.
|
|
|
62
69
|
## Use it from an editor
|
|
63
70
|
|
|
64
71
|
`cce-engine mcp` speaks the Model Context Protocol over stdio, so an MCP client
|
|
65
|
-
can read a project's control state directly.
|
|
72
|
+
can read a project's control state directly. [docs/MCP.md](docs/MCP.md) walks the
|
|
73
|
+
whole path on this repository, from install to a connected agent, and shows how
|
|
74
|
+
the continuity check runs in CI on a runner that holds the store. Four read-only tools:
|
|
66
75
|
`resume_packet`, `list_assumptions`, `list_invalidations`, `continuity_check`.
|
|
67
76
|
|
|
68
77
|
The subcommand first ships in 0.1.6. The published 0.1.0, 0.1.2 and 0.1.3
|
|
@@ -130,15 +139,15 @@ with `.\.venv\Scripts\python.exe -m pip install causal-continuity-engine`
|
|
|
130
139
|
and invoke the environment's `cce-engine.exe` by its absolute path in the
|
|
131
140
|
commands below. After activation, `cce-engine --help` checks the installation.
|
|
132
141
|
|
|
133
|
-
**Upgrading an existing project:** this release uses processor 1.
|
|
134
|
-
extractor 1.
|
|
142
|
+
**Upgrading an existing project:** this release uses processor 1.10.0 and
|
|
143
|
+
extractor 1.5.0; projections written by 0.2.0's processor 1.9.0 or earlier
|
|
135
144
|
processors are refused. There is no in-place projection or authority upgrade. Stop writers,
|
|
136
145
|
preserve the complete old project state (including its database and any SQLite
|
|
137
146
|
sidecars), and re-ingest retained sources into a separate new project/store.
|
|
138
147
|
Review and explicitly confirm the new proposals there; old prose is not approval.
|
|
139
148
|
Do not remove markers or sidecars to bypass refusal. Payloads already removed
|
|
140
149
|
by retention cannot be reconstructed from their digests. New users can simply
|
|
141
|
-
start a fresh project. See [the upgrade notes for this release](CHANGELOG.md#
|
|
150
|
+
start a fresh project. See [the upgrade notes for this release](CHANGELOG.md#030--2026-10-16);
|
|
142
151
|
the [0.1.6 notes](CHANGELOG.md#016--2026-09-20) remain historical.
|
|
143
152
|
|
|
144
153
|
To run this exact checkout instead of the published package, or to work on the
|
|
@@ -156,6 +165,12 @@ python -m pip install -e .
|
|
|
156
165
|
On Windows, use the PowerShell environment-creation and activation commands
|
|
157
166
|
above, then `python -m pip install -e .` from the checkout.
|
|
158
167
|
|
|
168
|
+
The editable install is enough for the engine but not for the test suite,
|
|
169
|
+
which fails at collection without the hash-locked development tools. Install
|
|
170
|
+
them with `python -m pip install -r requirements-dev.lock` (or `just setup`),
|
|
171
|
+
then run `python -m pytest tests -q`. The full suite can take around fifteen
|
|
172
|
+
minutes.
|
|
173
|
+
|
|
159
174
|
Global flags come **before** the subcommand: `cce-engine --dir <path> <subcommand>`.
|
|
160
175
|
|
|
161
176
|
```bash
|
|
@@ -864,6 +879,9 @@ condition.
|
|
|
864
879
|
- [docs/PUBLIC-FLIP.md](docs/PUBLIC-FLIP.md) — the register of every control
|
|
865
880
|
that could not be enabled while this repository was private, with the exact
|
|
866
881
|
command that turned each one on at the flip.
|
|
882
|
+
- [docs/MCP.md](docs/MCP.md) — connecting an agent over MCP, walked end to end on
|
|
883
|
+
this repository: install, init, ingest real events, confirm, verify, packet, check,
|
|
884
|
+
client configuration, and the freshness rule.
|
|
867
885
|
- [docs/RELEASE.md](docs/RELEASE.md) — the reproducible, signed, immutable-ready
|
|
868
886
|
release procedure and its continuity-bound attestation roadmap.
|
|
869
887
|
- [docs/RESEARCH-ROADMAP.md](docs/RESEARCH-ROADMAP.md) — standards-backed
|
|
@@ -7,7 +7,7 @@ authenticated runtime records are not replay-derived (ADR-001). Storage is
|
|
|
7
7
|
SQLite implementing the PostgreSQL-first relational adjacency design (ADR-011).
|
|
8
8
|
"""
|
|
9
9
|
|
|
10
|
-
__version__ = "0.
|
|
10
|
+
__version__ = "0.3.0"
|
|
11
11
|
|
|
12
12
|
SCHEMA_VERSIONS = {
|
|
13
13
|
"anchor": "cce.anchor.v1",
|
{causal_continuity_engine-0.2.0 → causal_continuity_engine-0.3.0}/causal_continuity_engine/engine.py
RENAMED
|
@@ -65,7 +65,7 @@ from .evidence import (
|
|
|
65
65
|
run_determinism_probe,
|
|
66
66
|
run_mutation_probe,
|
|
67
67
|
)
|
|
68
|
-
from .extraction import DeterministicExtractor, normalize_statement
|
|
68
|
+
from .extraction import DeterministicExtractor, explicit_negations, normalize_statement
|
|
69
69
|
from .github import (
|
|
70
70
|
TRUSTED_ASSOCIATIONS,
|
|
71
71
|
WEBHOOK_BODY_MAX_BYTES,
|
|
@@ -104,12 +104,14 @@ from .store import (
|
|
|
104
104
|
)
|
|
105
105
|
from .verifiers import VerifierRunner, VerifierSpec, record_verification
|
|
106
106
|
|
|
107
|
-
PROCESSOR_VERSION = "cce-processor/1.
|
|
107
|
+
PROCESSOR_VERSION = "cce-processor/1.10.0"
|
|
108
108
|
_AUTHORITY_SCHEMA = "cce.authority-decision.v1"
|
|
109
109
|
_AUTHORITY_SOURCE = "cce:authority-decision:v1"
|
|
110
110
|
_AUTHORITY_EXTRACTOR = "cce.authority-decision"
|
|
111
111
|
_PROPOSAL_SCHEMA = "cce.proposal-id.v1"
|
|
112
112
|
_AUTHORITY_KINDS = frozenset({"requirement", "constraint", "decision", "assumption", "task"})
|
|
113
|
+
_INVALIDATION_OPERATIONS = frozenset({"confirm_invalidation", "reject_invalidation"})
|
|
114
|
+
_NOTICE_SCHEMA = "cce.contradiction-notice.v1"
|
|
113
115
|
# Version of the statement normalization contract that feeds stable_node_id.
|
|
114
116
|
# It is deliberately separate from the extractor version: pattern behavior can
|
|
115
117
|
# change without changing identity, while an identity change survives forever
|
|
@@ -141,8 +143,8 @@ _CONTINUITY_STATE_FIELDS = (
|
|
|
141
143
|
_AUTHORIZED_ASSOCIATIONS = TRUSTED_ASSOCIATIONS
|
|
142
144
|
|
|
143
145
|
_VOLATILE_PROJECTION_KEYS = {
|
|
144
|
-
"captured_at", "checked_at", "completed_at", "composed_at", "
|
|
145
|
-
"decided_at", "fired_at", "generated_at", "packet_generation_time",
|
|
146
|
+
"captured_at", "checked_at", "completed_at", "composed_at", "confirmed_at",
|
|
147
|
+
"created_at", "decided_at", "fired_at", "generated_at", "packet_generation_time",
|
|
146
148
|
"ran_at", "started_at", "superseded_at",
|
|
147
149
|
}
|
|
148
150
|
_INTERNAL_RANDOM_ID = re.compile(
|
|
@@ -171,6 +173,15 @@ def _authority_request(request: dict) -> dict:
|
|
|
171
173
|
required.add("authority_scope")
|
|
172
174
|
optional = {"note"}
|
|
173
175
|
binding = "confirmation"
|
|
176
|
+
elif isinstance(operation, str) and operation in _INVALIDATION_OPERATIONS:
|
|
177
|
+
# The human gate of CI-005, as a canonical decision: accepting or
|
|
178
|
+
# rejecting a pending invalidation binds the exact pending revision,
|
|
179
|
+
# so a notice re-fired or already decided under another request
|
|
180
|
+
# cannot be confirmed by a stale operand set (ADR-133).
|
|
181
|
+
required = common | {"invalidation_id", "expected_invalidation_version",
|
|
182
|
+
"expected_invalidation_digest"}
|
|
183
|
+
optional = {"note"}
|
|
184
|
+
binding = "invalidation"
|
|
174
185
|
else:
|
|
175
186
|
raise ValueError("unknown authority operation")
|
|
176
187
|
if not required <= request.keys() or request.keys() - required - optional:
|
|
@@ -212,6 +223,8 @@ def _authority_request(request: dict) -> dict:
|
|
|
212
223
|
|
|
213
224
|
|
|
214
225
|
def _confirmation_id(event: dict, request: dict) -> str:
|
|
226
|
+
if request["operation"] in _INVALIDATION_OPERATIONS:
|
|
227
|
+
return request["invalidation_id"]
|
|
215
228
|
if request["operation"] != "confirm":
|
|
216
229
|
return request["confirmation_id"]
|
|
217
230
|
basis = [_AUTHORITY_SCHEMA, event["tenant_id"], event["project_id"], event["event_id"]]
|
|
@@ -2451,6 +2464,27 @@ class Engine:
|
|
|
2451
2464
|
"expected_confirmation_digest": digest_obj(state),
|
|
2452
2465
|
"authority_scope": state["authority_scope"]}
|
|
2453
2466
|
|
|
2467
|
+
def authority_invalidation(self, project_id: str, invalidation_id: str) -> dict:
|
|
2468
|
+
"""Return exact request operands for a pending invalidation; this is not
|
|
2469
|
+
an authorization operation (ADR-133).
|
|
2470
|
+
|
|
2471
|
+
The digest covers the pending revision's semantic data with processor
|
|
2472
|
+
wall clocks removed, so the same operands bind on live ingest and on
|
|
2473
|
+
canonical replay.
|
|
2474
|
+
"""
|
|
2475
|
+
self._require_project(project_id)
|
|
2476
|
+
try:
|
|
2477
|
+
inv = self.graph.get(invalidation_id, tenant_id=self.tenant_id,
|
|
2478
|
+
project_id=project_id, entity_type="invalidation")
|
|
2479
|
+
except KeyError:
|
|
2480
|
+
raise ValueError("unknown invalidation") from None
|
|
2481
|
+
if inv["status"] != "pending_confirmation":
|
|
2482
|
+
raise ValueError("invalidation is not pending confirmation")
|
|
2483
|
+
return {"invalidation_id": invalidation_id,
|
|
2484
|
+
"expected_invalidation_version": inv["version"],
|
|
2485
|
+
"expected_invalidation_digest": digest_obj(self._projection_value(
|
|
2486
|
+
inv["data"], {}, anonymize_unknown_ids=False))}
|
|
2487
|
+
|
|
2454
2488
|
def authority_is_current(self, node: dict) -> bool:
|
|
2455
2489
|
"""Canonical authority witness, not human authentication or status resolution.
|
|
2456
2490
|
|
|
@@ -2484,6 +2518,11 @@ class Engine:
|
|
|
2484
2518
|
def _validate_authority_operands(self, project_id: str, request: dict) -> dict | None:
|
|
2485
2519
|
if request["tenant_id"] != self.tenant_id or request["project_id"] != project_id:
|
|
2486
2520
|
raise PermissionError("authority request is outside the local project")
|
|
2521
|
+
if request["operation"] in _INVALIDATION_OPERATIONS:
|
|
2522
|
+
binding = self.authority_invalidation(project_id, request["invalidation_id"])
|
|
2523
|
+
if any(request[key] != value for key, value in binding.items()):
|
|
2524
|
+
raise ValueError("authority request does not bind the pending invalidation")
|
|
2525
|
+
return None
|
|
2487
2526
|
if request["operation"] == "confirm":
|
|
2488
2527
|
binding = self.authority_proposal(project_id, request["proposal_id"])
|
|
2489
2528
|
if any(request[key] != value for key, value in binding.items()):
|
|
@@ -2710,6 +2749,15 @@ class Engine:
|
|
|
2710
2749
|
def _process_authority_decision(self, event: dict, report: dict) -> None:
|
|
2711
2750
|
request = self._authority_payload(event)
|
|
2712
2751
|
project_id = event["project_id"]
|
|
2752
|
+
if request["operation"] in _INVALIDATION_OPERATIONS:
|
|
2753
|
+
self._validate_authority_operands(project_id, request)
|
|
2754
|
+
inv = self.invalidation.confirm(
|
|
2755
|
+
request["invalidation_id"], actor="owner-local",
|
|
2756
|
+
accept=request["operation"] == "confirm_invalidation",
|
|
2757
|
+
event_id=event["event_id"])
|
|
2758
|
+
report["invalidations"].append(inv["node_id"])
|
|
2759
|
+
report["created"].append({"node_id": inv["node_id"], "kind": "invalidation"})
|
|
2760
|
+
return
|
|
2713
2761
|
state = self._validate_authority_operands(project_id, request)
|
|
2714
2762
|
node_id = _confirmation_id(event, request)
|
|
2715
2763
|
criticality = None
|
|
@@ -3182,6 +3230,8 @@ class Engine:
|
|
|
3182
3230
|
if quarantined_block:
|
|
3183
3231
|
self.store.audit(actor="extractor", action="injection.quarantined",
|
|
3184
3232
|
object_id=node.id, detail=item.statement[:200])
|
|
3233
|
+
if not quarantined_block and authority_rank(authority) >= authority_rank("human_intent"):
|
|
3234
|
+
self._notice_explicit_negations(event, text, report)
|
|
3185
3235
|
if not quarantined_block:
|
|
3186
3236
|
# Read producer history rather than current mutable metadata. An
|
|
3187
3237
|
# unrelated field edit or equal restatement does not withdraw this
|
|
@@ -3231,6 +3281,56 @@ class Engine:
|
|
|
3231
3281
|
valid_to=event["recorded_at"], event_id=event_id)
|
|
3232
3282
|
return not quarantined_block
|
|
3233
3283
|
|
|
3284
|
+
def _notice_explicit_negations(self, event: dict, text: str, report: dict) -> None:
|
|
3285
|
+
"""Raise a human-gated notice when trusted prose explicitly negates an
|
|
3286
|
+
active assumption (ADR-133).
|
|
3287
|
+
|
|
3288
|
+
"The feed is NOT ordered by timestamp" or "we no longer assume the feed
|
|
3289
|
+
is ordered by timestamp", from an author the project trusts, fires a
|
|
3290
|
+
`contradictory_evidence` invalidation in `pending_confirmation`: the
|
|
3291
|
+
assumption and everything built on it are untouched until the owner
|
|
3292
|
+
confirms or rejects the notice through the authority grammar (CI-005).
|
|
3293
|
+
One notice per assumption; a rejected notice is not re-raised for the
|
|
3294
|
+
same sentence. This reads nothing for meaning: only the assumption's
|
|
3295
|
+
own wording with one negator, or a negated assumption cue, counts.
|
|
3296
|
+
"""
|
|
3297
|
+
project_id = event["project_id"]
|
|
3298
|
+
active = [node for node in self.graph.current(
|
|
3299
|
+
project_id, "assumption", status=["active", "supported"], tenant_id=self.tenant_id)
|
|
3300
|
+
if self.authority_is_current(node)]
|
|
3301
|
+
if not active:
|
|
3302
|
+
return
|
|
3303
|
+
denied = explicit_negations(
|
|
3304
|
+
text, {node["node_id"]: node["data"].get("statement", "") for node in active})
|
|
3305
|
+
if not denied:
|
|
3306
|
+
return
|
|
3307
|
+
recorded = self.graph.current(
|
|
3308
|
+
project_id, "invalidation", status=["open", "pending_confirmation", "rejected"],
|
|
3309
|
+
tenant_id=self.tenant_id)
|
|
3310
|
+
for node_id in sorted(denied):
|
|
3311
|
+
try:
|
|
3312
|
+
reason = validate_human_text(
|
|
3313
|
+
denied[node_id][:300], field="invalidation reason", max_length=300)
|
|
3314
|
+
except ValueError:
|
|
3315
|
+
# The sentence carried control characters; the notice still
|
|
3316
|
+
# stands, the quoted text does not.
|
|
3317
|
+
reason = "explicit negation of the assumption (sentence withheld)"
|
|
3318
|
+
if any(inv["data"].get("trigger_type") == "contradictory_evidence"
|
|
3319
|
+
and inv["data"].get("target_node_id") == node_id
|
|
3320
|
+
and (inv["status"] != "rejected" or inv["data"].get("reason") == reason)
|
|
3321
|
+
for inv in recorded):
|
|
3322
|
+
continue
|
|
3323
|
+
# The identity derives from the ingest event and the target, so a
|
|
3324
|
+
# later canonical decision that names it replays onto the same node.
|
|
3325
|
+
notice_id = "inv_" + digest_obj(
|
|
3326
|
+
[_NOTICE_SCHEMA, self.tenant_id, project_id, event["event_id"], node_id])[7:31]
|
|
3327
|
+
inv = self.invalidation.fire(
|
|
3328
|
+
tenant_id=self.tenant_id, project_id=project_id, target_node_id=node_id,
|
|
3329
|
+
trigger_type="contradictory_evidence", trigger_confidence=0.9,
|
|
3330
|
+
reason=reason, event_id=event["event_id"], requires_human=True,
|
|
3331
|
+
invalidation_id=notice_id)
|
|
3332
|
+
report["invalidations"].append(inv["node_id"])
|
|
3333
|
+
|
|
3234
3334
|
@staticmethod
|
|
3235
3335
|
def _initial_status(entity_type: str, item, existing) -> str | None:
|
|
3236
3336
|
if existing is not None:
|
|
@@ -4554,8 +4654,23 @@ class Engine:
|
|
|
4554
4654
|
+ "; ".join(shape_errors))
|
|
4555
4655
|
check = verify_envelope(proof, self.signer)
|
|
4556
4656
|
if not check["valid"]:
|
|
4557
|
-
|
|
4558
|
-
|
|
4657
|
+
# Name the check that failed (PA-002). One undifferentiated
|
|
4658
|
+
# message made an envelope under an unregistered key, or one
|
|
4659
|
+
# whose recorded status no longer matches its contents, read
|
|
4660
|
+
# exactly like an edited body, so the operator could not tell
|
|
4661
|
+
# which of the four to repair. The gate phrase itself is
|
|
4662
|
+
# unchanged: tests/test_instrument_validation.py keys on it.
|
|
4663
|
+
failed = [name for name, ok in (
|
|
4664
|
+
("digest", check.get("digest_ok")),
|
|
4665
|
+
("signature", check.get("signature_ok")),
|
|
4666
|
+
("authenticity", check.get("authentic")),
|
|
4667
|
+
("sufficiency", check.get("sufficiency_ok", True)),
|
|
4668
|
+
) if not ok]
|
|
4669
|
+
detail = check.get("reason")
|
|
4670
|
+
raise PermissionError(
|
|
4671
|
+
"completion rejected: proof envelope invalid or tampered"
|
|
4672
|
+
f" (failed: {', '.join(failed) or 'unclassified'})"
|
|
4673
|
+
+ (f": {detail}" if detail else ""))
|
|
4559
4674
|
if proof.get("status") != "verified":
|
|
4560
4675
|
raise PermissionError(
|
|
4561
4676
|
f"completion rejected: proof status is {proof.get('status')!r},"
|
|
@@ -31,7 +31,7 @@ from .core import canonical_json, strict_json_loads
|
|
|
31
31
|
from .ontology import AUTHORITY_RANK, authority_rank
|
|
32
32
|
|
|
33
33
|
EXTRACTOR_NAME = "cce-deterministic"
|
|
34
|
-
EXTRACTOR_VERSION = "1.
|
|
34
|
+
EXTRACTOR_VERSION = "1.5.0"
|
|
35
35
|
|
|
36
36
|
|
|
37
37
|
@dataclass
|
|
@@ -92,6 +92,17 @@ _CLAUSE_TAIL = r"(?:[^.\n]|\.(?!\s|\Z)|" + _SOFT_WRAP + r")"
|
|
|
92
92
|
# statement is worse than dropping one: nothing downstream can tell.
|
|
93
93
|
_GAP = r"(?:[ \t]|" + _SOFT_WRAP + r")+"
|
|
94
94
|
|
|
95
|
+
# A cue that is itself negated denies the assumption instead of making it.
|
|
96
|
+
# "We no longer assume X" and "nothing relies on X" extracted the same positive
|
|
97
|
+
# proposal as "We assume X", so a sentence written to retire an assumption
|
|
98
|
+
# could, once confirmed, re-assert it. The negator has to stand immediately
|
|
99
|
+
# before the cue (allowing only a pronoun or "to" in between); a `not`
|
|
100
|
+
# elsewhere in the sentence is not read as denial (ADR-132).
|
|
101
|
+
_NEGATED_CUE = re.compile(
|
|
102
|
+
r"(?:\bnot|n['\u2019]t|\bcannot|\bnever|\bno[ \t]+longer|\bstop(?:s|ped)?|"
|
|
103
|
+
r"\bwithout|\bnothing|\bnobody|\bno[ \t]+one)"
|
|
104
|
+
r"(?:[ \t]+(?:we|you|they|it|to|be))*[ \t]*\Z", re.I)
|
|
105
|
+
|
|
95
106
|
# Patterns: (kind, regex, base_confidence)
|
|
96
107
|
_PATTERNS: list[tuple[str, re.Pattern, float]] = [
|
|
97
108
|
("assumption", re.compile(
|
|
@@ -233,6 +244,10 @@ class DeterministicExtractor:
|
|
|
233
244
|
if _boundary_has_mark_or_zwj_continuation(text, raw_after):
|
|
234
245
|
result.abstained += 1
|
|
235
246
|
continue
|
|
247
|
+
if kind == "assumption" and _NEGATED_CUE.search(
|
|
248
|
+
prose[max(0, m.start() - 40):m.start()]):
|
|
249
|
+
result.abstained += 1
|
|
250
|
+
continue
|
|
236
251
|
statement = _clean(
|
|
237
252
|
text[offsets[m.start("s")]:offsets[m.end("s")]])
|
|
238
253
|
if not _plausible(statement):
|
|
@@ -364,6 +379,90 @@ def normalize_statement(statement: str) -> str:
|
|
|
364
379
|
return re.sub(r"\s+", " ", s).strip()
|
|
365
380
|
|
|
366
381
|
|
|
382
|
+
# An explicit negation of a statement: the statement itself with exactly one
|
|
383
|
+
# negator inserted after an auxiliary ("the feed is NOT ordered"), or a negated
|
|
384
|
+
# assumption cue ("we no longer assume the feed is ordered"), or the assumption
|
|
385
|
+
# named and called wrong. This is the same closed lexical control as ADR-119,
|
|
386
|
+
# aimed at assumptions: it reads nothing for meaning, so "the feed arrives
|
|
387
|
+
# unordered" is outside it and stays outside it on purpose (ADR-133).
|
|
388
|
+
_SENTENCE_END = re.compile(r"(?<=[.!?])\s+|\n+")
|
|
389
|
+
_AUXILIARY_NEGATION = re.compile(
|
|
390
|
+
r"\b(is|are|was|were|does|do|did|can|will|has|have|had|should|must|shall|would|could)"
|
|
391
|
+
r"(?:\s+not|n['\u2019]t|\s+never|\s+no\s+longer)\b", re.I)
|
|
392
|
+
_IRREGULAR_NEGATION = (
|
|
393
|
+
(re.compile(r"\bcannot\b", re.I), "can"),
|
|
394
|
+
(re.compile(r"\bwon['\u2019]t\b", re.I), "will"),
|
|
395
|
+
)
|
|
396
|
+
_NEGATED_CUE_CLAUSE = re.compile(
|
|
397
|
+
r"(?:\bnot|n['\u2019]t|\bcannot|\bnever|\bno\s+longer|\bstop(?:s|ped)?|\bwithout|"
|
|
398
|
+
r"\bnothing|\bnobody|\bno\s+one)(?:\s+(?:we|you|they|it|to|be))*\s+"
|
|
399
|
+
r"(?:assum(?:e|es|ing|ption)(?:\s+that)?|rel(?:y|ies|ying)\s+on(?:\s+the\s+fact\s+that)?|"
|
|
400
|
+
r"depend(?:s|ing)?\s+on(?:\s+the\s+fact\s+that)?)\s+(?P<s>.+)", re.I)
|
|
401
|
+
_ASSUMPTION_CALLED_WRONG = re.compile(
|
|
402
|
+
r"\bthe\s+assumption\s+that\s+(?P<s>.+?)\s+(?:is|was)\s+"
|
|
403
|
+
r"(?:wrong|false|incorrect|invalid|no\s+longer\s+(?:true|valid))\b", re.I)
|
|
404
|
+
|
|
405
|
+
|
|
406
|
+
def _denial_candidates(sentence: str) -> list[str]:
|
|
407
|
+
"""Statements a single sentence explicitly denies, before normalization."""
|
|
408
|
+
found = []
|
|
409
|
+
cue = _NEGATED_CUE_CLAUSE.search(sentence)
|
|
410
|
+
if cue:
|
|
411
|
+
found.append(cue.group("s"))
|
|
412
|
+
wrong = _ASSUMPTION_CALLED_WRONG.search(sentence)
|
|
413
|
+
if wrong:
|
|
414
|
+
found.append(wrong.group("s"))
|
|
415
|
+
positive, count = _AUXILIARY_NEGATION.subn(r"\1", sentence, count=1)
|
|
416
|
+
if count == 0:
|
|
417
|
+
for pattern, auxiliary in _IRREGULAR_NEGATION:
|
|
418
|
+
positive, count = pattern.subn(auxiliary, sentence, count=1)
|
|
419
|
+
if count:
|
|
420
|
+
break
|
|
421
|
+
if count == 1 and not _AUXILIARY_NEGATION.search(positive):
|
|
422
|
+
found.append(positive)
|
|
423
|
+
return found
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
def explicit_negations(text: str, statements: dict[str, str]) -> dict[str, str]:
|
|
427
|
+
"""Map each statement key that `text` explicitly denies to the denying
|
|
428
|
+
sentence (ADR-133).
|
|
429
|
+
|
|
430
|
+
`statements` maps an opaque key to a statement, normally an active
|
|
431
|
+
assumption's node id to its statement. Only prose is read: fenced code,
|
|
432
|
+
quotes and HTML comments are masked exactly as for extraction, and format
|
|
433
|
+
controls are removed before matching. Comparison uses the extractor's
|
|
434
|
+
normalized statement identity, so case, punctuation and stop words do not
|
|
435
|
+
matter but every content word does.
|
|
436
|
+
"""
|
|
437
|
+
if not text or not statements:
|
|
438
|
+
return {}
|
|
439
|
+
wanted = {}
|
|
440
|
+
for key, statement in statements.items():
|
|
441
|
+
normalized = normalize_statement(statement or "")
|
|
442
|
+
if normalized:
|
|
443
|
+
wanted.setdefault(normalized, []).append(key)
|
|
444
|
+
if not wanted:
|
|
445
|
+
return {}
|
|
446
|
+
visible, _ = _without_invisibles(text)
|
|
447
|
+
denied: dict[str, str] = {}
|
|
448
|
+
for raw in _SENTENCE_END.split(_prose_only(visible)):
|
|
449
|
+
sentence = re.sub(r"\s+", " ", raw).strip().rstrip(".!?;:,")
|
|
450
|
+
if not sentence:
|
|
451
|
+
continue
|
|
452
|
+
# "Correction: the feed is not ordered" and "Checked the producer today:
|
|
453
|
+
# the feed is not ordered" carry one leading label or clause ending in a
|
|
454
|
+
# colon; it is not part of the statement being denied. The clause is
|
|
455
|
+
# bounded so a colon deep inside unrelated prose cannot manufacture one.
|
|
456
|
+
unlabelled = re.sub(r"^[^:]{1,80}:\s+", "", sentence)
|
|
457
|
+
candidates = _denial_candidates(sentence)
|
|
458
|
+
if unlabelled != sentence:
|
|
459
|
+
candidates += _denial_candidates(unlabelled)
|
|
460
|
+
for candidate in candidates:
|
|
461
|
+
for key in wanted.get(normalize_statement(candidate.rstrip(".!?;:,")), ()):
|
|
462
|
+
denied.setdefault(key, sentence)
|
|
463
|
+
return denied
|
|
464
|
+
|
|
465
|
+
|
|
367
466
|
# Regions of a comment that are not the author asserting something. A fenced
|
|
368
467
|
# block is code, and code comments are full of modal verbs; a blockquote is
|
|
369
468
|
# someone else's words, frequently quoted in order to disagree with them; an
|