From 359b6028a9f30bace5a71ffaf3b97ad03435f04e Mon Sep 17 00:00:00 2001 From: Coden Date: Tue, 11 Aug 2026 12:29:00 +0900 Subject: [PATCH 1/4] feat: add provider-free P2-P6 evaluation --- CHANGELOG.md | 5 + context-guard-kit/bash_reference_policy.py | 2 +- context-guard-kit/phase_evaluation.py | 1227 +++++++++++++++++ packages/context-guard-receipt/README.md | 16 + .../context-guard-receipt/bin/launcher.cjs | 18 +- .../dev/package_check.py | 7 + .../dev/packaged_acceptance.py | 56 + .../context-guard-receipt/package-files.json | 455 +++++- .../python/context_guard_receipt/cli.py | 61 +- .../context_guard_receipt/phase_evaluation.py | 1227 +++++++++++++++++ .../schemas/phase-evaluation-p2.schema.json | 119 ++ .../schemas/phase-evaluation-p3.schema.json | 197 +++ .../schemas/phase-evaluation-p4.schema.json | 179 +++ .../schemas/phase-evaluation-p5.schema.json | 171 +++ .../schemas/phase-evaluation-p6.schema.json | 158 +++ .../phase-evaluation-result.schema.json | 776 +++++++++++ .../test_g001_distribution_contract.py | 8 + .../test_g015_phase_evaluation_cli.py | 155 +++ .../bin/bash_reference_policy.py | 2 +- .../p2-p6-provider-free-implementation.md | 108 ++ research/token-savings-roadmap.md | 50 +- tests/test_contextguard_stage2_feasibility.py | 26 +- tests/test_phase_evaluation.py | 583 ++++++++ 23 files changed, 5513 insertions(+), 93 deletions(-) create mode 100644 context-guard-kit/phase_evaluation.py create mode 100644 packages/context-guard-receipt/python/context_guard_receipt/phase_evaluation.py create mode 100644 packages/context-guard-receipt/schemas/phase-evaluation-p2.schema.json create mode 100644 packages/context-guard-receipt/schemas/phase-evaluation-p3.schema.json create mode 100644 packages/context-guard-receipt/schemas/phase-evaluation-p4.schema.json create mode 100644 packages/context-guard-receipt/schemas/phase-evaluation-p5.schema.json create mode 100644 packages/context-guard-receipt/schemas/phase-evaluation-p6.schema.json create mode 100644 packages/context-guard-receipt/schemas/phase-evaluation-result.schema.json create mode 100644 packages/context-guard-receipt/tests/contract/test_g015_phase_evaluation_cli.py create mode 100644 research/p2-p6-provider-free-implementation.md create mode 100644 tests/test_phase_evaluation.py diff --git a/CHANGELOG.md b/CHANGELOG.md index d75bfb31..74c29413 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,11 @@ All notable changes for the ContextGuard plugin are documented here. ## [Unreleased] +- Added a provider-free `context-guard-receipt evaluate phase` surface with + closed P2-P6 input/result schemas. It computes shadow/canary/router/adjunct/ + specialized-track readiness from bounded canonical local records while + keeping runtime activation, generalization, provider calls, and savings + claims disabled behind the existing sequential phase gates. - Changed the discarded v2 Bash canary's fixed marker write from denied shell output redirection to an existing MiniShell-v1-supported `python3 -c` route. Both real hook modes now guard the exact command in provider-free tests, and diff --git a/context-guard-kit/bash_reference_policy.py b/context-guard-kit/bash_reference_policy.py index 341efd78..003e88f8 100644 --- a/context-guard-kit/bash_reference_policy.py +++ b/context-guard-kit/bash_reference_policy.py @@ -36,7 +36,7 @@ # Audited digest of Receipt's package-files.json for each exact dependency # version. Invalid or missing pins are deliberately unavailable in production. EXPECTED_RECEIPT_PACKAGE_FILES_SHA256_BY_VERSION: dict[str, str] = { - "0.2.0": "17a930f7877127698c8189181d19fae7e973c446d03cf65dc9cb4b520f316f6e", + "0.2.0": "1b5070852db414d6365e685daf44f1f803b26598e1f6d8880566b5140714f428", } _TRANSACTION_ID_RE = re.compile(r"^[a-f0-9]{64}$") _EXACT_NPM_VERSION_RE = re.compile(r"^[0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z.-]+)?$") diff --git a/context-guard-kit/phase_evaluation.py b/context-guard-kit/phase_evaluation.py new file mode 100644 index 00000000..65f492f2 --- /dev/null +++ b/context-guard-kit/phase_evaluation.py @@ -0,0 +1,1227 @@ +"""Pure, fail-closed P2-P6 evaluation over caller-supplied local records. + +The module computes eligibility only. Caller-supplied records can never grant +runtime activation or public-claim authority. +""" + +from __future__ import annotations + +import re +from typing import Final + + +_DIGEST: Final = re.compile(r"sha256:[0-9a-f]{64}\Z") +_IDENTIFIER: Final = re.compile(r"[A-Za-z0-9][A-Za-z0-9._-]{0,63}\Z") +_MAX_RECORDS: Final = 10_000 +_P2_TOP: Final = frozenset( + { + "schema_version", + "phase_id", + "baseline_fallback_verified", + "activation_authorized", + "dependency_gates_passed", + "observed_at", + "minimum_recall_basis_points", + "records", + } +) +_P2_ROW: Final = frozenset( + { + "record_id", + "stratum", + "relevant", + "candidate_omission", + "recalled", + "source_digest", + "rehydrated_digest", + "fresh_until", + "protection", + "construction_cost_microunits", + } +) +_P3_TOP: Final = frozenset( + { + "schema_version", + "phase_id", + "baseline_fallback_verified", + "activation_authorized", + "dependency_gates_passed", + "claim_scope_bound", + "evidence_origin", + "matched_pair_ids", + "attempts", + "thresholds", + } +) +_P3_ATTEMPT: Final = frozenset( + { + "attempt_id", + "pair_id", + "arm", + "task_success", + "corrections", + "retrievals", + "measurement", + } +) +_P3_RETRIEVAL: Final = frozenset({"retrieval_id", "exact"}) +_P3_MEASUREMENT: Final = frozenset( + { + "primary_tokens", + "provider_cost_microunits", + "retry_cost_microunits", + "correction_cost_microunits", + "retrieval_cost_microunits", + "external_cost_microunits", + "local_compute_cost_microunits", + } +) +_P3_COST_FIELDS: Final = _P3_MEASUREMENT - {"primary_tokens"} +_P3_THRESHOLDS: Final = frozenset( + { + "maximum_failure_rate_increase_basis_points", + "require_corrections_non_inferior", + "require_fully_loaded_cost_improvement", + } +) +_P4_TOP: Final = frozenset( + { + "schema_version", + "phase_id", + "baseline_fallback_verified", + "dependency_gates_passed", + "activation_authorized", + "minimum_confidence_basis_points", + "trials", + } +) +_P4_TRIAL: Final = frozenset( + { + "trial_id", + "advisory_status", + "advisory_route", + "confidence_basis_points", + "bypass_reasons", + "outcomes", + } +) +_P4_OUTCOMES: Final = frozenset( + {"advisory", "always_pass_through", "always_on"} +) +_P4_OUTCOME: Final = frozenset( + {"quality_basis_points", "total_cost_microunits", "cache_accounting"} +) +_P4_CACHE: Final = frozenset( + { + "creation_microunits", + "read_microunits", + "invalidation_microunits", + "latency_microunits", + "provider_cost_microunits", + } +) +_P5_TOP: Final = frozenset( + { + "schema_version", + "phase_id", + "dependency_gates_passed", + "activation_authorized", + "current_revision_digest", + "current_source_digest", + "current_test_digest", + "adjuncts", + } +) +_P5_ADJUNCT_IDS: Final = frozenset( + {"execution_twin", "failure_cone", "typed_blueprint"} +) +_P5_ADJUNCT: Final = frozenset( + { + "adjunct_id", + "revision_digest", + "source_digest", + "test_digest", + "evidence_digests", + "failure_cases", + "bypass_verified", + "fallback_verified", + "baseline_quality_basis_points", + "adjunct_quality_basis_points", + "baseline_cost_microunits", + "adjunct_cost_microunits", + } +) +_P5_FAILURE_CASE: Final = frozenset( + {"case_id", "exit_status", "root_cause", "duplicate_of"} +) +_P6_TOP: Final = frozenset( + {"schema_version", "phase_id", "dependency_gates_passed", "tracks"} +) +_P6_TRACK_IDS: Final = frozenset( + { + "context_leases", + "scout_surgeon", + "counterfactual_ledger", + "negative_firewall", + "bounded_compilation", + } +) +_P6_TRACK: Final = frozenset( + { + "track_id", + "surface", + "workload_digest", + "baseline_digest", + "scope_digest", + "privacy_boundary_digest", + "privacy_verified", + "baseline_quality_basis_points", + "track_quality_basis_points", + "population_count", + "baseline_failure_count", + "track_failure_count", + "maximum_failure_rate_increase_basis_points", + "baseline_corrections", + "track_corrections", + "cost_model_digest", + "baseline_cost_microunits", + "track_cost_microunits", + "fallback_verified", + "rollback_verified", + "activation_authorized", + "provider_evidence_digest", + } +) + + +def _exact_dict(value: object, keys: frozenset[str]) -> bool: + return type(value) is dict and set(value) == keys + + +def _valid_identifier(value: object) -> bool: + return type(value) is str and _IDENTIFIER.fullmatch(value) is not None + + +def _valid_digest(value: object) -> bool: + return type(value) is str and _DIGEST.fullmatch(value) is not None + + +def _nonnegative_integer(value: object) -> bool: + return type(value) is int and value >= 0 + + +def _deduplicate(values: list[str]) -> list[str]: + return list(dict.fromkeys(values)) + + +def _closed_result(phase_id: str, blockers: list[str], **values: object) -> dict[str, object]: + result: dict[str, object] = { + "schema_version": "contextguard.phase-evaluation.result/v1", + "phase_id": phase_id, + "implementation_readiness": False, + "evaluation_evidence_complete": False, + "provider_evidence": False, + "activation_eligibility": False, + "activation_authority": False, + "claim_authority": False, + "fallback": "exact_unchanged_baseline", + "blockers": _deduplicate(blockers), + } + result.update(values) + return result + + +def evaluate_p2(record: object) -> dict[str, object]: + """Evaluate P2 shadow recall and exact rehydration without applying a route.""" + + if not _exact_dict(record, _P2_TOP): + return _closed_result( + "p2", + ["malformed_record", "external_activation_authority_required"], + evaluated_record_count=0, + construction_cost_microunits=0, + strata=[], + ) + data = record + blockers: list[str] = [] + if ( + data["schema_version"] != "contextguard.phase-evaluation.p2/v1" + or data["phase_id"] != "p2" + ): + blockers.append("malformed_record") + for field in ( + "baseline_fallback_verified", + "activation_authorized", + "dependency_gates_passed", + ): + if type(data[field]) is not bool: + blockers.append("malformed_record") + + observed_at = data["observed_at"] + threshold = data["minimum_recall_basis_points"] + rows = data["records"] + if not _nonnegative_integer(observed_at): + blockers.append("malformed_record") + observed_at = 0 + if type(threshold) is not int or not 0 <= threshold <= 10_000: + blockers.append("malformed_record") + threshold = 10_000 + if type(rows) is not list or not rows or len(rows) > _MAX_RECORDS: + blockers.append("malformed_record") + rows = [] + + record_ids: set[str] = set() + stratum_counts: dict[str, list[int]] = {} + construction_cost = 0 + for row in rows: + if not _exact_dict(row, _P2_ROW): + blockers.append("malformed_record") + continue + record_id = row["record_id"] + stratum = row["stratum"] + if not _valid_identifier(record_id) or record_id in record_ids: + blockers.append("malformed_record") + else: + record_ids.add(record_id) + if not _valid_identifier(stratum): + blockers.append("malformed_record") + continue + if any( + type(row[field]) is not bool + for field in ("relevant", "candidate_omission", "recalled") + ): + blockers.append("malformed_record") + continue + if not _valid_digest(row["source_digest"]): + blockers.append("non_rehydratable_record") + if not _nonnegative_integer(row["fresh_until"]) or row["fresh_until"] <= observed_at: + blockers.append("stale_record") + protection = row["protection"] + if protection not in {"eligible", "protected", "ambiguous"}: + blockers.append("malformed_record") + if not _nonnegative_integer(row["construction_cost_microunits"]): + blockers.append("construction_cost_incomplete") + else: + construction_cost += row["construction_cost_microunits"] + + if row["candidate_omission"]: + if protection != "eligible": + blockers.append("protected_omission") + if not _valid_digest(row["rehydrated_digest"]): + blockers.append("non_rehydratable_record") + elif row["rehydrated_digest"] != row["source_digest"]: + blockers.append("non_rehydratable_record") + elif row["rehydrated_digest"] is not None: + blockers.append("malformed_record") + + counts = stratum_counts.setdefault(stratum, [0, 0]) + if row["relevant"]: + counts[0] += 1 + if row["recalled"]: + counts[1] += 1 + + strata: list[dict[str, object]] = [] + if not any(relevant for relevant, _ in stratum_counts.values()): + blockers.append("recall_unavailable") + for stratum in sorted(stratum_counts): + relevant, recalled = stratum_counts[stratum] + if relevant == 0: + continue + recall_basis_points = recalled * 10_000 // relevant + threshold_passed = recall_basis_points >= threshold + if not threshold_passed: + blockers.append("recall_threshold_failed") + strata.append( + { + "stratum": stratum, + "relevant_record_count": relevant, + "recalled_relevant_record_count": recalled, + "recall_basis_points": recall_basis_points, + "threshold_passed": threshold_passed, + } + ) + if data["baseline_fallback_verified"] is not True: + blockers.append("exact_fallback_unverified") + + local_blockers = { + "malformed_record", + "non_rehydratable_record", + "stale_record", + "protected_omission", + "construction_cost_incomplete", + "recall_unavailable", + "recall_threshold_failed", + "exact_fallback_unverified", + } + local_ready = not any(blocker in local_blockers for blocker in blockers) + activation_eligible = ( + local_ready + and data["dependency_gates_passed"] is True + and data["activation_authorized"] is True + ) + if data["dependency_gates_passed"] is not True: + blockers.append("dependency_gates_incomplete") + if data["activation_authorized"] is not True: + blockers.append("activation_not_recorded") + blockers.append("external_activation_authority_required") + return _closed_result( + "p2", + blockers, + implementation_readiness=local_ready, + evaluation_evidence_complete=local_ready, + activation_eligibility=activation_eligible, + evaluated_record_count=len(rows), + construction_cost_microunits=construction_cost, + strata=strata, + ) + + +def evaluate_p3(record: object) -> dict[str, object]: + """Evaluate P3 matched canary evidence and computed guardrails.""" + + if not _exact_dict(record, _P3_TOP): + return _closed_result( + "p3", + ["malformed_record", "external_activation_authority_required", "external_claim_authority_required"], + evaluated_attempt_count=0, + evaluated_pair_count=0, + evaluated_retrieval_count=0, + baseline_fully_loaded_cost_microunits=0, + canary_fully_loaded_cost_microunits=0, + ) + data = record + blockers: list[str] = [] + if ( + data["schema_version"] != "contextguard.phase-evaluation.p3/v1" + or data["phase_id"] != "p3" + ): + blockers.append("malformed_record") + for field in ( + "baseline_fallback_verified", + "activation_authorized", + "dependency_gates_passed", + "claim_scope_bound", + ): + if type(data[field]) is not bool: + blockers.append("malformed_record") + + pair_ids = data["matched_pair_ids"] + attempts = data["attempts"] + if ( + type(pair_ids) is not list + or not pair_ids + or len(pair_ids) > _MAX_RECORDS + or any(not _valid_identifier(pair_id) for pair_id in pair_ids) + or len(set(pair_ids)) != len(pair_ids) + ): + blockers.append("malformed_record") + pair_ids = [] + if ( + type(attempts) is not list + or not attempts + or len(attempts) > _MAX_RECORDS * 2 + ): + blockers.append("malformed_record") + attempts = [] + + thresholds = data["thresholds"] + if not _exact_dict(thresholds, _P3_THRESHOLDS): + blockers.append("malformed_record") + thresholds = {} + maximum_failure_increase = thresholds.get( + "maximum_failure_rate_increase_basis_points" + ) + if type(maximum_failure_increase) is not int or not 0 <= maximum_failure_increase <= 10_000: + blockers.append("malformed_record") + maximum_failure_increase = 0 + if thresholds.get("require_corrections_non_inferior") is not True: + blockers.append("required_guardrail_disabled") + if thresholds.get("require_fully_loaded_cost_improvement") is not True: + blockers.append("required_guardrail_disabled") + + expected_pairs = set(pair_ids) + attempt_ids: set[str] = set() + pair_members: dict[str, dict[str, dict[str, object]]] = {} + retrieval_count = 0 + measurement_complete = True + for attempt in attempts: + if not _exact_dict(attempt, _P3_ATTEMPT): + blockers.append("malformed_record") + continue + attempt_id = attempt["attempt_id"] + pair_id = attempt["pair_id"] + arm = attempt["arm"] + if not _valid_identifier(attempt_id) or attempt_id in attempt_ids: + blockers.append("malformed_record") + else: + attempt_ids.add(attempt_id) + if not _valid_identifier(pair_id) or pair_id not in expected_pairs: + blockers.append("matched_population_incomplete") + if arm not in {"baseline", "canary"}: + blockers.append("malformed_record") + elif _valid_identifier(pair_id) and pair_id in expected_pairs: + members = pair_members.setdefault(pair_id, {}) + if arm in members: + blockers.append("matched_population_incomplete") + else: + members[arm] = attempt + if type(attempt["task_success"]) is not bool: + blockers.append("malformed_record") + if not _nonnegative_integer(attempt["corrections"]): + blockers.append("malformed_record") + + retrievals = attempt["retrievals"] + if type(retrievals) is not list or len(retrievals) > _MAX_RECORDS: + blockers.append("malformed_record") + retrievals = [] + retrieval_ids: set[str] = set() + for retrieval in retrievals: + if not _exact_dict(retrieval, _P3_RETRIEVAL): + blockers.append("malformed_record") + continue + retrieval_id = retrieval["retrieval_id"] + if not _valid_identifier(retrieval_id) or retrieval_id in retrieval_ids: + blockers.append("malformed_record") + else: + retrieval_ids.add(retrieval_id) + if retrieval["exact"] is not True: + blockers.append("exact_retrieval_incomplete") + retrieval_count += 1 + + measurement = attempt["measurement"] + if not _exact_dict(measurement, _P3_MEASUREMENT) or any( + not _nonnegative_integer(measurement.get(field)) + for field in _P3_MEASUREMENT + ): + blockers.append("provider_measurement_incomplete") + measurement_complete = False + + if set(pair_members) != expected_pairs or any( + set(pair_members.get(pair_id, {})) != {"baseline", "canary"} + for pair_id in expected_pairs + ): + blockers.append("matched_population_incomplete") + if len(attempts) != len(expected_pairs) * 2: + blockers.append("matched_population_incomplete") + + baseline_failures = 0 + canary_failures = 0 + baseline_corrections = 0 + canary_corrections = 0 + baseline_cost = 0 + canary_cost = 0 + complete_pairs = 0 + for pair_id in pair_ids: + members = pair_members.get(pair_id, {}) + if set(members) != {"baseline", "canary"}: + continue + complete_pairs += 1 + baseline = members["baseline"] + canary = members["canary"] + if type(baseline["task_success"]) is bool: + baseline_failures += int(not baseline["task_success"]) + if type(canary["task_success"]) is bool: + canary_failures += int(not canary["task_success"]) + if _nonnegative_integer(baseline["corrections"]): + baseline_corrections += baseline["corrections"] + if _nonnegative_integer(canary["corrections"]): + canary_corrections += canary["corrections"] + for arm_name, member in (("baseline", baseline), ("canary", canary)): + measurement = member["measurement"] + if not _exact_dict(measurement, _P3_MEASUREMENT) or any( + not _nonnegative_integer(measurement.get(field)) + for field in _P3_MEASUREMENT + ): + continue + total = sum(measurement[field] for field in _P3_COST_FIELDS) + if arm_name == "baseline": + baseline_cost += total + else: + canary_cost += total + + if complete_pairs: + if ( + (canary_failures - baseline_failures) * 10_000 + > maximum_failure_increase * complete_pairs + ): + blockers.append("failure_guardrail_failed") + if canary_corrections > baseline_corrections: + blockers.append("correction_guardrail_failed") + if measurement_complete and canary_cost >= baseline_cost: + blockers.append("fully_loaded_cost_not_improved") + if data["evidence_origin"] != "provider_measured": + blockers.append("provider_measurement_incomplete") + measurement_complete = False + if data["baseline_fallback_verified"] is not True: + blockers.append("exact_fallback_unverified") + + local_blockers = { + "malformed_record", + "matched_population_incomplete", + "exact_retrieval_incomplete", + "exact_fallback_unverified", + "required_guardrail_disabled", + } + evaluation_blockers = local_blockers | { + "provider_measurement_incomplete", + "failure_guardrail_failed", + "correction_guardrail_failed", + "fully_loaded_cost_not_improved", + } + implementation_ready = not any(blocker in local_blockers for blocker in blockers) + provider_evidence = measurement_complete and not any( + blocker in { + "malformed_record", + "matched_population_incomplete", + "provider_measurement_incomplete", + } + for blocker in blockers + ) + evaluation_complete = not any(blocker in evaluation_blockers for blocker in blockers) + activation_eligible = ( + evaluation_complete + and data["dependency_gates_passed"] is True + and data["activation_authorized"] is True + and data["claim_scope_bound"] is True + ) + if data["dependency_gates_passed"] is not True: + blockers.append("dependency_gates_incomplete") + if data["activation_authorized"] is not True: + blockers.append("activation_not_recorded") + if data["claim_scope_bound"] is not True: + blockers.append("claim_scope_unbound") + blockers.extend( + ["external_activation_authority_required", "external_claim_authority_required"] + ) + return _closed_result( + "p3", + blockers, + implementation_readiness=implementation_ready, + evaluation_evidence_complete=evaluation_complete, + provider_evidence=provider_evidence, + activation_eligibility=activation_eligible, + evaluated_attempt_count=len(attempts), + evaluated_pair_count=complete_pairs, + evaluated_retrieval_count=retrieval_count, + baseline_failure_count=baseline_failures, + canary_failure_count=canary_failures, + baseline_correction_count=baseline_corrections, + canary_correction_count=canary_corrections, + baseline_fully_loaded_cost_microunits=baseline_cost, + canary_fully_loaded_cost_microunits=canary_cost, + ) + + +def evaluate_p4(record: object) -> dict[str, object]: + """Evaluate advisory router regret without changing a runtime route.""" + + empty_values = { + "runtime_route_changed": False, + "selected_route": "pass_through", + "evaluated_trial_count": 0, + "abstention_count": 0, + "failure_count": 0, + "confidence_basis_points": [], + "bypass_reason_counts": {}, + "trials": [], + } + if not _exact_dict(record, _P4_TOP): + return _closed_result( + "p4", + ["malformed_record", "external_activation_authority_required"], + **empty_values, + ) + data = record + blockers: list[str] = [] + if ( + data["schema_version"] != "contextguard.phase-evaluation.p4/v1" + or data["phase_id"] != "p4" + ): + blockers.append("malformed_record") + for field in ( + "baseline_fallback_verified", + "dependency_gates_passed", + "activation_authorized", + ): + if type(data[field]) is not bool: + blockers.append("malformed_record") + + minimum_confidence = data["minimum_confidence_basis_points"] + if type(minimum_confidence) is not int or not 0 <= minimum_confidence <= 10_000: + blockers.append("malformed_record") + minimum_confidence = 10_000 + trials = data["trials"] + if type(trials) is not list or not trials or len(trials) > _MAX_RECORDS: + blockers.append("malformed_record") + trials = [] + + trial_ids: set[str] = set() + reports: list[dict[str, object]] = [] + confidences: list[int] = [] + reason_counts: dict[str, int] = {} + abstention_count = 0 + failure_count = 0 + advisory_routes: set[str] = set() + all_trials_eligible = bool(trials) + for trial in trials: + reasons: list[str] = [] + regret: int | None = None + confidence = 0 + status: object = None + advisory_route: object = None + trial_id: object = None + if not _exact_dict(trial, _P4_TRIAL): + reasons.append("malformed_record") + else: + raw_trial_id = trial["trial_id"] + raw_status = trial["advisory_status"] + raw_advisory_route = trial["advisory_route"] + if not _valid_identifier(raw_trial_id) or raw_trial_id in trial_ids: + reasons.append("malformed_record") + else: + trial_id = raw_trial_id + trial_ids.add(trial_id) + if type(raw_status) is not str or raw_status not in { + "selected", + "abstained", + "failed", + }: + reasons.append("malformed_record") + else: + status = raw_status + if status == "selected": + if type(raw_advisory_route) is not str or raw_advisory_route not in { + "pass_through", + "on", + }: + reasons.append("malformed_record") + else: + advisory_route = raw_advisory_route + advisory_routes.add(advisory_route) + elif raw_advisory_route is not None: + reasons.append("malformed_record") + if status == "abstained": + abstention_count += 1 + reasons.append("abstained") + elif status == "failed": + failure_count += 1 + reasons.append("failed") + + confidence_value = trial["confidence_basis_points"] + if type(confidence_value) is not int or not 0 <= confidence_value <= 10_000: + reasons.append("malformed_record") + else: + confidence = confidence_value + confidences.append(confidence) + if confidence < minimum_confidence: + reasons.append("low_confidence") + + supplied_reasons = trial["bypass_reasons"] + if ( + type(supplied_reasons) is not list + or any(not _valid_identifier(reason) for reason in supplied_reasons) + ): + reasons.append("malformed_record") + else: + reasons.extend(supplied_reasons) + + outcomes = trial["outcomes"] + parsed: dict[str, tuple[int, int]] = {} + if not _exact_dict(outcomes, _P4_OUTCOMES): + reasons.append("cache_accounting_incomplete") + else: + for policy in sorted(_P4_OUTCOMES): + outcome = outcomes[policy] + if not _exact_dict(outcome, _P4_OUTCOME): + reasons.append("cache_accounting_incomplete") + continue + quality = outcome["quality_basis_points"] + total_cost = outcome["total_cost_microunits"] + accounting = outcome["cache_accounting"] + if ( + type(quality) is not int + or not 0 <= quality <= 10_000 + or not _nonnegative_integer(total_cost) + or not _exact_dict(accounting, _P4_CACHE) + or any( + not _nonnegative_integer(accounting.get(field)) + for field in _P4_CACHE + ) + ): + reasons.append("cache_accounting_incomplete") + continue + if total_cost != sum(accounting[field] for field in _P4_CACHE): + reasons.append("cache_accounting_incomplete") + continue + parsed[policy] = (quality, total_cost) + if set(parsed) == _P4_OUTCOMES: + fixed = min( + (parsed["always_pass_through"], parsed["always_on"]), + key=lambda value: (-value[0], value[1]), + ) + advisory = parsed["advisory"] + regret = fixed[1] - advisory[1] + if advisory[0] < fixed[0]: + reasons.append("quality_regression") + if regret < 0: + reasons.append("negative_regret") + + reasons = _deduplicate(reasons) + eligible = not reasons + if not eligible: + all_trials_eligible = False + evaluation_route = advisory_route if eligible else "pass_through" + reports.append( + { + "trial_id": trial_id, + "advisory_status": status, + "advisory_route": advisory_route, + "confidence_basis_points": confidence, + "regret_microunits": regret, + "evaluation_route": evaluation_route, + "bypass_reasons": reasons, + } + ) + for reason in reasons: + reason_counts[reason] = reason_counts.get(reason, 0) + 1 + if reason in { + "malformed_record", + "cache_accounting_incomplete", + "quality_regression", + "negative_regret", + "low_confidence", + "abstained", + "failed", + }: + blockers.append(reason) + + if data["baseline_fallback_verified"] is not True: + blockers.append("exact_fallback_unverified") + evaluation_blockers = { + "malformed_record", + "cache_accounting_incomplete", + "quality_regression", + "negative_regret", + "low_confidence", + "abstained", + "failed", + "exact_fallback_unverified", + } + evaluation_complete = all_trials_eligible and not any( + blocker in evaluation_blockers for blocker in blockers + ) + activation_eligible = ( + evaluation_complete + and data["dependency_gates_passed"] is True + and data["activation_authorized"] is True + ) + if data["dependency_gates_passed"] is not True: + blockers.append("dependency_gates_incomplete") + if data["activation_authorized"] is not True: + blockers.append("activation_not_recorded") + blockers.append("external_activation_authority_required") + selected_route = ( + next(iter(advisory_routes)) + if evaluation_complete and len(advisory_routes) == 1 + else "pass_through" + ) + return _closed_result( + "p4", + blockers, + implementation_readiness=not any( + blocker in {"malformed_record", "exact_fallback_unverified"} + for blocker in blockers + ), + evaluation_evidence_complete=evaluation_complete, + activation_eligibility=activation_eligible, + runtime_route_changed=False, + selected_route=selected_route, + evaluated_trial_count=len(trials), + abstention_count=abstention_count, + failure_count=failure_count, + confidence_basis_points=confidences, + bypass_reason_counts={key: reason_counts[key] for key in sorted(reason_counts)}, + trials=reports, + ) + + +def evaluate_p5(record: object) -> dict[str, object]: + """Evaluate each P5 adjunct independently without applying any adjunct.""" + + empty_values = { + "runtime_changed": False, + "evaluated_adjunct_count": 0, + "eligible_adjuncts": [], + "adjuncts": [], + } + if not _exact_dict(record, _P5_TOP): + return _closed_result( + "p5", + ["malformed_record", "external_activation_authority_required"], + **empty_values, + ) + data = record + phase_blockers: list[str] = [] + if ( + data["schema_version"] != "contextguard.phase-evaluation.p5/v1" + or data["phase_id"] != "p5" + ): + phase_blockers.append("malformed_record") + for field in ("dependency_gates_passed", "activation_authorized"): + if type(data[field]) is not bool: + phase_blockers.append("malformed_record") + for field in ( + "current_revision_digest", + "current_source_digest", + "current_test_digest", + ): + if not _valid_digest(data[field]): + phase_blockers.append("malformed_record") + + adjuncts = data["adjuncts"] + if type(adjuncts) is not list or len(adjuncts) != len(_P5_ADJUNCT_IDS): + phase_blockers.append("malformed_record") + adjuncts = [] + + reports: list[dict[str, object]] = [] + seen_adjuncts: set[str] = set() + eligible_adjuncts: list[str] = [] + for adjunct in adjuncts: + blockers = list(phase_blockers) + adjunct_id: object = None + if not _exact_dict(adjunct, _P5_ADJUNCT): + blockers.append("malformed_record") + else: + raw_adjunct_id = adjunct["adjunct_id"] + if ( + type(raw_adjunct_id) is not str + or raw_adjunct_id not in _P5_ADJUNCT_IDS + or raw_adjunct_id in seen_adjuncts + ): + blockers.append("malformed_record") + else: + adjunct_id = raw_adjunct_id + seen_adjuncts.add(adjunct_id) + + freshness_fields = ( + ("revision_digest", "current_revision_digest", "stale_revision"), + ("source_digest", "current_source_digest", "stale_source"), + ("test_digest", "current_test_digest", "stale_test_state"), + ) + for bound_field, current_field, blocker in freshness_fields: + if not _valid_digest(adjunct[bound_field]): + blockers.append("malformed_record") + elif adjunct[bound_field] != data[current_field]: + blockers.append(blocker) + + evidence = adjunct["evidence_digests"] + if ( + type(evidence) is not list + or not evidence + or len(evidence) > _MAX_RECORDS + or any(not _valid_digest(digest) for digest in evidence) + or len(set(evidence)) != len(evidence) + ): + blockers.append("evidence_incomplete") + + failures = adjunct["failure_cases"] + prior_failures: dict[str, tuple[int, str]] = {} + if type(failures) is not list or not failures or len(failures) > _MAX_RECORDS: + blockers.append("differentiation_incomplete") + else: + for failure in failures: + if not _exact_dict(failure, _P5_FAILURE_CASE): + blockers.append("differentiation_incomplete") + continue + case_id = failure["case_id"] + exit_status = failure["exit_status"] + root_cause = failure["root_cause"] + duplicate_of = failure["duplicate_of"] + if ( + not _valid_identifier(case_id) + or case_id in prior_failures + or not _nonnegative_integer(exit_status) + or not _valid_identifier(root_cause) + or (duplicate_of is not None and not _valid_identifier(duplicate_of)) + ): + blockers.append("differentiation_incomplete") + continue + if duplicate_of is not None: + original = prior_failures.get(duplicate_of) + if original is None: + blockers.append("differentiation_incomplete") + elif original != (exit_status, root_cause): + blockers.append("distinct_failure_deduplicated") + prior_failures[case_id] = (exit_status, root_cause) + + if adjunct["bypass_verified"] is not True: + blockers.append("bypass_unverified") + if adjunct["fallback_verified"] is not True: + blockers.append("exact_fallback_unverified") + baseline_quality = adjunct["baseline_quality_basis_points"] + adjunct_quality = adjunct["adjunct_quality_basis_points"] + if any( + type(value) is not int or not 0 <= value <= 10_000 + for value in (baseline_quality, adjunct_quality) + ): + blockers.append("quality_evidence_incomplete") + elif adjunct_quality < baseline_quality: + blockers.append("quality_regression") + baseline_cost = adjunct["baseline_cost_microunits"] + adjunct_cost = adjunct["adjunct_cost_microunits"] + if not _nonnegative_integer(baseline_cost) or not _nonnegative_integer(adjunct_cost): + blockers.append("cost_evidence_incomplete") + elif adjunct_cost >= baseline_cost: + blockers.append("fully_loaded_cost_not_improved") + + blockers = _deduplicate(blockers) + eligible = not blockers + if eligible and type(adjunct_id) is str: + eligible_adjuncts.append(adjunct_id) + reports.append( + { + "adjunct_id": adjunct_id, + "decision": "eligible" if eligible else "bypass", + "reversible": bool( + _exact_dict(adjunct, _P5_ADJUNCT) + and adjunct["bypass_verified"] is True + and adjunct["fallback_verified"] is True + ), + "blockers": blockers, + } + ) + + if seen_adjuncts != _P5_ADJUNCT_IDS: + phase_blockers.append("malformed_record") + if data["dependency_gates_passed"] is not True: + phase_blockers.append("dependency_gates_incomplete") + if data["activation_authorized"] is not True: + phase_blockers.append("activation_not_recorded") + phase_blockers.append("external_activation_authority_required") + all_eligible = len(eligible_adjuncts) == len(_P5_ADJUNCT_IDS) + return _closed_result( + "p5", + phase_blockers, + implementation_readiness=all_eligible, + evaluation_evidence_complete=all_eligible, + activation_eligibility=( + all_eligible + and data["dependency_gates_passed"] is True + and data["activation_authorized"] is True + ), + runtime_changed=False, + evaluated_adjunct_count=len(adjuncts), + eligible_adjuncts=eligible_adjuncts, + adjuncts=reports, + ) + + +def evaluate_p6(record: object) -> dict[str, object]: + """Evaluate frozen P6 tracks independently without changing runtime state.""" + + empty_values = { + "runtime_changed": False, + "evaluated_track_count": 0, + "eligible_tracks": [], + "tracks": [], + } + if not _exact_dict(record, _P6_TOP): + return _closed_result( + "p6", + ["malformed_record", "external_activation_authority_required", "claim_blocked"], + **empty_values, + ) + data = record + phase_blockers: list[str] = [] + if ( + data["schema_version"] != "contextguard.phase-evaluation.p6/v1" + or data["phase_id"] != "p6" + or type(data["dependency_gates_passed"]) is not bool + ): + phase_blockers.append("malformed_record") + + tracks = data["tracks"] + if type(tracks) is not list or len(tracks) != len(_P6_TRACK_IDS): + phase_blockers.append("malformed_record") + tracks = [] + + reports: list[dict[str, object]] = [] + eligible_tracks: list[str] = [] + seen_tracks: set[str] = set() + for track in tracks: + blockers: list[str] = [] + track_id: object = None + evidence = { + "workload_evidence": False, + "baseline_evidence": False, + "scope_evidence": False, + "privacy_evidence": False, + "quality_evidence": False, + "failure_guardrail_evidence": False, + "correction_guardrail_evidence": False, + "cost_model_evidence": False, + "cost_evidence": False, + "fallback_evidence": False, + "rollback_evidence": False, + "authority_evidence": False, + "provider_evidence": False, + } + surface: object = None + if not _exact_dict(track, _P6_TRACK): + blockers.append("malformed_record") + else: + raw_track_id = track["track_id"] + raw_surface = track["surface"] + if ( + type(raw_track_id) is not str + or raw_track_id not in _P6_TRACK_IDS + or raw_track_id in seen_tracks + ): + blockers.append("malformed_record") + else: + track_id = raw_track_id + seen_tracks.add(track_id) + if type(raw_surface) is not str or raw_surface not in { + "evaluation_only", + "plan_only", + }: + blockers.append("malformed_record") + else: + surface = raw_surface + + evidence["workload_evidence"] = _valid_digest(track["workload_digest"]) + if not evidence["workload_evidence"]: + blockers.append("workload_evidence_incomplete") + evidence["baseline_evidence"] = _valid_digest(track["baseline_digest"]) + if not evidence["baseline_evidence"]: + blockers.append("baseline_evidence_incomplete") + evidence["scope_evidence"] = _valid_digest(track["scope_digest"]) + if not evidence["scope_evidence"]: + blockers.append("scope_evidence_incomplete") + evidence["privacy_evidence"] = ( + _valid_digest(track["privacy_boundary_digest"]) + and track["privacy_verified"] is True + ) + if not evidence["privacy_evidence"]: + blockers.append("privacy_evidence_incomplete") + + baseline_quality = track["baseline_quality_basis_points"] + track_quality = track["track_quality_basis_points"] + quality_values_valid = all( + type(value) is int and 0 <= value <= 10_000 + for value in (baseline_quality, track_quality) + ) + evidence["quality_evidence"] = quality_values_valid + if not quality_values_valid: + blockers.append("quality_evidence_incomplete") + elif track_quality < baseline_quality: + blockers.append("quality_regression") + + population_count = track["population_count"] + baseline_failure_count = track["baseline_failure_count"] + track_failure_count = track["track_failure_count"] + maximum_failure_increase = track[ + "maximum_failure_rate_increase_basis_points" + ] + failure_values_valid = ( + type(population_count) is int + and 1 <= population_count <= _MAX_RECORDS + and type(baseline_failure_count) is int + and 0 <= baseline_failure_count <= population_count + and type(track_failure_count) is int + and 0 <= track_failure_count <= population_count + and type(maximum_failure_increase) is int + and 0 <= maximum_failure_increase <= 10_000 + ) + if not failure_values_valid: + blockers.append("failure_evidence_incomplete") + else: + failure_guardrail_passed = ( + (track_failure_count - baseline_failure_count) * 10_000 + <= maximum_failure_increase * population_count + ) + evidence["failure_guardrail_evidence"] = failure_guardrail_passed + if not failure_guardrail_passed: + blockers.append("failure_guardrail_failed") + + baseline_corrections = track["baseline_corrections"] + track_corrections = track["track_corrections"] + correction_values_valid = _nonnegative_integer( + baseline_corrections + ) and _nonnegative_integer(track_corrections) + if not correction_values_valid: + blockers.append("correction_evidence_incomplete") + else: + correction_guardrail_passed = track_corrections <= baseline_corrections + evidence["correction_guardrail_evidence"] = correction_guardrail_passed + if not correction_guardrail_passed: + blockers.append("correction_guardrail_failed") + + evidence["cost_model_evidence"] = _valid_digest(track["cost_model_digest"]) + if not evidence["cost_model_evidence"]: + blockers.append("cost_model_incomplete") + + baseline_cost = track["baseline_cost_microunits"] + track_cost = track["track_cost_microunits"] + costs_valid = _nonnegative_integer(baseline_cost) and _nonnegative_integer(track_cost) + evidence["cost_evidence"] = costs_valid + if not costs_valid: + blockers.append("cost_evidence_incomplete") + elif track_cost >= baseline_cost: + blockers.append("fully_loaded_cost_not_improved") + + evidence["fallback_evidence"] = track["fallback_verified"] is True + if not evidence["fallback_evidence"]: + blockers.append("exact_fallback_unverified") + evidence["rollback_evidence"] = track["rollback_verified"] is True + if not evidence["rollback_evidence"]: + blockers.append("rollback_unverified") + evidence["authority_evidence"] = track["activation_authorized"] is True + if not evidence["authority_evidence"]: + blockers.append("activation_not_recorded") + evidence["provider_evidence"] = _valid_digest(track["provider_evidence_digest"]) + if not evidence["provider_evidence"]: + blockers.append("provider_measurement_incomplete") + if surface == "plan_only": + blockers.append("plan_only_non_runtime") + + if data["dependency_gates_passed"] is not True: + blockers.append("dependency_gates_incomplete") + blockers = _deduplicate(blockers) + plan_only = surface == "plan_only" + eligible = not blockers and not plan_only + if eligible and type(track_id) is str: + eligible_tracks.append(track_id) + reports.append( + { + "track_id": track_id, + "surface": surface, + "decision": "plan_only" if plan_only else ("eligible" if eligible else "fallback"), + "fallback": "exact_unchanged_baseline", + "activation_eligibility": eligible, + "activation_authority": False, + "claim_authority": False, + "generalization_allowed": False, + "blockers": blockers, + **evidence, + } + ) + + if seen_tracks != _P6_TRACK_IDS: + phase_blockers.append("malformed_record") + if data["dependency_gates_passed"] is not True: + phase_blockers.append("dependency_gates_incomplete") + phase_blockers.extend(["external_activation_authority_required", "claim_blocked"]) + all_eligible = len(eligible_tracks) == len(_P6_TRACK_IDS) + return _closed_result( + "p6", + phase_blockers, + implementation_readiness=all_eligible, + evaluation_evidence_complete=all_eligible, + provider_evidence=bool(reports) and all(report["provider_evidence"] for report in reports), + activation_eligibility=all_eligible and data["dependency_gates_passed"] is True, + runtime_changed=False, + evaluated_track_count=len(tracks), + eligible_tracks=eligible_tracks, + tracks=reports, + ) diff --git a/packages/context-guard-receipt/README.md b/packages/context-guard-receipt/README.md index ece2b79d..30ee17a1 100644 --- a/packages/context-guard-receipt/README.md +++ b/packages/context-guard-receipt/README.md @@ -9,6 +9,7 @@ diagnostics: ```text context-guard-receipt inspect boundary +context-guard-receipt evaluate phase --input context-guard-receipt assemble --kind evidence|blueprint|tool-schemas --descriptor --root context-guard-receipt run --escrow --root --state-dir [--timeout-seconds --max-channel-bytes --max-total-bytes ] -- [args...] context-guard-receipt inspect diagnostics --input @@ -372,3 +373,18 @@ same-UID isolation boundary or an atomic defense against trusted actors. Use `context-guard-receipt --help` for the human-readable command summary and `context-guard-receipt-mcp --help` for the explicit bounded stdio MCP summary. + +## Closed phase evaluation + +`context-guard-receipt evaluate phase --input ` evaluates one canonical +P2, P3, P4, P5, or P6 local record. Input is capped at 2 MiB, parsed with the +package's duplicate-key rejecting canonical JSON parser, and constrained by the +phase schemas shipped under `schemas/phase-evaluation-*.schema.json`. + +The evaluator is provider-free and advisory. It reads no credentials, provider +state, settings, hooks, or network resources; performs no provider or model +call; mutates no request or runtime route; and grants neither activation nor +claim authority. Invalid, incomplete, stale, or uneconomic evidence preserves +the exact unchanged baseline or the phase's independently verified exact local +fallback. Its numeric fields are caller-supplied evaluation measurements, not +token, cost, percentage, or savings claims made by this package. diff --git a/packages/context-guard-receipt/bin/launcher.cjs b/packages/context-guard-receipt/bin/launcher.cjs index 0934b290..c1afc627 100644 --- a/packages/context-guard-receipt/bin/launcher.cjs +++ b/packages/context-guard-receipt/bin/launcher.cjs @@ -46,6 +46,7 @@ const EXPECTED_FILES = [ 'python/context_guard_receipt/identity.py', 'python/context_guard_receipt/mcp.py', 'python/context_guard_receipt/merged_capture.py', + 'python/context_guard_receipt/phase_evaluation.py', 'python/context_guard_receipt/protection.py', 'python/context_guard_receipt/receipts.py', 'python/context_guard_receipt/reference_expiry.py', @@ -69,6 +70,12 @@ const EXPECTED_FILES = [ 'schemas/evidence-reference.schema.json', 'schemas/expansion-envelope.schema.json', 'schemas/expansion-refusal.schema.json', + 'schemas/phase-evaluation-p2.schema.json', + 'schemas/phase-evaluation-p3.schema.json', + 'schemas/phase-evaluation-p4.schema.json', + 'schemas/phase-evaluation-p5.schema.json', + 'schemas/phase-evaluation-p6.schema.json', + 'schemas/phase-evaluation-result.schema.json', 'schemas/protection-decision.schema.json', 'schemas/reference-expiry-inspection.schema.json', 'schemas/reference-expiry-metadata.schema.json', @@ -104,7 +111,7 @@ const TRUSTED_EXECUTABLE_FILES = new Set([ const TRUSTED_PAYLOAD_DIGESTS = { 'LICENSE': 'c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4', 'NOTICE': '40978c42e96a7b452cb77ef41f28961ca880e46ee7fa7c9589afa4d532655779', - 'README.md': 'b075742abc57962a5c10c9edcc41c67a16947a2ddd13fb64fb97e7c4d27e57e7', + 'README.md': 'e8e43ac7c76bb032080eed9ccb4be00f2c5c840fb44bba93f4e1c93f666d4a89', 'bin/context-guard-receipt-mcp.cjs': '883b893d5ee484d63b78174ace60e171dc26e032d05dd19298fb6d6c5229cffd', 'bin/context-guard-receipt.cjs': 'bdab50b0476e40024ea64f1f6cd0a46260b4707e2297d212bf5034cfd5a87ff8', 'package.json': 'daf789323e9b194943b7222bd0bf112432460afe0174d0a3363cbadbbd37c475', @@ -113,7 +120,7 @@ const TRUSTED_PAYLOAD_DIGESTS = { 'python/context_guard_receipt/blueprint.py': 'f4b8b617832ebe4bd5dc585f762a20b71b37ce79d54b6cd751f1e5fde5b785f0', 'python/context_guard_receipt/bootstrap.py': 'fa846a8968c5199618ab68a86424c0cb88c32250291faf3ac37f26d14d4b018e', 'python/context_guard_receipt/canonical.py': '91b57a1ebf2cc8fa0025ccfc8eaf6f50bc9363e6d3bc05c517b2014bf8a590c7', - 'python/context_guard_receipt/cli.py': 'e93c8970a1f06cff4511e62c1e6d7803f94d083239b159a2839da7e8ca3a0bb9', + 'python/context_guard_receipt/cli.py': '0a60b550aa620e029fdc809749160d1fb3382864557e6016d56b4780c8f4c430', 'python/context_guard_receipt/cli_io.py': '2de5ef56762e015264527306f19b1b72995cc3fffd8cd6cb58c8206e255c5baf', 'python/context_guard_receipt/contracts.py': '1127a9b90bf2da63a097b066c7f1678109dcf622f40dd6746ef055aa7a98e39e', 'python/context_guard_receipt/diagnostic_ledger.py': '3cc7865709c273b72136c48b1026ed5cd2830ea1bf76da4e424da08ccc13499d', @@ -124,6 +131,7 @@ const TRUSTED_PAYLOAD_DIGESTS = { 'python/context_guard_receipt/identity.py': '31d4a0ba5e2a04b277a027a872ee0172c5d27ed09b60c41f53f286dd2d8b963c', 'python/context_guard_receipt/mcp.py': 'db251fdd3e3d98cd83fd9a29ee0b90cb308c1bfa3fbed9122a217c80e75fe4c2', 'python/context_guard_receipt/merged_capture.py': 'a19c605a47b666f302b8b993d1e0973bfded46c1974022c2620c5ef5d598b7cf', + 'python/context_guard_receipt/phase_evaluation.py': 'e9e6747e1955789793a22826b71f73265607fd80ab30bf48f6dfae05852f1104', 'python/context_guard_receipt/protection.py': '67ae06abb102292b3db09a6731a4aab90b3bc6ceb6dbe836fc636f82f783c347', 'python/context_guard_receipt/receipts.py': '11c02d9df36be0dec2316594fd083ec39a1284325ded440de075081d2e56ddb0', 'python/context_guard_receipt/reference_expiry.py': '2445292456776d5fcbf789f75a71781d64f12865958249d192cfc5a5ff27f2f6', @@ -147,6 +155,12 @@ const TRUSTED_PAYLOAD_DIGESTS = { 'schemas/evidence-reference.schema.json': 'f94fa353dac99a08793461ca9ec72962ce12de2e5328f94039048190db70071e', 'schemas/expansion-envelope.schema.json': 'f838f84a06a433e62706467aa40097194f458bb2b3d42c600159558bed292d71', 'schemas/expansion-refusal.schema.json': 'c5196da89d9b96349deb4c2c0ad2970d6f27d7760f9236b6d07d702443ee9da0', + 'schemas/phase-evaluation-p2.schema.json': 'd4390e71109e2704c4bc6f0935997d2b4b3f7d7cfc49ed92ef05e27eb21807bd', + 'schemas/phase-evaluation-p3.schema.json': 'ac0687ce2cd43ec4954d3fb0a876284fa75829435244913b5f81b275a4c234d7', + 'schemas/phase-evaluation-p4.schema.json': '58bb474f5655b60155aeba0a7dc135697cb52c08364692bf10a14ec5ca68fdda', + 'schemas/phase-evaluation-p5.schema.json': 'b4e7f888c8a041065af130c808d8bb47c5eb42e395e18d8f8c53ea4c7eac1457', + 'schemas/phase-evaluation-p6.schema.json': 'ed19929a20da8609c472f2d96ca5de9e32f7d32365b640876c9dbb22d9e33b00', + 'schemas/phase-evaluation-result.schema.json': 'a608f3426c7a4814f7d081be2963b979d03e895a6e44e85058aa67ead43368af', 'schemas/protection-decision.schema.json': 'e7cf1b413d286347fda8f0f3a993676212e257f7e280757657032c23b5f9415f', 'schemas/reference-expiry-inspection.schema.json': '6f862e4e39ebb09e14952b542d4a28a52c618900ecfa07dca063846c638721e1', 'schemas/reference-expiry-metadata.schema.json': 'a72ed7c5f422732437cdc9e61e00efc5ea7e765c74955243f5b11b8a6eb12a73', diff --git a/packages/context-guard-receipt/dev/package_check.py b/packages/context-guard-receipt/dev/package_check.py index bbf69578..ca6c8880 100644 --- a/packages/context-guard-receipt/dev/package_check.py +++ b/packages/context-guard-receipt/dev/package_check.py @@ -47,6 +47,7 @@ "python/context_guard_receipt/identity.py", "python/context_guard_receipt/mcp.py", "python/context_guard_receipt/merged_capture.py", + "python/context_guard_receipt/phase_evaluation.py", "python/context_guard_receipt/protection.py", "python/context_guard_receipt/reference_expiry.py", "python/context_guard_receipt/receipts.py", @@ -70,6 +71,12 @@ "schemas/evidence-reference.schema.json", "schemas/expansion-envelope.schema.json", "schemas/expansion-refusal.schema.json", + "schemas/phase-evaluation-p2.schema.json", + "schemas/phase-evaluation-p3.schema.json", + "schemas/phase-evaluation-p4.schema.json", + "schemas/phase-evaluation-p5.schema.json", + "schemas/phase-evaluation-p6.schema.json", + "schemas/phase-evaluation-result.schema.json", "schemas/protection-decision.schema.json", "schemas/reference-expiry-inspection.schema.json", "schemas/reference-expiry-metadata.schema.json", diff --git a/packages/context-guard-receipt/dev/packaged_acceptance.py b/packages/context-guard-receipt/dev/packaged_acceptance.py index c3135e4a..78224871 100644 --- a/packages/context-guard-receipt/dev/packaged_acceptance.py +++ b/packages/context-guard-receipt/dev/packaged_acceptance.py @@ -98,6 +98,34 @@ def reference_expiry_request(capability: str, *, expires_at_unix_ms: int) -> byt ).encode("ascii") +def phase_evaluation_request() -> bytes: + return canonical_json( + { + "activation_authorized": True, + "baseline_fallback_verified": True, + "dependency_gates_passed": True, + "minimum_recall_basis_points": 9_000, + "observed_at": 100, + "phase_id": "p2", + "records": [ + { + "candidate_omission": True, + "construction_cost_microunits": 12, + "fresh_until": 101, + "protection": "eligible", + "recalled": True, + "record_id": "installed-p2", + "rehydrated_digest": "sha256:" + "1" * 64, + "relevant": True, + "source_digest": "sha256:" + "1" * 64, + "stratum": "installed", + } + ], + "schema_version": "contextguard.phase-evaluation.p2/v1", + } + ).encode("ascii") + + def tree_snapshot(root: Path) -> dict[str, tuple[str, int, str]]: result: dict[str, tuple[str, int, str]] = {} for path in sorted((root, *root.rglob("*"))): @@ -155,6 +183,34 @@ def distribution() -> None: expected = {"evidence_boundary": EXPECTED_BOUNDARY, "operation": "inspect_boundary", "schema_version": "contextguard-receipt-cli-response/v1", "status": "ok"} if response.returncode != 0 or response.stdout != canonical_json(expected) or response.stderr or sentinel.exists(): raise RuntimeError("installed receipt command failed its closed-boundary smoke test") + evaluated = run_binary( + [ + str(Path(node).resolve()), + str(receipt_bin), + "evaluate", + "phase", + "--input", + "-", + ], + cwd=install_directory, + environment=environment, + input_bytes=phase_evaluation_request(), + ) + try: + evaluation = json.loads(evaluated.stdout) + except (UnicodeDecodeError, json.JSONDecodeError) as exc: + raise RuntimeError("installed evaluator output was not JSON") from exc + if ( + evaluated.returncode != 0 + or evaluated.stderr + or evaluation.get("phase_id") != "p2" + or evaluation.get("implementation_readiness") is not True + or evaluation.get("activation_authority") is not False + or evaluation.get("claim_authority") is not False + or evaluation.get("fallback") != "exact_unchanged_baseline" + or sentinel.exists() + ): + raise RuntimeError("installed receipt evaluator failed its closed local smoke test") missing_helper_environment = dict(environment) missing_helper_environment.pop(PYTHON_ENV) missing_helper_environment["PATH"] = str(root / "missing-runtime-helper") diff --git a/packages/context-guard-receipt/package-files.json b/packages/context-guard-receipt/package-files.json index 01e09af1..270fcc63 100644 --- a/packages/context-guard-receipt/package-files.json +++ b/packages/context-guard-receipt/package-files.json @@ -1,75 +1,390 @@ { "files": [ - {"mode": "0644", "path": "LICENSE", "sha256": "c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4"}, - {"mode": "0644", "path": "NOTICE", "sha256": "40978c42e96a7b452cb77ef41f28961ca880e46ee7fa7c9589afa4d532655779"}, - {"mode": "0644", "path": "README.md", "sha256": "b075742abc57962a5c10c9edcc41c67a16947a2ddd13fb64fb97e7c4d27e57e7"}, - {"mode": "0755", "path": "bin/context-guard-receipt-mcp.cjs", "sha256": "883b893d5ee484d63b78174ace60e171dc26e032d05dd19298fb6d6c5229cffd"}, - {"mode": "0755", "path": "bin/context-guard-receipt.cjs", "sha256": "bdab50b0476e40024ea64f1f6cd0a46260b4707e2297d212bf5034cfd5a87ff8"}, - {"mode": "0644", "path": "bin/launcher.cjs", "sha256": "7f9c630fdcd8df5fe561a15dcafe1961218b9d5b28b3a3b0c6ccfba4ffa96fed"}, - {"mode": "0644", "path": "package.json", "sha256": "daf789323e9b194943b7222bd0bf112432460afe0174d0a3363cbadbbd37c475"}, - {"mode": "0644", "path": "python/context_guard_receipt/__init__.py", "sha256": "1046588c63e24a72c3a57ab0ebd6d60d86c158358b5bbd50ca15cf26322fabc6"}, - {"mode": "0644", "path": "python/context_guard_receipt/assembly.py", "sha256": "0e28b6e0874477314436eecb532c767d61efe6d506ae8f79d98fae4b41dd35ea"}, - {"mode": "0644", "path": "python/context_guard_receipt/blueprint.py", "sha256": "f4b8b617832ebe4bd5dc585f762a20b71b37ce79d54b6cd751f1e5fde5b785f0"}, - {"mode": "0644", "path": "python/context_guard_receipt/bootstrap.py", "sha256": "fa846a8968c5199618ab68a86424c0cb88c32250291faf3ac37f26d14d4b018e"}, - {"mode": "0644", "path": "python/context_guard_receipt/canonical.py", "sha256": "91b57a1ebf2cc8fa0025ccfc8eaf6f50bc9363e6d3bc05c517b2014bf8a590c7"}, - {"mode": "0644", "path": "python/context_guard_receipt/cli.py", "sha256": "e93c8970a1f06cff4511e62c1e6d7803f94d083239b159a2839da7e8ca3a0bb9"}, - {"mode": "0644", "path": "python/context_guard_receipt/cli_io.py", "sha256": "2de5ef56762e015264527306f19b1b72995cc3fffd8cd6cb58c8206e255c5baf"}, - {"mode": "0644", "path": "python/context_guard_receipt/contracts.py", "sha256": "1127a9b90bf2da63a097b066c7f1678109dcf622f40dd6746ef055aa7a98e39e"}, - {"mode": "0644", "path": "python/context_guard_receipt/diagnostic_ledger.py", "sha256": "3cc7865709c273b72136c48b1026ed5cd2830ea1bf76da4e424da08ccc13499d"}, - {"mode": "0644", "path": "python/context_guard_receipt/diagnostics.py", "sha256": "9a95f511b639091d0aacef69c0d4a311ad81e5a97299ab45ddc7fc23579e0e52"}, - {"mode": "0644", "path": "python/context_guard_receipt/evidence_pack.py", "sha256": "3fb5540dcee31cd6ded4883e4f4c99fb89ee17c2484f3e2ee33ebe741454d0f8"}, - {"mode": "0644", "path": "python/context_guard_receipt/execution_twin.py", "sha256": "510239b13c37ef15dcc838222b07ada49877e5540c351a51a121983b1fe031af"}, - {"mode": "0644", "path": "python/context_guard_receipt/expansion.py", "sha256": "9b848e555f05a621665c6b167a49e5d8085ffc9c6906f040f43fd2a87e981f2b"}, - {"mode": "0644", "path": "python/context_guard_receipt/identity.py", "sha256": "31d4a0ba5e2a04b277a027a872ee0172c5d27ed09b60c41f53f286dd2d8b963c"}, - {"mode": "0644", "path": "python/context_guard_receipt/mcp.py", "sha256": "db251fdd3e3d98cd83fd9a29ee0b90cb308c1bfa3fbed9122a217c80e75fe4c2"}, - {"mode": "0644", "path": "python/context_guard_receipt/merged_capture.py", "sha256": "a19c605a47b666f302b8b993d1e0973bfded46c1974022c2620c5ef5d598b7cf"}, - {"mode": "0644", "path": "python/context_guard_receipt/protection.py", "sha256": "67ae06abb102292b3db09a6731a4aab90b3bc6ceb6dbe836fc636f82f783c347"}, - {"mode": "0644", "path": "python/context_guard_receipt/receipts.py", "sha256": "11c02d9df36be0dec2316594fd083ec39a1284325ded440de075081d2e56ddb0"}, - {"mode": "0644", "path": "python/context_guard_receipt/reference_expiry.py", "sha256": "2445292456776d5fcbf789f75a71781d64f12865958249d192cfc5a5ff27f2f6"}, - {"mode": "0644", "path": "python/context_guard_receipt/router.py", "sha256": "22b395d0a8a0522fcc9b12c1b12493e90aafb9e374937725a2bdaf223188529c"}, - {"mode": "0644", "path": "python/context_guard_receipt/runner.py", "sha256": "2193f7ae1032990b2ff5d954bd97ea6de67d58a9eb2cf395e939b25593a79cf8"}, - {"mode": "0644", "path": "python/context_guard_receipt/sanitizer.py", "sha256": "ddf7d4d81dbb73156fa2274c7adf06475c4688b1e08341835aff4eeb81a72fc8"}, - {"mode": "0644", "path": "python/context_guard_receipt/store.py", "sha256": "6bf5f033ebc1cfaf72dbfa685ed4ce3339dfe0af53b88bab726ed0847767e8ed"}, - {"mode": "0644", "path": "python/context_guard_receipt/tool_schemas.py", "sha256": "f84a8bc2f2232250dfe0782aaddf35c9842720f4815c6d2d8e4bd95757546bbc"}, - {"mode": "0644", "path": "schemas/assembly-receipt.schema.json", "sha256": "05ab76b261ca18ed8d165cb4e43395006e7196fdeccb53603c3ed77ca3bdfe88"}, - {"mode": "0644", "path": "schemas/blueprint-descriptor.schema.json", "sha256": "4424c2c482dc8d4184f1bd7ac6e1e45ad4ee36ee97da13e75b0986b2da8c9b09"}, - {"mode": "0644", "path": "schemas/capability-record.schema.json", "sha256": "86df8398c5199a0d4e3d58ee7d8e2a4171e0103a5ea05644f00f1c343889c114"}, - {"mode": "0644", "path": "schemas/command-capture-receipt.schema.json", "sha256": "7bcdaeb52fdfa4cbb3dc57b8d4b3b1cfa318bb7d8af11574ae0e23126ffa954b"}, - {"mode": "0644", "path": "schemas/diagnostic-ledger-entry.schema.json", "sha256": "8ea3ee4db48fb6d54b1bb613253f3313a38d33516ff328887feb6dfcc5c6c2ef"}, - {"mode": "0644", "path": "schemas/diagnostic-ledger-inspection.schema.json", "sha256": "2258c63aba7ada14949fe7db2e757d42551009d026d3152e3d95191c934b110f"}, - {"mode": "0644", "path": "schemas/diagnostic-ledger-metadata.schema.json", "sha256": "2ab1092790c97e0aa9439dd6f1f59004368a9e71e9b7dc1d849a2d2f59369e2a"}, - {"mode": "0644", "path": "schemas/diagnostics-report.schema.json", "sha256": "b779475abbfdd76c9b6fca8f39b9b0c4e058f8e65a0ff9d7f583a1b8b01db38c"}, - {"mode": "0644", "path": "schemas/diagnostics-request.schema.json", "sha256": "7779d364170db90b8e7b71a342156d0b5bb0fe8ff8b423c21df29005d7efa2b4"}, - {"mode": "0644", "path": "schemas/evidence-boundary.schema.json", "sha256": "b510303bd09adcaf7150415aab5cae3adbe4c99b8482c07a45bb978ad4e82ba7"}, - {"mode": "0644", "path": "schemas/evidence-descriptor.schema.json", "sha256": "29fa127eeafb8c52c05c7cdc8b1b929919e47e8e94aa8a5e6cd81ea2cf973dff"}, - {"mode": "0644", "path": "schemas/evidence-pack.schema.json", "sha256": "5ff6823d166b245a488e6d0f96512ae025b7836f7f46e5e14dc4508edfad6692"}, - {"mode": "0644", "path": "schemas/evidence-reference.schema.json", "sha256": "f94fa353dac99a08793461ca9ec72962ce12de2e5328f94039048190db70071e"}, - {"mode": "0644", "path": "schemas/expansion-envelope.schema.json", "sha256": "f838f84a06a433e62706467aa40097194f458bb2b3d42c600159558bed292d71"}, - {"mode": "0644", "path": "schemas/expansion-refusal.schema.json", "sha256": "c5196da89d9b96349deb4c2c0ad2970d6f27d7760f9236b6d07d702443ee9da0"}, - {"mode": "0644", "path": "schemas/protection-decision.schema.json", "sha256": "e7cf1b413d286347fda8f0f3a993676212e257f7e280757657032c23b5f9415f"}, - {"mode": "0644", "path": "schemas/reference-expiry-inspection.schema.json", "sha256": "6f862e4e39ebb09e14952b542d4a28a52c618900ecfa07dca063846c638721e1"}, - {"mode": "0644", "path": "schemas/reference-expiry-metadata.schema.json", "sha256": "a72ed7c5f422732437cdc9e61e00efc5ea7e765c74955243f5b11b8a6eb12a73"}, - {"mode": "0644", "path": "schemas/reference-expiry-record.schema.json", "sha256": "450940d3f9d6d0baf7540c2bb2269f23ce8c53106658d7e9fdfe80392b7bad0e"}, - {"mode": "0644", "path": "schemas/reference-expiry-request.schema.json", "sha256": "9b96d2dac7ed9e23af17fbcb9311b4d2a5f3c7c04d042de380af3732887d6c89"}, - {"mode": "0644", "path": "schemas/reference-expiry-result.schema.json", "sha256": "ed0c72aed6f21fdd3d78332768981da8db19e69a130cb881d3b86b7d61c13d82"}, - {"mode": "0644", "path": "schemas/shadow-firewall-report.schema.json", "sha256": "016a0d7320b9dc8c444f7488fdcd8bd33752fcfd27c1e906741972b9de50d04c"}, - {"mode": "0644", "path": "schemas/source-identity.schema.json", "sha256": "c20007a9a03e8168feb7b413e035e1d3ef2cdad23a7c404dc25014a03411b047"}, - {"mode": "0644", "path": "schemas/store-commit.schema.json", "sha256": "e078e14eade2395772936ecd8ec8a9add8b4a71ea45a1b6935645a83a46147ad"}, - {"mode": "0644", "path": "schemas/store-metadata.schema.json", "sha256": "be6a83707fa541436e5930e444cfc6431d618ef65f561718a5ed66bf42f447db"}, - {"mode": "0644", "path": "schemas/tool-schema-bundle.schema.json", "sha256": "bebb1d2ef79cfd76a870f6be554f7e1708e5015912885adc720ab3bc9495428d"}, - {"mode": "0644", "path": "schemas/tool-schema-catalog-reference.schema.json", "sha256": "306109a80512c6c6685bfcc00592fd81961030c459115717775ead6d79e8b4e7"}, - {"mode": "0644", "path": "schemas/tool-schema-descriptor.schema.json", "sha256": "1ebf3da9f7e81fc7de2eb9c19769011e6dbb590323a17a1febb2def9c85d3c87"}, - {"mode": "0644", "path": "schemas/tool-schema-expansion-envelope.schema.json", "sha256": "870aaafcd8e40bad739ebd9de316fe4ef15dc2e46ccee015dcb9ad1d886b68d2"}, - {"mode": "0644", "path": "schemas/tool-schema-expansion-refusal.schema.json", "sha256": "e2bc67e71069d3f4c493db4e4aa946d65ee037eded5963ba00bf5c6bae51eefd"}, - {"mode": "0644", "path": "schemas/tool-schema-expansion-request.schema.json", "sha256": "0f21e070d4480279a849ba510c74cd26df8a1f8c0cfed5f0e7f73d6b9079dc39"}, - {"mode": "0644", "path": "schemas/tool-schema-receipt.schema.json", "sha256": "08621631baf4bc9abd01681c7e5194a73c6d7dd6f42571e7b91baffe323e2745"}, - {"mode": "0644", "path": "schemas/tool-schema-reference.schema.json", "sha256": "09f047b9d935e49e9b50e8e13e792a99bfe72eb356c1ad87e51dc0d0ac47f571"}, - {"mode": "0644", "path": "schemas/twin-event.schema.json", "sha256": "fb74363bd595b8f8034ba33622bfa4018f566b75491232b462e8674de13671fb"}, - {"mode": "0644", "path": "schemas/twin-metadata.schema.json", "sha256": "ab137d319fd151beaf9ae595633ab2d2be748b4efb6e1636b3052b663da6f9b8"}, - {"mode": "0644", "path": "schemas/twin-request.schema.json", "sha256": "0955de5d331555fb5662a3038022df6a7cf679692f77d4747923fbd9121d9acd"}, - {"mode": "0644", "path": "schemas/twin-result.schema.json", "sha256": "14b7cec1a2818d1fa0fac61b05dd77ffc683a01812372b64a8c5f24660b73735"}, - {"mode": "0644", "path": "schemas/twin-snapshot.schema.json", "sha256": "c80da58c9c3ef2d49fdf0527d3310b611c87fde6c9fc6e9ee889d2cb127b65ff"}, - {"mode": "0644", "path": "schemas/typed-blueprint.schema.json", "sha256": "d784099a65a700d9e9e72ea6993b8480c9bf7c7efa2f222a7f341a271526b97c"} + { + "mode": "0644", + "path": "LICENSE", + "sha256": "c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4" + }, + { + "mode": "0644", + "path": "NOTICE", + "sha256": "40978c42e96a7b452cb77ef41f28961ca880e46ee7fa7c9589afa4d532655779" + }, + { + "mode": "0644", + "path": "README.md", + "sha256": "e8e43ac7c76bb032080eed9ccb4be00f2c5c840fb44bba93f4e1c93f666d4a89" + }, + { + "mode": "0755", + "path": "bin/context-guard-receipt-mcp.cjs", + "sha256": "883b893d5ee484d63b78174ace60e171dc26e032d05dd19298fb6d6c5229cffd" + }, + { + "mode": "0755", + "path": "bin/context-guard-receipt.cjs", + "sha256": "bdab50b0476e40024ea64f1f6cd0a46260b4707e2297d212bf5034cfd5a87ff8" + }, + { + "mode": "0644", + "path": "bin/launcher.cjs", + "sha256": "4282241f133eda1745da80f85c982ef0f6be68087f3eaa6fd230d4948abfe6ec" + }, + { + "mode": "0644", + "path": "package.json", + "sha256": "daf789323e9b194943b7222bd0bf112432460afe0174d0a3363cbadbbd37c475" + }, + { + "mode": "0644", + "path": "python/context_guard_receipt/__init__.py", + "sha256": "1046588c63e24a72c3a57ab0ebd6d60d86c158358b5bbd50ca15cf26322fabc6" + }, + { + "mode": "0644", + "path": "python/context_guard_receipt/assembly.py", + "sha256": "0e28b6e0874477314436eecb532c767d61efe6d506ae8f79d98fae4b41dd35ea" + }, + { + "mode": "0644", + "path": "python/context_guard_receipt/blueprint.py", + "sha256": "f4b8b617832ebe4bd5dc585f762a20b71b37ce79d54b6cd751f1e5fde5b785f0" + }, + { + "mode": "0644", + "path": "python/context_guard_receipt/bootstrap.py", + "sha256": "fa846a8968c5199618ab68a86424c0cb88c32250291faf3ac37f26d14d4b018e" + }, + { + "mode": "0644", + "path": "python/context_guard_receipt/canonical.py", + "sha256": "91b57a1ebf2cc8fa0025ccfc8eaf6f50bc9363e6d3bc05c517b2014bf8a590c7" + }, + { + "mode": "0644", + "path": "python/context_guard_receipt/cli.py", + "sha256": "0a60b550aa620e029fdc809749160d1fb3382864557e6016d56b4780c8f4c430" + }, + { + "mode": "0644", + "path": "python/context_guard_receipt/cli_io.py", + "sha256": "2de5ef56762e015264527306f19b1b72995cc3fffd8cd6cb58c8206e255c5baf" + }, + { + "mode": "0644", + "path": "python/context_guard_receipt/contracts.py", + "sha256": "1127a9b90bf2da63a097b066c7f1678109dcf622f40dd6746ef055aa7a98e39e" + }, + { + "mode": "0644", + "path": "python/context_guard_receipt/diagnostic_ledger.py", + "sha256": "3cc7865709c273b72136c48b1026ed5cd2830ea1bf76da4e424da08ccc13499d" + }, + { + "mode": "0644", + "path": "python/context_guard_receipt/diagnostics.py", + "sha256": "9a95f511b639091d0aacef69c0d4a311ad81e5a97299ab45ddc7fc23579e0e52" + }, + { + "mode": "0644", + "path": "python/context_guard_receipt/evidence_pack.py", + "sha256": "3fb5540dcee31cd6ded4883e4f4c99fb89ee17c2484f3e2ee33ebe741454d0f8" + }, + { + "mode": "0644", + "path": "python/context_guard_receipt/execution_twin.py", + "sha256": "510239b13c37ef15dcc838222b07ada49877e5540c351a51a121983b1fe031af" + }, + { + "mode": "0644", + "path": "python/context_guard_receipt/expansion.py", + "sha256": "9b848e555f05a621665c6b167a49e5d8085ffc9c6906f040f43fd2a87e981f2b" + }, + { + "mode": "0644", + "path": "python/context_guard_receipt/identity.py", + "sha256": "31d4a0ba5e2a04b277a027a872ee0172c5d27ed09b60c41f53f286dd2d8b963c" + }, + { + "mode": "0644", + "path": "python/context_guard_receipt/mcp.py", + "sha256": "db251fdd3e3d98cd83fd9a29ee0b90cb308c1bfa3fbed9122a217c80e75fe4c2" + }, + { + "mode": "0644", + "path": "python/context_guard_receipt/merged_capture.py", + "sha256": "a19c605a47b666f302b8b993d1e0973bfded46c1974022c2620c5ef5d598b7cf" + }, + { + "mode": "0644", + "path": "python/context_guard_receipt/phase_evaluation.py", + "sha256": "e9e6747e1955789793a22826b71f73265607fd80ab30bf48f6dfae05852f1104" + }, + { + "mode": "0644", + "path": "python/context_guard_receipt/protection.py", + "sha256": "67ae06abb102292b3db09a6731a4aab90b3bc6ceb6dbe836fc636f82f783c347" + }, + { + "mode": "0644", + "path": "python/context_guard_receipt/receipts.py", + "sha256": "11c02d9df36be0dec2316594fd083ec39a1284325ded440de075081d2e56ddb0" + }, + { + "mode": "0644", + "path": "python/context_guard_receipt/reference_expiry.py", + "sha256": "2445292456776d5fcbf789f75a71781d64f12865958249d192cfc5a5ff27f2f6" + }, + { + "mode": "0644", + "path": "python/context_guard_receipt/router.py", + "sha256": "22b395d0a8a0522fcc9b12c1b12493e90aafb9e374937725a2bdaf223188529c" + }, + { + "mode": "0644", + "path": "python/context_guard_receipt/runner.py", + "sha256": "2193f7ae1032990b2ff5d954bd97ea6de67d58a9eb2cf395e939b25593a79cf8" + }, + { + "mode": "0644", + "path": "python/context_guard_receipt/sanitizer.py", + "sha256": "ddf7d4d81dbb73156fa2274c7adf06475c4688b1e08341835aff4eeb81a72fc8" + }, + { + "mode": "0644", + "path": "python/context_guard_receipt/store.py", + "sha256": "6bf5f033ebc1cfaf72dbfa685ed4ce3339dfe0af53b88bab726ed0847767e8ed" + }, + { + "mode": "0644", + "path": "python/context_guard_receipt/tool_schemas.py", + "sha256": "f84a8bc2f2232250dfe0782aaddf35c9842720f4815c6d2d8e4bd95757546bbc" + }, + { + "mode": "0644", + "path": "schemas/assembly-receipt.schema.json", + "sha256": "05ab76b261ca18ed8d165cb4e43395006e7196fdeccb53603c3ed77ca3bdfe88" + }, + { + "mode": "0644", + "path": "schemas/blueprint-descriptor.schema.json", + "sha256": "4424c2c482dc8d4184f1bd7ac6e1e45ad4ee36ee97da13e75b0986b2da8c9b09" + }, + { + "mode": "0644", + "path": "schemas/capability-record.schema.json", + "sha256": "86df8398c5199a0d4e3d58ee7d8e2a4171e0103a5ea05644f00f1c343889c114" + }, + { + "mode": "0644", + "path": "schemas/command-capture-receipt.schema.json", + "sha256": "7bcdaeb52fdfa4cbb3dc57b8d4b3b1cfa318bb7d8af11574ae0e23126ffa954b" + }, + { + "mode": "0644", + "path": "schemas/diagnostic-ledger-entry.schema.json", + "sha256": "8ea3ee4db48fb6d54b1bb613253f3313a38d33516ff328887feb6dfcc5c6c2ef" + }, + { + "mode": "0644", + "path": "schemas/diagnostic-ledger-inspection.schema.json", + "sha256": "2258c63aba7ada14949fe7db2e757d42551009d026d3152e3d95191c934b110f" + }, + { + "mode": "0644", + "path": "schemas/diagnostic-ledger-metadata.schema.json", + "sha256": "2ab1092790c97e0aa9439dd6f1f59004368a9e71e9b7dc1d849a2d2f59369e2a" + }, + { + "mode": "0644", + "path": "schemas/diagnostics-report.schema.json", + "sha256": "b779475abbfdd76c9b6fca8f39b9b0c4e058f8e65a0ff9d7f583a1b8b01db38c" + }, + { + "mode": "0644", + "path": "schemas/diagnostics-request.schema.json", + "sha256": "7779d364170db90b8e7b71a342156d0b5bb0fe8ff8b423c21df29005d7efa2b4" + }, + { + "mode": "0644", + "path": "schemas/evidence-boundary.schema.json", + "sha256": "b510303bd09adcaf7150415aab5cae3adbe4c99b8482c07a45bb978ad4e82ba7" + }, + { + "mode": "0644", + "path": "schemas/evidence-descriptor.schema.json", + "sha256": "29fa127eeafb8c52c05c7cdc8b1b929919e47e8e94aa8a5e6cd81ea2cf973dff" + }, + { + "mode": "0644", + "path": "schemas/evidence-pack.schema.json", + "sha256": "5ff6823d166b245a488e6d0f96512ae025b7836f7f46e5e14dc4508edfad6692" + }, + { + "mode": "0644", + "path": "schemas/evidence-reference.schema.json", + "sha256": "f94fa353dac99a08793461ca9ec72962ce12de2e5328f94039048190db70071e" + }, + { + "mode": "0644", + "path": "schemas/expansion-envelope.schema.json", + "sha256": "f838f84a06a433e62706467aa40097194f458bb2b3d42c600159558bed292d71" + }, + { + "mode": "0644", + "path": "schemas/expansion-refusal.schema.json", + "sha256": "c5196da89d9b96349deb4c2c0ad2970d6f27d7760f9236b6d07d702443ee9da0" + }, + { + "mode": "0644", + "path": "schemas/phase-evaluation-p2.schema.json", + "sha256": "d4390e71109e2704c4bc6f0935997d2b4b3f7d7cfc49ed92ef05e27eb21807bd" + }, + { + "mode": "0644", + "path": "schemas/phase-evaluation-p3.schema.json", + "sha256": "ac0687ce2cd43ec4954d3fb0a876284fa75829435244913b5f81b275a4c234d7" + }, + { + "mode": "0644", + "path": "schemas/phase-evaluation-p4.schema.json", + "sha256": "58bb474f5655b60155aeba0a7dc135697cb52c08364692bf10a14ec5ca68fdda" + }, + { + "mode": "0644", + "path": "schemas/phase-evaluation-p5.schema.json", + "sha256": "b4e7f888c8a041065af130c808d8bb47c5eb42e395e18d8f8c53ea4c7eac1457" + }, + { + "mode": "0644", + "path": "schemas/phase-evaluation-p6.schema.json", + "sha256": "ed19929a20da8609c472f2d96ca5de9e32f7d32365b640876c9dbb22d9e33b00" + }, + { + "mode": "0644", + "path": "schemas/phase-evaluation-result.schema.json", + "sha256": "a608f3426c7a4814f7d081be2963b979d03e895a6e44e85058aa67ead43368af" + }, + { + "mode": "0644", + "path": "schemas/protection-decision.schema.json", + "sha256": "e7cf1b413d286347fda8f0f3a993676212e257f7e280757657032c23b5f9415f" + }, + { + "mode": "0644", + "path": "schemas/reference-expiry-inspection.schema.json", + "sha256": "6f862e4e39ebb09e14952b542d4a28a52c618900ecfa07dca063846c638721e1" + }, + { + "mode": "0644", + "path": "schemas/reference-expiry-metadata.schema.json", + "sha256": "a72ed7c5f422732437cdc9e61e00efc5ea7e765c74955243f5b11b8a6eb12a73" + }, + { + "mode": "0644", + "path": "schemas/reference-expiry-record.schema.json", + "sha256": "450940d3f9d6d0baf7540c2bb2269f23ce8c53106658d7e9fdfe80392b7bad0e" + }, + { + "mode": "0644", + "path": "schemas/reference-expiry-request.schema.json", + "sha256": "9b96d2dac7ed9e23af17fbcb9311b4d2a5f3c7c04d042de380af3732887d6c89" + }, + { + "mode": "0644", + "path": "schemas/reference-expiry-result.schema.json", + "sha256": "ed0c72aed6f21fdd3d78332768981da8db19e69a130cb881d3b86b7d61c13d82" + }, + { + "mode": "0644", + "path": "schemas/shadow-firewall-report.schema.json", + "sha256": "016a0d7320b9dc8c444f7488fdcd8bd33752fcfd27c1e906741972b9de50d04c" + }, + { + "mode": "0644", + "path": "schemas/source-identity.schema.json", + "sha256": "c20007a9a03e8168feb7b413e035e1d3ef2cdad23a7c404dc25014a03411b047" + }, + { + "mode": "0644", + "path": "schemas/store-commit.schema.json", + "sha256": "e078e14eade2395772936ecd8ec8a9add8b4a71ea45a1b6935645a83a46147ad" + }, + { + "mode": "0644", + "path": "schemas/store-metadata.schema.json", + "sha256": "be6a83707fa541436e5930e444cfc6431d618ef65f561718a5ed66bf42f447db" + }, + { + "mode": "0644", + "path": "schemas/tool-schema-bundle.schema.json", + "sha256": "bebb1d2ef79cfd76a870f6be554f7e1708e5015912885adc720ab3bc9495428d" + }, + { + "mode": "0644", + "path": "schemas/tool-schema-catalog-reference.schema.json", + "sha256": "306109a80512c6c6685bfcc00592fd81961030c459115717775ead6d79e8b4e7" + }, + { + "mode": "0644", + "path": "schemas/tool-schema-descriptor.schema.json", + "sha256": "1ebf3da9f7e81fc7de2eb9c19769011e6dbb590323a17a1febb2def9c85d3c87" + }, + { + "mode": "0644", + "path": "schemas/tool-schema-expansion-envelope.schema.json", + "sha256": "870aaafcd8e40bad739ebd9de316fe4ef15dc2e46ccee015dcb9ad1d886b68d2" + }, + { + "mode": "0644", + "path": "schemas/tool-schema-expansion-refusal.schema.json", + "sha256": "e2bc67e71069d3f4c493db4e4aa946d65ee037eded5963ba00bf5c6bae51eefd" + }, + { + "mode": "0644", + "path": "schemas/tool-schema-expansion-request.schema.json", + "sha256": "0f21e070d4480279a849ba510c74cd26df8a1f8c0cfed5f0e7f73d6b9079dc39" + }, + { + "mode": "0644", + "path": "schemas/tool-schema-receipt.schema.json", + "sha256": "08621631baf4bc9abd01681c7e5194a73c6d7dd6f42571e7b91baffe323e2745" + }, + { + "mode": "0644", + "path": "schemas/tool-schema-reference.schema.json", + "sha256": "09f047b9d935e49e9b50e8e13e792a99bfe72eb356c1ad87e51dc0d0ac47f571" + }, + { + "mode": "0644", + "path": "schemas/twin-event.schema.json", + "sha256": "fb74363bd595b8f8034ba33622bfa4018f566b75491232b462e8674de13671fb" + }, + { + "mode": "0644", + "path": "schemas/twin-metadata.schema.json", + "sha256": "ab137d319fd151beaf9ae595633ab2d2be748b4efb6e1636b3052b663da6f9b8" + }, + { + "mode": "0644", + "path": "schemas/twin-request.schema.json", + "sha256": "0955de5d331555fb5662a3038022df6a7cf679692f77d4747923fbd9121d9acd" + }, + { + "mode": "0644", + "path": "schemas/twin-result.schema.json", + "sha256": "14b7cec1a2818d1fa0fac61b05dd77ffc683a01812372b64a8c5f24660b73735" + }, + { + "mode": "0644", + "path": "schemas/twin-snapshot.schema.json", + "sha256": "c80da58c9c3ef2d49fdf0527d3310b611c87fde6c9fc6e9ee889d2cb127b65ff" + }, + { + "mode": "0644", + "path": "schemas/typed-blueprint.schema.json", + "sha256": "d784099a65a700d9e9e72ea6993b8480c9bf7c7efa2f222a7f341a271526b97c" + } ], "schema_version": "contextguard-receipt-package-files/v1" } diff --git a/packages/context-guard-receipt/python/context_guard_receipt/cli.py b/packages/context-guard-receipt/python/context_guard_receipt/cli.py index 7e0d34f4..c779cddc 100644 --- a/packages/context-guard-receipt/python/context_guard_receipt/cli.py +++ b/packages/context-guard-receipt/python/context_guard_receipt/cli.py @@ -52,7 +52,7 @@ ) -HELP = """usage: context-guard-receipt \n\nCommands:\n inspect boundary\n assemble --kind --descriptor --root [options]\n run --escrow --root --state-dir [--timeout-seconds --max-channel-bytes --max-total-bytes ] -- [args...]\n expand --root --state-dir [options]\n expand tool-schema --request --root --state-dir [options]\n import merged-capture --spool --transaction-id <64hex> --root --state-dir [--disclosure-days 7]\n recover merged-capture --transaction-id <64hex> --root --state-dir \n inspect merged-capture-import --root --state-dir \n inspect diagnostics --input [--state-scope durable --root --state-dir ]\n inspect firewall --input \n inspect diagnostic-ledger --state-scope durable --root --state-dir [--limit ]\n inspect twin --experimental-twin --input --root --state-dir \n inspect twin --experimental-twin --root --state-dir [--limit ]\n inspect reference-expiry --experimental-reference-expiry --input --root --state-dir \n inspect reference-expiry --experimental-reference-expiry --root --state-dir [--limit ]\n inspect [options]\n\nEvidence, blueprint, and tool-schema assembly plus exact local expansion are available. Run is explicit local capture only. Merged-capture import accepts only a completed private canonical sanitized UTF-8 spool and applies a fixed seven-day reference deadline. Diagnostics, firewall findings, and the experimental twin are advisory and non-applying. Experimental reference expiry revokes only compact local references and retains artifacts. The companion is provider-free and makes no host-request, network, or token-saving claim. Remaining commands are inert.\n""" +HELP = """usage: context-guard-receipt \n\nCommands:\n inspect boundary\n evaluate phase --input \n assemble --kind --descriptor --root [options]\n run --escrow --root --state-dir [--timeout-seconds --max-channel-bytes --max-total-bytes ] -- [args...]\n expand --root --state-dir [options]\n expand tool-schema --request --root --state-dir [options]\n import merged-capture --spool --transaction-id <64hex> --root --state-dir [--disclosure-days 7]\n recover merged-capture --transaction-id <64hex> --root --state-dir \n inspect merged-capture-import --root --state-dir \n inspect diagnostics --input [--state-scope durable --root --state-dir ]\n inspect firewall --input \n inspect diagnostic-ledger --state-scope durable --root --state-dir [--limit ]\n inspect twin --experimental-twin --input --root --state-dir \n inspect twin --experimental-twin --root --state-dir [--limit ]\n inspect reference-expiry --experimental-reference-expiry --input --root --state-dir \n inspect reference-expiry --experimental-reference-expiry --root --state-dir [--limit ]\n inspect [options]\n\nEvidence, blueprint, and tool-schema assembly plus exact local expansion are available. Run is explicit local capture only. Merged-capture import accepts only a completed private canonical sanitized UTF-8 spool and applies a fixed seven-day reference deadline. Diagnostics, firewall findings, and the experimental twin are advisory and non-applying. Experimental reference expiry revokes only compact local references and retains artifacts. The companion is provider-free and makes no host-request, network, or token-saving claim. Remaining commands are inert.\n""" MCP_HELP = """usage: context-guard-receipt-mcp --root \n\nRun the bounded local stdio MCP surface for one fixed repository root. Capabilities are process-local and expire when the process exits. No registration, provider, model, credential, or network access is performed.\n""" ASSEMBLY_KINDS = frozenset({"evidence", "blueprint", "tool-schemas"}) @@ -83,6 +83,14 @@ _RUN_MAX_CAPTURE_BYTES = 900_000 _TWIN_REQUEST_MAX_BYTES = 64 * 1024 _REFERENCE_EXPIRY_REQUEST_MAX_BYTES = 4096 +_PHASE_EVALUATION_MAX_BYTES = 2 * 1024 * 1024 +_PHASE_EVALUATION_LIMITS = JSONLimits( + max_document_bytes=_PHASE_EVALUATION_MAX_BYTES, + max_depth=16, + max_total_values=250_000, + max_object_members=32, + max_string_bytes=1024, +) _BASH_REFERENCE_BROKER_READY = ( b"READY contextguard-bash-reference-broker/v1\n" ) @@ -278,6 +286,15 @@ def _valid_run(arguments: Sequence[str]) -> bool: return _parse_run_invocation(arguments) is not None +def _valid_evaluate(arguments: Sequence[str]) -> bool: + return ( + len(arguments) == 3 + and arguments[0] == "phase" + and arguments[1] == "--input" + and _is_file_argument(arguments[2]) + ) + + def _valid_expand(arguments: Sequence[str]) -> bool: if arguments and arguments[0] == "tool-schema": seen = _parse_options( @@ -1423,6 +1440,46 @@ def _inspect_merged_capture(arguments: Sequence[str]) -> int: return 0 +def _evaluate_phase(arguments: Sequence[str]) -> int: + operation = "evaluate_phase" + try: + raw = read_descriptor( + arguments[2], maximum_bytes=_PHASE_EVALUATION_MAX_BYTES + ) + record = parse_canonical_json_bytes(raw, _PHASE_EVALUATION_LIMITS) + except CliIOError: + return emit_error(operation, "error", "evaluation_input_unavailable", 74) + except CanonicalJSONError: + return emit_error(operation, "error", "evaluation_input_rejected", 65) + + phase_id = record.get("phase_id") if type(record) is dict else None + try: + from .phase_evaluation import ( + evaluate_p2, + evaluate_p3, + evaluate_p4, + evaluate_p5, + evaluate_p6, + ) + + evaluator = { + "p2": evaluate_p2, + "p3": evaluate_p3, + "p4": evaluate_p4, + "p5": evaluate_p5, + "p6": evaluate_p6, + }.get(phase_id) + if evaluator is None: + return emit_error(operation, "error", "evaluation_phase_rejected", 65) + payload = canonical_json_bytes(evaluator(record)) + write_stdout(payload) + except CliIOError: + return emit_error(operation, "error", "evaluation_delivery_failed", 74) + except Exception: + return emit_error(operation, "error", "evaluation_internal_failure", 70) + return 0 + + def receipt_main(arguments: Sequence[str]) -> int: arguments = tuple(arguments) if arguments and arguments[0] == "--private-bash-reference-broker-v1": @@ -1435,6 +1492,8 @@ def receipt_main(arguments: Sequence[str]) -> int: if arguments == ("inspect", "boundary"): print(canonical_json(response(operation="inspect_boundary", status="ok")), end="") return 0 + if arguments and arguments[0] == "evaluate" and _valid_evaluate(arguments[1:]): + return _evaluate_phase(arguments[1:]) if arguments and arguments[0] == "assemble" and _valid_assemble(arguments[1:]): return _assemble(arguments[1:]) if arguments and arguments[0] == "run" and _valid_run(arguments[1:]): diff --git a/packages/context-guard-receipt/python/context_guard_receipt/phase_evaluation.py b/packages/context-guard-receipt/python/context_guard_receipt/phase_evaluation.py new file mode 100644 index 00000000..65f492f2 --- /dev/null +++ b/packages/context-guard-receipt/python/context_guard_receipt/phase_evaluation.py @@ -0,0 +1,1227 @@ +"""Pure, fail-closed P2-P6 evaluation over caller-supplied local records. + +The module computes eligibility only. Caller-supplied records can never grant +runtime activation or public-claim authority. +""" + +from __future__ import annotations + +import re +from typing import Final + + +_DIGEST: Final = re.compile(r"sha256:[0-9a-f]{64}\Z") +_IDENTIFIER: Final = re.compile(r"[A-Za-z0-9][A-Za-z0-9._-]{0,63}\Z") +_MAX_RECORDS: Final = 10_000 +_P2_TOP: Final = frozenset( + { + "schema_version", + "phase_id", + "baseline_fallback_verified", + "activation_authorized", + "dependency_gates_passed", + "observed_at", + "minimum_recall_basis_points", + "records", + } +) +_P2_ROW: Final = frozenset( + { + "record_id", + "stratum", + "relevant", + "candidate_omission", + "recalled", + "source_digest", + "rehydrated_digest", + "fresh_until", + "protection", + "construction_cost_microunits", + } +) +_P3_TOP: Final = frozenset( + { + "schema_version", + "phase_id", + "baseline_fallback_verified", + "activation_authorized", + "dependency_gates_passed", + "claim_scope_bound", + "evidence_origin", + "matched_pair_ids", + "attempts", + "thresholds", + } +) +_P3_ATTEMPT: Final = frozenset( + { + "attempt_id", + "pair_id", + "arm", + "task_success", + "corrections", + "retrievals", + "measurement", + } +) +_P3_RETRIEVAL: Final = frozenset({"retrieval_id", "exact"}) +_P3_MEASUREMENT: Final = frozenset( + { + "primary_tokens", + "provider_cost_microunits", + "retry_cost_microunits", + "correction_cost_microunits", + "retrieval_cost_microunits", + "external_cost_microunits", + "local_compute_cost_microunits", + } +) +_P3_COST_FIELDS: Final = _P3_MEASUREMENT - {"primary_tokens"} +_P3_THRESHOLDS: Final = frozenset( + { + "maximum_failure_rate_increase_basis_points", + "require_corrections_non_inferior", + "require_fully_loaded_cost_improvement", + } +) +_P4_TOP: Final = frozenset( + { + "schema_version", + "phase_id", + "baseline_fallback_verified", + "dependency_gates_passed", + "activation_authorized", + "minimum_confidence_basis_points", + "trials", + } +) +_P4_TRIAL: Final = frozenset( + { + "trial_id", + "advisory_status", + "advisory_route", + "confidence_basis_points", + "bypass_reasons", + "outcomes", + } +) +_P4_OUTCOMES: Final = frozenset( + {"advisory", "always_pass_through", "always_on"} +) +_P4_OUTCOME: Final = frozenset( + {"quality_basis_points", "total_cost_microunits", "cache_accounting"} +) +_P4_CACHE: Final = frozenset( + { + "creation_microunits", + "read_microunits", + "invalidation_microunits", + "latency_microunits", + "provider_cost_microunits", + } +) +_P5_TOP: Final = frozenset( + { + "schema_version", + "phase_id", + "dependency_gates_passed", + "activation_authorized", + "current_revision_digest", + "current_source_digest", + "current_test_digest", + "adjuncts", + } +) +_P5_ADJUNCT_IDS: Final = frozenset( + {"execution_twin", "failure_cone", "typed_blueprint"} +) +_P5_ADJUNCT: Final = frozenset( + { + "adjunct_id", + "revision_digest", + "source_digest", + "test_digest", + "evidence_digests", + "failure_cases", + "bypass_verified", + "fallback_verified", + "baseline_quality_basis_points", + "adjunct_quality_basis_points", + "baseline_cost_microunits", + "adjunct_cost_microunits", + } +) +_P5_FAILURE_CASE: Final = frozenset( + {"case_id", "exit_status", "root_cause", "duplicate_of"} +) +_P6_TOP: Final = frozenset( + {"schema_version", "phase_id", "dependency_gates_passed", "tracks"} +) +_P6_TRACK_IDS: Final = frozenset( + { + "context_leases", + "scout_surgeon", + "counterfactual_ledger", + "negative_firewall", + "bounded_compilation", + } +) +_P6_TRACK: Final = frozenset( + { + "track_id", + "surface", + "workload_digest", + "baseline_digest", + "scope_digest", + "privacy_boundary_digest", + "privacy_verified", + "baseline_quality_basis_points", + "track_quality_basis_points", + "population_count", + "baseline_failure_count", + "track_failure_count", + "maximum_failure_rate_increase_basis_points", + "baseline_corrections", + "track_corrections", + "cost_model_digest", + "baseline_cost_microunits", + "track_cost_microunits", + "fallback_verified", + "rollback_verified", + "activation_authorized", + "provider_evidence_digest", + } +) + + +def _exact_dict(value: object, keys: frozenset[str]) -> bool: + return type(value) is dict and set(value) == keys + + +def _valid_identifier(value: object) -> bool: + return type(value) is str and _IDENTIFIER.fullmatch(value) is not None + + +def _valid_digest(value: object) -> bool: + return type(value) is str and _DIGEST.fullmatch(value) is not None + + +def _nonnegative_integer(value: object) -> bool: + return type(value) is int and value >= 0 + + +def _deduplicate(values: list[str]) -> list[str]: + return list(dict.fromkeys(values)) + + +def _closed_result(phase_id: str, blockers: list[str], **values: object) -> dict[str, object]: + result: dict[str, object] = { + "schema_version": "contextguard.phase-evaluation.result/v1", + "phase_id": phase_id, + "implementation_readiness": False, + "evaluation_evidence_complete": False, + "provider_evidence": False, + "activation_eligibility": False, + "activation_authority": False, + "claim_authority": False, + "fallback": "exact_unchanged_baseline", + "blockers": _deduplicate(blockers), + } + result.update(values) + return result + + +def evaluate_p2(record: object) -> dict[str, object]: + """Evaluate P2 shadow recall and exact rehydration without applying a route.""" + + if not _exact_dict(record, _P2_TOP): + return _closed_result( + "p2", + ["malformed_record", "external_activation_authority_required"], + evaluated_record_count=0, + construction_cost_microunits=0, + strata=[], + ) + data = record + blockers: list[str] = [] + if ( + data["schema_version"] != "contextguard.phase-evaluation.p2/v1" + or data["phase_id"] != "p2" + ): + blockers.append("malformed_record") + for field in ( + "baseline_fallback_verified", + "activation_authorized", + "dependency_gates_passed", + ): + if type(data[field]) is not bool: + blockers.append("malformed_record") + + observed_at = data["observed_at"] + threshold = data["minimum_recall_basis_points"] + rows = data["records"] + if not _nonnegative_integer(observed_at): + blockers.append("malformed_record") + observed_at = 0 + if type(threshold) is not int or not 0 <= threshold <= 10_000: + blockers.append("malformed_record") + threshold = 10_000 + if type(rows) is not list or not rows or len(rows) > _MAX_RECORDS: + blockers.append("malformed_record") + rows = [] + + record_ids: set[str] = set() + stratum_counts: dict[str, list[int]] = {} + construction_cost = 0 + for row in rows: + if not _exact_dict(row, _P2_ROW): + blockers.append("malformed_record") + continue + record_id = row["record_id"] + stratum = row["stratum"] + if not _valid_identifier(record_id) or record_id in record_ids: + blockers.append("malformed_record") + else: + record_ids.add(record_id) + if not _valid_identifier(stratum): + blockers.append("malformed_record") + continue + if any( + type(row[field]) is not bool + for field in ("relevant", "candidate_omission", "recalled") + ): + blockers.append("malformed_record") + continue + if not _valid_digest(row["source_digest"]): + blockers.append("non_rehydratable_record") + if not _nonnegative_integer(row["fresh_until"]) or row["fresh_until"] <= observed_at: + blockers.append("stale_record") + protection = row["protection"] + if protection not in {"eligible", "protected", "ambiguous"}: + blockers.append("malformed_record") + if not _nonnegative_integer(row["construction_cost_microunits"]): + blockers.append("construction_cost_incomplete") + else: + construction_cost += row["construction_cost_microunits"] + + if row["candidate_omission"]: + if protection != "eligible": + blockers.append("protected_omission") + if not _valid_digest(row["rehydrated_digest"]): + blockers.append("non_rehydratable_record") + elif row["rehydrated_digest"] != row["source_digest"]: + blockers.append("non_rehydratable_record") + elif row["rehydrated_digest"] is not None: + blockers.append("malformed_record") + + counts = stratum_counts.setdefault(stratum, [0, 0]) + if row["relevant"]: + counts[0] += 1 + if row["recalled"]: + counts[1] += 1 + + strata: list[dict[str, object]] = [] + if not any(relevant for relevant, _ in stratum_counts.values()): + blockers.append("recall_unavailable") + for stratum in sorted(stratum_counts): + relevant, recalled = stratum_counts[stratum] + if relevant == 0: + continue + recall_basis_points = recalled * 10_000 // relevant + threshold_passed = recall_basis_points >= threshold + if not threshold_passed: + blockers.append("recall_threshold_failed") + strata.append( + { + "stratum": stratum, + "relevant_record_count": relevant, + "recalled_relevant_record_count": recalled, + "recall_basis_points": recall_basis_points, + "threshold_passed": threshold_passed, + } + ) + if data["baseline_fallback_verified"] is not True: + blockers.append("exact_fallback_unverified") + + local_blockers = { + "malformed_record", + "non_rehydratable_record", + "stale_record", + "protected_omission", + "construction_cost_incomplete", + "recall_unavailable", + "recall_threshold_failed", + "exact_fallback_unverified", + } + local_ready = not any(blocker in local_blockers for blocker in blockers) + activation_eligible = ( + local_ready + and data["dependency_gates_passed"] is True + and data["activation_authorized"] is True + ) + if data["dependency_gates_passed"] is not True: + blockers.append("dependency_gates_incomplete") + if data["activation_authorized"] is not True: + blockers.append("activation_not_recorded") + blockers.append("external_activation_authority_required") + return _closed_result( + "p2", + blockers, + implementation_readiness=local_ready, + evaluation_evidence_complete=local_ready, + activation_eligibility=activation_eligible, + evaluated_record_count=len(rows), + construction_cost_microunits=construction_cost, + strata=strata, + ) + + +def evaluate_p3(record: object) -> dict[str, object]: + """Evaluate P3 matched canary evidence and computed guardrails.""" + + if not _exact_dict(record, _P3_TOP): + return _closed_result( + "p3", + ["malformed_record", "external_activation_authority_required", "external_claim_authority_required"], + evaluated_attempt_count=0, + evaluated_pair_count=0, + evaluated_retrieval_count=0, + baseline_fully_loaded_cost_microunits=0, + canary_fully_loaded_cost_microunits=0, + ) + data = record + blockers: list[str] = [] + if ( + data["schema_version"] != "contextguard.phase-evaluation.p3/v1" + or data["phase_id"] != "p3" + ): + blockers.append("malformed_record") + for field in ( + "baseline_fallback_verified", + "activation_authorized", + "dependency_gates_passed", + "claim_scope_bound", + ): + if type(data[field]) is not bool: + blockers.append("malformed_record") + + pair_ids = data["matched_pair_ids"] + attempts = data["attempts"] + if ( + type(pair_ids) is not list + or not pair_ids + or len(pair_ids) > _MAX_RECORDS + or any(not _valid_identifier(pair_id) for pair_id in pair_ids) + or len(set(pair_ids)) != len(pair_ids) + ): + blockers.append("malformed_record") + pair_ids = [] + if ( + type(attempts) is not list + or not attempts + or len(attempts) > _MAX_RECORDS * 2 + ): + blockers.append("malformed_record") + attempts = [] + + thresholds = data["thresholds"] + if not _exact_dict(thresholds, _P3_THRESHOLDS): + blockers.append("malformed_record") + thresholds = {} + maximum_failure_increase = thresholds.get( + "maximum_failure_rate_increase_basis_points" + ) + if type(maximum_failure_increase) is not int or not 0 <= maximum_failure_increase <= 10_000: + blockers.append("malformed_record") + maximum_failure_increase = 0 + if thresholds.get("require_corrections_non_inferior") is not True: + blockers.append("required_guardrail_disabled") + if thresholds.get("require_fully_loaded_cost_improvement") is not True: + blockers.append("required_guardrail_disabled") + + expected_pairs = set(pair_ids) + attempt_ids: set[str] = set() + pair_members: dict[str, dict[str, dict[str, object]]] = {} + retrieval_count = 0 + measurement_complete = True + for attempt in attempts: + if not _exact_dict(attempt, _P3_ATTEMPT): + blockers.append("malformed_record") + continue + attempt_id = attempt["attempt_id"] + pair_id = attempt["pair_id"] + arm = attempt["arm"] + if not _valid_identifier(attempt_id) or attempt_id in attempt_ids: + blockers.append("malformed_record") + else: + attempt_ids.add(attempt_id) + if not _valid_identifier(pair_id) or pair_id not in expected_pairs: + blockers.append("matched_population_incomplete") + if arm not in {"baseline", "canary"}: + blockers.append("malformed_record") + elif _valid_identifier(pair_id) and pair_id in expected_pairs: + members = pair_members.setdefault(pair_id, {}) + if arm in members: + blockers.append("matched_population_incomplete") + else: + members[arm] = attempt + if type(attempt["task_success"]) is not bool: + blockers.append("malformed_record") + if not _nonnegative_integer(attempt["corrections"]): + blockers.append("malformed_record") + + retrievals = attempt["retrievals"] + if type(retrievals) is not list or len(retrievals) > _MAX_RECORDS: + blockers.append("malformed_record") + retrievals = [] + retrieval_ids: set[str] = set() + for retrieval in retrievals: + if not _exact_dict(retrieval, _P3_RETRIEVAL): + blockers.append("malformed_record") + continue + retrieval_id = retrieval["retrieval_id"] + if not _valid_identifier(retrieval_id) or retrieval_id in retrieval_ids: + blockers.append("malformed_record") + else: + retrieval_ids.add(retrieval_id) + if retrieval["exact"] is not True: + blockers.append("exact_retrieval_incomplete") + retrieval_count += 1 + + measurement = attempt["measurement"] + if not _exact_dict(measurement, _P3_MEASUREMENT) or any( + not _nonnegative_integer(measurement.get(field)) + for field in _P3_MEASUREMENT + ): + blockers.append("provider_measurement_incomplete") + measurement_complete = False + + if set(pair_members) != expected_pairs or any( + set(pair_members.get(pair_id, {})) != {"baseline", "canary"} + for pair_id in expected_pairs + ): + blockers.append("matched_population_incomplete") + if len(attempts) != len(expected_pairs) * 2: + blockers.append("matched_population_incomplete") + + baseline_failures = 0 + canary_failures = 0 + baseline_corrections = 0 + canary_corrections = 0 + baseline_cost = 0 + canary_cost = 0 + complete_pairs = 0 + for pair_id in pair_ids: + members = pair_members.get(pair_id, {}) + if set(members) != {"baseline", "canary"}: + continue + complete_pairs += 1 + baseline = members["baseline"] + canary = members["canary"] + if type(baseline["task_success"]) is bool: + baseline_failures += int(not baseline["task_success"]) + if type(canary["task_success"]) is bool: + canary_failures += int(not canary["task_success"]) + if _nonnegative_integer(baseline["corrections"]): + baseline_corrections += baseline["corrections"] + if _nonnegative_integer(canary["corrections"]): + canary_corrections += canary["corrections"] + for arm_name, member in (("baseline", baseline), ("canary", canary)): + measurement = member["measurement"] + if not _exact_dict(measurement, _P3_MEASUREMENT) or any( + not _nonnegative_integer(measurement.get(field)) + for field in _P3_MEASUREMENT + ): + continue + total = sum(measurement[field] for field in _P3_COST_FIELDS) + if arm_name == "baseline": + baseline_cost += total + else: + canary_cost += total + + if complete_pairs: + if ( + (canary_failures - baseline_failures) * 10_000 + > maximum_failure_increase * complete_pairs + ): + blockers.append("failure_guardrail_failed") + if canary_corrections > baseline_corrections: + blockers.append("correction_guardrail_failed") + if measurement_complete and canary_cost >= baseline_cost: + blockers.append("fully_loaded_cost_not_improved") + if data["evidence_origin"] != "provider_measured": + blockers.append("provider_measurement_incomplete") + measurement_complete = False + if data["baseline_fallback_verified"] is not True: + blockers.append("exact_fallback_unverified") + + local_blockers = { + "malformed_record", + "matched_population_incomplete", + "exact_retrieval_incomplete", + "exact_fallback_unverified", + "required_guardrail_disabled", + } + evaluation_blockers = local_blockers | { + "provider_measurement_incomplete", + "failure_guardrail_failed", + "correction_guardrail_failed", + "fully_loaded_cost_not_improved", + } + implementation_ready = not any(blocker in local_blockers for blocker in blockers) + provider_evidence = measurement_complete and not any( + blocker in { + "malformed_record", + "matched_population_incomplete", + "provider_measurement_incomplete", + } + for blocker in blockers + ) + evaluation_complete = not any(blocker in evaluation_blockers for blocker in blockers) + activation_eligible = ( + evaluation_complete + and data["dependency_gates_passed"] is True + and data["activation_authorized"] is True + and data["claim_scope_bound"] is True + ) + if data["dependency_gates_passed"] is not True: + blockers.append("dependency_gates_incomplete") + if data["activation_authorized"] is not True: + blockers.append("activation_not_recorded") + if data["claim_scope_bound"] is not True: + blockers.append("claim_scope_unbound") + blockers.extend( + ["external_activation_authority_required", "external_claim_authority_required"] + ) + return _closed_result( + "p3", + blockers, + implementation_readiness=implementation_ready, + evaluation_evidence_complete=evaluation_complete, + provider_evidence=provider_evidence, + activation_eligibility=activation_eligible, + evaluated_attempt_count=len(attempts), + evaluated_pair_count=complete_pairs, + evaluated_retrieval_count=retrieval_count, + baseline_failure_count=baseline_failures, + canary_failure_count=canary_failures, + baseline_correction_count=baseline_corrections, + canary_correction_count=canary_corrections, + baseline_fully_loaded_cost_microunits=baseline_cost, + canary_fully_loaded_cost_microunits=canary_cost, + ) + + +def evaluate_p4(record: object) -> dict[str, object]: + """Evaluate advisory router regret without changing a runtime route.""" + + empty_values = { + "runtime_route_changed": False, + "selected_route": "pass_through", + "evaluated_trial_count": 0, + "abstention_count": 0, + "failure_count": 0, + "confidence_basis_points": [], + "bypass_reason_counts": {}, + "trials": [], + } + if not _exact_dict(record, _P4_TOP): + return _closed_result( + "p4", + ["malformed_record", "external_activation_authority_required"], + **empty_values, + ) + data = record + blockers: list[str] = [] + if ( + data["schema_version"] != "contextguard.phase-evaluation.p4/v1" + or data["phase_id"] != "p4" + ): + blockers.append("malformed_record") + for field in ( + "baseline_fallback_verified", + "dependency_gates_passed", + "activation_authorized", + ): + if type(data[field]) is not bool: + blockers.append("malformed_record") + + minimum_confidence = data["minimum_confidence_basis_points"] + if type(minimum_confidence) is not int or not 0 <= minimum_confidence <= 10_000: + blockers.append("malformed_record") + minimum_confidence = 10_000 + trials = data["trials"] + if type(trials) is not list or not trials or len(trials) > _MAX_RECORDS: + blockers.append("malformed_record") + trials = [] + + trial_ids: set[str] = set() + reports: list[dict[str, object]] = [] + confidences: list[int] = [] + reason_counts: dict[str, int] = {} + abstention_count = 0 + failure_count = 0 + advisory_routes: set[str] = set() + all_trials_eligible = bool(trials) + for trial in trials: + reasons: list[str] = [] + regret: int | None = None + confidence = 0 + status: object = None + advisory_route: object = None + trial_id: object = None + if not _exact_dict(trial, _P4_TRIAL): + reasons.append("malformed_record") + else: + raw_trial_id = trial["trial_id"] + raw_status = trial["advisory_status"] + raw_advisory_route = trial["advisory_route"] + if not _valid_identifier(raw_trial_id) or raw_trial_id in trial_ids: + reasons.append("malformed_record") + else: + trial_id = raw_trial_id + trial_ids.add(trial_id) + if type(raw_status) is not str or raw_status not in { + "selected", + "abstained", + "failed", + }: + reasons.append("malformed_record") + else: + status = raw_status + if status == "selected": + if type(raw_advisory_route) is not str or raw_advisory_route not in { + "pass_through", + "on", + }: + reasons.append("malformed_record") + else: + advisory_route = raw_advisory_route + advisory_routes.add(advisory_route) + elif raw_advisory_route is not None: + reasons.append("malformed_record") + if status == "abstained": + abstention_count += 1 + reasons.append("abstained") + elif status == "failed": + failure_count += 1 + reasons.append("failed") + + confidence_value = trial["confidence_basis_points"] + if type(confidence_value) is not int or not 0 <= confidence_value <= 10_000: + reasons.append("malformed_record") + else: + confidence = confidence_value + confidences.append(confidence) + if confidence < minimum_confidence: + reasons.append("low_confidence") + + supplied_reasons = trial["bypass_reasons"] + if ( + type(supplied_reasons) is not list + or any(not _valid_identifier(reason) for reason in supplied_reasons) + ): + reasons.append("malformed_record") + else: + reasons.extend(supplied_reasons) + + outcomes = trial["outcomes"] + parsed: dict[str, tuple[int, int]] = {} + if not _exact_dict(outcomes, _P4_OUTCOMES): + reasons.append("cache_accounting_incomplete") + else: + for policy in sorted(_P4_OUTCOMES): + outcome = outcomes[policy] + if not _exact_dict(outcome, _P4_OUTCOME): + reasons.append("cache_accounting_incomplete") + continue + quality = outcome["quality_basis_points"] + total_cost = outcome["total_cost_microunits"] + accounting = outcome["cache_accounting"] + if ( + type(quality) is not int + or not 0 <= quality <= 10_000 + or not _nonnegative_integer(total_cost) + or not _exact_dict(accounting, _P4_CACHE) + or any( + not _nonnegative_integer(accounting.get(field)) + for field in _P4_CACHE + ) + ): + reasons.append("cache_accounting_incomplete") + continue + if total_cost != sum(accounting[field] for field in _P4_CACHE): + reasons.append("cache_accounting_incomplete") + continue + parsed[policy] = (quality, total_cost) + if set(parsed) == _P4_OUTCOMES: + fixed = min( + (parsed["always_pass_through"], parsed["always_on"]), + key=lambda value: (-value[0], value[1]), + ) + advisory = parsed["advisory"] + regret = fixed[1] - advisory[1] + if advisory[0] < fixed[0]: + reasons.append("quality_regression") + if regret < 0: + reasons.append("negative_regret") + + reasons = _deduplicate(reasons) + eligible = not reasons + if not eligible: + all_trials_eligible = False + evaluation_route = advisory_route if eligible else "pass_through" + reports.append( + { + "trial_id": trial_id, + "advisory_status": status, + "advisory_route": advisory_route, + "confidence_basis_points": confidence, + "regret_microunits": regret, + "evaluation_route": evaluation_route, + "bypass_reasons": reasons, + } + ) + for reason in reasons: + reason_counts[reason] = reason_counts.get(reason, 0) + 1 + if reason in { + "malformed_record", + "cache_accounting_incomplete", + "quality_regression", + "negative_regret", + "low_confidence", + "abstained", + "failed", + }: + blockers.append(reason) + + if data["baseline_fallback_verified"] is not True: + blockers.append("exact_fallback_unverified") + evaluation_blockers = { + "malformed_record", + "cache_accounting_incomplete", + "quality_regression", + "negative_regret", + "low_confidence", + "abstained", + "failed", + "exact_fallback_unverified", + } + evaluation_complete = all_trials_eligible and not any( + blocker in evaluation_blockers for blocker in blockers + ) + activation_eligible = ( + evaluation_complete + and data["dependency_gates_passed"] is True + and data["activation_authorized"] is True + ) + if data["dependency_gates_passed"] is not True: + blockers.append("dependency_gates_incomplete") + if data["activation_authorized"] is not True: + blockers.append("activation_not_recorded") + blockers.append("external_activation_authority_required") + selected_route = ( + next(iter(advisory_routes)) + if evaluation_complete and len(advisory_routes) == 1 + else "pass_through" + ) + return _closed_result( + "p4", + blockers, + implementation_readiness=not any( + blocker in {"malformed_record", "exact_fallback_unverified"} + for blocker in blockers + ), + evaluation_evidence_complete=evaluation_complete, + activation_eligibility=activation_eligible, + runtime_route_changed=False, + selected_route=selected_route, + evaluated_trial_count=len(trials), + abstention_count=abstention_count, + failure_count=failure_count, + confidence_basis_points=confidences, + bypass_reason_counts={key: reason_counts[key] for key in sorted(reason_counts)}, + trials=reports, + ) + + +def evaluate_p5(record: object) -> dict[str, object]: + """Evaluate each P5 adjunct independently without applying any adjunct.""" + + empty_values = { + "runtime_changed": False, + "evaluated_adjunct_count": 0, + "eligible_adjuncts": [], + "adjuncts": [], + } + if not _exact_dict(record, _P5_TOP): + return _closed_result( + "p5", + ["malformed_record", "external_activation_authority_required"], + **empty_values, + ) + data = record + phase_blockers: list[str] = [] + if ( + data["schema_version"] != "contextguard.phase-evaluation.p5/v1" + or data["phase_id"] != "p5" + ): + phase_blockers.append("malformed_record") + for field in ("dependency_gates_passed", "activation_authorized"): + if type(data[field]) is not bool: + phase_blockers.append("malformed_record") + for field in ( + "current_revision_digest", + "current_source_digest", + "current_test_digest", + ): + if not _valid_digest(data[field]): + phase_blockers.append("malformed_record") + + adjuncts = data["adjuncts"] + if type(adjuncts) is not list or len(adjuncts) != len(_P5_ADJUNCT_IDS): + phase_blockers.append("malformed_record") + adjuncts = [] + + reports: list[dict[str, object]] = [] + seen_adjuncts: set[str] = set() + eligible_adjuncts: list[str] = [] + for adjunct in adjuncts: + blockers = list(phase_blockers) + adjunct_id: object = None + if not _exact_dict(adjunct, _P5_ADJUNCT): + blockers.append("malformed_record") + else: + raw_adjunct_id = adjunct["adjunct_id"] + if ( + type(raw_adjunct_id) is not str + or raw_adjunct_id not in _P5_ADJUNCT_IDS + or raw_adjunct_id in seen_adjuncts + ): + blockers.append("malformed_record") + else: + adjunct_id = raw_adjunct_id + seen_adjuncts.add(adjunct_id) + + freshness_fields = ( + ("revision_digest", "current_revision_digest", "stale_revision"), + ("source_digest", "current_source_digest", "stale_source"), + ("test_digest", "current_test_digest", "stale_test_state"), + ) + for bound_field, current_field, blocker in freshness_fields: + if not _valid_digest(adjunct[bound_field]): + blockers.append("malformed_record") + elif adjunct[bound_field] != data[current_field]: + blockers.append(blocker) + + evidence = adjunct["evidence_digests"] + if ( + type(evidence) is not list + or not evidence + or len(evidence) > _MAX_RECORDS + or any(not _valid_digest(digest) for digest in evidence) + or len(set(evidence)) != len(evidence) + ): + blockers.append("evidence_incomplete") + + failures = adjunct["failure_cases"] + prior_failures: dict[str, tuple[int, str]] = {} + if type(failures) is not list or not failures or len(failures) > _MAX_RECORDS: + blockers.append("differentiation_incomplete") + else: + for failure in failures: + if not _exact_dict(failure, _P5_FAILURE_CASE): + blockers.append("differentiation_incomplete") + continue + case_id = failure["case_id"] + exit_status = failure["exit_status"] + root_cause = failure["root_cause"] + duplicate_of = failure["duplicate_of"] + if ( + not _valid_identifier(case_id) + or case_id in prior_failures + or not _nonnegative_integer(exit_status) + or not _valid_identifier(root_cause) + or (duplicate_of is not None and not _valid_identifier(duplicate_of)) + ): + blockers.append("differentiation_incomplete") + continue + if duplicate_of is not None: + original = prior_failures.get(duplicate_of) + if original is None: + blockers.append("differentiation_incomplete") + elif original != (exit_status, root_cause): + blockers.append("distinct_failure_deduplicated") + prior_failures[case_id] = (exit_status, root_cause) + + if adjunct["bypass_verified"] is not True: + blockers.append("bypass_unverified") + if adjunct["fallback_verified"] is not True: + blockers.append("exact_fallback_unverified") + baseline_quality = adjunct["baseline_quality_basis_points"] + adjunct_quality = adjunct["adjunct_quality_basis_points"] + if any( + type(value) is not int or not 0 <= value <= 10_000 + for value in (baseline_quality, adjunct_quality) + ): + blockers.append("quality_evidence_incomplete") + elif adjunct_quality < baseline_quality: + blockers.append("quality_regression") + baseline_cost = adjunct["baseline_cost_microunits"] + adjunct_cost = adjunct["adjunct_cost_microunits"] + if not _nonnegative_integer(baseline_cost) or not _nonnegative_integer(adjunct_cost): + blockers.append("cost_evidence_incomplete") + elif adjunct_cost >= baseline_cost: + blockers.append("fully_loaded_cost_not_improved") + + blockers = _deduplicate(blockers) + eligible = not blockers + if eligible and type(adjunct_id) is str: + eligible_adjuncts.append(adjunct_id) + reports.append( + { + "adjunct_id": adjunct_id, + "decision": "eligible" if eligible else "bypass", + "reversible": bool( + _exact_dict(adjunct, _P5_ADJUNCT) + and adjunct["bypass_verified"] is True + and adjunct["fallback_verified"] is True + ), + "blockers": blockers, + } + ) + + if seen_adjuncts != _P5_ADJUNCT_IDS: + phase_blockers.append("malformed_record") + if data["dependency_gates_passed"] is not True: + phase_blockers.append("dependency_gates_incomplete") + if data["activation_authorized"] is not True: + phase_blockers.append("activation_not_recorded") + phase_blockers.append("external_activation_authority_required") + all_eligible = len(eligible_adjuncts) == len(_P5_ADJUNCT_IDS) + return _closed_result( + "p5", + phase_blockers, + implementation_readiness=all_eligible, + evaluation_evidence_complete=all_eligible, + activation_eligibility=( + all_eligible + and data["dependency_gates_passed"] is True + and data["activation_authorized"] is True + ), + runtime_changed=False, + evaluated_adjunct_count=len(adjuncts), + eligible_adjuncts=eligible_adjuncts, + adjuncts=reports, + ) + + +def evaluate_p6(record: object) -> dict[str, object]: + """Evaluate frozen P6 tracks independently without changing runtime state.""" + + empty_values = { + "runtime_changed": False, + "evaluated_track_count": 0, + "eligible_tracks": [], + "tracks": [], + } + if not _exact_dict(record, _P6_TOP): + return _closed_result( + "p6", + ["malformed_record", "external_activation_authority_required", "claim_blocked"], + **empty_values, + ) + data = record + phase_blockers: list[str] = [] + if ( + data["schema_version"] != "contextguard.phase-evaluation.p6/v1" + or data["phase_id"] != "p6" + or type(data["dependency_gates_passed"]) is not bool + ): + phase_blockers.append("malformed_record") + + tracks = data["tracks"] + if type(tracks) is not list or len(tracks) != len(_P6_TRACK_IDS): + phase_blockers.append("malformed_record") + tracks = [] + + reports: list[dict[str, object]] = [] + eligible_tracks: list[str] = [] + seen_tracks: set[str] = set() + for track in tracks: + blockers: list[str] = [] + track_id: object = None + evidence = { + "workload_evidence": False, + "baseline_evidence": False, + "scope_evidence": False, + "privacy_evidence": False, + "quality_evidence": False, + "failure_guardrail_evidence": False, + "correction_guardrail_evidence": False, + "cost_model_evidence": False, + "cost_evidence": False, + "fallback_evidence": False, + "rollback_evidence": False, + "authority_evidence": False, + "provider_evidence": False, + } + surface: object = None + if not _exact_dict(track, _P6_TRACK): + blockers.append("malformed_record") + else: + raw_track_id = track["track_id"] + raw_surface = track["surface"] + if ( + type(raw_track_id) is not str + or raw_track_id not in _P6_TRACK_IDS + or raw_track_id in seen_tracks + ): + blockers.append("malformed_record") + else: + track_id = raw_track_id + seen_tracks.add(track_id) + if type(raw_surface) is not str or raw_surface not in { + "evaluation_only", + "plan_only", + }: + blockers.append("malformed_record") + else: + surface = raw_surface + + evidence["workload_evidence"] = _valid_digest(track["workload_digest"]) + if not evidence["workload_evidence"]: + blockers.append("workload_evidence_incomplete") + evidence["baseline_evidence"] = _valid_digest(track["baseline_digest"]) + if not evidence["baseline_evidence"]: + blockers.append("baseline_evidence_incomplete") + evidence["scope_evidence"] = _valid_digest(track["scope_digest"]) + if not evidence["scope_evidence"]: + blockers.append("scope_evidence_incomplete") + evidence["privacy_evidence"] = ( + _valid_digest(track["privacy_boundary_digest"]) + and track["privacy_verified"] is True + ) + if not evidence["privacy_evidence"]: + blockers.append("privacy_evidence_incomplete") + + baseline_quality = track["baseline_quality_basis_points"] + track_quality = track["track_quality_basis_points"] + quality_values_valid = all( + type(value) is int and 0 <= value <= 10_000 + for value in (baseline_quality, track_quality) + ) + evidence["quality_evidence"] = quality_values_valid + if not quality_values_valid: + blockers.append("quality_evidence_incomplete") + elif track_quality < baseline_quality: + blockers.append("quality_regression") + + population_count = track["population_count"] + baseline_failure_count = track["baseline_failure_count"] + track_failure_count = track["track_failure_count"] + maximum_failure_increase = track[ + "maximum_failure_rate_increase_basis_points" + ] + failure_values_valid = ( + type(population_count) is int + and 1 <= population_count <= _MAX_RECORDS + and type(baseline_failure_count) is int + and 0 <= baseline_failure_count <= population_count + and type(track_failure_count) is int + and 0 <= track_failure_count <= population_count + and type(maximum_failure_increase) is int + and 0 <= maximum_failure_increase <= 10_000 + ) + if not failure_values_valid: + blockers.append("failure_evidence_incomplete") + else: + failure_guardrail_passed = ( + (track_failure_count - baseline_failure_count) * 10_000 + <= maximum_failure_increase * population_count + ) + evidence["failure_guardrail_evidence"] = failure_guardrail_passed + if not failure_guardrail_passed: + blockers.append("failure_guardrail_failed") + + baseline_corrections = track["baseline_corrections"] + track_corrections = track["track_corrections"] + correction_values_valid = _nonnegative_integer( + baseline_corrections + ) and _nonnegative_integer(track_corrections) + if not correction_values_valid: + blockers.append("correction_evidence_incomplete") + else: + correction_guardrail_passed = track_corrections <= baseline_corrections + evidence["correction_guardrail_evidence"] = correction_guardrail_passed + if not correction_guardrail_passed: + blockers.append("correction_guardrail_failed") + + evidence["cost_model_evidence"] = _valid_digest(track["cost_model_digest"]) + if not evidence["cost_model_evidence"]: + blockers.append("cost_model_incomplete") + + baseline_cost = track["baseline_cost_microunits"] + track_cost = track["track_cost_microunits"] + costs_valid = _nonnegative_integer(baseline_cost) and _nonnegative_integer(track_cost) + evidence["cost_evidence"] = costs_valid + if not costs_valid: + blockers.append("cost_evidence_incomplete") + elif track_cost >= baseline_cost: + blockers.append("fully_loaded_cost_not_improved") + + evidence["fallback_evidence"] = track["fallback_verified"] is True + if not evidence["fallback_evidence"]: + blockers.append("exact_fallback_unverified") + evidence["rollback_evidence"] = track["rollback_verified"] is True + if not evidence["rollback_evidence"]: + blockers.append("rollback_unverified") + evidence["authority_evidence"] = track["activation_authorized"] is True + if not evidence["authority_evidence"]: + blockers.append("activation_not_recorded") + evidence["provider_evidence"] = _valid_digest(track["provider_evidence_digest"]) + if not evidence["provider_evidence"]: + blockers.append("provider_measurement_incomplete") + if surface == "plan_only": + blockers.append("plan_only_non_runtime") + + if data["dependency_gates_passed"] is not True: + blockers.append("dependency_gates_incomplete") + blockers = _deduplicate(blockers) + plan_only = surface == "plan_only" + eligible = not blockers and not plan_only + if eligible and type(track_id) is str: + eligible_tracks.append(track_id) + reports.append( + { + "track_id": track_id, + "surface": surface, + "decision": "plan_only" if plan_only else ("eligible" if eligible else "fallback"), + "fallback": "exact_unchanged_baseline", + "activation_eligibility": eligible, + "activation_authority": False, + "claim_authority": False, + "generalization_allowed": False, + "blockers": blockers, + **evidence, + } + ) + + if seen_tracks != _P6_TRACK_IDS: + phase_blockers.append("malformed_record") + if data["dependency_gates_passed"] is not True: + phase_blockers.append("dependency_gates_incomplete") + phase_blockers.extend(["external_activation_authority_required", "claim_blocked"]) + all_eligible = len(eligible_tracks) == len(_P6_TRACK_IDS) + return _closed_result( + "p6", + phase_blockers, + implementation_readiness=all_eligible, + evaluation_evidence_complete=all_eligible, + provider_evidence=bool(reports) and all(report["provider_evidence"] for report in reports), + activation_eligibility=all_eligible and data["dependency_gates_passed"] is True, + runtime_changed=False, + evaluated_track_count=len(tracks), + eligible_tracks=eligible_tracks, + tracks=reports, + ) diff --git a/packages/context-guard-receipt/schemas/phase-evaluation-p2.schema.json b/packages/context-guard-receipt/schemas/phase-evaluation-p2.schema.json new file mode 100644 index 00000000..8950c6a3 --- /dev/null +++ b/packages/context-guard-receipt/schemas/phase-evaluation-p2.schema.json @@ -0,0 +1,119 @@ +{ + "$defs": { + "record": { + "additionalProperties": false, + "properties": { + "candidate_omission": { + "type": "boolean" + }, + "construction_cost_microunits": { + "minimum": 0, + "type": "integer" + }, + "fresh_until": { + "minimum": 0, + "type": "integer" + }, + "protection": { + "enum": [ + "eligible", + "protected", + "ambiguous" + ] + }, + "recalled": { + "type": "boolean" + }, + "record_id": { + "maxLength": 64, + "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$", + "type": "string" + }, + "rehydrated_digest": { + "anyOf": [ + { + "pattern": "^sha256:[0-9a-f]{64}$", + "type": "string" + }, + { + "type": "null" + } + ] + }, + "relevant": { + "type": "boolean" + }, + "source_digest": { + "pattern": "^sha256:[0-9a-f]{64}$", + "type": "string" + }, + "stratum": { + "maxLength": 64, + "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$", + "type": "string" + } + }, + "required": [ + "candidate_omission", + "construction_cost_microunits", + "fresh_until", + "protection", + "recalled", + "record_id", + "rehydrated_digest", + "relevant", + "source_digest", + "stratum" + ], + "type": "object" + } + }, + "$id": "https://contextguard.dev/schemas/phase-evaluation-p2.schema.json", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "additionalProperties": false, + "properties": { + "activation_authorized": { + "type": "boolean" + }, + "baseline_fallback_verified": { + "type": "boolean" + }, + "dependency_gates_passed": { + "type": "boolean" + }, + "minimum_recall_basis_points": { + "maximum": 10000, + "minimum": 0, + "type": "integer" + }, + "observed_at": { + "minimum": 0, + "type": "integer" + }, + "phase_id": { + "const": "p2" + }, + "records": { + "items": { + "$ref": "#/$defs/record" + }, + "maxItems": 10000, + "minItems": 1, + "type": "array" + }, + "schema_version": { + "const": "contextguard.phase-evaluation.p2/v1" + } + }, + "required": [ + "activation_authorized", + "baseline_fallback_verified", + "dependency_gates_passed", + "minimum_recall_basis_points", + "observed_at", + "phase_id", + "records", + "schema_version" + ], + "type": "object" +} diff --git a/packages/context-guard-receipt/schemas/phase-evaluation-p3.schema.json b/packages/context-guard-receipt/schemas/phase-evaluation-p3.schema.json new file mode 100644 index 00000000..8580464d --- /dev/null +++ b/packages/context-guard-receipt/schemas/phase-evaluation-p3.schema.json @@ -0,0 +1,197 @@ +{ + "$defs": { + "attempt": { + "additionalProperties": false, + "properties": { + "arm": { + "enum": [ + "baseline", + "canary" + ] + }, + "attempt_id": { + "maxLength": 64, + "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$", + "type": "string" + }, + "corrections": { + "minimum": 0, + "type": "integer" + }, + "measurement": { + "$ref": "#/$defs/measurement" + }, + "pair_id": { + "maxLength": 64, + "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$", + "type": "string" + }, + "retrievals": { + "items": { + "$ref": "#/$defs/retrieval" + }, + "maxItems": 10000, + "type": "array", + "uniqueItems": true + }, + "task_success": { + "type": "boolean" + } + }, + "required": [ + "arm", + "attempt_id", + "corrections", + "measurement", + "pair_id", + "retrievals", + "task_success" + ], + "type": "object" + }, + "measurement": { + "additionalProperties": false, + "properties": { + "correction_cost_microunits": { + "minimum": 0, + "type": "integer" + }, + "external_cost_microunits": { + "minimum": 0, + "type": "integer" + }, + "local_compute_cost_microunits": { + "minimum": 0, + "type": "integer" + }, + "primary_tokens": { + "minimum": 0, + "type": "integer" + }, + "provider_cost_microunits": { + "minimum": 0, + "type": "integer" + }, + "retrieval_cost_microunits": { + "minimum": 0, + "type": "integer" + }, + "retry_cost_microunits": { + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "correction_cost_microunits", + "external_cost_microunits", + "local_compute_cost_microunits", + "primary_tokens", + "provider_cost_microunits", + "retrieval_cost_microunits", + "retry_cost_microunits" + ], + "type": "object" + }, + "retrieval": { + "additionalProperties": false, + "properties": { + "exact": { + "const": true + }, + "retrieval_id": { + "maxLength": 64, + "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$", + "type": "string" + } + }, + "required": [ + "exact", + "retrieval_id" + ], + "type": "object" + }, + "thresholds": { + "additionalProperties": false, + "properties": { + "maximum_failure_rate_increase_basis_points": { + "maximum": 10000, + "minimum": 0, + "type": "integer" + }, + "require_corrections_non_inferior": { + "const": true + }, + "require_fully_loaded_cost_improvement": { + "const": true + } + }, + "required": [ + "maximum_failure_rate_increase_basis_points", + "require_corrections_non_inferior", + "require_fully_loaded_cost_improvement" + ], + "type": "object" + } + }, + "$id": "https://contextguard.dev/schemas/phase-evaluation-p3.schema.json", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "additionalProperties": false, + "properties": { + "activation_authorized": { + "type": "boolean" + }, + "attempts": { + "items": { + "$ref": "#/$defs/attempt" + }, + "maxItems": 20000, + "minItems": 1, + "type": "array" + }, + "baseline_fallback_verified": { + "type": "boolean" + }, + "claim_scope_bound": { + "type": "boolean" + }, + "dependency_gates_passed": { + "type": "boolean" + }, + "evidence_origin": { + "const": "provider_measured" + }, + "matched_pair_ids": { + "items": { + "maxLength": 64, + "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$", + "type": "string" + }, + "maxItems": 10000, + "minItems": 1, + "type": "array", + "uniqueItems": true + }, + "phase_id": { + "const": "p3" + }, + "schema_version": { + "const": "contextguard.phase-evaluation.p3/v1" + }, + "thresholds": { + "$ref": "#/$defs/thresholds" + } + }, + "required": [ + "activation_authorized", + "attempts", + "baseline_fallback_verified", + "claim_scope_bound", + "dependency_gates_passed", + "evidence_origin", + "matched_pair_ids", + "phase_id", + "schema_version", + "thresholds" + ], + "type": "object" +} diff --git a/packages/context-guard-receipt/schemas/phase-evaluation-p4.schema.json b/packages/context-guard-receipt/schemas/phase-evaluation-p4.schema.json new file mode 100644 index 00000000..e560eac2 --- /dev/null +++ b/packages/context-guard-receipt/schemas/phase-evaluation-p4.schema.json @@ -0,0 +1,179 @@ +{ + "$defs": { + "cache": { + "additionalProperties": false, + "properties": { + "creation_microunits": { + "minimum": 0, + "type": "integer" + }, + "invalidation_microunits": { + "minimum": 0, + "type": "integer" + }, + "latency_microunits": { + "minimum": 0, + "type": "integer" + }, + "provider_cost_microunits": { + "minimum": 0, + "type": "integer" + }, + "read_microunits": { + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "creation_microunits", + "invalidation_microunits", + "latency_microunits", + "provider_cost_microunits", + "read_microunits" + ], + "type": "object" + }, + "outcome": { + "additionalProperties": false, + "properties": { + "cache_accounting": { + "$ref": "#/$defs/cache" + }, + "quality_basis_points": { + "maximum": 10000, + "minimum": 0, + "type": "integer" + }, + "total_cost_microunits": { + "minimum": 0, + "type": "integer" + } + }, + "required": [ + "cache_accounting", + "quality_basis_points", + "total_cost_microunits" + ], + "type": "object" + }, + "outcomes": { + "additionalProperties": false, + "properties": { + "advisory": { + "$ref": "#/$defs/outcome" + }, + "always_on": { + "$ref": "#/$defs/outcome" + }, + "always_pass_through": { + "$ref": "#/$defs/outcome" + } + }, + "required": [ + "advisory", + "always_on", + "always_pass_through" + ], + "type": "object" + }, + "trial": { + "additionalProperties": false, + "properties": { + "advisory_route": { + "anyOf": [ + { + "enum": [ + "pass_through", + "on" + ] + }, + { + "type": "null" + } + ] + }, + "advisory_status": { + "enum": [ + "selected", + "abstained", + "failed" + ] + }, + "bypass_reasons": { + "items": { + "maxLength": 64, + "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$", + "type": "string" + }, + "maxItems": 10000, + "type": "array" + }, + "confidence_basis_points": { + "maximum": 10000, + "minimum": 0, + "type": "integer" + }, + "outcomes": { + "$ref": "#/$defs/outcomes" + }, + "trial_id": { + "maxLength": 64, + "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$", + "type": "string" + } + }, + "required": [ + "advisory_route", + "advisory_status", + "bypass_reasons", + "confidence_basis_points", + "outcomes", + "trial_id" + ], + "type": "object" + } + }, + "$id": "https://contextguard.dev/schemas/phase-evaluation-p4.schema.json", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "additionalProperties": false, + "properties": { + "activation_authorized": { + "type": "boolean" + }, + "baseline_fallback_verified": { + "type": "boolean" + }, + "dependency_gates_passed": { + "type": "boolean" + }, + "minimum_confidence_basis_points": { + "maximum": 10000, + "minimum": 0, + "type": "integer" + }, + "phase_id": { + "const": "p4" + }, + "schema_version": { + "const": "contextguard.phase-evaluation.p4/v1" + }, + "trials": { + "items": { + "$ref": "#/$defs/trial" + }, + "maxItems": 10000, + "minItems": 1, + "type": "array" + } + }, + "required": [ + "activation_authorized", + "baseline_fallback_verified", + "dependency_gates_passed", + "minimum_confidence_basis_points", + "phase_id", + "schema_version", + "trials" + ], + "type": "object" +} diff --git a/packages/context-guard-receipt/schemas/phase-evaluation-p5.schema.json b/packages/context-guard-receipt/schemas/phase-evaluation-p5.schema.json new file mode 100644 index 00000000..cf59df31 --- /dev/null +++ b/packages/context-guard-receipt/schemas/phase-evaluation-p5.schema.json @@ -0,0 +1,171 @@ +{ + "$defs": { + "adjunct": { + "additionalProperties": false, + "properties": { + "adjunct_cost_microunits": { + "minimum": 0, + "type": "integer" + }, + "adjunct_id": { + "enum": [ + "execution_twin", + "failure_cone", + "typed_blueprint" + ] + }, + "adjunct_quality_basis_points": { + "maximum": 10000, + "minimum": 0, + "type": "integer" + }, + "baseline_cost_microunits": { + "minimum": 0, + "type": "integer" + }, + "baseline_quality_basis_points": { + "maximum": 10000, + "minimum": 0, + "type": "integer" + }, + "bypass_verified": { + "type": "boolean" + }, + "evidence_digests": { + "items": { + "pattern": "^sha256:[0-9a-f]{64}$", + "type": "string" + }, + "maxItems": 10000, + "minItems": 1, + "type": "array", + "uniqueItems": true + }, + "failure_cases": { + "items": { + "$ref": "#/$defs/failure_case" + }, + "maxItems": 10000, + "minItems": 1, + "type": "array" + }, + "fallback_verified": { + "type": "boolean" + }, + "revision_digest": { + "pattern": "^sha256:[0-9a-f]{64}$", + "type": "string" + }, + "source_digest": { + "pattern": "^sha256:[0-9a-f]{64}$", + "type": "string" + }, + "test_digest": { + "pattern": "^sha256:[0-9a-f]{64}$", + "type": "string" + } + }, + "required": [ + "adjunct_cost_microunits", + "adjunct_id", + "adjunct_quality_basis_points", + "baseline_cost_microunits", + "baseline_quality_basis_points", + "bypass_verified", + "evidence_digests", + "failure_cases", + "fallback_verified", + "revision_digest", + "source_digest", + "test_digest" + ], + "type": "object" + }, + "failure_case": { + "additionalProperties": false, + "properties": { + "case_id": { + "maxLength": 64, + "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$", + "type": "string" + }, + "duplicate_of": { + "anyOf": [ + { + "maxLength": 64, + "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$", + "type": "string" + }, + { + "type": "null" + } + ] + }, + "exit_status": { + "minimum": 0, + "type": "integer" + }, + "root_cause": { + "maxLength": 64, + "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$", + "type": "string" + } + }, + "required": [ + "case_id", + "duplicate_of", + "exit_status", + "root_cause" + ], + "type": "object" + } + }, + "$id": "https://contextguard.dev/schemas/phase-evaluation-p5.schema.json", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "additionalProperties": false, + "properties": { + "activation_authorized": { + "type": "boolean" + }, + "adjuncts": { + "items": { + "$ref": "#/$defs/adjunct" + }, + "maxItems": 3, + "minItems": 3, + "type": "array" + }, + "current_revision_digest": { + "pattern": "^sha256:[0-9a-f]{64}$", + "type": "string" + }, + "current_source_digest": { + "pattern": "^sha256:[0-9a-f]{64}$", + "type": "string" + }, + "current_test_digest": { + "pattern": "^sha256:[0-9a-f]{64}$", + "type": "string" + }, + "dependency_gates_passed": { + "type": "boolean" + }, + "phase_id": { + "const": "p5" + }, + "schema_version": { + "const": "contextguard.phase-evaluation.p5/v1" + } + }, + "required": [ + "activation_authorized", + "adjuncts", + "current_revision_digest", + "current_source_digest", + "current_test_digest", + "dependency_gates_passed", + "phase_id", + "schema_version" + ], + "type": "object" +} diff --git a/packages/context-guard-receipt/schemas/phase-evaluation-p6.schema.json b/packages/context-guard-receipt/schemas/phase-evaluation-p6.schema.json new file mode 100644 index 00000000..5e029161 --- /dev/null +++ b/packages/context-guard-receipt/schemas/phase-evaluation-p6.schema.json @@ -0,0 +1,158 @@ +{ + "$defs": { + "track": { + "additionalProperties": false, + "properties": { + "activation_authorized": { + "type": "boolean" + }, + "baseline_corrections": { + "minimum": 0, + "type": "integer" + }, + "baseline_cost_microunits": { + "minimum": 0, + "type": "integer" + }, + "baseline_digest": { + "pattern": "^sha256:[0-9a-f]{64}$", + "type": "string" + }, + "baseline_failure_count": { + "minimum": 0, + "type": "integer" + }, + "baseline_quality_basis_points": { + "maximum": 10000, + "minimum": 0, + "type": "integer" + }, + "cost_model_digest": { + "pattern": "^sha256:[0-9a-f]{64}$", + "type": "string" + }, + "fallback_verified": { + "type": "boolean" + }, + "maximum_failure_rate_increase_basis_points": { + "maximum": 10000, + "minimum": 0, + "type": "integer" + }, + "population_count": { + "maximum": 10000, + "minimum": 1, + "type": "integer" + }, + "privacy_boundary_digest": { + "pattern": "^sha256:[0-9a-f]{64}$", + "type": "string" + }, + "privacy_verified": { + "type": "boolean" + }, + "provider_evidence_digest": { + "pattern": "^sha256:[0-9a-f]{64}$", + "type": "string" + }, + "rollback_verified": { + "type": "boolean" + }, + "scope_digest": { + "pattern": "^sha256:[0-9a-f]{64}$", + "type": "string" + }, + "surface": { + "enum": [ + "evaluation_only", + "plan_only" + ] + }, + "track_corrections": { + "minimum": 0, + "type": "integer" + }, + "track_cost_microunits": { + "minimum": 0, + "type": "integer" + }, + "track_failure_count": { + "minimum": 0, + "type": "integer" + }, + "track_id": { + "enum": [ + "context_leases", + "scout_surgeon", + "counterfactual_ledger", + "negative_firewall", + "bounded_compilation" + ] + }, + "track_quality_basis_points": { + "maximum": 10000, + "minimum": 0, + "type": "integer" + }, + "workload_digest": { + "pattern": "^sha256:[0-9a-f]{64}$", + "type": "string" + } + }, + "required": [ + "activation_authorized", + "baseline_corrections", + "baseline_cost_microunits", + "baseline_digest", + "baseline_failure_count", + "baseline_quality_basis_points", + "cost_model_digest", + "fallback_verified", + "maximum_failure_rate_increase_basis_points", + "population_count", + "privacy_boundary_digest", + "privacy_verified", + "provider_evidence_digest", + "rollback_verified", + "scope_digest", + "surface", + "track_corrections", + "track_cost_microunits", + "track_failure_count", + "track_id", + "track_quality_basis_points", + "workload_digest" + ], + "type": "object" + } + }, + "$id": "https://contextguard.dev/schemas/phase-evaluation-p6.schema.json", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "additionalProperties": false, + "properties": { + "dependency_gates_passed": { + "type": "boolean" + }, + "phase_id": { + "const": "p6" + }, + "schema_version": { + "const": "contextguard.phase-evaluation.p6/v1" + }, + "tracks": { + "items": { + "$ref": "#/$defs/track" + }, + "maxItems": 5, + "minItems": 5, + "type": "array" + } + }, + "required": [ + "dependency_gates_passed", + "phase_id", + "schema_version", + "tracks" + ], + "type": "object" +} diff --git a/packages/context-guard-receipt/schemas/phase-evaluation-result.schema.json b/packages/context-guard-receipt/schemas/phase-evaluation-result.schema.json new file mode 100644 index 00000000..a4be4512 --- /dev/null +++ b/packages/context-guard-receipt/schemas/phase-evaluation-result.schema.json @@ -0,0 +1,776 @@ +{ + "$defs": { + "adjunct": { + "additionalProperties": false, + "properties": { + "adjunct_id": { + "anyOf": [ + { + "enum": [ + "execution_twin", + "failure_cone", + "typed_blueprint" + ] + }, + { + "type": "null" + } + ] + }, + "blockers": { + "items": { + "maxLength": 128, + "pattern": "^[a-z][a-z0-9_]{0,127}$", + "type": "string" + }, + "maxItems": 128, + "type": "array", + "uniqueItems": true + }, + "decision": { + "enum": [ + "eligible", + "bypass" + ] + }, + "reversible": { + "type": "boolean" + } + }, + "required": [ + "adjunct_id", + "blockers", + "decision", + "reversible" + ], + "type": "object" + }, + "p2": { + "additionalProperties": false, + "properties": { + "activation_authority": { + "const": false + }, + "activation_eligibility": { + "type": "boolean" + }, + "blockers": { + "items": { + "maxLength": 128, + "pattern": "^[a-z][a-z0-9_]{0,127}$", + "type": "string" + }, + "maxItems": 128, + "type": "array", + "uniqueItems": true + }, + "claim_authority": { + "const": false + }, + "construction_cost_microunits": { + "minimum": 0, + "type": "integer" + }, + "evaluated_record_count": { + "minimum": 0, + "type": "integer" + }, + "evaluation_evidence_complete": { + "type": "boolean" + }, + "fallback": { + "const": "exact_unchanged_baseline" + }, + "implementation_readiness": { + "type": "boolean" + }, + "phase_id": { + "const": "p2" + }, + "provider_evidence": { + "type": "boolean" + }, + "schema_version": { + "const": "contextguard.phase-evaluation.result/v1" + }, + "strata": { + "items": { + "$ref": "#/$defs/stratum" + }, + "maxItems": 10000, + "type": "array" + } + }, + "required": [ + "activation_authority", + "activation_eligibility", + "blockers", + "claim_authority", + "construction_cost_microunits", + "evaluated_record_count", + "evaluation_evidence_complete", + "fallback", + "implementation_readiness", + "phase_id", + "provider_evidence", + "schema_version", + "strata" + ], + "type": "object" + }, + "p3": { + "additionalProperties": false, + "properties": { + "activation_authority": { + "const": false + }, + "activation_eligibility": { + "type": "boolean" + }, + "baseline_correction_count": { + "minimum": 0, + "type": "integer" + }, + "baseline_failure_count": { + "minimum": 0, + "type": "integer" + }, + "baseline_fully_loaded_cost_microunits": { + "minimum": 0, + "type": "integer" + }, + "blockers": { + "items": { + "maxLength": 128, + "pattern": "^[a-z][a-z0-9_]{0,127}$", + "type": "string" + }, + "maxItems": 128, + "type": "array", + "uniqueItems": true + }, + "canary_correction_count": { + "minimum": 0, + "type": "integer" + }, + "canary_failure_count": { + "minimum": 0, + "type": "integer" + }, + "canary_fully_loaded_cost_microunits": { + "minimum": 0, + "type": "integer" + }, + "claim_authority": { + "const": false + }, + "evaluated_attempt_count": { + "minimum": 0, + "type": "integer" + }, + "evaluated_pair_count": { + "minimum": 0, + "type": "integer" + }, + "evaluated_retrieval_count": { + "minimum": 0, + "type": "integer" + }, + "evaluation_evidence_complete": { + "type": "boolean" + }, + "fallback": { + "const": "exact_unchanged_baseline" + }, + "implementation_readiness": { + "type": "boolean" + }, + "phase_id": { + "const": "p3" + }, + "provider_evidence": { + "type": "boolean" + }, + "schema_version": { + "const": "contextguard.phase-evaluation.result/v1" + } + }, + "required": [ + "activation_authority", + "activation_eligibility", + "baseline_correction_count", + "baseline_failure_count", + "baseline_fully_loaded_cost_microunits", + "blockers", + "canary_correction_count", + "canary_failure_count", + "canary_fully_loaded_cost_microunits", + "claim_authority", + "evaluated_attempt_count", + "evaluated_pair_count", + "evaluated_retrieval_count", + "evaluation_evidence_complete", + "fallback", + "implementation_readiness", + "phase_id", + "provider_evidence", + "schema_version" + ], + "type": "object" + }, + "p4": { + "additionalProperties": false, + "properties": { + "abstention_count": { + "minimum": 0, + "type": "integer" + }, + "activation_authority": { + "const": false + }, + "activation_eligibility": { + "type": "boolean" + }, + "blockers": { + "items": { + "maxLength": 128, + "pattern": "^[a-z][a-z0-9_]{0,127}$", + "type": "string" + }, + "maxItems": 128, + "type": "array", + "uniqueItems": true + }, + "bypass_reason_counts": { + "additionalProperties": { + "minimum": 0, + "type": "integer" + }, + "properties": {}, + "propertyNames": { + "maxLength": 128, + "pattern": "^[a-z][a-z0-9_]{0,127}$", + "type": "string" + }, + "required": [], + "type": "object" + }, + "claim_authority": { + "const": false + }, + "confidence_basis_points": { + "items": { + "maximum": 10000, + "minimum": 0, + "type": "integer" + }, + "maxItems": 10000, + "type": "array" + }, + "evaluated_trial_count": { + "minimum": 0, + "type": "integer" + }, + "evaluation_evidence_complete": { + "type": "boolean" + }, + "failure_count": { + "minimum": 0, + "type": "integer" + }, + "fallback": { + "const": "exact_unchanged_baseline" + }, + "implementation_readiness": { + "type": "boolean" + }, + "phase_id": { + "const": "p4" + }, + "provider_evidence": { + "type": "boolean" + }, + "runtime_route_changed": { + "const": false + }, + "schema_version": { + "const": "contextguard.phase-evaluation.result/v1" + }, + "selected_route": { + "enum": [ + "pass_through", + "on" + ] + }, + "trials": { + "items": { + "$ref": "#/$defs/trial" + }, + "maxItems": 10000, + "type": "array" + } + }, + "required": [ + "abstention_count", + "activation_authority", + "activation_eligibility", + "blockers", + "bypass_reason_counts", + "claim_authority", + "confidence_basis_points", + "evaluated_trial_count", + "evaluation_evidence_complete", + "failure_count", + "fallback", + "implementation_readiness", + "phase_id", + "provider_evidence", + "runtime_route_changed", + "schema_version", + "selected_route", + "trials" + ], + "type": "object" + }, + "p5": { + "additionalProperties": false, + "properties": { + "activation_authority": { + "const": false + }, + "activation_eligibility": { + "type": "boolean" + }, + "adjuncts": { + "items": { + "$ref": "#/$defs/adjunct" + }, + "maxItems": 3, + "type": "array" + }, + "blockers": { + "items": { + "maxLength": 128, + "pattern": "^[a-z][a-z0-9_]{0,127}$", + "type": "string" + }, + "maxItems": 128, + "type": "array", + "uniqueItems": true + }, + "claim_authority": { + "const": false + }, + "eligible_adjuncts": { + "items": { + "enum": [ + "execution_twin", + "failure_cone", + "typed_blueprint" + ] + }, + "maxItems": 3, + "type": "array", + "uniqueItems": true + }, + "evaluated_adjunct_count": { + "minimum": 0, + "type": "integer" + }, + "evaluation_evidence_complete": { + "type": "boolean" + }, + "fallback": { + "const": "exact_unchanged_baseline" + }, + "implementation_readiness": { + "type": "boolean" + }, + "phase_id": { + "const": "p5" + }, + "provider_evidence": { + "type": "boolean" + }, + "runtime_changed": { + "const": false + }, + "schema_version": { + "const": "contextguard.phase-evaluation.result/v1" + } + }, + "required": [ + "activation_authority", + "activation_eligibility", + "adjuncts", + "blockers", + "claim_authority", + "eligible_adjuncts", + "evaluated_adjunct_count", + "evaluation_evidence_complete", + "fallback", + "implementation_readiness", + "phase_id", + "provider_evidence", + "runtime_changed", + "schema_version" + ], + "type": "object" + }, + "p6": { + "additionalProperties": false, + "properties": { + "activation_authority": { + "const": false + }, + "activation_eligibility": { + "type": "boolean" + }, + "blockers": { + "items": { + "maxLength": 128, + "pattern": "^[a-z][a-z0-9_]{0,127}$", + "type": "string" + }, + "maxItems": 128, + "type": "array", + "uniqueItems": true + }, + "claim_authority": { + "const": false + }, + "eligible_tracks": { + "items": { + "enum": [ + "context_leases", + "scout_surgeon", + "counterfactual_ledger", + "negative_firewall", + "bounded_compilation" + ] + }, + "maxItems": 5, + "type": "array", + "uniqueItems": true + }, + "evaluated_track_count": { + "minimum": 0, + "type": "integer" + }, + "evaluation_evidence_complete": { + "type": "boolean" + }, + "fallback": { + "const": "exact_unchanged_baseline" + }, + "implementation_readiness": { + "type": "boolean" + }, + "phase_id": { + "const": "p6" + }, + "provider_evidence": { + "type": "boolean" + }, + "runtime_changed": { + "const": false + }, + "schema_version": { + "const": "contextguard.phase-evaluation.result/v1" + }, + "tracks": { + "items": { + "$ref": "#/$defs/track" + }, + "maxItems": 5, + "type": "array" + } + }, + "required": [ + "activation_authority", + "activation_eligibility", + "blockers", + "claim_authority", + "eligible_tracks", + "evaluated_track_count", + "evaluation_evidence_complete", + "fallback", + "implementation_readiness", + "phase_id", + "provider_evidence", + "runtime_changed", + "schema_version", + "tracks" + ], + "type": "object" + }, + "stratum": { + "additionalProperties": false, + "properties": { + "recall_basis_points": { + "maximum": 10000, + "minimum": 0, + "type": "integer" + }, + "recalled_relevant_record_count": { + "minimum": 0, + "type": "integer" + }, + "relevant_record_count": { + "minimum": 0, + "type": "integer" + }, + "stratum": { + "maxLength": 64, + "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$", + "type": "string" + }, + "threshold_passed": { + "type": "boolean" + } + }, + "required": [ + "recall_basis_points", + "recalled_relevant_record_count", + "relevant_record_count", + "stratum", + "threshold_passed" + ], + "type": "object" + }, + "track": { + "additionalProperties": false, + "properties": { + "activation_authority": { + "const": false + }, + "activation_eligibility": { + "type": "boolean" + }, + "authority_evidence": { + "type": "boolean" + }, + "baseline_evidence": { + "type": "boolean" + }, + "blockers": { + "items": { + "maxLength": 128, + "pattern": "^[a-z][a-z0-9_]{0,127}$", + "type": "string" + }, + "maxItems": 128, + "type": "array", + "uniqueItems": true + }, + "claim_authority": { + "const": false + }, + "correction_guardrail_evidence": { + "type": "boolean" + }, + "cost_evidence": { + "type": "boolean" + }, + "cost_model_evidence": { + "type": "boolean" + }, + "decision": { + "enum": [ + "eligible", + "fallback", + "plan_only" + ] + }, + "failure_guardrail_evidence": { + "type": "boolean" + }, + "fallback": { + "const": "exact_unchanged_baseline" + }, + "fallback_evidence": { + "type": "boolean" + }, + "generalization_allowed": { + "const": false + }, + "privacy_evidence": { + "type": "boolean" + }, + "provider_evidence": { + "type": "boolean" + }, + "quality_evidence": { + "type": "boolean" + }, + "rollback_evidence": { + "type": "boolean" + }, + "scope_evidence": { + "type": "boolean" + }, + "surface": { + "anyOf": [ + { + "enum": [ + "evaluation_only", + "plan_only" + ] + }, + { + "type": "null" + } + ] + }, + "track_id": { + "anyOf": [ + { + "enum": [ + "context_leases", + "scout_surgeon", + "counterfactual_ledger", + "negative_firewall", + "bounded_compilation" + ] + }, + { + "type": "null" + } + ] + }, + "workload_evidence": { + "type": "boolean" + } + }, + "required": [ + "activation_authority", + "activation_eligibility", + "authority_evidence", + "baseline_evidence", + "blockers", + "claim_authority", + "correction_guardrail_evidence", + "cost_evidence", + "cost_model_evidence", + "decision", + "failure_guardrail_evidence", + "fallback", + "fallback_evidence", + "generalization_allowed", + "privacy_evidence", + "provider_evidence", + "quality_evidence", + "rollback_evidence", + "scope_evidence", + "surface", + "track_id", + "workload_evidence" + ], + "type": "object" + }, + "trial": { + "additionalProperties": false, + "properties": { + "advisory_route": { + "anyOf": [ + { + "enum": [ + "pass_through", + "on" + ] + }, + { + "type": "null" + } + ] + }, + "advisory_status": { + "anyOf": [ + { + "enum": [ + "selected", + "abstained", + "failed" + ] + }, + { + "type": "null" + } + ] + }, + "bypass_reasons": { + "items": { + "maxLength": 128, + "pattern": "^[a-z][a-z0-9_]{0,127}$", + "type": "string" + }, + "maxItems": 128, + "type": "array", + "uniqueItems": true + }, + "confidence_basis_points": { + "maximum": 10000, + "minimum": 0, + "type": "integer" + }, + "evaluation_route": { + "enum": [ + "pass_through", + "on" + ] + }, + "regret_microunits": { + "anyOf": [ + { + "type": "integer" + }, + { + "type": "null" + } + ] + }, + "trial_id": { + "anyOf": [ + { + "maxLength": 64, + "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$", + "type": "string" + }, + { + "type": "null" + } + ] + } + }, + "required": [ + "advisory_route", + "advisory_status", + "bypass_reasons", + "confidence_basis_points", + "evaluation_route", + "regret_microunits", + "trial_id" + ], + "type": "object" + } + }, + "$id": "https://contextguard.dev/schemas/phase-evaluation-result.schema.json", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "oneOf": [ + { + "$ref": "#/$defs/p2" + }, + { + "$ref": "#/$defs/p3" + }, + { + "$ref": "#/$defs/p4" + }, + { + "$ref": "#/$defs/p5" + }, + { + "$ref": "#/$defs/p6" + } + ] +} diff --git a/packages/context-guard-receipt/tests/contract/test_g001_distribution_contract.py b/packages/context-guard-receipt/tests/contract/test_g001_distribution_contract.py index 21e8a7ba..7aa0c100 100644 --- a/packages/context-guard-receipt/tests/contract/test_g001_distribution_contract.py +++ b/packages/context-guard-receipt/tests/contract/test_g001_distribution_contract.py @@ -45,6 +45,7 @@ "usage: context-guard-receipt \n\n" "Commands:\n" " inspect boundary\n" + " evaluate phase --input \n" " assemble --kind --descriptor --root [options]\n" " run --escrow --root --state-dir " "[--timeout-seconds --max-channel-bytes " @@ -144,6 +145,7 @@ "python/context_guard_receipt/identity.py": 0o644, "python/context_guard_receipt/mcp.py": 0o644, "python/context_guard_receipt/merged_capture.py": 0o644, + "python/context_guard_receipt/phase_evaluation.py": 0o644, "python/context_guard_receipt/protection.py": 0o644, "python/context_guard_receipt/reference_expiry.py": 0o644, "python/context_guard_receipt/receipts.py": 0o644, @@ -167,6 +169,12 @@ "schemas/evidence-reference.schema.json": 0o644, "schemas/expansion-envelope.schema.json": 0o644, "schemas/expansion-refusal.schema.json": 0o644, + "schemas/phase-evaluation-p2.schema.json": 0o644, + "schemas/phase-evaluation-p3.schema.json": 0o644, + "schemas/phase-evaluation-p4.schema.json": 0o644, + "schemas/phase-evaluation-p5.schema.json": 0o644, + "schemas/phase-evaluation-p6.schema.json": 0o644, + "schemas/phase-evaluation-result.schema.json": 0o644, "schemas/protection-decision.schema.json": 0o644, "schemas/reference-expiry-inspection.schema.json": 0o644, "schemas/reference-expiry-metadata.schema.json": 0o644, diff --git a/packages/context-guard-receipt/tests/contract/test_g015_phase_evaluation_cli.py b/packages/context-guard-receipt/tests/contract/test_g015_phase_evaluation_cli.py new file mode 100644 index 00000000..f1f879bf --- /dev/null +++ b/packages/context-guard-receipt/tests/contract/test_g015_phase_evaluation_cli.py @@ -0,0 +1,155 @@ +from __future__ import annotations + +import json +import os +import subprocess +import sys +import unittest +from pathlib import Path + + +PACKAGE_ROOT = Path(__file__).resolve().parents[2] +REPOSITORY_ROOT = PACKAGE_ROOT.parents[1] +BOOTSTRAP = PACKAGE_ROOT / "python/context_guard_receipt/bootstrap.py" +PACKAGED_EVALUATOR = PACKAGE_ROOT / "python/context_guard_receipt/phase_evaluation.py" +CANONICAL_EVALUATOR = REPOSITORY_ROOT / "context-guard-kit/phase_evaluation.py" +SCHEMA_ROOT = PACKAGE_ROOT / "schemas" + + +def canonical_json(value: object) -> bytes: + return ( + json.dumps(value, ensure_ascii=True, separators=(",", ":"), sort_keys=True) + + "\n" + ).encode("ascii") + + +def p2_record() -> dict[str, object]: + return { + "activation_authorized": True, + "baseline_fallback_verified": True, + "dependency_gates_passed": True, + "minimum_recall_basis_points": 9_000, + "observed_at": 100, + "phase_id": "p2", + "records": [ + { + "candidate_omission": True, + "construction_cost_microunits": 12, + "fresh_until": 101, + "protection": "eligible", + "recalled": True, + "record_id": "r1", + "rehydrated_digest": "sha256:" + "1" * 64, + "relevant": True, + "source_digest": "sha256:" + "1" * 64, + "stratum": "refactor", + } + ], + "schema_version": "contextguard.phase-evaluation.p2/v1", + } + + +class G015PhaseEvaluationCliTests(unittest.TestCase): + def run_cli(self, payload: bytes) -> subprocess.CompletedProcess[bytes]: + return subprocess.run( + [ + str(Path(sys.executable).resolve()), + "-I", + "-S", + "-B", + str(BOOTSTRAP), + "receipt", + "evaluate", + "phase", + "--input", + "-", + ], + cwd=PACKAGE_ROOT, + env={"LANG": "C", "PATH": os.defpath, "PYTHONDONTWRITEBYTECODE": "1"}, + input=payload, + stdout=subprocess.PIPE, + stderr=subprocess.PIPE, + check=False, + ) + + def test_packaged_evaluator_is_the_exact_canonical_provider_free_copy(self) -> None: + """Break caught: the installed evaluator drifts from its reviewed source.""" + + self.assertEqual(PACKAGED_EVALUATOR.read_bytes(), CANONICAL_EVALUATOR.read_bytes()) + + def test_phase_schemas_close_every_nested_value_shape(self) -> None: + """Break caught: a published schema leaves nested evidence unconstrained.""" + + def assert_closed(schema: object, location: str) -> None: + self.assertIsInstance(schema, dict, location) + document = schema + if document.get("type") == "object": + additional = document.get("additionalProperties") + self.assertTrue( + additional is False + or (isinstance(additional, dict) and bool(additional)), + location, + ) + properties = document.get("properties") + required = document.get("required") + self.assertIsInstance(properties, dict, location) + self.assertIsInstance(required, list, location) + self.assertEqual(set(properties), set(required), location) + if isinstance(additional, dict): + assert_closed(additional, f"{location}/additionalProperties") + for keyword in ("properties", "$defs"): + children = document.get(keyword, {}) + if isinstance(children, dict): + for name, child in children.items(): + self.assertNotEqual(child, {}, f"{location}/{keyword}/{name}") + assert_closed(child, f"{location}/{keyword}/{name}") + items = document.get("items") + if items is not None: + self.assertNotEqual(items, {}, f"{location}/items") + assert_closed(items, f"{location}/items") + for keyword in ("allOf", "anyOf", "oneOf"): + variants = document.get(keyword, []) + if isinstance(variants, list): + for index, variant in enumerate(variants): + self.assertNotEqual(variant, {}, f"{location}/{keyword}/{index}") + assert_closed(variant, f"{location}/{keyword}/{index}") + + for phase_id in ("p2", "p3", "p4", "p5", "p6"): + path = SCHEMA_ROOT / f"phase-evaluation-{phase_id}.schema.json" + assert_closed(json.loads(path.read_text(encoding="utf-8")), path.name) + result_path = SCHEMA_ROOT / "phase-evaluation-result.schema.json" + assert_closed( + json.loads(result_path.read_text(encoding="utf-8")), result_path.name + ) + + def test_cli_evaluates_bounded_canonical_local_record_without_granting_authority(self) -> None: + """Break caught: the installed grammar cannot expose the closed local evaluator.""" + + completed = self.run_cli(canonical_json(p2_record())) + + self.assertEqual(completed.returncode, 0, completed.stderr) + result = json.loads(completed.stdout) + self.assertEqual(result["phase_id"], "p2") + self.assertTrue(result["implementation_readiness"]) + self.assertTrue(result["activation_eligibility"]) + self.assertFalse(result["activation_authority"]) + self.assertFalse(result["claim_authority"]) + self.assertEqual(result["fallback"], "exact_unchanged_baseline") + self.assertIn("external_activation_authority_required", result["blockers"]) + self.assertEqual(completed.stderr, b"") + + def test_cli_rejects_ambiguous_json_without_reflecting_input(self) -> None: + """Break caught: duplicate evidence keys reach an evaluator or an error echo.""" + + completed = self.run_cli(b'{"phase_id":"p2","phase_id":"private-value"}\n') + + self.assertEqual(completed.returncode, 65) + self.assertEqual(completed.stdout, b"") + response = json.loads(completed.stderr) + self.assertEqual(response["operation"], "evaluate_phase") + self.assertEqual(response["reason"], "evaluation_input_rejected") + self.assertNotIn(b"private-value", completed.stderr) + + +if __name__ == "__main__": + unittest.main() diff --git a/plugins/context-guard/bin/bash_reference_policy.py b/plugins/context-guard/bin/bash_reference_policy.py index 341efd78..003e88f8 100644 --- a/plugins/context-guard/bin/bash_reference_policy.py +++ b/plugins/context-guard/bin/bash_reference_policy.py @@ -36,7 +36,7 @@ # Audited digest of Receipt's package-files.json for each exact dependency # version. Invalid or missing pins are deliberately unavailable in production. EXPECTED_RECEIPT_PACKAGE_FILES_SHA256_BY_VERSION: dict[str, str] = { - "0.2.0": "17a930f7877127698c8189181d19fae7e973c446d03cf65dc9cb4b520f316f6e", + "0.2.0": "1b5070852db414d6365e685daf44f1f803b26598e1f6d8880566b5140714f428", } _TRANSACTION_ID_RE = re.compile(r"^[a-f0-9]{64}$") _EXACT_NPM_VERSION_RE = re.compile(r"^[0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z.-]+)?$") diff --git a/research/p2-p6-provider-free-implementation.md b/research/p2-p6-provider-free-implementation.md new file mode 100644 index 00000000..2619f3d9 --- /dev/null +++ b/research/p2-p6-provider-free-implementation.md @@ -0,0 +1,108 @@ +# P2-P6 provider-free implementation boundary + +_Status date: 2026-08-11 KST_ + +This document maps the stronger token-saving mechanisms that already exist to +the P2-P6 roadmap and defines the remaining local implementation work. It does +not activate a host route, authorize provider calls, or support a savings +claim. Until the phase gates pass, every surface remains explicit, default-off, +shadow-only, advisory, or evaluation-only. + +## Shipped mechanism inventory + +The inventory below is source- and test-backed. A mechanism can be implemented +without being activated, provider-measured, or eligible for a claim. + +| Mechanism | Phase use | Shipped source | Focused evidence | Frozen boundary | +| --- | --- | --- | --- | --- | +| Exact assembly and protected fallback | P2 candidate construction; P5 blueprint assembly | `packages/context-guard-receipt/python/context_guard_receipt/assembly.py` | `packages/context-guard-receipt/tests/contract/test_g005_assembly.py`, `test_g005_evidence_pack.py` | Caller supplies local bytes and an explicit root. Non-beneficial, protected, ambiguous, or unavailable issuance preserves exact input or refuses; it never changes a host request. | +| Exact capability expansion | P2/P3 rehydration; P5 whole/item fallback | `packages/context-guard-receipt/python/context_guard_receipt/expansion.py` | `packages/context-guard-receipt/tests/contract/test_g005_expansion.py`, `test_g008_expansion.py` | Capability-only, source-bound, selection-bound exact local recovery. Missing, invalid, or stale evidence closes rather than guessing. | +| Diagnostics and diagnostic ledger | P2 duplicate/staleness observation; P4 prefix/cache evaluation | `packages/context-guard-receipt/python/context_guard_receipt/diagnostics.py`, `diagnostic_ledger.py` | `packages/context-guard-receipt/tests/contract/test_g009_diagnostics.py`, `test_g009_ledger.py` | Keyed fingerprints and advisory metadata only. Diagnostics do not contain source content and do not apply a route. Durable state requires its explicit opt-in tuple. | +| Deterministic router and shadow firewall | P4 regret evaluation | `packages/context-guard-receipt/python/context_guard_receipt/router.py`, `assembly.py` | `packages/context-guard-receipt/tests/contract/test_g005_router.py`, `test_g005_assembly.py` | Byte-cost decision and shadow report only. The caller retains pass-through authority; no automatic routing or provider-cache conclusion follows. | +| Bounded runner and merged capture | P3 narrow disclosure evaluation; P5 repair-loop evidence | `packages/context-guard-receipt/python/context_guard_receipt/runner.py`, `merged_capture.py` | `packages/context-guard-receipt/tests/contract/test_g008_runner.py`, `test_g014_merged_capture.py` | Explicit local command or completed-capture input only, with sanitized bounded output and exact escrow expansion. It neither observes nor rewrites a provider request. | +| Typed blueprint | P5 edit-adjunct evaluation | `packages/context-guard-receipt/python/context_guard_receipt/blueprint.py`, `assembly.py` | `packages/context-guard-receipt/tests/contract/test_g005_assembly.py`, `test_g005_expansion.py` | Descriptor and local source evidence only. It emits a typed advisory artifact with exact whole/item fallback, never autonomous edit authority. | +| Execution twin | P5 repeated-context/revision evaluation; P6 specialized track | `packages/context-guard-receipt/python/context_guard_receipt/execution_twin.py` | `packages/context-guard-receipt/tests/contract/test_g010_twin.py`, `test_g010_cli.py` | Explicit `--experimental-twin` and isolated local state are required. Results are append-only comparison evidence, not replay, transcript mutation, or route authority. | +| Reference expiry | P3 stale-reference guard; P6 specialized track | `packages/context-guard-receipt/python/context_guard_receipt/reference_expiry.py` | `packages/context-guard-receipt/tests/contract/test_g011_reference_expiry.py`, `test_g011_cli.py` | Explicit `--experimental-reference-expiry` administration only. It changes capability eligibility, not source/store contents, and ordinary paths do not create expiry state. | +| Root-scoped MCP | P2/P3 explicit local retrieval; P6 specialized track | `packages/context-guard-receipt/python/context_guard_receipt/mcp.py` | `packages/context-guard-receipt/tests/contract/test_g012_mcp.py`, `test_g012_mcp_cli.py`, `packages/context-guard-receipt/tests/e2e/test_g012_mcp_stdio.py`, adversarial `test_g012_mcp_capabilities.py` and `test_g012_mcp_limits.py` | Ephemeral stdio server for one explicit absolute root. It installs no server/settings/hooks, creates no durable state, and exposes no runner, twin, expiry administration, credentials, or network. | +| Default-off experiments | P4 advisory cache work; P6 independent specialized tracks | `context-guard-kit/experimental_registry.py`, `benchmark_runner.py`, `cost_guard.py` | `tests/test_context_guard_kit.py` (registry, proof-carrying context, static relevance, semantic GC, learned/visual/local-proxy surfaces), `tests/test_context_guard_kit_benchmark_surfaces.py` | Registry enablement records local intent only. Plan/evaluation surfaces grant no runtime activation; explicit local emit/record surfaces remain separately gated. Provider-backed promotion and hosted savings claims remain unavailable. | + +Phase mapping is therefore additive, not a maturity claim: + +| Phase | Reused shipped mechanisms | Implementation readiness | Activation/evidence boundary | +| --- | --- | --- | --- | +| P2 shadow broker | assembly, expansion, evidence packs, protection, diagnostics, MCP | Local candidate construction, exact recovery, and diagnostic primitives exist. | No supported host observer is established; no live request mutation is authorized. P2 remains shadow/diagnostic and P1-F remains an unmet dependency. | +| P3 bounded disclosure | default-off `bash_reference_v1`, runner, merged capture, expansion, reference expiry, matched-study substrate | The narrow Bash reference route and local evaluation substrate exist. | Only explicit opt-in narrow output handling is implemented. No broader canary activation or provider/cost promotion evidence exists. | +| P4 router/cache | deterministic router, shadow firewall, diagnostics/ledger, cache score, cost guard, benchmark metadata | Local advisory decisions and separate accounting fields exist. | Automation stays off. Provider cache creation/read/invalidation economics and regret gates are not closed. | +| P5 adjuncts | execution twin, runner capture, typed blueprint, exact expansion | Local independent adjunct artifacts exist. | Each is explicit and advisory; no transcript rewrite, replay, autonomous edit, or coupled activation is authorized. | +| P6 specialized tracks | expiry, MCP, ledger, twin, proof-carrying-context verifier, semantic-GC/static-relevance/image-context plan gates | Bounded local components and evaluation surfaces exist per track. | No track inherits readiness from another. Every track remains default-off, plan/evaluation-only, or explicitly local until its own closed evidence and authority gates pass. | + +## Implemented frozen evaluation contract + +The closed evaluator is a pure, provider-free decision over +caller-supplied bounded local records. It must not read credentials, settings, +hooks, provider state, npm state, or the network; execute a provider; mutate a +request; activate a route; or emit a token, cost, percentage, or savings claim. + +For each phase, and for each P5 adjunct or P6 track independently, its result +must keep these four dimensions separate: + +1. `implementation_readiness`: whether the named local mechanisms and required + fallback/rollback surfaces are present and locally verified. +2. `activation_authority`: whether every dependency gate and a separate, + explicit phase/track activation authorization are present. +3. `provider_evidence`: whether the frozen provider-measured matched population + and fully loaded accounting required by the roadmap are complete. Local or + imported synthetic evidence cannot satisfy this dimension. +4. `claim_authority`: whether the claim-specific evidence and scope gates pass. + This is false whenever provider evidence, shifted-cost accounting, quality, + failure/correction guardrails, provenance, or scope binding is incomplete. + +The result is closed and deny-by-default: + +- unknown fields, duplicate phase/track IDs, missing required evidence, + ambiguous evidence, or inconsistent authority must produce a blocked result; +- `implementation_readiness=true` must never imply activation, provider + evidence, or claim authority; +- a blocked or unavailable result must select the exact unchanged baseline or + the independently verified exact local fallback—never a partial substitute; +- failure in one P5 adjunct or P6 track disables only that unit and cannot + promote, demote, or generalize another unit; +- activation requires the canonical roadmap dependency chain through that + phase plus separately recorded authority; this inventory supplies neither; +- evaluator output is advisory evidence only and cannot change runtime state. + +At this freeze, the only permissible repository-wide evaluation conclusion is: + +| Dimension | Frozen value | +| --- | --- | +| Implementation readiness | Per-mechanism and per-track; the inventory above establishes only the shipped local surfaces. | +| Activation authority | `false` for P2-P6 promotion. | +| Provider evidence | `incomplete`; P1-F and later phase-specific provider gates are not passed. | +| Claim authority | `false`; no savings claim is supported. | +| Fallback | Exact unchanged baseline, or the mechanism's independently verified exact local expansion when explicitly invoked. | + +## Provider-free implementation delivered + +`context-guard-kit/phase_evaluation.py` is the canonical evaluator and the +Receipt package ships an exact byte-identical copy. The installed entry point is +`context-guard-receipt evaluate phase --input `; it accepts at most 2 +MiB of duplicate-key-rejecting canonical JSON and emits canonical JSON. Closed, +recursively constrained input schemas for P2-P6 and a phase-specific result +schema ship with the package. + +The evaluator covers: + +1. P2 recall, exact rehydration, freshness, protected-zone, and construction-cost checks. +2. P3 matched failure/correction/retrieval and fully loaded cost guardrails. +3. P4 regret against always-pass-through and always-on plus separate cache accounting. +4. P5 revision freshness, source/test revalidation, failure differentiation, blueprint obligations, and independent bypass. +5. P6 independent workload, privacy, quality, cost, fallback, rollback, provider evidence, and non-generalization gates for every specialized track. + +The evaluator consumes only bounded canonical local records, changes no +runtime route, and emits no provider token, cost, percentage, or savings claim +of its own. Missing authority or provider measurements block activation without +blocking safe pass-through behavior. `tests/test_phase_evaluation.py` verifies +each phase's deny-by-default behavior and independent fallback; +`test_g015_phase_evaluation_cli.py` verifies installed CLI delivery, canonical +copy parity, ambiguous-input refusal, and recursively closed schemas. This +implementation grants no P2-P6 activation, provider-call, or claim authority. diff --git a/research/token-savings-roadmap.md b/research/token-savings-roadmap.md index f8153b51..71caa937 100644 --- a/research/token-savings-roadmap.md +++ b/research/token-savings-roadmap.md @@ -16,9 +16,9 @@ Current position: | Dimension | Status | | --- | --- | -| Shipped-code readiness | Narrow P3-style opt-in Bash-output reference route is merged. | +| Shipped-code readiness | Narrow P3-style Bash reference route is merged; closed provider-free P2-P6 evaluators are implemented but non-activating. | | Evidence readiness | P1-X: v7 stopped safely after 79 analytic identities; P1-F is not passed. | -| Release readiness | An attested immutable candidate exists; npm publication has not occurred. | +| Release readiness | Earlier attested candidates exist; the P2-P6 evaluator change still requires a new exact candidate. npm publication has not occurred. | | Public claim | Forbidden: the provider-backed v7 decision is P1-X and claim-disabled. | The roadmap is dependency-gated: @@ -262,6 +262,10 @@ over-limit study. Every decision remains descriptive-only with claims disabled. ## P2 — observe-only request-boundary broker +Implementation status: the packaged local evaluator now computes per-stratum +recall, exact rehydration, freshness, construction cost, and protected-zone +violations from a closed P2 record. It cannot observe or mutate a live request. + P2 may start only after `P1-F`, authoritative host evidence identifies a supported interception/attribution boundary, a P2 preregistration is approved, and any P2 provider/cost authorization is separately granted. @@ -281,6 +285,10 @@ the Bash route stays the only active narrow mechanism. ## P3 — bounded progressive-disclosure canary +Implementation status: the packaged evaluator now verifies exact matched pairs, +retrievals, failure/correction guardrails, and fully loaded provider-cost fields. +It evaluates imported measurements only and launches no canary or provider call. + The existing Bash reference route is the first narrow implementation candidate. Promotion requires: @@ -297,6 +305,10 @@ loaded cost immediately demotes the affected stratum to baseline. ## P4 — do-nothing router and cache economics +Implementation status: the packaged evaluator now compares advisory, +always-pass-through, and always-on outcomes with separate cache creation, read, +invalidation, latency, and provider-cost accounting. Its route is advisory only. + Build the router first as shadow/advisory metadata over the three deterministic routes. It must expose confidence and bypass reasons and retain abstentions and failures in its regret report. @@ -309,6 +321,10 @@ stratum. ## P5 — repeated-context and repair-loop adjuncts +Implementation status: execution-twin, failure-cone, and typed-blueprint +evidence now receive independent freshness, differentiation, fallback, quality, +and cost decisions. The evaluator applies none of the adjuncts. + Implement separately and promote incrementally: 1. append-only execution twin bound to revision, paths, commands, tests, failed @@ -324,6 +340,12 @@ authorized. ## P6 — specialized high-upside tracks +Implementation status: the five named tracks now have independent closed +evaluation records for scope, workload, baseline, privacy, quality, +failure/correction guardrails, complete cost model, fallback, rollback, provider +evidence, and authority. Plan-only tracks remain non-runtime, and no result may +generalize beyond its frozen scope. + Each track gets its own frozen workload and can be promoted only independently: - task-scoped expansion leases/context GC; @@ -341,14 +363,22 @@ cost accounting. ## Immediate ordered work -1. Refresh HANDOFF and establish this canonical roadmap. -2. Close all provider-free P1 readiness checks and write the external - authorization packet. -3. Use the recorded GitHub/P1/npm-candidate authorization. Keep npm `next`, npm - `latest`, artifact deletion, and P2-P6 provider work blocked. -4. Execute and analyze P1. Stop if its exit gate fails. -5. Implement/evaluate P2, then P3, then P4, then P5, then P6, checkpointing each - gate before starting the next. +Provider-free P2-P6 implementation is now closed behind the packaged local +evaluator. The evaluator and its schemas are explicit evaluation surfaces only: +P1-F and phase-specific external authority remain unmet, runtime activation and +claim authority remain false, and exact fallback is unchanged. Delivery may +proceed only to a GitHub PR or an npm candidate under separately recorded +authority; npm `next` and `latest` remain untouched. + +1. Finish package integrity, release smoke, full prepublish, review, and the + GitHub PR for the provider-free P2-P6 evaluator. +2. Build and attest a new exact npm candidate only. Keep npm `next`, npm + `latest`, artifact deletion, and all P2-P6 provider work blocked. +3. Keep active promotion stopped until a fresh P1-F exit exists. The current + P1-X evidence and local evaluator outputs unlock nothing. +4. After P1-F, obtain a new phase-specific call/spend/privacy authorization and + execute P2 through P6 sequentially; a failed gate stops later phases and + preserves the exact prior route. The external authorization packet records the user's bounded authority; the packet does not create or expand authority by itself. It must be frozen after diff --git a/tests/test_contextguard_stage2_feasibility.py b/tests/test_contextguard_stage2_feasibility.py index daaa15f0..597a2ea2 100644 --- a/tests/test_contextguard_stage2_feasibility.py +++ b/tests/test_contextguard_stage2_feasibility.py @@ -67,6 +67,7 @@ "context-guard-kit/bash_reference_policy.py", "context-guard-kit/benchmark_runner.py", "context-guard-kit/context_guard_commands.py", + "context-guard-kit/phase_evaluation.py", "context-guard-kit/rewrite_bash_for_token_budget.py", "context-guard-kit/setup_wizard.py", "context-guard-kit/trim_command_output.py", @@ -100,28 +101,29 @@ "tests/test_contextguard_stage2_feasibility.py", "tests/test_contextguard_stage2_protected_surfaces.py", "tests/test_npm_candidates.py", + "tests/test_phase_evaluation.py", "tests/test_release_candidate_smoke.py", "tests/test_workflows.py", } ) -EXPECTED_RECEIPT_COMPANION_INVENTORY_COUNT = 113 +EXPECTED_RECEIPT_COMPANION_INVENTORY_COUNT = 121 RECEIPT_COMPANION_INVENTORY = [ {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/LICENSE', 'sha256': 'c71d239df91726fc519c6eb72d318ec65820627232b2f796219e87dcf35d0ab4'}, {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/NOTICE', 'sha256': '40978c42e96a7b452cb77ef41f28961ca880e46ee7fa7c9589afa4d532655779'}, - {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/README.md', 'sha256': 'b075742abc57962a5c10c9edcc41c67a16947a2ddd13fb64fb97e7c4d27e57e7'}, + {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/README.md', 'sha256': 'e8e43ac7c76bb032080eed9ccb4be00f2c5c840fb44bba93f4e1c93f666d4a89'}, {'file_type': 'regular', 'mode': '0755', 'path': 'packages/context-guard-receipt/bin/context-guard-receipt-mcp.cjs', 'sha256': '883b893d5ee484d63b78174ace60e171dc26e032d05dd19298fb6d6c5229cffd'}, {'file_type': 'regular', 'mode': '0755', 'path': 'packages/context-guard-receipt/bin/context-guard-receipt.cjs', 'sha256': 'bdab50b0476e40024ea64f1f6cd0a46260b4707e2297d212bf5034cfd5a87ff8'}, - {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/bin/launcher.cjs', 'sha256': '7f9c630fdcd8df5fe561a15dcafe1961218b9d5b28b3a3b0c6ccfba4ffa96fed'}, - {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/dev/package_check.py', 'sha256': 'f2512995ad2773a028e5efee87f7545e696d8f188fcf8b566e8f5c43591d13c8'}, - {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/dev/packaged_acceptance.py', 'sha256': '4e19ffbc8ac97affed3c1bfc7204b09e09fa63cd750c745cac8ef1adf63e78dd'}, - {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/package-files.json', 'sha256': '17a930f7877127698c8189181d19fae7e973c446d03cf65dc9cb4b520f316f6e'}, + {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/bin/launcher.cjs', 'sha256': '4282241f133eda1745da80f85c982ef0f6be68087f3eaa6fd230d4948abfe6ec'}, + {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/dev/package_check.py', 'sha256': '10036c058031a9de14a310bbd385f78c8b1d50a2919b83949befa40642ab8424'}, + {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/dev/packaged_acceptance.py', 'sha256': '0c30434371b16e88176185e47a3f890d85ecf475c77db63f2f73b23b8f264ca1'}, + {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/package-files.json', 'sha256': '1b5070852db414d6365e685daf44f1f803b26598e1f6d8880566b5140714f428'}, {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/package.json', 'sha256': 'daf789323e9b194943b7222bd0bf112432460afe0174d0a3363cbadbbd37c475'}, {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/python/context_guard_receipt/__init__.py', 'sha256': '1046588c63e24a72c3a57ab0ebd6d60d86c158358b5bbd50ca15cf26322fabc6'}, {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/python/context_guard_receipt/assembly.py', 'sha256': '0e28b6e0874477314436eecb532c767d61efe6d506ae8f79d98fae4b41dd35ea'}, {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/python/context_guard_receipt/blueprint.py', 'sha256': 'f4b8b617832ebe4bd5dc585f762a20b71b37ce79d54b6cd751f1e5fde5b785f0'}, {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/python/context_guard_receipt/bootstrap.py', 'sha256': 'fa846a8968c5199618ab68a86424c0cb88c32250291faf3ac37f26d14d4b018e'}, {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/python/context_guard_receipt/canonical.py', 'sha256': '91b57a1ebf2cc8fa0025ccfc8eaf6f50bc9363e6d3bc05c517b2014bf8a590c7'}, - {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/python/context_guard_receipt/cli.py', 'sha256': 'e93c8970a1f06cff4511e62c1e6d7803f94d083239b159a2839da7e8ca3a0bb9'}, + {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/python/context_guard_receipt/cli.py', 'sha256': '0a60b550aa620e029fdc809749160d1fb3382864557e6016d56b4780c8f4c430'}, {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/python/context_guard_receipt/cli_io.py', 'sha256': '2de5ef56762e015264527306f19b1b72995cc3fffd8cd6cb58c8206e255c5baf'}, {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/python/context_guard_receipt/contracts.py', 'sha256': '1127a9b90bf2da63a097b066c7f1678109dcf622f40dd6746ef055aa7a98e39e'}, {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/python/context_guard_receipt/diagnostic_ledger.py', 'sha256': '3cc7865709c273b72136c48b1026ed5cd2830ea1bf76da4e424da08ccc13499d'}, @@ -132,6 +134,7 @@ {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/python/context_guard_receipt/identity.py', 'sha256': '31d4a0ba5e2a04b277a027a872ee0172c5d27ed09b60c41f53f286dd2d8b963c'}, {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/python/context_guard_receipt/mcp.py', 'sha256': 'db251fdd3e3d98cd83fd9a29ee0b90cb308c1bfa3fbed9122a217c80e75fe4c2'}, {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/python/context_guard_receipt/merged_capture.py', 'sha256': 'a19c605a47b666f302b8b993d1e0973bfded46c1974022c2620c5ef5d598b7cf'}, + {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/python/context_guard_receipt/phase_evaluation.py', 'sha256': 'e9e6747e1955789793a22826b71f73265607fd80ab30bf48f6dfae05852f1104'}, {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/python/context_guard_receipt/protection.py', 'sha256': '67ae06abb102292b3db09a6731a4aab90b3bc6ceb6dbe836fc636f82f783c347'}, {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/python/context_guard_receipt/receipts.py', 'sha256': '11c02d9df36be0dec2316594fd083ec39a1284325ded440de075081d2e56ddb0'}, {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/python/context_guard_receipt/reference_expiry.py', 'sha256': '2445292456776d5fcbf789f75a71781d64f12865958249d192cfc5a5ff27f2f6'}, @@ -155,6 +158,12 @@ {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/schemas/evidence-reference.schema.json', 'sha256': 'f94fa353dac99a08793461ca9ec72962ce12de2e5328f94039048190db70071e'}, {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/schemas/expansion-envelope.schema.json', 'sha256': 'f838f84a06a433e62706467aa40097194f458bb2b3d42c600159558bed292d71'}, {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/schemas/expansion-refusal.schema.json', 'sha256': 'c5196da89d9b96349deb4c2c0ad2970d6f27d7760f9236b6d07d702443ee9da0'}, + {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/schemas/phase-evaluation-p2.schema.json', 'sha256': 'd4390e71109e2704c4bc6f0935997d2b4b3f7d7cfc49ed92ef05e27eb21807bd'}, + {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/schemas/phase-evaluation-p3.schema.json', 'sha256': 'ac0687ce2cd43ec4954d3fb0a876284fa75829435244913b5f81b275a4c234d7'}, + {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/schemas/phase-evaluation-p4.schema.json', 'sha256': '58bb474f5655b60155aeba0a7dc135697cb52c08364692bf10a14ec5ca68fdda'}, + {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/schemas/phase-evaluation-p5.schema.json', 'sha256': 'b4e7f888c8a041065af130c808d8bb47c5eb42e395e18d8f8c53ea4c7eac1457'}, + {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/schemas/phase-evaluation-p6.schema.json', 'sha256': 'ed19929a20da8609c472f2d96ca5de9e32f7d32365b640876c9dbb22d9e33b00'}, + {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/schemas/phase-evaluation-result.schema.json', 'sha256': 'a608f3426c7a4814f7d081be2963b979d03e895a6e44e85058aa67ead43368af'}, {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/schemas/protection-decision.schema.json', 'sha256': 'e7cf1b413d286347fda8f0f3a993676212e257f7e280757657032c23b5f9415f'}, {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/schemas/reference-expiry-inspection.schema.json', 'sha256': '6f862e4e39ebb09e14952b542d4a28a52c618900ecfa07dca063846c638721e1'}, {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/schemas/reference-expiry-metadata.schema.json', 'sha256': 'a72ed7c5f422732437cdc9e61e00efc5ea7e765c74955243f5b11b8a6eb12a73'}, @@ -185,7 +194,7 @@ {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/tests/adversarial/test_g012_mcp_limits.py', 'sha256': 'f08aa835ab1ed03c24df35caa3c8bf97640886ddde467f2532ff722a9bf1aa82'}, {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/tests/contract/__init__.py', 'sha256': '5075760cded34ab259a764674a6620d857ab3eb623e037bf5066abe132de88bd'}, {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/tests/contract/test_boundary.py', 'sha256': '9bcefbbfbaad563cbc4765e3231caf09cd993bc147fe7e490d3d088e487b8754'}, - {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/tests/contract/test_g001_distribution_contract.py', 'sha256': '7af1c294242bb9739befda35491622e65664c4c574a25b5802a3e1eb378e3ebd'}, + {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/tests/contract/test_g001_distribution_contract.py', 'sha256': '3a67f55ca86170c1a9d63918c33a141336fcf056cb534abcb72d886e358818a5'}, {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/tests/contract/test_g002_canonical.py', 'sha256': '574a66140918d02765e5de7a1fa2e243843e32d464e438fe637c42aae41d7fe5'}, {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/tests/contract/test_g002_protection.py', 'sha256': 'b05064c39f88962a7b561532cfa2ef00b8a90605375cd06d9052ced8d0ef352e'}, {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/tests/contract/test_g003_identity.py', 'sha256': '0c6c7f18bed584fe707b8203d1534f78e21f192226f2dc5d961696eac4fa9bd9'}, @@ -216,6 +225,7 @@ {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/tests/contract/test_g012_mcp_cli.py', 'sha256': '539014f2009a78832467115d6b671c5807d2c4e16e7519ccde6ed70e629ec70a'}, {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/tests/contract/test_g013_package_audit.py', 'sha256': '948e8c6ff27851ef60a43570c7b5a0f30185d15b06e9504780943a3bd3067158'}, {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/tests/contract/test_g014_merged_capture.py', 'sha256': 'dacc04f7ac0b09b210ce9cbb2081d049707fff92cb9effad7c1e03a95669c600'}, + {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/tests/contract/test_g015_phase_evaluation_cli.py', 'sha256': 'f7359e7df53e82811149a377fce6305d8ce4e75cdf067cd123fd9dc1af9b2f77'}, {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/tests/e2e/__init__.py', 'sha256': '48a5ccfc49a840928c6de0ea2c978a12a0abd78e2f361ec96f6e9a0f15bddca0'}, {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/tests/e2e/test_g001_offline_distribution.py', 'sha256': '01d856590d17f5a457c1664b49c92f6b52378314a9b272df802a759113413b7d'}, {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/tests/e2e/test_g012_mcp_stdio.py', 'sha256': '96ac49c60d27d2558bbfaec9805135605c15ab25bd5de682dc63620ade52a8fc'}, diff --git a/tests/test_phase_evaluation.py b/tests/test_phase_evaluation.py new file mode 100644 index 00000000..88d48576 --- /dev/null +++ b/tests/test_phase_evaluation.py @@ -0,0 +1,583 @@ +from __future__ import annotations + +import copy +import sys +import unittest +from pathlib import Path + + +ROOT = Path(__file__).resolve().parents[1] +TOOLKIT_ROOT = ROOT / "context-guard-kit" +if str(TOOLKIT_ROOT) not in sys.path: + sys.path.insert(0, str(TOOLKIT_ROOT)) + +from phase_evaluation import evaluate_p2, evaluate_p3, evaluate_p4, evaluate_p5, evaluate_p6 + + +class P2ShadowEvaluationTests(unittest.TestCase): + def valid_record(self) -> dict[str, object]: + return { + "schema_version": "contextguard.phase-evaluation.p2/v1", + "phase_id": "p2", + "baseline_fallback_verified": True, + "activation_authorized": True, + "dependency_gates_passed": True, + "observed_at": 100, + "minimum_recall_basis_points": 9_000, + "records": [ + { + "record_id": "r1", + "stratum": "refactor", + "relevant": True, + "candidate_omission": True, + "recalled": True, + "source_digest": "sha256:" + "1" * 64, + "rehydrated_digest": "sha256:" + "1" * 64, + "fresh_until": 101, + "protection": "eligible", + "construction_cost_microunits": 12, + }, + { + "record_id": "r2", + "stratum": "refactor", + "relevant": False, + "candidate_omission": False, + "recalled": False, + "source_digest": "sha256:" + "2" * 64, + "rehydrated_digest": None, + "fresh_until": 101, + "protection": "protected", + "construction_cost_microunits": 3, + }, + ], + } + + def test_valid_shadow_records_are_locally_ready_but_never_grant_authority(self) -> None: + result = evaluate_p2(self.valid_record()) + self.assertTrue(result["implementation_readiness"]) + self.assertTrue(result["evaluation_evidence_complete"]) + self.assertTrue(result["activation_eligibility"]) + self.assertFalse(result["activation_authority"]) + self.assertFalse(result["claim_authority"]) + self.assertEqual(result["construction_cost_microunits"], 15) + self.assertEqual( + result["strata"], + [{ + "stratum": "refactor", + "relevant_record_count": 1, + "recalled_relevant_record_count": 1, + "recall_basis_points": 10_000, + "threshold_passed": True, + }], + ) + self.assertIn("external_activation_authority_required", result["blockers"]) + + def test_protected_retained_record_is_safe_but_protected_omission_is_blocked(self) -> None: + safe = evaluate_p2(self.valid_record()) + self.assertTrue(safe["implementation_readiness"]) + + unsafe = self.valid_record() + unsafe["records"][1]["candidate_omission"] = True + result = evaluate_p2(unsafe) + self.assertFalse(result["implementation_readiness"]) + self.assertIn("protected_omission", result["blockers"]) + + def test_each_unsafe_omission_condition_fails_closed(self) -> None: + mutations = { + "incomplete": lambda row: row.pop("recalled"), + "stale": lambda row: row.__setitem__("fresh_until", 100), + "non_rehydratable": lambda row: row.__setitem__("rehydrated_digest", "sha256:" + "2" * 64), + "cost": lambda row: row.__setitem__("construction_cost_microunits", None), + "malformed": lambda row: row.__setitem__("unexpected", True), + } + for name, mutate in mutations.items(): + with self.subTest(name=name): + record = self.valid_record() + mutate(record["records"][0]) + result = evaluate_p2(record) + self.assertFalse(result["implementation_readiness"]) + self.assertFalse(result["activation_authority"]) + self.assertFalse(result["claim_authority"]) + self.assertEqual(result["fallback"], "exact_unchanged_baseline") + + def test_recall_threshold_is_computed_per_stratum(self) -> None: + record = self.valid_record() + missed = copy.deepcopy(record["records"][0]) + missed["record_id"] = "r3" + missed["stratum"] = "debug" + missed["recalled"] = False + record["records"].append(missed) + result = evaluate_p2(record) + self.assertFalse(result["implementation_readiness"]) + self.assertIn("recall_threshold_failed", result["blockers"]) + self.assertEqual(result["strata"][0]["stratum"], "debug") + self.assertFalse(result["strata"][0]["threshold_passed"]) + + +class P3CanaryEvaluationTests(unittest.TestCase): + def valid_record(self) -> dict[str, object]: + def attempt( + attempt_id: str, + pair_id: str, + arm: str, + *, + provider_cost: int, + ) -> dict[str, object]: + return { + "attempt_id": attempt_id, + "pair_id": pair_id, + "arm": arm, + "task_success": True, + "corrections": 0, + "retrievals": [] if arm == "baseline" else [ + {"retrieval_id": attempt_id + "-get", "exact": True} + ], + "measurement": { + "primary_tokens": 1_000, + "provider_cost_microunits": provider_cost, + "retry_cost_microunits": 0, + "correction_cost_microunits": 0, + "retrieval_cost_microunits": 2, + "external_cost_microunits": 0, + "local_compute_cost_microunits": 1, + }, + } + + return { + "schema_version": "contextguard.phase-evaluation.p3/v1", + "phase_id": "p3", + "baseline_fallback_verified": True, + "activation_authorized": True, + "dependency_gates_passed": True, + "claim_scope_bound": True, + "evidence_origin": "provider_measured", + "matched_pair_ids": ["pair-1"], + "attempts": [ + attempt("b1", "pair-1", "baseline", provider_cost=100), + attempt("c1", "pair-1", "canary", provider_cost=80), + ], + "thresholds": { + "maximum_failure_rate_increase_basis_points": 999, + "require_corrections_non_inferior": True, + "require_fully_loaded_cost_improvement": True, + }, + } + + def test_complete_matched_population_is_eligible_but_local_input_never_grants_authority(self) -> None: + result = evaluate_p3(self.valid_record()) + self.assertTrue(result["implementation_readiness"]) + self.assertTrue(result["provider_evidence"]) + self.assertTrue(result["activation_eligibility"]) + self.assertFalse(result["activation_authority"]) + self.assertFalse(result["claim_authority"]) + self.assertEqual(result["evaluated_pair_count"], 1) + self.assertEqual(result["evaluated_retrieval_count"], 1) + self.assertEqual(result["baseline_fully_loaded_cost_microunits"], 103) + self.assertEqual(result["canary_fully_loaded_cost_microunits"], 83) + self.assertIn("external_activation_authority_required", result["blockers"]) + self.assertIn("external_claim_authority_required", result["blockers"]) + + def test_missing_extra_or_duplicate_pair_member_blocks_complete_population(self) -> None: + mutations = ("missing", "extra", "duplicate-arm") + for mutation in mutations: + with self.subTest(mutation=mutation): + record = self.valid_record() + if mutation == "missing": + record["attempts"].pop() + elif mutation == "extra": + extra = copy.deepcopy(record["attempts"][0]) + extra["attempt_id"] = "unmatched" + extra["pair_id"] = "pair-x" + record["attempts"].append(extra) + else: + record["attempts"][1]["arm"] = "baseline" + result = evaluate_p3(record) + self.assertFalse(result["provider_evidence"]) + self.assertFalse(result["activation_eligibility"]) + self.assertIn("matched_population_incomplete", result["blockers"]) + + def test_retrieval_failure_correction_and_fully_loaded_cost_are_computed_not_asserted(self) -> None: + mutations = { + "retrieval": lambda value: value["attempts"][1]["retrievals"][0].__setitem__("exact", False), + "failure": lambda value: value["attempts"][1].__setitem__("task_success", False), + "correction": lambda value: value["attempts"][1].__setitem__("corrections", 1), + "cost": lambda value: value["attempts"][1]["measurement"].__setitem__("provider_cost_microunits", 120), + "missing-cost": lambda value: value["attempts"][1]["measurement"].__setitem__("external_cost_microunits", None), + } + expected = { + "retrieval": "exact_retrieval_incomplete", + "failure": "failure_guardrail_failed", + "correction": "correction_guardrail_failed", + "cost": "fully_loaded_cost_not_improved", + "missing-cost": "provider_measurement_incomplete", + } + for name, mutate in mutations.items(): + with self.subTest(name=name): + record = self.valid_record() + mutate(record) + result = evaluate_p3(record) + self.assertFalse(result["activation_eligibility"]) + self.assertFalse(result["activation_authority"]) + self.assertFalse(result["claim_authority"]) + self.assertIn(expected[name], result["blockers"]) + + +class P4RouterEvaluationTests(unittest.TestCase): + def valid_record(self) -> dict[str, object]: + def outcome(*, quality: int, cost: int, provider: int) -> dict[str, object]: + return { + "quality_basis_points": quality, + "total_cost_microunits": cost, + "cache_accounting": { + "creation_microunits": 2, + "read_microunits": 1, + "invalidation_microunits": 1, + "latency_microunits": 1, + "provider_cost_microunits": provider, + }, + } + + return { + "schema_version": "contextguard.phase-evaluation.p4/v1", + "phase_id": "p4", + "baseline_fallback_verified": True, + "dependency_gates_passed": True, + "activation_authorized": True, + "minimum_confidence_basis_points": 8_000, + "trials": [{ + "trial_id": "route-1", + "advisory_status": "selected", + "advisory_route": "on", + "confidence_basis_points": 9_000, + "bypass_reasons": [], + "outcomes": { + "advisory": outcome(quality=9_500, cost=80, provider=75), + "always_pass_through": outcome(quality=9_400, cost=100, provider=95), + "always_on": outcome(quality=9_500, cost=90, provider=85), + }, + }], + } + + def test_nonnegative_regret_is_advisory_only_and_never_grants_runtime_authority(self) -> None: + result = evaluate_p4(self.valid_record()) + self.assertTrue(result["evaluation_evidence_complete"]) + self.assertTrue(result["activation_eligibility"]) + self.assertFalse(result["activation_authority"]) + self.assertFalse(result["claim_authority"]) + self.assertEqual(result["runtime_route_changed"], False) + self.assertEqual(result["selected_route"], "on") + self.assertEqual(result["trials"][0]["regret_microunits"], 10) + self.assertEqual(result["trials"][0]["evaluation_route"], "on") + + def test_negative_regret_low_confidence_and_quality_regression_select_pass_through(self) -> None: + def make_negative_regret(trial: dict[str, object]) -> None: + trial["outcomes"]["advisory"]["total_cost_microunits"] = 91 + trial["outcomes"]["advisory"]["cache_accounting"]["provider_cost_microunits"] = 86 + + mutations = { + "negative_regret": make_negative_regret, + "low_confidence": lambda trial: trial.__setitem__("confidence_basis_points", 7_999), + "quality_regression": lambda trial: trial["outcomes"]["advisory"].__setitem__("quality_basis_points", 9_399), + } + expected = { + "negative_regret": "negative_regret", + "low_confidence": "low_confidence", + "quality_regression": "quality_regression", + } + for name, mutate in mutations.items(): + with self.subTest(name=name): + record = self.valid_record() + mutate(record["trials"][0]) + result = evaluate_p4(record) + self.assertEqual(result["selected_route"], "pass_through") + self.assertEqual(result["trials"][0]["evaluation_route"], "pass_through") + self.assertIn(expected[name], result["trials"][0]["bypass_reasons"]) + self.assertFalse(result["activation_eligibility"]) + self.assertFalse(result["runtime_route_changed"]) + + def test_missing_each_cache_dimension_fails_closed(self) -> None: + for field in ( + "creation_microunits", + "read_microunits", + "invalidation_microunits", + "latency_microunits", + "provider_cost_microunits", + ): + with self.subTest(field=field): + record = self.valid_record() + record["trials"][0]["outcomes"]["always_on"]["cache_accounting"].pop(field) + result = evaluate_p4(record) + self.assertEqual(result["selected_route"], "pass_through") + self.assertIn("cache_accounting_incomplete", result["blockers"]) + self.assertIn("cache_accounting_incomplete", result["trials"][0]["bypass_reasons"]) + + def test_abstentions_failures_confidence_and_caller_bypass_reasons_remain_visible(self) -> None: + record = self.valid_record() + abstention = copy.deepcopy(record["trials"][0]) + abstention["trial_id"] = "route-2" + abstention["advisory_status"] = "abstained" + abstention["advisory_route"] = None + abstention["confidence_basis_points"] = 4_000 + abstention["bypass_reasons"] = ["unsupported_shape"] + failure = copy.deepcopy(abstention) + failure["trial_id"] = "route-3" + failure["advisory_status"] = "failed" + failure["confidence_basis_points"] = 0 + failure["bypass_reasons"] = ["router_error"] + record["trials"].extend([abstention, failure]) + + result = evaluate_p4(record) + self.assertEqual(result["abstention_count"], 1) + self.assertEqual(result["failure_count"], 1) + self.assertEqual(result["confidence_basis_points"], [9_000, 4_000, 0]) + self.assertEqual( + result["bypass_reason_counts"], + {"abstained": 1, "failed": 1, "low_confidence": 2, "router_error": 1, "unsupported_shape": 1}, + ) + self.assertEqual(result["selected_route"], "pass_through") + self.assertFalse(result["runtime_route_changed"]) + + def test_malformed_route_identity_is_not_reflected_in_output(self) -> None: + record = self.valid_record() + record["trials"][0]["trial_id"] = {"private": "value"} + record["trials"][0]["advisory_status"] = ["selected"] + record["trials"][0]["advisory_route"] = {"route": "on"} + + report = evaluate_p4(record)["trials"][0] + self.assertIsNone(report["trial_id"]) + self.assertIsNone(report["advisory_status"]) + self.assertIsNone(report["advisory_route"]) + + +class P5AdjunctEvaluationTests(unittest.TestCase): + def valid_record(self) -> dict[str, object]: + def adjunct(adjunct_id: str, suffix: str) -> dict[str, object]: + return { + "adjunct_id": adjunct_id, + "revision_digest": "sha256:" + "1" * 64, + "source_digest": "sha256:" + "2" * 64, + "test_digest": "sha256:" + "3" * 64, + "evidence_digests": ["sha256:" + suffix * 64], + "failure_cases": [ + {"case_id": "case-a", "exit_status": 1, "root_cause": "compile", "duplicate_of": None}, + {"case_id": "case-b", "exit_status": 1, "root_cause": "compile", "duplicate_of": "case-a"}, + ], + "bypass_verified": True, + "fallback_verified": True, + "baseline_quality_basis_points": 9_000, + "adjunct_quality_basis_points": 9_100, + "baseline_cost_microunits": 100, + "adjunct_cost_microunits": 90, + } + + return { + "schema_version": "contextguard.phase-evaluation.p5/v1", + "phase_id": "p5", + "dependency_gates_passed": True, + "activation_authorized": True, + "current_revision_digest": "sha256:" + "1" * 64, + "current_source_digest": "sha256:" + "2" * 64, + "current_test_digest": "sha256:" + "3" * 64, + "adjuncts": [ + adjunct("execution_twin", "4"), + adjunct("failure_cone", "5"), + adjunct("typed_blueprint", "6"), + ], + } + + def test_each_complete_adjunct_is_independently_eligible_but_advisory_only(self) -> None: + result = evaluate_p5(self.valid_record()) + self.assertEqual(result["evaluated_adjunct_count"], 3) + self.assertFalse(result["activation_authority"]) + self.assertFalse(result["claim_authority"]) + self.assertFalse(result["runtime_changed"]) + self.assertEqual( + [(item["adjunct_id"], item["decision"]) for item in result["adjuncts"]], + [("execution_twin", "eligible"), ("failure_cone", "eligible"), ("typed_blueprint", "eligible")], + ) + + def test_stale_revision_source_or_test_disables_only_the_affected_adjunct(self) -> None: + fields = ("revision_digest", "source_digest", "test_digest") + expected = ("stale_revision", "stale_source", "stale_test_state") + for field, blocker in zip(fields, expected): + with self.subTest(field=field): + record = self.valid_record() + record["adjuncts"][1][field] = "sha256:" + "9" * 64 + result = evaluate_p5(record) + reports = {item["adjunct_id"]: item for item in result["adjuncts"]} + self.assertEqual(reports["failure_cone"]["decision"], "bypass") + self.assertIn(blocker, reports["failure_cone"]["blockers"]) + self.assertEqual(reports["execution_twin"]["decision"], "eligible") + self.assertEqual(reports["typed_blueprint"]["decision"], "eligible") + + def test_different_exit_status_or_root_cause_is_never_deduplicated(self) -> None: + for field, value in (("exit_status", 2), ("root_cause", "link")): + with self.subTest(field=field): + record = self.valid_record() + record["adjuncts"][1]["failure_cases"][1][field] = value + result = evaluate_p5(record) + reports = {item["adjunct_id"]: item for item in result["adjuncts"]} + self.assertEqual(reports["failure_cone"]["decision"], "bypass") + self.assertIn("distinct_failure_deduplicated", reports["failure_cone"]["blockers"]) + self.assertEqual(reports["execution_twin"]["decision"], "eligible") + + def test_every_gate_is_independent_and_reversible(self) -> None: + mutations = { + "evidence_incomplete": lambda item: item.__setitem__("evidence_digests", []), + "bypass_unverified": lambda item: item.__setitem__("bypass_verified", False), + "exact_fallback_unverified": lambda item: item.__setitem__("fallback_verified", False), + "quality_regression": lambda item: item.__setitem__("adjunct_quality_basis_points", 8_999), + "fully_loaded_cost_not_improved": lambda item: item.__setitem__("adjunct_cost_microunits", 100), + } + for blocker, mutate in mutations.items(): + with self.subTest(blocker=blocker): + record = self.valid_record() + mutate(record["adjuncts"][2]) + blocked = evaluate_p5(record) + reports = {item["adjunct_id"]: item for item in blocked["adjuncts"]} + self.assertEqual(reports["typed_blueprint"]["decision"], "bypass") + self.assertIn(blocker, reports["typed_blueprint"]["blockers"]) + self.assertEqual(reports["execution_twin"]["decision"], "eligible") + self.assertEqual(reports["failure_cone"]["decision"], "eligible") + + restored = self.valid_record() + restored_result = evaluate_p5(restored) + restored_reports = {item["adjunct_id"]: item for item in restored_result["adjuncts"]} + self.assertEqual(restored_reports["typed_blueprint"]["decision"], "eligible") + + def test_invalid_adjunct_identity_is_not_reflected_in_output(self) -> None: + record = self.valid_record() + record["adjuncts"][0]["adjunct_id"] = {"private": "value"} + + report = evaluate_p5(record)["adjuncts"][0] + self.assertIsNone(report["adjunct_id"]) + + +class P6SpecializedTrackEvaluationTests(unittest.TestCase): + TRACKS = ( + "context_leases", + "scout_surgeon", + "counterfactual_ledger", + "negative_firewall", + "bounded_compilation", + ) + + def valid_record(self) -> dict[str, object]: + def track(track_id: str, suffix: str) -> dict[str, object]: + return { + "track_id": track_id, + "surface": "evaluation_only", + "workload_digest": "sha256:" + suffix * 64, + "baseline_digest": "sha256:" + "a" * 64, + "scope_digest": "sha256:" + "c" * 64, + "privacy_boundary_digest": "sha256:" + "d" * 64, + "privacy_verified": True, + "baseline_quality_basis_points": 9_000, + "track_quality_basis_points": 9_100, + "population_count": 10, + "baseline_failure_count": 1, + "track_failure_count": 1, + "maximum_failure_rate_increase_basis_points": 999, + "baseline_corrections": 1, + "track_corrections": 1, + "cost_model_digest": "sha256:" + "e" * 64, + "baseline_cost_microunits": 100, + "track_cost_microunits": 90, + "fallback_verified": True, + "rollback_verified": True, + "activation_authorized": True, + "provider_evidence_digest": "sha256:" + "b" * 64, + } + + return { + "schema_version": "contextguard.phase-evaluation.p6/v1", + "phase_id": "p6", + "dependency_gates_passed": True, + "tracks": [track(track_id, str(index + 1)) for index, track_id in enumerate(self.TRACKS)], + } + + def test_complete_tracks_keep_all_evidence_independent_and_never_change_runtime(self) -> None: + result = evaluate_p6(self.valid_record()) + self.assertEqual(result["evaluated_track_count"], 5) + self.assertFalse(result["runtime_changed"]) + self.assertFalse(result["activation_authority"]) + self.assertFalse(result["claim_authority"]) + for report, track_id in zip(result["tracks"], self.TRACKS): + self.assertEqual(report["track_id"], track_id) + self.assertEqual(report["decision"], "eligible") + self.assertTrue(report["workload_evidence"]) + self.assertTrue(report["baseline_evidence"]) + self.assertTrue(report["scope_evidence"]) + self.assertTrue(report["privacy_evidence"]) + self.assertTrue(report["quality_evidence"]) + self.assertTrue(report["failure_guardrail_evidence"]) + self.assertTrue(report["correction_guardrail_evidence"]) + self.assertTrue(report["cost_model_evidence"]) + self.assertTrue(report["cost_evidence"]) + self.assertTrue(report["fallback_evidence"]) + self.assertTrue(report["rollback_evidence"]) + self.assertTrue(report["authority_evidence"]) + self.assertTrue(report["provider_evidence"]) + self.assertFalse(report["generalization_allowed"]) + + def test_failed_or_uneconomic_track_falls_back_without_generalizing(self) -> None: + mutations = { + "privacy_evidence_incomplete": lambda track: track.__setitem__("privacy_verified", False), + "quality_regression": lambda track: track.__setitem__("track_quality_basis_points", 8_999), + "fully_loaded_cost_not_improved": lambda track: track.__setitem__("track_cost_microunits", 100), + "exact_fallback_unverified": lambda track: track.__setitem__("fallback_verified", False), + "rollback_unverified": lambda track: track.__setitem__("rollback_verified", False), + "activation_not_recorded": lambda track: track.__setitem__("activation_authorized", False), + "provider_measurement_incomplete": lambda track: track.__setitem__("provider_evidence_digest", None), + "scope_evidence_incomplete": lambda track: track.__setitem__("scope_digest", "invalid"), + "failure_guardrail_failed": lambda track: track.__setitem__("track_failure_count", 2), + "correction_guardrail_failed": lambda track: track.__setitem__("track_corrections", 2), + "cost_model_incomplete": lambda track: track.__setitem__("cost_model_digest", "invalid"), + } + for blocker, mutate in mutations.items(): + with self.subTest(blocker=blocker): + record = self.valid_record() + mutate(record["tracks"][2]) + reports = {item["track_id"]: item for item in evaluate_p6(record)["tracks"]} + self.assertEqual(reports["counterfactual_ledger"]["decision"], "fallback") + self.assertEqual(reports["counterfactual_ledger"]["fallback"], "exact_unchanged_baseline") + self.assertIn(blocker, reports["counterfactual_ledger"]["blockers"]) + self.assertEqual(reports["context_leases"]["decision"], "eligible") + self.assertEqual(reports["bounded_compilation"]["decision"], "eligible") + + def test_workload_and_baseline_are_required_per_track(self) -> None: + for field, blocker in ( + ("workload_digest", "workload_evidence_incomplete"), + ("baseline_digest", "baseline_evidence_incomplete"), + ): + with self.subTest(field=field): + record = self.valid_record() + record["tracks"][1][field] = "invalid" + reports = {item["track_id"]: item for item in evaluate_p6(record)["tracks"]} + self.assertEqual(reports["scout_surgeon"]["decision"], "fallback") + self.assertIn(blocker, reports["scout_surgeon"]["blockers"]) + self.assertEqual(reports["negative_firewall"]["decision"], "eligible") + + def test_plan_only_track_is_non_runtime_and_claim_blocked_even_with_complete_evidence(self) -> None: + record = self.valid_record() + record["tracks"][4]["surface"] = "plan_only" + result = evaluate_p6(record) + report = {item["track_id"]: item for item in result["tracks"]}["bounded_compilation"] + self.assertEqual(report["decision"], "plan_only") + self.assertIn("plan_only_non_runtime", report["blockers"]) + self.assertFalse(report["activation_eligibility"]) + self.assertFalse(report["claim_authority"]) + self.assertFalse(result["runtime_changed"]) + + def test_invalid_track_identity_and_surface_are_not_reflected_in_output(self) -> None: + record = self.valid_record() + record["tracks"][0]["track_id"] = {"private": "value"} + record["tracks"][0]["surface"] = ["evaluation_only"] + + report = evaluate_p6(record)["tracks"][0] + self.assertIsNone(report["track_id"]) + self.assertIsNone(report["surface"]) + + +if __name__ == "__main__": + unittest.main() From 019e6fea844161dd8d734b445addf3b6185298a4 Mon Sep 17 00:00:00 2001 From: Coden Date: Tue, 11 Aug 2026 12:34:04 +0900 Subject: [PATCH 2/4] fix: fail closed on malformed P6 phase identity --- context-guard-kit/bash_reference_policy.py | 2 +- context-guard-kit/phase_evaluation.py | 2 +- packages/context-guard-receipt/bin/launcher.cjs | 2 +- packages/context-guard-receipt/package-files.json | 4 ++-- .../context_guard_receipt/phase_evaluation.py | 2 +- plugins/context-guard/bin/bash_reference_policy.py | 2 +- tests/test_contextguard_stage2_feasibility.py | 7 ++++--- tests/test_phase_evaluation.py | 14 ++++++++++++++ 8 files changed, 25 insertions(+), 10 deletions(-) diff --git a/context-guard-kit/bash_reference_policy.py b/context-guard-kit/bash_reference_policy.py index 003e88f8..fbd87d76 100644 --- a/context-guard-kit/bash_reference_policy.py +++ b/context-guard-kit/bash_reference_policy.py @@ -36,7 +36,7 @@ # Audited digest of Receipt's package-files.json for each exact dependency # version. Invalid or missing pins are deliberately unavailable in production. EXPECTED_RECEIPT_PACKAGE_FILES_SHA256_BY_VERSION: dict[str, str] = { - "0.2.0": "1b5070852db414d6365e685daf44f1f803b26598e1f6d8880566b5140714f428", + "0.2.0": "de036a8d3256f6ffc6786928fa86e8d64a553708e5258d1900bb4964d8aa3d19", } _TRANSACTION_ID_RE = re.compile(r"^[a-f0-9]{64}$") _EXACT_NPM_VERSION_RE = re.compile(r"^[0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z.-]+)?$") diff --git a/context-guard-kit/phase_evaluation.py b/context-guard-kit/phase_evaluation.py index 65f492f2..8ab9d1ac 100644 --- a/context-guard-kit/phase_evaluation.py +++ b/context-guard-kit/phase_evaluation.py @@ -1049,7 +1049,7 @@ def evaluate_p6(record: object) -> dict[str, object]: eligible_tracks: list[str] = [] seen_tracks: set[str] = set() for track in tracks: - blockers: list[str] = [] + blockers = list(phase_blockers) track_id: object = None evidence = { "workload_evidence": False, diff --git a/packages/context-guard-receipt/bin/launcher.cjs b/packages/context-guard-receipt/bin/launcher.cjs index c1afc627..bc0c9632 100644 --- a/packages/context-guard-receipt/bin/launcher.cjs +++ b/packages/context-guard-receipt/bin/launcher.cjs @@ -131,7 +131,7 @@ const TRUSTED_PAYLOAD_DIGESTS = { 'python/context_guard_receipt/identity.py': '31d4a0ba5e2a04b277a027a872ee0172c5d27ed09b60c41f53f286dd2d8b963c', 'python/context_guard_receipt/mcp.py': 'db251fdd3e3d98cd83fd9a29ee0b90cb308c1bfa3fbed9122a217c80e75fe4c2', 'python/context_guard_receipt/merged_capture.py': 'a19c605a47b666f302b8b993d1e0973bfded46c1974022c2620c5ef5d598b7cf', - 'python/context_guard_receipt/phase_evaluation.py': 'e9e6747e1955789793a22826b71f73265607fd80ab30bf48f6dfae05852f1104', + 'python/context_guard_receipt/phase_evaluation.py': 'f25a4f31a96d5579823945d62b119193741f5599ec1a3301f5e6ab774dd15bf5', 'python/context_guard_receipt/protection.py': '67ae06abb102292b3db09a6731a4aab90b3bc6ceb6dbe836fc636f82f783c347', 'python/context_guard_receipt/receipts.py': '11c02d9df36be0dec2316594fd083ec39a1284325ded440de075081d2e56ddb0', 'python/context_guard_receipt/reference_expiry.py': '2445292456776d5fcbf789f75a71781d64f12865958249d192cfc5a5ff27f2f6', diff --git a/packages/context-guard-receipt/package-files.json b/packages/context-guard-receipt/package-files.json index 270fcc63..7dab15d0 100644 --- a/packages/context-guard-receipt/package-files.json +++ b/packages/context-guard-receipt/package-files.json @@ -28,7 +28,7 @@ { "mode": "0644", "path": "bin/launcher.cjs", - "sha256": "4282241f133eda1745da80f85c982ef0f6be68087f3eaa6fd230d4948abfe6ec" + "sha256": "20972582a35a845e024a11907b7faae8eec86ff235ae3333ad9715b683619797" }, { "mode": "0644", @@ -118,7 +118,7 @@ { "mode": "0644", "path": "python/context_guard_receipt/phase_evaluation.py", - "sha256": "e9e6747e1955789793a22826b71f73265607fd80ab30bf48f6dfae05852f1104" + "sha256": "f25a4f31a96d5579823945d62b119193741f5599ec1a3301f5e6ab774dd15bf5" }, { "mode": "0644", diff --git a/packages/context-guard-receipt/python/context_guard_receipt/phase_evaluation.py b/packages/context-guard-receipt/python/context_guard_receipt/phase_evaluation.py index 65f492f2..8ab9d1ac 100644 --- a/packages/context-guard-receipt/python/context_guard_receipt/phase_evaluation.py +++ b/packages/context-guard-receipt/python/context_guard_receipt/phase_evaluation.py @@ -1049,7 +1049,7 @@ def evaluate_p6(record: object) -> dict[str, object]: eligible_tracks: list[str] = [] seen_tracks: set[str] = set() for track in tracks: - blockers: list[str] = [] + blockers = list(phase_blockers) track_id: object = None evidence = { "workload_evidence": False, diff --git a/plugins/context-guard/bin/bash_reference_policy.py b/plugins/context-guard/bin/bash_reference_policy.py index 003e88f8..fbd87d76 100644 --- a/plugins/context-guard/bin/bash_reference_policy.py +++ b/plugins/context-guard/bin/bash_reference_policy.py @@ -36,7 +36,7 @@ # Audited digest of Receipt's package-files.json for each exact dependency # version. Invalid or missing pins are deliberately unavailable in production. EXPECTED_RECEIPT_PACKAGE_FILES_SHA256_BY_VERSION: dict[str, str] = { - "0.2.0": "1b5070852db414d6365e685daf44f1f803b26598e1f6d8880566b5140714f428", + "0.2.0": "de036a8d3256f6ffc6786928fa86e8d64a553708e5258d1900bb4964d8aa3d19", } _TRANSACTION_ID_RE = re.compile(r"^[a-f0-9]{64}$") _EXACT_NPM_VERSION_RE = re.compile(r"^[0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z.-]+)?$") diff --git a/tests/test_contextguard_stage2_feasibility.py b/tests/test_contextguard_stage2_feasibility.py index 597a2ea2..f2645247 100644 --- a/tests/test_contextguard_stage2_feasibility.py +++ b/tests/test_contextguard_stage2_feasibility.py @@ -84,6 +84,7 @@ "plugins/context-guard/bin/context-guard-trim-output", "plugins/context-guard/lib/context_guard_commands.py", "research/benchmark-plan.md", + "research/p2-p6-provider-free-implementation.md", "research/p1-live-authorization-packet.md", "research/token-savings-roadmap.md", "scripts/build_npm_candidates.py", @@ -113,10 +114,10 @@ {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/README.md', 'sha256': 'e8e43ac7c76bb032080eed9ccb4be00f2c5c840fb44bba93f4e1c93f666d4a89'}, {'file_type': 'regular', 'mode': '0755', 'path': 'packages/context-guard-receipt/bin/context-guard-receipt-mcp.cjs', 'sha256': '883b893d5ee484d63b78174ace60e171dc26e032d05dd19298fb6d6c5229cffd'}, {'file_type': 'regular', 'mode': '0755', 'path': 'packages/context-guard-receipt/bin/context-guard-receipt.cjs', 'sha256': 'bdab50b0476e40024ea64f1f6cd0a46260b4707e2297d212bf5034cfd5a87ff8'}, - {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/bin/launcher.cjs', 'sha256': '4282241f133eda1745da80f85c982ef0f6be68087f3eaa6fd230d4948abfe6ec'}, + {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/bin/launcher.cjs', 'sha256': '20972582a35a845e024a11907b7faae8eec86ff235ae3333ad9715b683619797'}, {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/dev/package_check.py', 'sha256': '10036c058031a9de14a310bbd385f78c8b1d50a2919b83949befa40642ab8424'}, {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/dev/packaged_acceptance.py', 'sha256': '0c30434371b16e88176185e47a3f890d85ecf475c77db63f2f73b23b8f264ca1'}, - {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/package-files.json', 'sha256': '1b5070852db414d6365e685daf44f1f803b26598e1f6d8880566b5140714f428'}, + {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/package-files.json', 'sha256': 'de036a8d3256f6ffc6786928fa86e8d64a553708e5258d1900bb4964d8aa3d19'}, {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/package.json', 'sha256': 'daf789323e9b194943b7222bd0bf112432460afe0174d0a3363cbadbbd37c475'}, {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/python/context_guard_receipt/__init__.py', 'sha256': '1046588c63e24a72c3a57ab0ebd6d60d86c158358b5bbd50ca15cf26322fabc6'}, {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/python/context_guard_receipt/assembly.py', 'sha256': '0e28b6e0874477314436eecb532c767d61efe6d506ae8f79d98fae4b41dd35ea'}, @@ -134,7 +135,7 @@ {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/python/context_guard_receipt/identity.py', 'sha256': '31d4a0ba5e2a04b277a027a872ee0172c5d27ed09b60c41f53f286dd2d8b963c'}, {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/python/context_guard_receipt/mcp.py', 'sha256': 'db251fdd3e3d98cd83fd9a29ee0b90cb308c1bfa3fbed9122a217c80e75fe4c2'}, {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/python/context_guard_receipt/merged_capture.py', 'sha256': 'a19c605a47b666f302b8b993d1e0973bfded46c1974022c2620c5ef5d598b7cf'}, - {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/python/context_guard_receipt/phase_evaluation.py', 'sha256': 'e9e6747e1955789793a22826b71f73265607fd80ab30bf48f6dfae05852f1104'}, + {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/python/context_guard_receipt/phase_evaluation.py', 'sha256': 'f25a4f31a96d5579823945d62b119193741f5599ec1a3301f5e6ab774dd15bf5'}, {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/python/context_guard_receipt/protection.py', 'sha256': '67ae06abb102292b3db09a6731a4aab90b3bc6ceb6dbe836fc636f82f783c347'}, {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/python/context_guard_receipt/receipts.py', 'sha256': '11c02d9df36be0dec2316594fd083ec39a1284325ded440de075081d2e56ddb0'}, {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/python/context_guard_receipt/reference_expiry.py', 'sha256': '2445292456776d5fcbf789f75a71781d64f12865958249d192cfc5a5ff27f2f6'}, diff --git a/tests/test_phase_evaluation.py b/tests/test_phase_evaluation.py index 88d48576..80e98f1b 100644 --- a/tests/test_phase_evaluation.py +++ b/tests/test_phase_evaluation.py @@ -569,6 +569,20 @@ def test_plan_only_track_is_non_runtime_and_claim_blocked_even_with_complete_evi self.assertFalse(report["claim_authority"]) self.assertFalse(result["runtime_changed"]) + def test_malformed_phase_identity_cannot_report_track_readiness(self) -> None: + record = self.valid_record() + record["schema_version"] = "contextguard.phase-evaluation.p6/v2" + + result = evaluate_p6(record) + + self.assertFalse(result["implementation_readiness"]) + self.assertFalse(result["evaluation_evidence_complete"]) + self.assertFalse(result["activation_eligibility"]) + self.assertIn("malformed_record", result["blockers"]) + self.assertTrue( + all("malformed_record" in report["blockers"] for report in result["tracks"]) + ) + def test_invalid_track_identity_and_surface_are_not_reflected_in_output(self) -> None: record = self.valid_record() record["tracks"][0]["track_id"] = {"private": "value"} From 78ac88ab4cc5956e79bcb75221f4c35a8e592147 Mon Sep 17 00:00:00 2001 From: Coden Date: Tue, 11 Aug 2026 12:42:01 +0900 Subject: [PATCH 3/4] fix: close phase evaluation output boundaries --- context-guard-kit/bash_reference_policy.py | 2 +- context-guard-kit/phase_evaluation.py | 9 ++++- .../context-guard-receipt/bin/launcher.cjs | 6 +-- .../context-guard-receipt/package-files.json | 8 ++-- .../python/context_guard_receipt/cli.py | 13 ++++++- .../context_guard_receipt/phase_evaluation.py | 9 ++++- .../schemas/phase-evaluation-p4.schema.json | 4 +- .../test_g015_phase_evaluation_cli.py | 37 +++++++++++++++++++ .../bin/bash_reference_policy.py | 2 +- tests/test_contextguard_stage2_feasibility.py | 12 +++--- tests/test_phase_evaluation.py | 21 +++++++++++ 11 files changed, 101 insertions(+), 22 deletions(-) diff --git a/context-guard-kit/bash_reference_policy.py b/context-guard-kit/bash_reference_policy.py index fbd87d76..4ab2125b 100644 --- a/context-guard-kit/bash_reference_policy.py +++ b/context-guard-kit/bash_reference_policy.py @@ -36,7 +36,7 @@ # Audited digest of Receipt's package-files.json for each exact dependency # version. Invalid or missing pins are deliberately unavailable in production. EXPECTED_RECEIPT_PACKAGE_FILES_SHA256_BY_VERSION: dict[str, str] = { - "0.2.0": "de036a8d3256f6ffc6786928fa86e8d64a553708e5258d1900bb4964d8aa3d19", + "0.2.0": "8cda063773c77ca337af519001e3b43b8f4519d757d1a4d22ea46e9474bfcace", } _TRANSACTION_ID_RE = re.compile(r"^[a-f0-9]{64}$") _EXACT_NPM_VERSION_RE = re.compile(r"^[0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z.-]+)?$") diff --git a/context-guard-kit/phase_evaluation.py b/context-guard-kit/phase_evaluation.py index 8ab9d1ac..53748054 100644 --- a/context-guard-kit/phase_evaluation.py +++ b/context-guard-kit/phase_evaluation.py @@ -12,6 +12,7 @@ _DIGEST: Final = re.compile(r"sha256:[0-9a-f]{64}\Z") _IDENTIFIER: Final = re.compile(r"[A-Za-z0-9][A-Za-z0-9._-]{0,63}\Z") +_BLOCKER: Final = re.compile(r"[a-z][a-z0-9_]{0,127}\Z") _MAX_RECORDS: Final = 10_000 _P2_TOP: Final = frozenset( { @@ -206,6 +207,10 @@ def _valid_digest(value: object) -> bool: return type(value) is str and _DIGEST.fullmatch(value) is not None +def _valid_blocker(value: object) -> bool: + return type(value) is str and _BLOCKER.fullmatch(value) is not None + + def _nonnegative_integer(value: object) -> bool: return type(value) is int and value >= 0 @@ -712,14 +717,13 @@ def evaluate_p4(record: object) -> dict[str, object]: reasons.append("malformed_record") else: confidence = confidence_value - confidences.append(confidence) if confidence < minimum_confidence: reasons.append("low_confidence") supplied_reasons = trial["bypass_reasons"] if ( type(supplied_reasons) is not list - or any(not _valid_identifier(reason) for reason in supplied_reasons) + or any(not _valid_blocker(reason) for reason in supplied_reasons) ): reasons.append("malformed_record") else: @@ -766,6 +770,7 @@ def evaluate_p4(record: object) -> dict[str, object]: if regret < 0: reasons.append("negative_regret") + confidences.append(confidence) reasons = _deduplicate(reasons) eligible = not reasons if not eligible: diff --git a/packages/context-guard-receipt/bin/launcher.cjs b/packages/context-guard-receipt/bin/launcher.cjs index bc0c9632..7bed109e 100644 --- a/packages/context-guard-receipt/bin/launcher.cjs +++ b/packages/context-guard-receipt/bin/launcher.cjs @@ -120,7 +120,7 @@ const TRUSTED_PAYLOAD_DIGESTS = { 'python/context_guard_receipt/blueprint.py': 'f4b8b617832ebe4bd5dc585f762a20b71b37ce79d54b6cd751f1e5fde5b785f0', 'python/context_guard_receipt/bootstrap.py': 'fa846a8968c5199618ab68a86424c0cb88c32250291faf3ac37f26d14d4b018e', 'python/context_guard_receipt/canonical.py': '91b57a1ebf2cc8fa0025ccfc8eaf6f50bc9363e6d3bc05c517b2014bf8a590c7', - 'python/context_guard_receipt/cli.py': '0a60b550aa620e029fdc809749160d1fb3382864557e6016d56b4780c8f4c430', + 'python/context_guard_receipt/cli.py': '8d475b0afba46d2e0eb1b54ec721186ce3d10d2e75f3e07112dc27fbedd3a769', 'python/context_guard_receipt/cli_io.py': '2de5ef56762e015264527306f19b1b72995cc3fffd8cd6cb58c8206e255c5baf', 'python/context_guard_receipt/contracts.py': '1127a9b90bf2da63a097b066c7f1678109dcf622f40dd6746ef055aa7a98e39e', 'python/context_guard_receipt/diagnostic_ledger.py': '3cc7865709c273b72136c48b1026ed5cd2830ea1bf76da4e424da08ccc13499d', @@ -131,7 +131,7 @@ const TRUSTED_PAYLOAD_DIGESTS = { 'python/context_guard_receipt/identity.py': '31d4a0ba5e2a04b277a027a872ee0172c5d27ed09b60c41f53f286dd2d8b963c', 'python/context_guard_receipt/mcp.py': 'db251fdd3e3d98cd83fd9a29ee0b90cb308c1bfa3fbed9122a217c80e75fe4c2', 'python/context_guard_receipt/merged_capture.py': 'a19c605a47b666f302b8b993d1e0973bfded46c1974022c2620c5ef5d598b7cf', - 'python/context_guard_receipt/phase_evaluation.py': 'f25a4f31a96d5579823945d62b119193741f5599ec1a3301f5e6ab774dd15bf5', + 'python/context_guard_receipt/phase_evaluation.py': '2ee911bb898e28d5ba23e7bd3599a41125a0e7d13c9e4c9359a84e7ff721dc46', 'python/context_guard_receipt/protection.py': '67ae06abb102292b3db09a6731a4aab90b3bc6ceb6dbe836fc636f82f783c347', 'python/context_guard_receipt/receipts.py': '11c02d9df36be0dec2316594fd083ec39a1284325ded440de075081d2e56ddb0', 'python/context_guard_receipt/reference_expiry.py': '2445292456776d5fcbf789f75a71781d64f12865958249d192cfc5a5ff27f2f6', @@ -157,7 +157,7 @@ const TRUSTED_PAYLOAD_DIGESTS = { 'schemas/expansion-refusal.schema.json': 'c5196da89d9b96349deb4c2c0ad2970d6f27d7760f9236b6d07d702443ee9da0', 'schemas/phase-evaluation-p2.schema.json': 'd4390e71109e2704c4bc6f0935997d2b4b3f7d7cfc49ed92ef05e27eb21807bd', 'schemas/phase-evaluation-p3.schema.json': 'ac0687ce2cd43ec4954d3fb0a876284fa75829435244913b5f81b275a4c234d7', - 'schemas/phase-evaluation-p4.schema.json': '58bb474f5655b60155aeba0a7dc135697cb52c08364692bf10a14ec5ca68fdda', + 'schemas/phase-evaluation-p4.schema.json': '05bf46ffad4d2d7ce7c0af623a6d57cdc3ce79baede3f1a005b5562966033580', 'schemas/phase-evaluation-p5.schema.json': 'b4e7f888c8a041065af130c808d8bb47c5eb42e395e18d8f8c53ea4c7eac1457', 'schemas/phase-evaluation-p6.schema.json': 'ed19929a20da8609c472f2d96ca5de9e32f7d32365b640876c9dbb22d9e33b00', 'schemas/phase-evaluation-result.schema.json': 'a608f3426c7a4814f7d081be2963b979d03e895a6e44e85058aa67ead43368af', diff --git a/packages/context-guard-receipt/package-files.json b/packages/context-guard-receipt/package-files.json index 7dab15d0..4526e342 100644 --- a/packages/context-guard-receipt/package-files.json +++ b/packages/context-guard-receipt/package-files.json @@ -28,7 +28,7 @@ { "mode": "0644", "path": "bin/launcher.cjs", - "sha256": "20972582a35a845e024a11907b7faae8eec86ff235ae3333ad9715b683619797" + "sha256": "79da1e6a43d0cf4c57f682aaa7ba55f7dd53edffe7743032b66bd55b9ea2c16b" }, { "mode": "0644", @@ -63,7 +63,7 @@ { "mode": "0644", "path": "python/context_guard_receipt/cli.py", - "sha256": "0a60b550aa620e029fdc809749160d1fb3382864557e6016d56b4780c8f4c430" + "sha256": "8d475b0afba46d2e0eb1b54ec721186ce3d10d2e75f3e07112dc27fbedd3a769" }, { "mode": "0644", @@ -118,7 +118,7 @@ { "mode": "0644", "path": "python/context_guard_receipt/phase_evaluation.py", - "sha256": "f25a4f31a96d5579823945d62b119193741f5599ec1a3301f5e6ab774dd15bf5" + "sha256": "2ee911bb898e28d5ba23e7bd3599a41125a0e7d13c9e4c9359a84e7ff721dc46" }, { "mode": "0644", @@ -248,7 +248,7 @@ { "mode": "0644", "path": "schemas/phase-evaluation-p4.schema.json", - "sha256": "58bb474f5655b60155aeba0a7dc135697cb52c08364692bf10a14ec5ca68fdda" + "sha256": "05bf46ffad4d2d7ce7c0af623a6d57cdc3ce79baede3f1a005b5562966033580" }, { "mode": "0644", diff --git a/packages/context-guard-receipt/python/context_guard_receipt/cli.py b/packages/context-guard-receipt/python/context_guard_receipt/cli.py index c779cddc..7ad6e05b 100644 --- a/packages/context-guard-receipt/python/context_guard_receipt/cli.py +++ b/packages/context-guard-receipt/python/context_guard_receipt/cli.py @@ -91,6 +91,13 @@ max_object_members=32, max_string_bytes=1024, ) +_PHASE_EVALUATION_RESULT_LIMITS = JSONLimits( + max_document_bytes=8 * 1024 * 1024, + max_depth=16, + max_total_values=500_000, + max_object_members=32, + max_string_bytes=1024, +) _BASH_REFERENCE_BROKER_READY = ( b"READY contextguard-bash-reference-broker/v1\n" ) @@ -1453,6 +1460,8 @@ def _evaluate_phase(arguments: Sequence[str]) -> int: return emit_error(operation, "error", "evaluation_input_rejected", 65) phase_id = record.get("phase_id") if type(record) is dict else None + if type(phase_id) is not str: + return emit_error(operation, "error", "evaluation_phase_rejected", 65) try: from .phase_evaluation import ( evaluate_p2, @@ -1471,8 +1480,10 @@ def _evaluate_phase(arguments: Sequence[str]) -> int: }.get(phase_id) if evaluator is None: return emit_error(operation, "error", "evaluation_phase_rejected", 65) - payload = canonical_json_bytes(evaluator(record)) + payload = canonical_json_bytes(evaluator(record), _PHASE_EVALUATION_RESULT_LIMITS) write_stdout(payload) + except CanonicalJSONError: + return emit_error(operation, "error", "evaluation_result_rejected", 65) except CliIOError: return emit_error(operation, "error", "evaluation_delivery_failed", 74) except Exception: diff --git a/packages/context-guard-receipt/python/context_guard_receipt/phase_evaluation.py b/packages/context-guard-receipt/python/context_guard_receipt/phase_evaluation.py index 8ab9d1ac..53748054 100644 --- a/packages/context-guard-receipt/python/context_guard_receipt/phase_evaluation.py +++ b/packages/context-guard-receipt/python/context_guard_receipt/phase_evaluation.py @@ -12,6 +12,7 @@ _DIGEST: Final = re.compile(r"sha256:[0-9a-f]{64}\Z") _IDENTIFIER: Final = re.compile(r"[A-Za-z0-9][A-Za-z0-9._-]{0,63}\Z") +_BLOCKER: Final = re.compile(r"[a-z][a-z0-9_]{0,127}\Z") _MAX_RECORDS: Final = 10_000 _P2_TOP: Final = frozenset( { @@ -206,6 +207,10 @@ def _valid_digest(value: object) -> bool: return type(value) is str and _DIGEST.fullmatch(value) is not None +def _valid_blocker(value: object) -> bool: + return type(value) is str and _BLOCKER.fullmatch(value) is not None + + def _nonnegative_integer(value: object) -> bool: return type(value) is int and value >= 0 @@ -712,14 +717,13 @@ def evaluate_p4(record: object) -> dict[str, object]: reasons.append("malformed_record") else: confidence = confidence_value - confidences.append(confidence) if confidence < minimum_confidence: reasons.append("low_confidence") supplied_reasons = trial["bypass_reasons"] if ( type(supplied_reasons) is not list - or any(not _valid_identifier(reason) for reason in supplied_reasons) + or any(not _valid_blocker(reason) for reason in supplied_reasons) ): reasons.append("malformed_record") else: @@ -766,6 +770,7 @@ def evaluate_p4(record: object) -> dict[str, object]: if regret < 0: reasons.append("negative_regret") + confidences.append(confidence) reasons = _deduplicate(reasons) eligible = not reasons if not eligible: diff --git a/packages/context-guard-receipt/schemas/phase-evaluation-p4.schema.json b/packages/context-guard-receipt/schemas/phase-evaluation-p4.schema.json index e560eac2..7371d2a2 100644 --- a/packages/context-guard-receipt/schemas/phase-evaluation-p4.schema.json +++ b/packages/context-guard-receipt/schemas/phase-evaluation-p4.schema.json @@ -101,8 +101,8 @@ }, "bypass_reasons": { "items": { - "maxLength": 64, - "pattern": "^[A-Za-z0-9][A-Za-z0-9._-]{0,63}$", + "maxLength": 128, + "pattern": "^[a-z][a-z0-9_]{0,127}$", "type": "string" }, "maxItems": 10000, diff --git a/packages/context-guard-receipt/tests/contract/test_g015_phase_evaluation_cli.py b/packages/context-guard-receipt/tests/contract/test_g015_phase_evaluation_cli.py index f1f879bf..51e213c6 100644 --- a/packages/context-guard-receipt/tests/contract/test_g015_phase_evaluation_cli.py +++ b/packages/context-guard-receipt/tests/contract/test_g015_phase_evaluation_cli.py @@ -150,6 +150,43 @@ def test_cli_rejects_ambiguous_json_without_reflecting_input(self) -> None: self.assertEqual(response["reason"], "evaluation_input_rejected") self.assertNotIn(b"private-value", completed.stderr) + def test_cli_rejects_non_string_phase_without_internal_error(self) -> None: + """Break caught: an unhashable phase identifier reaches mapping dispatch.""" + + completed = self.run_cli(canonical_json({"phase_id": ["p2"]})) + + self.assertEqual(completed.returncode, 65) + self.assertEqual(completed.stdout, b"") + response = json.loads(completed.stderr) + self.assertEqual(response["operation"], "evaluate_phase") + self.assertEqual(response["reason"], "evaluation_phase_rejected") + + def test_cli_encodes_a_valid_result_larger_than_the_generic_json_limit(self) -> None: + """Break caught: valid phase output incorrectly uses the 64 KiB default.""" + + record = p2_record() + record["records"] = [ + { + "candidate_omission": False, + "construction_cost_microunits": 1, + "fresh_until": 101, + "protection": "eligible", + "recalled": True, + "record_id": f"r{index}", + "rehydrated_digest": None, + "relevant": True, + "source_digest": "sha256:" + "1" * 64, + "stratum": f"s{index}", + } + for index in range(800) + ] + + completed = self.run_cli(canonical_json(record)) + + self.assertEqual(completed.returncode, 0, completed.stderr) + self.assertGreater(len(completed.stdout), 64 * 1024) + self.assertEqual(len(json.loads(completed.stdout)["strata"]), 800) + if __name__ == "__main__": unittest.main() diff --git a/plugins/context-guard/bin/bash_reference_policy.py b/plugins/context-guard/bin/bash_reference_policy.py index fbd87d76..4ab2125b 100644 --- a/plugins/context-guard/bin/bash_reference_policy.py +++ b/plugins/context-guard/bin/bash_reference_policy.py @@ -36,7 +36,7 @@ # Audited digest of Receipt's package-files.json for each exact dependency # version. Invalid or missing pins are deliberately unavailable in production. EXPECTED_RECEIPT_PACKAGE_FILES_SHA256_BY_VERSION: dict[str, str] = { - "0.2.0": "de036a8d3256f6ffc6786928fa86e8d64a553708e5258d1900bb4964d8aa3d19", + "0.2.0": "8cda063773c77ca337af519001e3b43b8f4519d757d1a4d22ea46e9474bfcace", } _TRANSACTION_ID_RE = re.compile(r"^[a-f0-9]{64}$") _EXACT_NPM_VERSION_RE = re.compile(r"^[0-9]+\.[0-9]+\.[0-9]+(?:-[0-9A-Za-z.-]+)?$") diff --git a/tests/test_contextguard_stage2_feasibility.py b/tests/test_contextguard_stage2_feasibility.py index f2645247..3a269358 100644 --- a/tests/test_contextguard_stage2_feasibility.py +++ b/tests/test_contextguard_stage2_feasibility.py @@ -114,17 +114,17 @@ {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/README.md', 'sha256': 'e8e43ac7c76bb032080eed9ccb4be00f2c5c840fb44bba93f4e1c93f666d4a89'}, {'file_type': 'regular', 'mode': '0755', 'path': 'packages/context-guard-receipt/bin/context-guard-receipt-mcp.cjs', 'sha256': '883b893d5ee484d63b78174ace60e171dc26e032d05dd19298fb6d6c5229cffd'}, {'file_type': 'regular', 'mode': '0755', 'path': 'packages/context-guard-receipt/bin/context-guard-receipt.cjs', 'sha256': 'bdab50b0476e40024ea64f1f6cd0a46260b4707e2297d212bf5034cfd5a87ff8'}, - {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/bin/launcher.cjs', 'sha256': '20972582a35a845e024a11907b7faae8eec86ff235ae3333ad9715b683619797'}, + {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/bin/launcher.cjs', 'sha256': '79da1e6a43d0cf4c57f682aaa7ba55f7dd53edffe7743032b66bd55b9ea2c16b'}, {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/dev/package_check.py', 'sha256': '10036c058031a9de14a310bbd385f78c8b1d50a2919b83949befa40642ab8424'}, {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/dev/packaged_acceptance.py', 'sha256': '0c30434371b16e88176185e47a3f890d85ecf475c77db63f2f73b23b8f264ca1'}, - {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/package-files.json', 'sha256': 'de036a8d3256f6ffc6786928fa86e8d64a553708e5258d1900bb4964d8aa3d19'}, + {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/package-files.json', 'sha256': '8cda063773c77ca337af519001e3b43b8f4519d757d1a4d22ea46e9474bfcace'}, {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/package.json', 'sha256': 'daf789323e9b194943b7222bd0bf112432460afe0174d0a3363cbadbbd37c475'}, {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/python/context_guard_receipt/__init__.py', 'sha256': '1046588c63e24a72c3a57ab0ebd6d60d86c158358b5bbd50ca15cf26322fabc6'}, {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/python/context_guard_receipt/assembly.py', 'sha256': '0e28b6e0874477314436eecb532c767d61efe6d506ae8f79d98fae4b41dd35ea'}, {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/python/context_guard_receipt/blueprint.py', 'sha256': 'f4b8b617832ebe4bd5dc585f762a20b71b37ce79d54b6cd751f1e5fde5b785f0'}, {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/python/context_guard_receipt/bootstrap.py', 'sha256': 'fa846a8968c5199618ab68a86424c0cb88c32250291faf3ac37f26d14d4b018e'}, {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/python/context_guard_receipt/canonical.py', 'sha256': '91b57a1ebf2cc8fa0025ccfc8eaf6f50bc9363e6d3bc05c517b2014bf8a590c7'}, - {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/python/context_guard_receipt/cli.py', 'sha256': '0a60b550aa620e029fdc809749160d1fb3382864557e6016d56b4780c8f4c430'}, + {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/python/context_guard_receipt/cli.py', 'sha256': '8d475b0afba46d2e0eb1b54ec721186ce3d10d2e75f3e07112dc27fbedd3a769'}, {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/python/context_guard_receipt/cli_io.py', 'sha256': '2de5ef56762e015264527306f19b1b72995cc3fffd8cd6cb58c8206e255c5baf'}, {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/python/context_guard_receipt/contracts.py', 'sha256': '1127a9b90bf2da63a097b066c7f1678109dcf622f40dd6746ef055aa7a98e39e'}, {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/python/context_guard_receipt/diagnostic_ledger.py', 'sha256': '3cc7865709c273b72136c48b1026ed5cd2830ea1bf76da4e424da08ccc13499d'}, @@ -135,7 +135,7 @@ {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/python/context_guard_receipt/identity.py', 'sha256': '31d4a0ba5e2a04b277a027a872ee0172c5d27ed09b60c41f53f286dd2d8b963c'}, {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/python/context_guard_receipt/mcp.py', 'sha256': 'db251fdd3e3d98cd83fd9a29ee0b90cb308c1bfa3fbed9122a217c80e75fe4c2'}, {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/python/context_guard_receipt/merged_capture.py', 'sha256': 'a19c605a47b666f302b8b993d1e0973bfded46c1974022c2620c5ef5d598b7cf'}, - {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/python/context_guard_receipt/phase_evaluation.py', 'sha256': 'f25a4f31a96d5579823945d62b119193741f5599ec1a3301f5e6ab774dd15bf5'}, + {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/python/context_guard_receipt/phase_evaluation.py', 'sha256': '2ee911bb898e28d5ba23e7bd3599a41125a0e7d13c9e4c9359a84e7ff721dc46'}, {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/python/context_guard_receipt/protection.py', 'sha256': '67ae06abb102292b3db09a6731a4aab90b3bc6ceb6dbe836fc636f82f783c347'}, {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/python/context_guard_receipt/receipts.py', 'sha256': '11c02d9df36be0dec2316594fd083ec39a1284325ded440de075081d2e56ddb0'}, {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/python/context_guard_receipt/reference_expiry.py', 'sha256': '2445292456776d5fcbf789f75a71781d64f12865958249d192cfc5a5ff27f2f6'}, @@ -161,7 +161,7 @@ {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/schemas/expansion-refusal.schema.json', 'sha256': 'c5196da89d9b96349deb4c2c0ad2970d6f27d7760f9236b6d07d702443ee9da0'}, {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/schemas/phase-evaluation-p2.schema.json', 'sha256': 'd4390e71109e2704c4bc6f0935997d2b4b3f7d7cfc49ed92ef05e27eb21807bd'}, {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/schemas/phase-evaluation-p3.schema.json', 'sha256': 'ac0687ce2cd43ec4954d3fb0a876284fa75829435244913b5f81b275a4c234d7'}, - {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/schemas/phase-evaluation-p4.schema.json', 'sha256': '58bb474f5655b60155aeba0a7dc135697cb52c08364692bf10a14ec5ca68fdda'}, + {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/schemas/phase-evaluation-p4.schema.json', 'sha256': '05bf46ffad4d2d7ce7c0af623a6d57cdc3ce79baede3f1a005b5562966033580'}, {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/schemas/phase-evaluation-p5.schema.json', 'sha256': 'b4e7f888c8a041065af130c808d8bb47c5eb42e395e18d8f8c53ea4c7eac1457'}, {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/schemas/phase-evaluation-p6.schema.json', 'sha256': 'ed19929a20da8609c472f2d96ca5de9e32f7d32365b640876c9dbb22d9e33b00'}, {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/schemas/phase-evaluation-result.schema.json', 'sha256': 'a608f3426c7a4814f7d081be2963b979d03e895a6e44e85058aa67ead43368af'}, @@ -226,7 +226,7 @@ {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/tests/contract/test_g012_mcp_cli.py', 'sha256': '539014f2009a78832467115d6b671c5807d2c4e16e7519ccde6ed70e629ec70a'}, {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/tests/contract/test_g013_package_audit.py', 'sha256': '948e8c6ff27851ef60a43570c7b5a0f30185d15b06e9504780943a3bd3067158'}, {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/tests/contract/test_g014_merged_capture.py', 'sha256': 'dacc04f7ac0b09b210ce9cbb2081d049707fff92cb9effad7c1e03a95669c600'}, - {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/tests/contract/test_g015_phase_evaluation_cli.py', 'sha256': 'f7359e7df53e82811149a377fce6305d8ce4e75cdf067cd123fd9dc1af9b2f77'}, + {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/tests/contract/test_g015_phase_evaluation_cli.py', 'sha256': '56f6c2c33a5b39bdb93dd76ddb41bc625362b2538ea05dfaec6700dc2015b55b'}, {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/tests/e2e/__init__.py', 'sha256': '48a5ccfc49a840928c6de0ea2c978a12a0abd78e2f361ec96f6e9a0f15bddca0'}, {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/tests/e2e/test_g001_offline_distribution.py', 'sha256': '01d856590d17f5a457c1664b49c92f6b52378314a9b272df802a759113413b7d'}, {'file_type': 'regular', 'mode': '0644', 'path': 'packages/context-guard-receipt/tests/e2e/test_g012_mcp_stdio.py', 'sha256': '96ac49c60d27d2558bbfaec9805135605c15ab25bd5de682dc63620ade52a8fc'}, diff --git a/tests/test_phase_evaluation.py b/tests/test_phase_evaluation.py index 80e98f1b..9dd5d774 100644 --- a/tests/test_phase_evaluation.py +++ b/tests/test_phase_evaluation.py @@ -348,6 +348,27 @@ def test_malformed_route_identity_is_not_reflected_in_output(self) -> None: self.assertIsNone(report["advisory_status"]) self.assertIsNone(report["advisory_route"]) + def test_malformed_trial_keeps_confidence_rows_index_aligned(self) -> None: + record = self.valid_record() + record["trials"].append({"unexpected": True}) + + result = evaluate_p4(record) + + self.assertEqual(result["evaluated_trial_count"], 2) + self.assertEqual(len(result["trials"]), 2) + self.assertEqual(result["confidence_basis_points"], [9_000, 0]) + + def test_bypass_reason_must_match_the_closed_result_vocabulary(self) -> None: + record = self.valid_record() + record["trials"][0]["bypass_reasons"] = ["private.marker"] + + result = evaluate_p4(record) + + report = result["trials"][0] + self.assertEqual(report["evaluation_route"], "pass_through") + self.assertEqual(report["bypass_reasons"], ["malformed_record"]) + self.assertNotIn("private.marker", str(result)) + class P5AdjunctEvaluationTests(unittest.TestCase): def valid_record(self) -> dict[str, object]: From bcee38bf216905f7f5eb4601ae885c0731f6493e Mon Sep 17 00:00:00 2001 From: Coden Date: Tue, 11 Aug 2026 12:45:00 +0900 Subject: [PATCH 4/4] test: require malformed P6 track reports --- tests/test_phase_evaluation.py | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/test_phase_evaluation.py b/tests/test_phase_evaluation.py index 9dd5d774..ca218cd4 100644 --- a/tests/test_phase_evaluation.py +++ b/tests/test_phase_evaluation.py @@ -600,6 +600,7 @@ def test_malformed_phase_identity_cannot_report_track_readiness(self) -> None: self.assertFalse(result["evaluation_evidence_complete"]) self.assertFalse(result["activation_eligibility"]) self.assertIn("malformed_record", result["blockers"]) + self.assertTrue(result["tracks"]) self.assertTrue( all("malformed_record" in report["blockers"] for report in result["tracks"]) )