From 001210458f638786162a1ea8300f193ff1fc6160 Mon Sep 17 00:00:00 2001 From: yaojingang Date: Tue, 16 Jun 2026 22:17:09 +0800 Subject: [PATCH] Extract adaptation safety helpers --- reports/architecture_maintainability.json | 22 +-- reports/architecture_maintainability.md | 10 +- reports/context_budget.json | 20 +-- reports/context_budget.md | 2 +- reports/context_budget_summary.json | 10 +- reports/python_compatibility.json | 14 +- reports/python_compatibility.md | 2 +- reports/security_trust_report.json | 48 +++++- reports/security_trust_report.md | 12 +- reports/skill-os-2-review.md | 2 +- scripts/adaptation_patch_safety.py | 76 ++++++++++ scripts/adaptation_report_contracts.py | 111 ++++++++++++++ scripts/apply_adaptation.py | 169 +--------------------- 13 files changed, 286 insertions(+), 212 deletions(-) create mode 100644 scripts/adaptation_patch_safety.py create mode 100644 scripts/adaptation_report_contracts.py diff --git a/reports/architecture_maintainability.json b/reports/architecture_maintainability.json index 1e05115..e606f77 100644 --- a/reports/architecture_maintainability.json +++ b/reports/architecture_maintainability.json @@ -4,10 +4,10 @@ "generated_at": "2026-06-16", "skill_dir": ".", "summary": { - "python_file_count": 206, - "script_file_count": 133, + "python_file_count": 208, + "script_file_count": 135, "test_file_count": 73, - "internal_module_count": 49, + "internal_module_count": 51, "cli_script_count": 86, "command_handler_count": 68, "entrypoint_command_handler_count": 18, @@ -15,20 +15,13 @@ "warn_line_threshold": 900, "watch_line_threshold": 720, "block_line_threshold": 1500, - "largest_file_lines": 706, + "largest_file_lines": 696, "watchlist_count": 0, "hotspot_count": 0, "blocker_count": 0, "decision": "pass" }, "largest_files": [ - { - "path": "scripts/apply_adaptation.py", - "lines": 706, - "kind": "cli-script", - "severity": "pass", - "recommendation": "Watch this file before adding new responsibilities; extract a helper module when one concern dominates." - }, { "path": "tests/verify_yao_cli.py", "lines": 696, @@ -105,6 +98,13 @@ "kind": "cli-script", "severity": "pass", "recommendation": "Watch this file before adding new responsibilities; extract a helper module when one concern dominates." + }, + { + "path": "scripts/render_benchmark_reproducibility.py", + "lines": 595, + "kind": "cli-script", + "severity": "pass", + "recommendation": "Watch this file before adding new responsibilities; extract a helper module when one concern dominates." } ], "watchlist": [], diff --git a/reports/architecture_maintainability.md b/reports/architecture_maintainability.md index 07f7988..846a19b 100644 --- a/reports/architecture_maintainability.md +++ b/reports/architecture_maintainability.md @@ -5,15 +5,15 @@ Generated at: `2026-06-16` ## Summary - decision: `pass` -- python files: `206` -- scripts: `133` +- python files: `208` +- scripts: `135` - tests: `73` -- internal modules: `49` +- internal modules: `51` - CLI scripts: `86` - Yao CLI command handlers: `68` - entrypoint command handlers: `18` - command modules: `6` -- largest file lines: `706` +- largest file lines: `696` - watch threshold lines: `720` - watchlist: `0` - hotspots: `0` @@ -33,7 +33,6 @@ No near-threshold files found. | File | Lines | Kind | Severity | | --- | ---: | --- | --- | -| `scripts/apply_adaptation.py` | `706` | `cli-script` | `pass` | | `tests/verify_yao_cli.py` | `696` | `test` | `pass` | | `scripts/world_class_evidence_contract.py` | `686` | `internal-module` | `pass` | | `scripts/render_review_viewer.py` | `685` | `cli-script` | `pass` | @@ -45,6 +44,7 @@ No near-threshold files found. | `scripts/review_studio_gates.py` | `643` | `internal-module` | `pass` | | `scripts/cross_packager.py` | `638` | `cli-script` | `pass` | | `scripts/build_skill_atlas.py` | `637` | `cli-script` | `pass` | +| `scripts/render_benchmark_reproducibility.py` | `595` | `cli-script` | `pass` | ## Release Rule diff --git a/reports/context_budget.json b/reports/context_budget.json index bc82b2a..8c80286 100644 --- a/reports/context_budget.json +++ b/reports/context_budget.json @@ -6,16 +6,16 @@ "context_budget_tier": "production", "context_budget_limit": 1000, "skill_body_tokens": 797, - "other_text_tokens": 1052511, + "other_text_tokens": 1053172, "estimated_initial_load_tokens": 990, - "estimated_total_text_tokens": 1053308, - "deferred_resource_tokens": 487478, + "estimated_total_text_tokens": 1053969, + "deferred_resource_tokens": 487673, "deferred_resource_warn_threshold": 120000, "deferred_resource_dirs": [ { "path": "scripts", - "estimated_tokens": 427380, - "file_count": 133 + "estimated_tokens": 427575, + "file_count": 135 }, { "path": "references", @@ -36,8 +36,8 @@ "large_deferred_resource_dirs": [ { "path": "scripts", - "estimated_tokens": 427380, - "file_count": 133 + "estimated_tokens": 427575, + "file_count": 135 } ], "deferred_resource_governance": { @@ -59,14 +59,14 @@ ], "missing": [], "path": "scripts", - "estimated_tokens": 427380, - "file_count": 133, + "estimated_tokens": 427575, + "file_count": 135, "rationale": "Script resources are deterministic deferred tools, not initial-load prompt context." } ], "summary": "Large deferred resources are indexed and backed by evidence." }, - "relevant_file_count": 658, + "relevant_file_count": 662, "unused_resource_dirs": [], "quality_signal_points": 130, "quality_density": 131.3 diff --git a/reports/context_budget.md b/reports/context_budget.md index f065f48..ed70295 100644 --- a/reports/context_budget.md +++ b/reports/context_budget.md @@ -2,7 +2,7 @@ | Target | Path | Tier | Limit | Initial | SKILL | Deferred | Resource Governance | Large Deferred Dirs | Quality Density | Unused Dirs | Status | | --- | --- | --- | ---: | ---: | ---: | ---: | --- | --- | ---: | --- | --- | -| root | `.` | `production` | 1000 | 990 | 797 | 487478 | `governed` | scripts:427380 | 131.3 | - | ok | +| root | `.` | `production` | 1000 | 990 | 797 | 487673 | `governed` | scripts:427575 | 131.3 | - | ok | | complex-release-orchestrator | `examples/complex-release-orchestrator/generated-skill` | `production` | 1000 | 790 | 718 | 1657 | `not-required` | - | 164.6 | - | ok | | governed-incident-command | `examples/governed-incident-command/generated-skill` | `production` | 1000 | 760 | 658 | 1030 | `not-required` | - | 171.1 | - | ok | diff --git a/reports/context_budget_summary.json b/reports/context_budget_summary.json index 923386b..5f9e288 100644 --- a/reports/context_budget_summary.json +++ b/reports/context_budget_summary.json @@ -8,12 +8,12 @@ "budget_limit": 1000, "initial_tokens": 990, "skill_body_tokens": 797, - "deferred_resource_tokens": 487478, + "deferred_resource_tokens": 487673, "large_deferred_resource_dirs": [ { "path": "scripts", - "estimated_tokens": 427380, - "file_count": 133 + "estimated_tokens": 427575, + "file_count": 135 } ], "deferred_resource_governance": { @@ -35,8 +35,8 @@ ], "missing": [], "path": "scripts", - "estimated_tokens": 427380, - "file_count": 133, + "estimated_tokens": 427575, + "file_count": 135, "rationale": "Script resources are deterministic deferred tools, not initial-load prompt context." } ], diff --git a/reports/python_compatibility.json b/reports/python_compatibility.json index 79f6697..5d34587 100644 --- a/reports/python_compatibility.json +++ b/reports/python_compatibility.json @@ -5,7 +5,7 @@ "root": ".", "summary": { "target_python": "3.11", - "file_count": 209, + "file_count": 211, "issue_count": 0, "syntax_error_count": 0, "fstring_311_violation_count": 0, @@ -40,6 +40,18 @@ "issue_count": 0, "issues": [] }, + { + "path": "scripts/adaptation_patch_safety.py", + "ok": true, + "issue_count": 0, + "issues": [] + }, + { + "path": "scripts/adaptation_report_contracts.py", + "ok": true, + "issue_count": 0, + "issues": [] + }, { "path": "scripts/adjudicate_output_review.py", "ok": true, diff --git a/reports/python_compatibility.md b/reports/python_compatibility.md index 4e955b1..332c4a8 100644 --- a/reports/python_compatibility.md +++ b/reports/python_compatibility.md @@ -6,7 +6,7 @@ Generated at: `2026-06-16` - decision: `pass` - target python: `3.11` -- files scanned: `209` +- files scanned: `211` - issues: `0` - syntax errors: `0` - f-string 3.11 violations: `0` diff --git a/reports/security_trust_report.json b/reports/security_trust_report.json index c48d516..c1041e2 100644 --- a/reports/security_trust_report.json +++ b/reports/security_trust_report.json @@ -2,9 +2,9 @@ "ok": true, "skill_dir": ".", "summary": { - "scanned_files": 224, - "script_count": 133, - "internal_module_count": 47, + "scanned_files": 226, + "script_count": 135, + "internal_module_count": 49, "secret_findings": 0, "dependency_files": [ "requirements-ci.txt" @@ -22,13 +22,41 @@ "help_smoke_failed_count": 0, "interactive_script_count": 0, "package_hash_scope": "source-contract-without-generated-reports", - "package_hash_file_count": 224, - "package_sha256": "c74c96a2bc7dfd7e12c89cb475a6853fcd012bcfe7b4a3905007b896ed2cf957" + "package_hash_file_count": 226, + "package_sha256": "11432d13467e6822ac0fff5d8bde27fdf326322dfed62aca7cb9ad5ee1a706ab" }, "failures": [], "warnings": [], "secrets": [], "scripts": [ + { + "path": "scripts/adaptation_patch_safety.py", + "interface": "internal-module", + "interface_declared": true, + "interface_reason": "Shared adaptation patch target parsing and target-file baseline verification helpers.", + "has_argparse": false, + "has_main_guard": false, + "uses_input": false, + "uses_network": false, + "uses_file_write": false, + "uses_subprocess": false, + "network_urls": [], + "network_hosts": [] + }, + { + "path": "scripts/adaptation_report_contracts.py", + "interface": "internal-module", + "interface_declared": true, + "interface_reason": "Shared adaptation approval and regression report contract decoration helpers.", + "has_argparse": false, + "has_main_guard": false, + "uses_input": false, + "uses_network": false, + "uses_file_write": false, + "uses_subprocess": false, + "network_urls": [], + "network_hosts": [] + }, { "path": "scripts/adjudicate_output_review.py", "interface": "cli", @@ -1943,7 +1971,7 @@ "checked_count": 86, "passed_count": 86, "failed_count": 0, - "skipped_count": 47, + "skipped_count": 49, "failed_scripts": [], "results": [ { @@ -2808,6 +2836,14 @@ } ], "skipped": [ + { + "path": "scripts/adaptation_patch_safety.py", + "reason": "internal module" + }, + { + "path": "scripts/adaptation_report_contracts.py", + "reason": "internal module" + }, { "path": "scripts/build_skill_atlas_layout.py", "reason": "internal module" diff --git a/reports/security_trust_report.md b/reports/security_trust_report.md index 25cca9d..5a305e3 100644 --- a/reports/security_trust_report.md +++ b/reports/security_trust_report.md @@ -1,9 +1,9 @@ # Security Trust Report - OK: `True` -- Scanned files: `224` -- Scripts: `133` -- Internal script modules: `47` +- Scanned files: `226` +- Scripts: `135` +- Internal script modules: `49` - Secret findings: `0` - Network-capable scripts: `3` - Network policy covered scripts: `3` @@ -15,8 +15,8 @@ - CLI help smoke failures: `0` - Interactive scripts: `0` - Package hash scope: `source-contract-without-generated-reports` -- Package hash files: `224` -- Package SHA256: `c74c96a2bc7dfd7e12c89cb475a6853fcd012bcfe7b4a3905007b896ed2cf957` +- Package hash files: `226` +- Package SHA256: `11432d13467e6822ac0fff5d8bde27fdf326322dfed62aca7cb9ad5ee1a706ab` ## Failures @@ -62,6 +62,8 @@ | Script | Interface | Declared | Argparse | Main Guard | Input | Network | File Write | Subprocess | Reason | | --- | --- | --- | --- | --- | --- | --- | --- | --- | --- | +| scripts/adaptation_patch_safety.py | internal-module | True | False | False | False | False | False | False | Shared adaptation patch target parsing and target-file baseline verification helpers. | +| scripts/adaptation_report_contracts.py | internal-module | True | False | False | False | False | False | False | Shared adaptation approval and regression report contract decoration helpers. | | scripts/adjudicate_output_review.py | cli | False | True | True | False | False | True | False | Default CLI classification; add SCRIPT_INTERFACE for internal modules. | | scripts/apply_adaptation.py | cli | True | True | True | False | False | True | True | Approval-gated adaptive patch application with dry-run, allowlist, regression, and rollback evidence. | | scripts/build_confusion_matrix.py | cli | False | True | True | False | False | True | False | Default CLI classification; add SCRIPT_INTERFACE for internal modules. | diff --git a/reports/skill-os-2-review.md b/reports/skill-os-2-review.md index 61bc001..3520905 100644 --- a/reports/skill-os-2-review.md +++ b/reports/skill-os-2-review.md @@ -127,7 +127,7 @@ Next move: add real client or installer permission enforcement integration. | Output Eval | `5` cases, with-skill pass rate `100`, baseline pass rate `0`, with file-backed, near-neighbor, boundary coverage, `10` local command-runner execution runs, `0` recorded fixture runs, `0` provider model-executed runs in root release evidence, `10` estimated token counts, provider runner v0 available, `5` blind A/B review pairs, a generated `reports/output_review_decisions.json` template, `0 / 5` reviewer decisions pending, `0` answer keys revealed, and `5` pending answers hidden | | Runtime Conformance | `5 / 5` targets passing | | Target Compiler | `5 / 5` compiled target contracts generated for OpenAI, Claude, generic, Agent Skills compatible, and VS Code / Copilot outputs, including target permission contracts and target-native behavior contracts | -| Trust | `0` secret findings, `1` pinned dependency file, `47` declared internal modules, `3 / 3` network-capable scripts covered by bounded host policy, `86 / 86` CLI help smoke checks passing across `133` scripts, source-contract hash scope explicit | +| Trust | `0` secret findings, `1` pinned dependency file, `49` declared internal modules, `3 / 3` network-capable scripts covered by bounded host policy, `86 / 86` CLI help smoke checks passing across `135` scripts, source-contract hash scope explicit | | Permission Governance | `3 / 3` required high-permission capabilities approved, `0` missing, `0` invalid, `0` expired | | Runtime Permission Probes | `4 / 4` target adapters probed, `0` native-enforcement adapters, `4` explicit metadata fallbacks, `4` residual risks retained for reviewer visibility | | Skill Atlas | `12` scanned skills, `1` actionable root skill, `1` telemetry report, `0` actionable route collisions, `0` actionable owner gaps, `0` actionable stale skills, `0` actionable drift signals, `24` scoped non-actionable issue signals retained for visibility | diff --git a/scripts/adaptation_patch_safety.py b/scripts/adaptation_patch_safety.py new file mode 100644 index 0000000..83a8ecd --- /dev/null +++ b/scripts/adaptation_patch_safety.py @@ -0,0 +1,76 @@ +"""Patch target and baseline hash checks for approval-gated adaptation.""" + +import hashlib +from pathlib import Path +from typing import Any + + +SCRIPT_INTERFACE = "internal-module" +SCRIPT_INTERFACE_REASON = "Shared adaptation patch target parsing and target-file baseline verification helpers." + +BLOCKED_PATH_PARTS = {".git", "__pycache__", ".pytest_cache", "dist"} +ABSENT_FILE_SHA256 = "__absent__" + + +def sha256_file(path: Path) -> str: + digest = hashlib.sha256() + with path.open("rb") as handle: + for chunk in iter(lambda: handle.read(1024 * 1024), b""): + digest.update(chunk) + return digest.hexdigest() + + +def target_file_sha256(skill_dir: Path, target_files: list[str]) -> dict[str, str]: + observed: dict[str, str] = {} + for target in target_files: + path = skill_dir / target + observed[target] = sha256_file(path) if path.exists() else ABSENT_FILE_SHA256 + return observed + + +def normalize_patch_path(raw: str) -> str | None: + token = raw.strip().split("\t", 1)[0].split(" ", 1)[0] + if token == "/dev/null": + return None + if token.startswith("a/") or token.startswith("b/"): + token = token[2:] + path = Path(token) + if path.is_absolute() or ".." in path.parts or any(part in BLOCKED_PATH_PARTS for part in path.parts): + raise ValueError(f"Unsafe patch path: {raw}") + if not token or token == ".": + raise ValueError(f"Empty patch path: {raw}") + return token + + +def patch_target_files(patch_text: str) -> list[str]: + targets: set[str] = set() + for line in patch_text.splitlines(): + if line.startswith("--- ") or line.startswith("+++ "): + raw = line[4:].strip() + path = normalize_patch_path(raw) + if path: + targets.add(path) + return sorted(targets) + + +def validate_target_file_sha256( + skill_dir: Path, + target_files: list[str], + expected_sha256: dict[str, Any], +) -> tuple[list[str], dict[str, str]]: + failures: list[str] = [] + observed: dict[str, str] = {} + for target in target_files: + path = skill_dir / target + expected = str(expected_sha256.get(target, "")) + if not expected: + failures.append(f"Approval target_file_sha256 missing target: {target}") + continue + if path.exists() and not path.is_file(): + failures.append(f"Patch target is not a file: {target}") + continue + current = sha256_file(path) if path.exists() else ABSENT_FILE_SHA256 + observed[target] = current + if current != expected: + failures.append(f"Target file baseline sha256 does not match approval ledger: {target}") + return failures, observed diff --git a/scripts/adaptation_report_contracts.py b/scripts/adaptation_report_contracts.py new file mode 100644 index 0000000..9114bd8 --- /dev/null +++ b/scripts/adaptation_report_contracts.py @@ -0,0 +1,111 @@ +"""Shared report contracts for approval-gated skill adaptation.""" + +from typing import Any + + +SCRIPT_INTERFACE = "internal-module" +SCRIPT_INTERFACE_REASON = "Shared adaptation approval and regression report contract decoration helpers." + +APPROVAL_SUMMARY_FIELDS = [ + "approval_count", + "active_approval_count", + "pending_review_count", + "applied_count", + "rollback_count", +] +APPROVAL_CONTRACT_FIELDS = [ + "approval_required", + "patch_sha256_required", + "allowlisted_targets_required", + "target_file_sha256_required", + "approval_draft_supported", + "dry_run_default", + "writes_repository_files_only_with_apply", + "rollback_required", +] +REGRESSION_SUMMARY_FIELDS = [ + "apply_supported", + "attempt_count", + "approval_draft_count", + "applied_count", + "dry_run_count", + "rollback_count", + "regression_run_count", + "regression_pass_count", + "failure_count", +] +APPLY_CONTRACT_FIELDS = [ + *APPROVAL_CONTRACT_FIELDS, + "safe_regression_commands_only", + "rollback_on_failure_default", +] +APPROVAL_CONTRACT = { + "approval_required": True, + "patch_sha256_required": True, + "allowlisted_targets_required": True, + "target_file_sha256_required": True, + "approval_draft_supported": True, + "dry_run_default": True, + "writes_repository_files_only_with_apply": True, + "rollback_required": True, +} +APPLY_CONTRACT = { + **APPROVAL_CONTRACT, + "safe_regression_commands_only": True, + "rollback_on_failure_default": True, +} + + +def top_level_mirrors( + summary: dict[str, Any], + contract: dict[str, Any], + summary_fields: list[str], + contract_fields: list[str], +) -> dict[str, Any]: + mirrored = {key: summary[key] for key in summary_fields if key in summary} + mirrored.update({key: contract[key] for key in contract_fields if key in contract}) + return mirrored + + +def report_contract(name: str, contract_key: str, summary_fields: list[str], contract_fields: list[str]) -> dict[str, Any]: + return { + "schema_version": "1.0", + "contract": name, + "top_level_mirrors_summary": True, + f"top_level_mirrors_{contract_key}": True, + "summary_fields": summary_fields, + f"{contract_key}_fields": contract_fields, + "source_of_truth": ["summary", contract_key], + } + + +def decorate_approval_ledger(ledger: dict[str, Any]) -> dict[str, Any]: + summary = ledger.get("summary", {}) if isinstance(ledger.get("summary"), dict) else {} + approval_contract = ( + ledger.get("approval_contract", {}) if isinstance(ledger.get("approval_contract"), dict) else {} + ) or dict(APPROVAL_CONTRACT) + ledger.update(top_level_mirrors(summary, approval_contract, APPROVAL_SUMMARY_FIELDS, APPROVAL_CONTRACT_FIELDS)) + ledger["approval_contract"] = approval_contract + ledger["report_contract"] = report_contract( + "adaptation-approval-ledger", + "approval_contract", + APPROVAL_SUMMARY_FIELDS, + APPROVAL_CONTRACT_FIELDS, + ) + return ledger + + +def decorate_regression_report(report: dict[str, Any]) -> dict[str, Any]: + summary = report.get("summary", {}) if isinstance(report.get("summary"), dict) else {} + apply_contract = ( + report.get("apply_contract", {}) if isinstance(report.get("apply_contract"), dict) else {} + ) or dict(APPLY_CONTRACT) + report.update(top_level_mirrors(summary, apply_contract, REGRESSION_SUMMARY_FIELDS, APPLY_CONTRACT_FIELDS)) + report["apply_contract"] = apply_contract + report["report_contract"] = report_contract( + "adaptation-regression-report", + "apply_contract", + REGRESSION_SUMMARY_FIELDS, + APPLY_CONTRACT_FIELDS, + ) + return report diff --git a/scripts/apply_adaptation.py b/scripts/apply_adaptation.py index ab1b587..52ecdc1 100644 --- a/scripts/apply_adaptation.py +++ b/scripts/apply_adaptation.py @@ -2,7 +2,6 @@ """Apply an approved adaptation patch with allowlisted targets and regression evidence.""" import argparse -import hashlib import json import os import shlex @@ -12,112 +11,14 @@ from datetime import date, datetime, timezone from pathlib import Path from typing import Any +from adaptation_patch_safety import patch_target_files, sha256_file, target_file_sha256, validate_target_file_sha256 +from adaptation_report_contracts import APPLY_CONTRACT, APPROVAL_CONTRACT, decorate_approval_ledger, decorate_regression_report + ROOT = Path(__file__).resolve().parent.parent SCRIPT_INTERFACE = "cli" SCRIPT_INTERFACE_REASON = "Approval-gated adaptive patch application with dry-run, allowlist, regression, and rollback evidence." -BLOCKED_PATH_PARTS = {".git", "__pycache__", ".pytest_cache", "dist"} -ABSENT_FILE_SHA256 = "__absent__" -APPROVAL_SUMMARY_FIELDS = [ - "approval_count", - "active_approval_count", - "pending_review_count", - "applied_count", - "rollback_count", -] -APPROVAL_CONTRACT_FIELDS = [ - "approval_required", - "patch_sha256_required", - "allowlisted_targets_required", - "target_file_sha256_required", - "approval_draft_supported", - "dry_run_default", - "writes_repository_files_only_with_apply", - "rollback_required", -] -REGRESSION_SUMMARY_FIELDS = [ - "apply_supported", - "attempt_count", - "approval_draft_count", - "applied_count", - "dry_run_count", - "rollback_count", - "regression_run_count", - "regression_pass_count", - "failure_count", -] -APPLY_CONTRACT_FIELDS = [ - *APPROVAL_CONTRACT_FIELDS, - "safe_regression_commands_only", - "rollback_on_failure_default", -] -APPROVAL_CONTRACT = { - "approval_required": True, - "patch_sha256_required": True, - "allowlisted_targets_required": True, - "target_file_sha256_required": True, - "approval_draft_supported": True, - "dry_run_default": True, - "writes_repository_files_only_with_apply": True, - "rollback_required": True, -} -APPLY_CONTRACT = { - **APPROVAL_CONTRACT, - "safe_regression_commands_only": True, - "rollback_on_failure_default": True, -} - - -def top_level_mirrors(summary: dict[str, Any], contract: dict[str, Any], summary_fields: list[str], contract_fields: list[str]) -> dict[str, Any]: - mirrored = {key: summary[key] for key in summary_fields if key in summary} - mirrored.update({key: contract[key] for key in contract_fields if key in contract}) - return mirrored - - -def report_contract(name: str, contract_key: str, summary_fields: list[str], contract_fields: list[str]) -> dict[str, Any]: - return { - "schema_version": "1.0", - "contract": name, - "top_level_mirrors_summary": True, - f"top_level_mirrors_{contract_key}": True, - "summary_fields": summary_fields, - f"{contract_key}_fields": contract_fields, - "source_of_truth": ["summary", contract_key], - } - - -def decorate_approval_ledger(ledger: dict[str, Any]) -> dict[str, Any]: - summary = ledger.get("summary", {}) if isinstance(ledger.get("summary"), dict) else {} - approval_contract = ( - ledger.get("approval_contract", {}) if isinstance(ledger.get("approval_contract"), dict) else {} - ) or dict(APPROVAL_CONTRACT) - ledger.update(top_level_mirrors(summary, approval_contract, APPROVAL_SUMMARY_FIELDS, APPROVAL_CONTRACT_FIELDS)) - ledger["approval_contract"] = approval_contract - ledger["report_contract"] = report_contract( - "adaptation-approval-ledger", - "approval_contract", - APPROVAL_SUMMARY_FIELDS, - APPROVAL_CONTRACT_FIELDS, - ) - return ledger - - -def decorate_regression_report(report: dict[str, Any]) -> dict[str, Any]: - summary = report.get("summary", {}) if isinstance(report.get("summary"), dict) else {} - apply_contract = ( - report.get("apply_contract", {}) if isinstance(report.get("apply_contract"), dict) else {} - ) or dict(APPLY_CONTRACT) - report.update(top_level_mirrors(summary, apply_contract, REGRESSION_SUMMARY_FIELDS, APPLY_CONTRACT_FIELDS)) - report["apply_contract"] = apply_contract - report["report_contract"] = report_contract( - "adaptation-regression-report", - "apply_contract", - REGRESSION_SUMMARY_FIELDS, - APPLY_CONTRACT_FIELDS, - ) - return report - def utc_now() -> str: return datetime.now(timezone.utc).replace(microsecond=0).isoformat().replace("+00:00", "Z") @@ -145,47 +46,6 @@ def load_json(path: Path) -> dict[str, Any]: return payload if isinstance(payload, dict) else {} -def sha256_file(path: Path) -> str: - digest = hashlib.sha256() - with path.open("rb") as handle: - for chunk in iter(lambda: handle.read(1024 * 1024), b""): - digest.update(chunk) - return digest.hexdigest() - - -def target_file_sha256(skill_dir: Path, target_files: list[str]) -> dict[str, str]: - observed: dict[str, str] = {} - for target in target_files: - path = skill_dir / target - observed[target] = sha256_file(path) if path.exists() else ABSENT_FILE_SHA256 - return observed - - -def normalize_patch_path(raw: str) -> str | None: - token = raw.strip().split("\t", 1)[0].split(" ", 1)[0] - if token == "/dev/null": - return None - if token.startswith("a/") or token.startswith("b/"): - token = token[2:] - path = Path(token) - if path.is_absolute() or ".." in path.parts or any(part in BLOCKED_PATH_PARTS for part in path.parts): - raise ValueError(f"Unsafe patch path: {raw}") - if not token or token == ".": - raise ValueError(f"Empty patch path: {raw}") - return token - - -def patch_target_files(patch_text: str) -> list[str]: - targets: set[str] = set() - for line in patch_text.splitlines(): - if line.startswith("--- ") or line.startswith("+++ "): - raw = line[4:].strip() - path = normalize_patch_path(raw) - if path: - targets.add(path) - return sorted(targets) - - def approved_entries(ledger: dict[str, Any]) -> list[dict[str, Any]]: entries = ledger.get("entries") return [item for item in entries if isinstance(item, dict)] if isinstance(entries, list) else [] @@ -263,29 +123,6 @@ def validate_approval(approval: dict[str, Any], today: date) -> list[str]: return failures -def validate_target_file_sha256( - skill_dir: Path, - target_files: list[str], - expected_sha256: dict[str, Any], -) -> tuple[list[str], dict[str, str]]: - failures: list[str] = [] - observed: dict[str, str] = {} - for target in target_files: - path = skill_dir / target - expected = str(expected_sha256.get(target, "")) - if not expected: - failures.append(f"Approval target_file_sha256 missing target: {target}") - continue - if path.exists() and not path.is_file(): - failures.append(f"Patch target is not a file: {target}") - continue - current = sha256_file(path) if path.exists() else ABSENT_FILE_SHA256 - observed[target] = current - if current != expected: - failures.append(f"Target file baseline sha256 does not match approval ledger: {target}") - return failures, observed - - def safe_command(command: str) -> tuple[bool, list[str], str]: try: parts = shlex.split(command)