From 85e91dfb6919ed8e02d277f482a5fd7102cf05bc Mon Sep 17 00:00:00 2001 From: docushell-admin Date: Wed, 24 Jun 2026 15:34:18 +0530 Subject: [PATCH] Add evidence anchor contract guard Signed-off-by: docushell-admin --- .github/scripts/test_ci_workflow.py | 9 + .../test_evidence_anchor_v1_contract.py | 254 ++++++++++++++++++ .../test_milestone_d_internal_contracts.py | 3 + .github/workflows/ci.yml | 2 + CHANGELOG.md | 1 + Makefile | 11 +- crates/ethos-cli/tests/evidence_anchor.rs | 9 +- docs/evidence-anchor-v1-contract.md | 64 +++++ docs/execution-status.md | 1 + docs/roadmap.md | 9 + .../verify/evidence_anchor_v1_contract.json | 65 +++++ schemas/README.md | 9 + ...ethos-evidence-anchor-contract.schema.json | 95 +++++++ schemas/validate_examples.py | 3 + 14 files changed, 533 insertions(+), 2 deletions(-) create mode 100644 .github/scripts/test_evidence_anchor_v1_contract.py create mode 100644 docs/evidence-anchor-v1-contract.md create mode 100644 examples/verify/evidence_anchor_v1_contract.json create mode 100644 schemas/ethos-evidence-anchor-contract.schema.json diff --git a/.github/scripts/test_ci_workflow.py b/.github/scripts/test_ci_workflow.py index 7c608bcc..38387183 100644 --- a/.github/scripts/test_ci_workflow.py +++ b/.github/scripts/test_ci_workflow.py @@ -94,8 +94,17 @@ def test_ci_workflow_guard_is_run_by_ci(self) -> None: ) self.assertLess( text.index("python3 .github/scripts/test_h2_source_snapshot_closeout.py"), + text.index("python3 .github/scripts/test_evidence_anchor_v1_contract.py"), + ) + self.assertLess( + text.index("python3 .github/scripts/test_evidence_anchor_v1_contract.py"), text.index("python3 .github/scripts/test_milestone_d_internal_contracts.py"), ) + self.assertIn("python3 .github/scripts/test_evidence_anchor_v1_contract.py", text) + self.assertEqual( + 1, + text.count("python3 .github/scripts/test_evidence_anchor_v1_contract.py"), + ) self.assertIn("python3 .github/scripts/test_milestone_d_internal_contracts.py", text) self.assertIn("python3 .github/scripts/test_milestone_b_closeout_record.py", text) self.assertIn("python3 .github/scripts/test_milestone_c_closeout_record.py", text) diff --git a/.github/scripts/test_evidence_anchor_v1_contract.py b/.github/scripts/test_evidence_anchor_v1_contract.py new file mode 100644 index 00000000..bdc7b7c5 --- /dev/null +++ b/.github/scripts/test_evidence_anchor_v1_contract.py @@ -0,0 +1,254 @@ +#!/usr/bin/env python3 +# +# Copyright 2026 The Ethos maintainers +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. +# + +from __future__ import annotations + +import ast +import json +import re +import unittest +from pathlib import Path + +from jsonschema import Draft202012Validator +from makefile_guard import makefile_text, target_block + + +ROOT = Path(__file__).resolve().parents[2] +CONTRACT = ROOT / "docs/evidence-anchor-v1-contract.md" +CONTRACT_INVENTORY = ROOT / "examples/verify/evidence_anchor_v1_contract.json" +CONTRACT_SCHEMA = ROOT / "schemas/ethos-evidence-anchor-contract.schema.json" +REQUEST_SCHEMA = ROOT / "schemas/ethos-evidence-anchor-request.schema.json" +REPORT_SCHEMA = ROOT / "schemas/ethos-evidence-anchor-report.schema.json" +SCHEMAS_README = ROOT / "schemas/README.md" +VALIDATE_EXAMPLES = ROOT / "schemas/validate_examples.py" +ROADMAP = ROOT / "docs/roadmap.md" +EXECUTION_STATUS = ROOT / "docs/execution-status.md" +README = ROOT / "README.md" +EVIDENCE_TYPES = ROOT / "crates/ethos-core/src/evidence_anchor.rs" +CLI_TESTS = ROOT / "crates/ethos-cli/tests/evidence_anchor.rs" + +EXPECTED_TARGET_COMMANDS = [ + "cargo test --locked -p ethos-cli --test evidence_anchor", + "cargo test --locked -p ethos-grounding-opendataloader-json", + "$(PYTHON) schemas/validate_examples.py", + "$(PYTHON) .github/scripts/test_execution_status.py", + "$(PYTHON) .github/scripts/test_roadmap_status.py", + "$(PYTHON) .github/scripts/test_evidence_anchor_v1_contract.py", + "git diff --check", +] +EXPECTED_ANCHOR_STATUSES = [ + "bound", + "mismatch", + "not_found", + "stale_fingerprint", + "capability_limited", + "unsupported_evidence_kind", +] +EXPECTED_BLOCKERS = [ + "semantic answer verification", + "multi-source joins", + "crop rendering or source-region image generation", + "hosted API or service surfaces", + "DocuShell-specific behavior", + "production positioning", + "benchmark, speed, footprint, parser-quality, or table-quality claims", +] + + +def load_json(path: Path) -> dict: + return json.loads(path.read_text(encoding="utf-8")) + + +def schema_errors(schema: dict, instance: dict) -> list: + return sorted( + Draft202012Validator(schema).iter_errors(instance), + key=lambda error: list(error.absolute_path), + ) + + +def rust_test_names(path: Path) -> set[str]: + text = path.read_text(encoding="utf-8") + return set(re.findall(r"(?m)^\s*fn ([a-z][a-z0-9_]*)\(", text)) + + +def snake_case(name: str) -> str: + return re.sub(r"(? list[str]: + text = EVIDENCE_TYPES.read_text(encoding="utf-8") + match = re.search(r"pub enum AnchorStatus \{(?P.*?)\n\}", text, re.DOTALL) + if match is None: + raise AssertionError("missing AnchorStatus enum") + variants = re.findall(r"(?m)^\s*([A-Z][A-Za-z0-9]*)\b", match.group("body")) + return [snake_case(variant) for variant in variants] + + +def contract_explicit_blockers() -> list[str]: + lines = CONTRACT.read_text(encoding="utf-8").splitlines() + try: + start = lines.index("## Explicit Blockers For This Slice") + 1 + except ValueError as exc: + raise AssertionError("missing explicit blockers section") from exc + + blockers: list[str] = [] + for line in lines[start:]: + if line.startswith("- "): + blockers.append(line.removeprefix("- ").rstrip(";.")) + elif blockers and line.strip(): + break + return blockers + + +def path_expr_to_repo_path(node: ast.AST) -> str: + roots = { + "ROOT": "", + "SCHEMAS": "schemas", + "EXAMPLES": "schemas/examples", + } + if isinstance(node, ast.Name) and node.id in roots: + return roots[node.id] + if isinstance(node, ast.Constant) and isinstance(node.value, str): + return node.value + if isinstance(node, ast.BinOp) and isinstance(node.op, ast.Div): + left = path_expr_to_repo_path(node.left) + right = path_expr_to_repo_path(node.right) + return f"{left}/{right}" if left else right + raise AssertionError(f"unsupported validate_examples path expression: {ast.dump(node)}") + + +def schema_example_validation_pairs() -> set[tuple[str, str]]: + tree = ast.parse(VALIDATE_EXAMPLES.read_text(encoding="utf-8")) + pairs_node = next( + node.value + for node in tree.body + if isinstance(node, ast.Assign) + and any(isinstance(target, ast.Name) and target.id == "PAIRS" for target in node.targets) + ) + + pairs = set() + for pair_node in pairs_node.elts: + schema_node, example_nodes = pair_node.elts + schema_path = f"schemas/{schema_node.value}" + for example_node in example_nodes.elts: + pairs.add((schema_path, path_expr_to_repo_path(example_node))) + return pairs + + +class EvidenceAnchorV1ContractTests(unittest.TestCase): + def test_target_is_declared_phony(self) -> None: + text = makefile_text() + + self.assertIn(".PHONY:", text) + self.assertIn("evidence-anchor-v1-contract", text) + + def test_target_composes_guard_commands(self) -> None: + commands = [line.strip() for line in target_block("evidence-anchor-v1-contract").splitlines() if line.strip()] + + self.assertEqual(EXPECTED_TARGET_COMMANDS, commands) + + def test_target_stays_contract_scoped(self) -> None: + block = target_block("evidence-anchor-v1-contract") + + for out_of_scope in [ + "release-candidate-prep", + "milestone-e-prep", + "release-hygiene", + "npm", + "pypi", + "cargo publish", + "gh release", + "verify-rendered-crops", + "compare-rendered-crops", + ]: + self.assertNotIn(out_of_scope, block) + + def test_contract_inventory_schema_validates_inventory(self) -> None: + schema = load_json(CONTRACT_SCHEMA) + inventory = load_json(CONTRACT_INVENTORY) + + Draft202012Validator.check_schema(schema) + self.assertEqual([], schema_errors(schema, inventory)) + + def test_contract_inventory_binds_existing_files_and_tests(self) -> None: + inventory = load_json(CONTRACT_INVENTORY) + + self.assertEqual(inventory["schema_version"], 1) + self.assertEqual(inventory["contract"], "evidence_anchor.v1") + self.assertEqual(inventory["status"], "source-only-public-beta-evaluation") + self.assertEqual(inventory["carrier"], "ethos evidence anchor") + for key in ["request_schema", "report_schema"]: + self.assertTrue((ROOT / inventory[key]).is_file(), key) + for path in inventory["implementation"]: + self.assertTrue((ROOT / path).is_file(), path) + self.assertLessEqual(set(inventory["cli_tests"]), rust_test_names(CLI_TESTS)) + + def test_schema_registry_validates_contract_inventory(self) -> None: + pair = ( + "schemas/ethos-evidence-anchor-contract.schema.json", + "examples/verify/evidence_anchor_v1_contract.json", + ) + + self.assertIn(pair, schema_example_validation_pairs()) + + def test_schema_readme_registers_contract_inventory(self) -> None: + text = SCHEMAS_README.read_text(encoding="utf-8") + + self.assertIn("`ethos-evidence-anchor-contract.schema.json`", text) + self.assertIn("`docs/evidence-anchor-v1-contract.md`", text) + self.assertIn("`examples/verify/evidence_anchor_v1_contract.json`", text) + + def test_status_docs_link_contract_without_expanding_posture(self) -> None: + for path in [ROADMAP, EXECUTION_STATUS]: + text = path.read_text(encoding="utf-8") + self.assertIn("evidence-anchor-v1-contract.md", text, path) + self.assertIn("make evidence-anchor-v1-contract", text, path) + + readme = README.read_text(encoding="utf-8") + self.assertIn("Status: public beta evaluation.", readme) + self.assertNotIn("production-ready", readme.lower()) + + def test_anchor_statuses_match_inventory(self) -> None: + inventory = load_json(CONTRACT_INVENTORY) + + self.assertEqual(EXPECTED_ANCHOR_STATUSES, inventory["supported_anchor_statuses"]) + self.assertEqual(EXPECTED_ANCHOR_STATUSES, anchor_status_variants()) + + def test_contract_records_current_boundaries(self) -> None: + text = re.sub(r"\s+", " ", CONTRACT.read_text(encoding="utf-8")) + inventory = load_json(CONTRACT_INVENTORY) + + for required in [ + "`ethos.evidence_anchor_request.v1`", + "`ethos.evidence_anchor_report.v1`", + "native Ethos JSON and OpenDataLoader-style JSON", + "non-bound per-ref outcomes remain report data and exit `0`", + "usage errors remain exit `2`", + "mismatch takes precedence over capability-limited", + ]: + self.assertIn(required, text) + self.assertEqual(EXPECTED_BLOCKERS, contract_explicit_blockers()) + self.assertEqual(EXPECTED_BLOCKERS, inventory["explicit_blockers"]) + + def test_request_and_report_schemas_are_strict_objects(self) -> None: + for path in [REQUEST_SCHEMA, REPORT_SCHEMA, CONTRACT_SCHEMA]: + schema = load_json(path) + self.assertEqual(False, schema.get("additionalProperties")) + + +if __name__ == "__main__": + unittest.main() diff --git a/.github/scripts/test_milestone_d_internal_contracts.py b/.github/scripts/test_milestone_d_internal_contracts.py index c8ecd34e..310638f7 100644 --- a/.github/scripts/test_milestone_d_internal_contracts.py +++ b/.github/scripts/test_milestone_d_internal_contracts.py @@ -330,6 +330,7 @@ def schemas_readme_contract_table_entries() -> dict[str, str]: schema_name: description for schema_name, description in schemas_readme_table_entries().items() if re.fullmatch(r"ethos-.+-contract\.schema\.json", schema_name) + and description.startswith("Milestone D ") } @@ -376,6 +377,7 @@ def discovered_d_contract_schemas() -> list[str]: return sorted( str(path.relative_to(ROOT)) for path in (ROOT / "schemas").glob("ethos-*-contract.schema.json") + if "Milestone D" in path.read_text(encoding="utf-8") ) @@ -401,6 +403,7 @@ def discovered_d_contract_inventories() -> list[str]: str(path.relative_to(ROOT)) for root in roots for path in root.glob("*_v1_contract.json") + if load_json(str(path.relative_to(ROOT))).get("status") == "source-only-pre-alpha" ) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 66b36ef6..1d92f7f6 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -84,6 +84,8 @@ jobs: run: python3 .github/scripts/test_h2_source_snapshot_candidate_evidence.py - name: H2 source-snapshot closeout tests run: python3 .github/scripts/test_h2_source_snapshot_closeout.py + - name: Evidence anchor v1 contract guard tests + run: python3 .github/scripts/test_evidence_anchor_v1_contract.py - name: Milestone D internal contract target tests run: python3 .github/scripts/test_milestone_d_internal_contracts.py - name: Milestone B closeout validation record tests diff --git a/CHANGELOG.md b/CHANGELOG.md index 65b138b4..64e68d96 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,6 +2,7 @@ ## Unreleased +- boundary-exception: add an `evidence_anchor` v1 guard target, CI guard step, and schema-bound inventory for the merged source-only command; no hosted, production, Windows, bundled PDFium, benchmark, parser-quality, table-quality, or release-posture boundary change. - boundary-exception: add source-only `ethos evidence anchor` schema and CLI surface for deterministic evidence refs; no hosted, production, Windows, bundled PDFium, benchmark, parser-quality, table-quality, or release-posture boundary change. - boundary-exception: refresh patch `0.1.1` execution status for published evaluation surfaces while retaining hosted, production, Windows, bundled PDFium, benchmark, `ethos-doc`, and `ethos-rag` blockers. - boundary-exception: document bounded patch `0.1.1` public install paths for published evaluation surfaces while retaining hosted, production, Windows, bundled PDFium, benchmark, `ethos-doc`, and `ethos-rag` blockers. diff --git a/Makefile b/Makefile index d8f98b6b..71ac3235 100644 --- a/Makefile +++ b/Makefile @@ -13,7 +13,7 @@ COMPARE_RENDERED_CROPS_LEFT ?= $(VERIFY_RENDERED_CROPS_OUT)/run1 COMPARE_RENDERED_CROPS_RIGHT ?= $(VERIFY_RENDERED_CROPS_OUT)/run2 LAYOUT_EVALUATOR_OUT ?= $(ROOT)/target/layout-evaluator-alpha -.PHONY: verify-alpha verify-alpha-tree rag-chunk-alpha security-report-alpha milestone-d-verify-citations-contract milestone-d-crop-element-contract milestone-d-sandbox-subprocess-contract milestone-d-internal-contracts milestone-e-prep release-candidate-prep light-check package-publication-dry-run-smoke verify-rendered-crops compare-rendered-crops layout-evaluator-alpha python-surface-test milestone-b-internal-checks milestone-c-internal-checks release-hygiene release-advisory third-party-license-manifest release-notice-draft +.PHONY: verify-alpha verify-alpha-tree rag-chunk-alpha security-report-alpha evidence-anchor-v1-contract milestone-d-verify-citations-contract milestone-d-crop-element-contract milestone-d-sandbox-subprocess-contract milestone-d-internal-contracts milestone-e-prep release-candidate-prep light-check package-publication-dry-run-smoke verify-rendered-crops compare-rendered-crops layout-evaluator-alpha python-surface-test milestone-b-internal-checks milestone-c-internal-checks release-hygiene release-advisory third-party-license-manifest release-notice-draft .PHONY: milestone-d-capability-downgrade-contract .PHONY: milestone-d-opendataloader-adapter-shape-contract .PHONY: milestone-d-grounding-source-contract @@ -52,6 +52,15 @@ security-report-alpha: $(PYTHON) .github/scripts/test_security_report_alpha.py git diff --check +evidence-anchor-v1-contract: + cargo test --locked -p ethos-cli --test evidence_anchor + cargo test --locked -p ethos-grounding-opendataloader-json + $(PYTHON) schemas/validate_examples.py + $(PYTHON) .github/scripts/test_execution_status.py + $(PYTHON) .github/scripts/test_roadmap_status.py + $(PYTHON) .github/scripts/test_evidence_anchor_v1_contract.py + git diff --check + milestone-d-verify-citations-contract: cargo test --locked -p ethos-cli --test verify $(PYTHON) schemas/validate_examples.py diff --git a/crates/ethos-cli/tests/evidence_anchor.rs b/crates/ethos-cli/tests/evidence_anchor.rs index a8dea1df..dc0f491e 100644 --- a/crates/ethos-cli/tests/evidence_anchor.rs +++ b/crates/ethos-cli/tests/evidence_anchor.rs @@ -16,10 +16,13 @@ use std::path::{Path, PathBuf}; use std::process::{Command, Output}; +use std::sync::atomic::{AtomicU64, Ordering}; use std::time::{SystemTime, UNIX_EPOCH}; use serde_json::Value; +static TEMP_COUNTER: AtomicU64 = AtomicU64::new(0); + fn ethos_bin() -> &'static str { env!("CARGO_BIN_EXE_ethos") } @@ -61,7 +64,11 @@ fn temp_json(name: &str, value: Value) -> PathBuf { .duration_since(UNIX_EPOCH) .expect("clock after unix epoch") .as_nanos(); - let path = std::env::temp_dir().join(format!("ethos-evidence-anchor-{name}-{nanos}.json")); + let pid = std::process::id(); + let counter = TEMP_COUNTER.fetch_add(1, Ordering::Relaxed); + let path = std::env::temp_dir().join(format!( + "ethos-evidence-anchor-{name}-{pid}-{nanos}-{counter}.json" + )); std::fs::write( &path, serde_json::to_string(&value).expect("temp JSON serializes"), diff --git a/docs/evidence-anchor-v1-contract.md b/docs/evidence-anchor-v1-contract.md new file mode 100644 index 00000000..77d190da --- /dev/null +++ b/docs/evidence-anchor-v1-contract.md @@ -0,0 +1,64 @@ +# Evidence Anchor V1 Contract Guard + +Status: source-only public beta evaluation guard for the current `ethos evidence anchor` surface. + +This note defines the narrow post-merge guard for `evidence_anchor` v1. It does not change the +approved public beta/evaluation posture, and it does not approve any hosted, production, benchmark, +parser-quality, table-quality, Windows, bundled-PDFium, `ethos-doc`, or `ethos-rag` surface. + +The current executable carrier is the `ethos evidence anchor` CLI command. It consumes a single +source artifact plus caller-provided evidence refs and emits a deterministic evidence anchor +report. The command remains parser- and app-agnostic: native Ethos JSON and OpenDataLoader-style +JSON are current grounding inputs, while downstream applications decide what to do with the +report. + +## Contract Surface + +`evidence_anchor` v1 covers: + +- request and report envelope identity through `ethos.evidence_anchor_request.v1` and + `ethos.evidence_anchor_report.v1`; +- caller-provided evidence refs with page, text, text-region, and table-cell locator checks; +- native Ethos JSON and OpenDataLoader-style JSON grounding inputs; +- deterministic per-ref outcomes: `bound`, `mismatch`, `not_found`, `stale_fingerprint`, + `capability_limited`, and `unsupported_evidence_kind`; +- usage errors for malformed request shape, missing required locator/text inputs, unknown + grounding modes, and unsupported source artifact shape. + +The executable inventory is `examples/verify/evidence_anchor_v1_contract.json`. It binds the +current command surface to schema examples, focused CLI tests, the OpenDataLoader adapter test +suite, and status/roadmap guard scripts. + +## Validation Target + +- `make evidence-anchor-v1-contract PYTHON=/bin/python` + +The target runs the focused evidence-anchor CLI tests, the OpenDataLoader adapter tests, +schema/example validation, status guards, roadmap guards, this contract guard, and whitespace diff +checks. + +## Boundaries Locked By This Guard + +- one command: `ethos evidence anchor`; +- one source artifact per invocation; +- caller-provided evidence refs in, deterministic anchor report out; +- native Ethos JSON and OpenDataLoader-style JSON only; +- non-bound per-ref outcomes remain report data and exit `0`; +- request/schema/source-shape usage errors remain exit `2`; +- table-cell expected text uses normalized equality, not substring matching; +- mismatch takes precedence over capability-limited when a checked required axis fails. + +## Explicit Blockers For This Slice + +This `evidence_anchor` guard does not add: + +- semantic answer verification; +- multi-source joins; +- crop rendering or source-region image generation; +- hosted API or service surfaces; +- DocuShell-specific behavior; +- production positioning; +- benchmark, speed, footprint, parser-quality, or table-quality claims. + +Until those blockers are explicitly handled, public language remains limited to public beta +evaluation of deterministic evidence anchoring over the currently approved surfaces. diff --git a/docs/execution-status.md b/docs/execution-status.md index ba4214de..f62df7ed 100644 --- a/docs/execution-status.md +++ b/docs/execution-status.md @@ -125,6 +125,7 @@ The committed implementation now includes: - Schema/example/profile validation is green through `schemas/validate_examples.py` using `jsonschema` draft 2020-12 validation, including the crop descriptor artifact contract plus referential-integrity and bbox sanity checks outside JSON Schema. Fixture validation also binds internal font-isolation PDFs to committed manifest hashes. - `ethos verify` now produces non-empty quote, value, presence, and table-cell verification checks over native Ethos document JSON and synthetic OpenDataLoader-style JSON through `--grounding opendataloader-json`; it also verifies quote/value/presence citations over pinned real OpenDataLoader 2.4.7 JSON, including grounded and ungrounded cases, maps explicit real OpenDataLoader-style row/cell structures to table-cell grounding, and normalizes conservative real-style text/child-container aliases when page/bbox/text data remains explicit. Citation/config inputs are rejected when they drift outside the closed schemas. The public demo harness covers grounded, ungrounded, split-quote, not-found, stale-fingerprint, unsupported non-v1 claim, capability-limited, malformed-citation, malformed OpenDataLoader-style input, and summary-format reject paths. - Verification semantics are now trust-honest at alpha scope: quote containment is explicitly labeled, value/table-cell checks require normalized equality, fingerprint-pinned citations fail closed when source fingerprints are unavailable, and structured capability limits explain why a run is downgraded. +- `ethos evidence anchor` now has a post-merge [`evidence_anchor` v1 guard](evidence-anchor-v1-contract.md) over the deterministic source-tracing command, request/report schemas, focused CLI tests, native Ethos JSON and OpenDataLoader-style grounding inputs, and explicit non-goals. Focused validation is `make evidence-anchor-v1-contract PYTHON=/bin/python`; PR CI also runs `.github/scripts/test_evidence_anchor_v1_contract.py` so the guard's richer drift checks are enforced automatically. This guard does not expand public beta/evaluation posture, hosted surfaces, production positioning, semantic answer verification, or benchmark/parser/table-quality claims. - `make verify-alpha` is the current alpha trust-loop command: it checks native examples, split-quote evidence matching, unsupported non-v1 claim reporting, synthetic OpenDataLoader-style examples, pinned real OpenDataLoader grounded/ungrounded examples, schema validation, verify-alpha case inventory coverage, usage diagnostics for malformed citations and malformed OpenDataLoader-style structures, byte-identical repeated verification reports, byte-identical native crop descriptors, summary diagnostics for an ungrounded native case, and foreign fixture manifest hash binding. `make milestone-b-internal-checks` composes the current internal Milestone B validation path across fixture validation, font-policy profile checks, verify alpha, layout evaluator, Python surface tests, and policy gates; CI has a static guard for that target's command wiring. - The Python surface under `python/ethos_pdf` shells out to a caller-provided local `ethos` CLI binary for `ethos doc parse` JSON, Markdown, text output, and source-bound `ethos crop_element` JSON/artifact arguments, and has stdlib unit tests that use a fake local command. The published `ethos-pdf==0.1.1` wheel exposes this bounded wrapper for evaluation; it does not bundle the CLI or PDFium. - Native Ethos verification can emit deterministic, schema-backed crop descriptor JSON artifacts through `--crop-dir`; these bind `document_fingerprint`, page, bbox, and check ids. Native `crop_ref` filenames are logical evidence references derived from document fingerprint, check id, and page, while descriptors still record the exact observed bbox. When `--crop-source-pdf` is supplied, the CLI validates source-PDF fingerprint binding and emits PNG crop artifacts whose filenames, byte hashes, dimensions, and source fingerprint are bound from the descriptor. `make verify-rendered-crops` checks same-host repeated-run stability for the rendered artifact path, and `make compare-rendered-crops` classifies two rendered-crop runs by separating logical evidence identity from rendered artifact byte equality. Cross-platform rendered image determinism is not claimed; the 2026-06-14 macOS arm64 vs Linux x64 validation record in `docs/validation/rendered-crops-2026-06-14.md` preserved document fingerprint and `payload_sha256` but failed rendered artifact byte equality because the evidence bbox differed slightly across platforms. diff --git a/docs/roadmap.md b/docs/roadmap.md index 78a0a306..3807f35c 100644 --- a/docs/roadmap.md +++ b/docs/roadmap.md @@ -53,6 +53,15 @@ The source-only [`sandbox_subprocess` v1 contract](milestone-d-sandbox-subproces classifies the existing PDF worker-process boundary behind `ethos doc parse` and `ethos fingerprint`; it does not add hardened sandbox rules or a new command or binding surface. +The post-merge [`evidence_anchor` v1 guard](evidence-anchor-v1-contract.md) +binds the current `ethos evidence anchor` command to request/report schemas, +focused CLI tests, native Ethos JSON and OpenDataLoader-style grounding inputs, +and explicit non-goals. Focused validation is +`make evidence-anchor-v1-contract PYTHON=/bin/python`; PR CI also runs +`.github/scripts/test_evidence_anchor_v1_contract.py` so the guard's richer drift checks are +enforced automatically. This guard does not expand public beta/evaluation posture, hosted +surfaces, production positioning, semantic answer verification, or benchmark/parser/table-quality +claims. Current Milestone D source-only final closeout is recorded in [`docs/validation/milestone-d-final-closeout-validation-2026-06-19.md`](validation/milestone-d-final-closeout-validation-2026-06-19.md). The prior contract closeout is recorded in diff --git a/examples/verify/evidence_anchor_v1_contract.json b/examples/verify/evidence_anchor_v1_contract.json new file mode 100644 index 00000000..90ec4116 --- /dev/null +++ b/examples/verify/evidence_anchor_v1_contract.json @@ -0,0 +1,65 @@ +{ + "schema_version": 1, + "contract": "evidence_anchor.v1", + "status": "source-only-public-beta-evaluation", + "carrier": "ethos evidence anchor", + "request_schema": "schemas/ethos-evidence-anchor-request.schema.json", + "report_schema": "schemas/ethos-evidence-anchor-report.schema.json", + "implementation": [ + "crates/ethos-core/src/evidence_anchor.rs", + "crates/ethos-verify/src/lib.rs", + "crates/ethos-cli/src/cmd/evidence.rs", + "crates/ethos-cli/src/main.rs", + "crates/ethos-cli/tests/evidence_anchor.rs" + ], + "cli_tests": [ + "help_lists_evidence_anchor", + "missing_evidence_refs_exits_usage", + "empty_refs_succeeds_with_empty_anchors", + "native_page_text_bbox_and_table_cell_bind", + "opendataloader_text_binds_and_bbox_is_capability_limited", + "non_bound_outcomes_still_exit_zero", + "repeated_input_is_byte_identical", + "unknown_grounding_and_source_shape_exit_two", + "usage_errors_are_exit_two", + "table_cell_expected_text_uses_exact_normalized_match", + "text_mismatch_takes_precedence_over_bbox_capability_limit" + ], + "supported_groundings": [ + { + "name": "native-ethos-json", + "cli_argument": "ethos-json", + "parser_scope": "native Ethos document JSON through the existing GroundingSource path", + "expected_capability_boundary": "page, text, text_bbox, and table_cell anchors can bind when the source provides the required evidence" + }, + { + "name": "opendataloader-json", + "cli_argument": "opendataloader-json", + "parser_scope": "OpenDataLoader-style JSON through the existing opendataloader-json adapter", + "expected_capability_boundary": "text anchors can bind; bbox detail remains capability_limited when coordinate capability is unavailable" + } + ], + "supported_anchor_statuses": [ + "bound", + "mismatch", + "not_found", + "stale_fingerprint", + "capability_limited", + "unsupported_evidence_kind" + ], + "deterministic_guards": [ + "schema-backed request and report examples", + "byte-identical repeated report output", + "usage errors exit 2", + "non-bound per-ref outcomes exit 0" + ], + "explicit_blockers": [ + "semantic answer verification", + "multi-source joins", + "crop rendering or source-region image generation", + "hosted API or service surfaces", + "DocuShell-specific behavior", + "production positioning", + "benchmark, speed, footprint, parser-quality, or table-quality claims" + ] +} diff --git a/schemas/README.md b/schemas/README.md index d92e7c9f..df48af7c 100644 --- a/schemas/README.md +++ b/schemas/README.md @@ -13,6 +13,7 @@ bumps and downstream sign-off; output-changing heuristics are semver events (PRD | `ethos-verification-report.schema.json` | `verification_report.json` | | `ethos-evidence-anchor-request.schema.json` | evidence refs consumed by `ethos evidence anchor --evidence-refs` | | `ethos-evidence-anchor-report.schema.json` | `evidence_anchor_report.json` emitted by `ethos evidence anchor` | +| `ethos-evidence-anchor-contract.schema.json` | `evidence_anchor` v1 source-only public beta evaluation guard inventory | | `ethos-verification-config.schema.json` | verification config (its c14n hash stamps reports) | | `ethos-crop-descriptor.schema.json` | crop descriptor JSON emitted by `ethos crop_element` and `ethos verify --crop-dir` | | `ethos-crop-element-request.schema.json` | source-only request envelope for Milestone D `crop_element` v1 contract work | @@ -52,6 +53,14 @@ security-report / verification-report examples). `verification-report-negative.example.json` shows a non-grounded report with a per-check `reason` label. +Evidence-anchor V1 guard work is tracked in `docs/evidence-anchor-v1-contract.md`. In this +source-only public beta evaluation guard, `evidence_anchor` names the deterministic source-tracing +contract currently carried by `ethos evidence anchor`; it does not add semantic answer +verification, hosted/API behavior, app-specific integration, crop rendering, or production +positioning. The contract inventory at `examples/verify/evidence_anchor_v1_contract.json` is +schema-validated here; its alignment with focused CLI tests, OpenDataLoader adapter coverage, +status docs, and Makefile wiring is checked by the evidence-anchor repository guard. + Milestone D `verify_citations` v1 contract work is tracked in `docs/milestone-d-verify-citations-contract.md`. In this source-only pre-alpha slice, `verify_citations` names the citation-input to verification-report contract currently carried by diff --git a/schemas/ethos-evidence-anchor-contract.schema.json b/schemas/ethos-evidence-anchor-contract.schema.json new file mode 100644 index 00000000..82608e5c --- /dev/null +++ b/schemas/ethos-evidence-anchor-contract.schema.json @@ -0,0 +1,95 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "urn:ethos:schema:evidence-anchor-contract:1", + "title": "Ethos evidence_anchor v1 contract inventory", + "description": "Source-only public beta evaluation guard inventory for the current evidence_anchor v1 contract carried by ethos evidence anchor. This validates inventory shape and vocabulary; executable alignment stays in the repository guard.", + "type": "object", + "required": [ + "schema_version", + "contract", + "status", + "carrier", + "request_schema", + "report_schema", + "implementation", + "cli_tests", + "supported_groundings", + "supported_anchor_statuses", + "deterministic_guards", + "explicit_blockers" + ], + "additionalProperties": false, + "properties": { + "schema_version": { "const": 1 }, + "contract": { "const": "evidence_anchor.v1" }, + "status": { "const": "source-only-public-beta-evaluation" }, + "carrier": { "const": "ethos evidence anchor" }, + "request_schema": { "$ref": "#/$defs/repo_path" }, + "report_schema": { "$ref": "#/$defs/repo_path" }, + "implementation": { + "type": "array", + "minItems": 1, + "items": { "$ref": "#/$defs/repo_path" }, + "uniqueItems": true + }, + "cli_tests": { + "type": "array", + "minItems": 1, + "items": { "$ref": "#/$defs/rust_method_name" }, + "uniqueItems": true + }, + "supported_groundings": { + "type": "array", + "minItems": 1, + "items": { "$ref": "#/$defs/supported_grounding" }, + "uniqueItems": true + }, + "supported_anchor_statuses": { + "type": "array", + "minItems": 1, + "items": { + "enum": [ + "bound", + "mismatch", + "not_found", + "stale_fingerprint", + "capability_limited", + "unsupported_evidence_kind" + ] + }, + "uniqueItems": true + }, + "deterministic_guards": { + "type": "array", + "minItems": 1, + "items": { "type": "string", "minLength": 1 }, + "uniqueItems": true + }, + "explicit_blockers": { + "type": "array", + "minItems": 1, + "items": { "type": "string", "minLength": 1 }, + "uniqueItems": true + } + }, + "$defs": { + "supported_grounding": { + "type": "object", + "required": [ + "name", + "cli_argument", + "parser_scope", + "expected_capability_boundary" + ], + "additionalProperties": false, + "properties": { + "name": { "enum": ["native-ethos-json", "opendataloader-json"] }, + "cli_argument": { "enum": ["ethos-json", "opendataloader-json"] }, + "parser_scope": { "type": "string", "minLength": 1 }, + "expected_capability_boundary": { "type": "string", "minLength": 1 } + } + }, + "repo_path": { "type": "string", "minLength": 1, "pattern": "^[A-Za-z0-9_./-]+$" }, + "rust_method_name": { "type": "string", "pattern": "^[a-z][a-z0-9_]*$" } + } +} diff --git a/schemas/validate_examples.py b/schemas/validate_examples.py index c7af0a3f..7f9ec0c5 100644 --- a/schemas/validate_examples.py +++ b/schemas/validate_examples.py @@ -85,6 +85,9 @@ EXAMPLES / "evidence-anchor-report.example.json", EXAMPLES / "evidence-anchor-report-negative.example.json", ]), + ("ethos-evidence-anchor-contract.schema.json", [ + ROOT / "examples" / "verify" / "evidence_anchor_v1_contract.json", + ]), ("ethos-verification-config.schema.json", [EXAMPLES / "verification-config.example.json"]), ("ethos-crop-descriptor.schema.json", [EXAMPLES / "crop-descriptor.example.json"]), ("ethos-crop-element-request.schema.json", [