claude-dev-env 1.95.0 → 2.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/_shared/advisor/CLAUDE.md +2 -2
- package/_shared/advisor/advisor-protocol.md +20 -20
- package/_shared/advisor/scripts/config/advisor_scripts_constants/model_tier_run_validator_constants.py +15 -12
- package/_shared/advisor/scripts/model_tier_run_validator.py +11 -10
- package/_shared/advisor/scripts/tests/test_model_tier_run_validator.py +25 -19
- package/_shared/advisor/scripts/tests/test_tier_model_ids.py +17 -17
- package/_shared/advisor/scripts/tier_model_ids.py +18 -18
- package/_shared/pr-loop/CLAUDE.md +1 -0
- package/_shared/pr-loop/scripts/CLAUDE.md +2 -1
- package/_shared/pr-loop/scripts/README.md +1 -0
- package/_shared/pr-loop/scripts/code_rules_gate.py +253 -1980
- package/_shared/pr-loop/scripts/code_rules_gate_parts/CLAUDE.md +32 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/__init__.py +7 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/added_line_maps.py +268 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/enforcer_loading.py +172 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/gate_arguments.py +70 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/gate_running.py +326 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/git_blob_readers.py +85 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/git_file_sets.py +331 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/staged_test_running.py +369 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/conftest.py +14 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_added_line_maps.py +118 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_enforcer_loading.py +17 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_gate_arguments.py +29 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_gate_running.py +99 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_git_blob_readers.py +69 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_git_file_sets.py +137 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_staged_test_running.py +116 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_violation_scoping.py +75 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/tests/test_wrapper_plumb_check.py +49 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/violation_scoping.py +328 -0
- package/_shared/pr-loop/scripts/code_rules_gate_parts/wrapper_plumb_check.py +206 -0
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/code_rules_gate_constants.py +24 -17
- package/_shared/pr-loop/scripts/pr_loop_shared_constants/reviews_disabled_constants.py +1 -0
- package/_shared/pr-loop/scripts/reviews_disabled.py +19 -2
- package/_shared/pr-loop/scripts/test_code_rules_gate.py +278 -0
- package/_shared/pr-loop/scripts/tests/test_code_rules_gate_constants.py +6 -39
- package/_shared/pr-loop/scripts/tests/test_reviews_disabled.py +43 -0
- package/_shared/pr-loop/worker-spawn.md +186 -0
- package/agents/code-verifier.md +1 -1
- package/bin/ever-shipped-skills.mjs +3 -0
- package/bin/expand_home_directory_tokens.mjs +1 -1
- package/bin/install.mjs +5 -2
- package/hooks/advisory/refactor_guard.py +3 -4
- package/hooks/blocking/CLAUDE.md +7 -1
- package/hooks/blocking/block_main_commit.py +2 -2
- package/hooks/blocking/claude_md_orphan_file_blocker.py +75 -699
- package/hooks/blocking/claude_md_orphan_file_blocker_parts/CLAUDE.md +28 -0
- package/hooks/blocking/claude_md_orphan_file_blocker_parts/__init__.py +1 -0
- package/hooks/blocking/claude_md_orphan_file_blocker_parts/config/__init__.py +1 -0
- package/hooks/blocking/claude_md_orphan_file_blocker_parts/config/orphan_blocker_constants.py +18 -0
- package/hooks/blocking/claude_md_orphan_file_blocker_parts/decision.py +81 -0
- package/hooks/blocking/claude_md_orphan_file_blocker_parts/references.py +307 -0
- package/hooks/blocking/claude_md_orphan_file_blocker_parts/scan_plan.py +124 -0
- package/hooks/blocking/claude_md_orphan_file_blocker_parts/subtree_scan.py +179 -0
- package/hooks/blocking/claude_md_orphan_file_blocker_parts/tests/conftest.py +10 -0
- package/hooks/blocking/claude_md_orphan_file_blocker_parts/tests/test_decision.py +34 -0
- package/hooks/blocking/claude_md_orphan_file_blocker_parts/tests/test_references.py +42 -0
- package/hooks/blocking/claude_md_orphan_file_blocker_parts/tests/test_scan_plan.py +27 -0
- package/hooks/blocking/claude_md_orphan_file_blocker_parts/tests/test_subtree_scan.py +30 -0
- package/hooks/blocking/code_rules_boolean_mustcheck.py +1 -1
- package/hooks/blocking/code_rules_mock_completeness.py +1 -1
- package/hooks/blocking/code_rules_optional_params.py +2 -2
- package/hooks/blocking/code_rules_shared.py +1 -1
- package/hooks/blocking/code_rules_test_assertions.py +1 -1
- package/hooks/blocking/code_rules_typeddict_stub.py +1 -1
- package/hooks/blocking/gh_pr_author_enforcer.py +1 -1
- package/hooks/blocking/inventory_intent_records/CLAUDE.md +26 -0
- package/hooks/blocking/inventory_intent_records/__init__.py +1 -0
- package/hooks/blocking/inventory_intent_records/config/__init__.py +1 -0
- package/hooks/blocking/inventory_intent_records/config/intent_records_constants.py +20 -0
- package/hooks/blocking/inventory_intent_records/records.py +271 -0
- package/hooks/blocking/inventory_intent_records/tests/conftest.py +10 -0
- package/hooks/blocking/inventory_intent_records/tests/test_records.py +80 -0
- package/hooks/blocking/package_inventory_stale_blocker.py +54 -384
- package/hooks/blocking/package_inventory_stale_blocker_parts/CLAUDE.md +26 -0
- package/hooks/blocking/package_inventory_stale_blocker_parts/__init__.py +1 -0
- package/hooks/blocking/package_inventory_stale_blocker_parts/config/__init__.py +1 -0
- package/hooks/blocking/package_inventory_stale_blocker_parts/config/inventory_blocker_constants.py +16 -0
- package/hooks/blocking/package_inventory_stale_blocker_parts/decision.py +84 -0
- package/hooks/blocking/package_inventory_stale_blocker_parts/inventory_detection.py +307 -0
- package/hooks/blocking/package_inventory_stale_blocker_parts/tests/conftest.py +10 -0
- package/hooks/blocking/package_inventory_stale_blocker_parts/tests/test_decision.py +38 -0
- package/hooks/blocking/package_inventory_stale_blocker_parts/tests/test_inventory_detection.py +61 -0
- package/hooks/blocking/pii_payload_scan.py +138 -42
- package/hooks/blocking/pii_prevention_blocker.py +185 -291
- package/hooks/blocking/pii_prevention_blocker_parts/CLAUDE.md +24 -0
- package/hooks/blocking/pii_prevention_blocker_parts/__init__.py +1 -0
- package/hooks/blocking/pii_prevention_blocker_parts/config/__init__.py +1 -0
- package/hooks/blocking/pii_prevention_blocker_parts/config/repository_resolution_constants.py +28 -0
- package/hooks/blocking/pii_prevention_blocker_parts/repository_exemption.py +214 -0
- package/hooks/blocking/pii_prevention_blocker_parts/repository_resolution.py +208 -0
- package/hooks/blocking/pr_description_command_parser.py +8 -4
- package/hooks/blocking/precommit_code_rules_gate.py +3 -3
- package/hooks/blocking/tdd_enforcer.py +97 -608
- package/hooks/blocking/tdd_enforcer_parts/CLAUDE.md +30 -0
- package/hooks/blocking/tdd_enforcer_parts/__init__.py +1 -0
- package/hooks/blocking/tdd_enforcer_parts/candidate_paths.py +142 -0
- package/hooks/blocking/tdd_enforcer_parts/config/__init__.py +1 -0
- package/hooks/blocking/tdd_enforcer_parts/config/tdd_enforcer_constants.py +32 -0
- package/hooks/blocking/tdd_enforcer_parts/content_analysis.py +268 -0
- package/hooks/blocking/tdd_enforcer_parts/decisions.py +92 -0
- package/hooks/blocking/tdd_enforcer_parts/freshness.py +80 -0
- package/hooks/blocking/tdd_enforcer_parts/git_tracking.py +63 -0
- package/hooks/blocking/tdd_enforcer_parts/path_classification.py +119 -0
- package/hooks/blocking/tdd_enforcer_parts/tests/conftest.py +10 -0
- package/hooks/blocking/tdd_enforcer_parts/tests/test_candidate_paths.py +31 -0
- package/hooks/blocking/tdd_enforcer_parts/tests/test_content_analysis.py +30 -0
- package/hooks/blocking/tdd_enforcer_parts/tests/test_decisions.py +34 -0
- package/hooks/blocking/tdd_enforcer_parts/tests/test_freshness.py +28 -0
- package/hooks/blocking/tdd_enforcer_parts/tests/test_git_tracking.py +48 -0
- package/hooks/blocking/tdd_enforcer_parts/tests/test_path_classification.py +36 -0
- package/hooks/blocking/test_inventory_deadlock_resolution.py +154 -0
- package/hooks/blocking/test_pii_payload_scan.py +168 -0
- package/hooks/blocking/test_tdd_enforcer_restore.py +108 -0
- package/hooks/blocking/test_verifier_verdict_minter.py +55 -158
- package/hooks/blocking/tests/conftest.py +10 -0
- package/hooks/blocking/tests/test_pii_prevention_blocker.py +260 -0
- package/hooks/blocking/tests/test_repository_exemption.py +105 -0
- package/hooks/blocking/tests/test_repository_resolution.py +108 -0
- package/hooks/diagnostic/hook_log_extractor.py +12 -10
- package/hooks/git-hooks/post_commit.py +3 -4
- package/hooks/hooks_constants/CLAUDE.md +2 -2
- package/hooks/hooks_constants/banned_identifiers_constants.py +0 -1
- package/hooks/hooks_constants/code_rules_path_utils_constants.py +1 -1
- package/hooks/hooks_constants/local_identity.py +59 -8
- package/hooks/hooks_constants/pii_prevention_constants.py +0 -6
- package/hooks/hooks_constants/test_local_identity.py +105 -3
- package/hooks/pyproject.toml +13 -36
- package/hooks/session/plugin_data_dir_cleanup.py +0 -1
- package/hooks/validation/mypy_validator.py +2 -2
- package/hooks/validators/health_check.py +1 -0
- package/hooks/validators/mypy_integration.py +2 -0
- package/hooks/validators/ruff_integration.py +3 -0
- package/hooks/workflow/auto_formatter.py +5 -4
- package/package.json +1 -1
- package/scripts/CLAUDE.md +4 -0
- package/scripts/dev_env_scripts_constants/CLAUDE.md +6 -4
- package/scripts/dev_env_scripts_constants/code_review_constants.py +71 -0
- package/scripts/dev_env_scripts_constants/grok_worker_constants.py +435 -0
- package/scripts/dev_env_scripts_constants/timing.py +7 -1
- package/scripts/grok_headless_runner.py +294 -0
- package/scripts/grok_worker_preflight.py +410 -0
- package/scripts/invoke_code_review.py +463 -0
- package/scripts/resolve_worker_spawn.py +619 -0
- package/scripts/spawn_grok_batch.py +672 -0
- package/scripts/test_grok_headless_runner.py +626 -0
- package/scripts/test_grok_worker_preflight.py +1054 -0
- package/scripts/test_invoke_code_review.py +672 -0
- package/scripts/test_resolve_worker_spawn.py +1014 -0
- package/scripts/test_spawn_grok_batch.py +1017 -0
- package/skills/CLAUDE.md +5 -3
- package/skills/_shared/pr-loop/scripts/build_audit_prompt.py +72 -13
- package/skills/_shared/pr-loop/scripts/build_fix_prompt.py +121 -14
- package/skills/_shared/pr-loop/scripts/skills_pr_loop_constants/path_resolver_constants.py +78 -0
- package/skills/_shared/pr-loop/scripts/test_build_audit_prompt.py +121 -0
- package/skills/_shared/pr-loop/scripts/test_build_fix_prompt.py +196 -6
- package/skills/autoconverge/CLAUDE.md +3 -3
- package/skills/autoconverge/SKILL.md +9 -3
- package/skills/autoconverge/reference/CLAUDE.md +2 -2
- package/skills/autoconverge/reference/convergence.md +33 -11
- package/skills/autoconverge/reference/stop-conditions.md +16 -5
- package/skills/autoconverge/workflow/CLAUDE.md +2 -1
- package/skills/autoconverge/workflow/converge.clean-audit.test.mjs +7 -2
- package/skills/autoconverge/workflow/converge.codex-gate.test.mjs +300 -0
- package/skills/autoconverge/workflow/converge.contract.test.mjs +5 -5
- package/skills/autoconverge/workflow/converge.copilot-gate.test.mjs +29 -29
- package/skills/autoconverge/workflow/converge.fix-progress.test.mjs +1 -1
- package/skills/autoconverge/workflow/converge.mjs +200 -16
- package/skills/bugteam/CLAUDE.md +2 -2
- package/skills/bugteam/CONSTRAINTS.md +3 -2
- package/skills/bugteam/PROMPTS.md +7 -6
- package/skills/bugteam/SKILL.md +18 -13
- package/skills/bugteam/reference/audit-and-teammates.md +215 -35
- package/skills/bugteam/reference/design-rationale.md +1 -1
- package/skills/bugteam/reference/obstacles/CLAUDE.md +1 -1
- package/skills/bugteam/reference/team-setup.md +8 -2
- package/skills/codex-review/CLAUDE.md +46 -0
- package/skills/codex-review/SKILL.md +181 -0
- package/skills/codex-review/reference/CLAUDE.md +15 -0
- package/skills/codex-review/reference/cli-contract.md +253 -0
- package/skills/codex-review/reference/loop-integration.md +118 -0
- package/skills/codex-review/scripts/codex_down_classifier.py +98 -0
- package/skills/codex-review/scripts/codex_review_scripts_constants/CLAUDE.md +18 -0
- package/skills/codex-review/scripts/codex_review_scripts_constants/__init__.py +1 -0
- package/skills/codex-review/scripts/codex_review_scripts_constants/classifier_constants.py +35 -0
- package/skills/codex-review/scripts/codex_review_scripts_constants/codex_usage_probe_constants.py +86 -0
- package/skills/codex-review/scripts/codex_review_scripts_constants/findings_constants.py +18 -0
- package/skills/codex-review/scripts/codex_review_scripts_constants/run_constants.py +45 -0
- package/skills/codex-review/scripts/codex_usage_probe.py +573 -0
- package/skills/codex-review/scripts/fixtures/auth_failure_synthetic.txt +1 -0
- package/skills/codex-review/scripts/fixtures/config_load_failure_v0.125.0.txt +1 -0
- package/skills/codex-review/scripts/fixtures/freeform_findings_v0.144.3.txt +6 -0
- package/skills/codex-review/scripts/fixtures/model_rejection_v0.125.0.jsonl +5 -0
- package/skills/codex-review/scripts/fixtures/structured_findings.txt +13 -0
- package/skills/codex-review/scripts/fixtures/success_stream_v0.144.3.jsonl +6 -0
- package/skills/codex-review/scripts/fixtures/unknown_failure_synthetic.txt +1 -0
- package/skills/codex-review/scripts/fixtures/usage_limit_synthetic.txt +1 -0
- package/skills/codex-review/scripts/parse_codex_findings.py +207 -0
- package/skills/codex-review/scripts/run_codex_review.py +415 -0
- package/skills/codex-review/scripts/test_codex_down_classifier.py +143 -0
- package/skills/codex-review/scripts/test_codex_usage_probe.py +678 -0
- package/skills/codex-review/scripts/test_parse_codex_findings.py +130 -0
- package/skills/codex-review/scripts/test_run_codex_review.py +812 -0
- package/skills/codex-review/test_skill_scaffold.py +192 -0
- package/skills/grok-spawn/CLAUDE.md +28 -0
- package/skills/grok-spawn/SKILL.md +226 -0
- package/skills/grok-spawn/reference/flag-profiles.md +132 -0
- package/skills/grok-spawn/reference/worker-briefs.md +152 -0
- package/skills/grokify/SKILL.md +9 -1
- package/skills/grokify/capability-claims.test.mjs +28 -0
- package/skills/grokify/evals/README.md +72 -0
- package/skills/grokify/evals/parse-payload.test.mjs +171 -0
- package/skills/grokify/evals/run-capability-evals.mjs +545 -0
- package/skills/orchestrator/SKILL.md +42 -29
- package/skills/orchestrator-refresh/SKILL.md +17 -9
- package/skills/pr-converge/SKILL.md +34 -13
- package/skills/pr-converge/reference/convergence-gates.md +42 -15
- package/skills/pr-converge/reference/fix-protocol.md +1 -1
- package/skills/pr-converge/reference/ground-rules.md +1 -1
- package/skills/pr-converge/reference/per-tick.md +130 -42
- package/skills/pr-converge/reference/state-schema.md +10 -0
- package/skills/pr-converge/scripts/CLAUDE.md +2 -0
- package/skills/pr-converge/scripts/_pr_converge_path_setup.py +5 -1
- package/skills/pr-converge/scripts/check_convergence.py +605 -29
- package/skills/pr-converge/scripts/check_convergence_availability.py +232 -0
- package/skills/pr-converge/scripts/check_convergence_gates.py +279 -235
- package/skills/pr-converge/scripts/check_convergence_thread_gates.py +1 -1
- package/skills/pr-converge/scripts/pr_converge_scripts_constants/convergence_gate_constants.py +36 -2
- package/skills/pr-converge/scripts/test__pr_converge_path_setup.py +4 -0
- package/skills/pr-converge/scripts/test_check_convergence.py +71 -3
- package/skills/pr-converge/scripts/test_check_convergence_availability.py +326 -0
- package/skills/pr-converge/scripts/test_check_convergence_codex.py +507 -0
- package/skills/pr-converge/scripts/test_check_convergence_contract.py +89 -17
- package/skills/pr-converge/scripts/test_check_convergence_fixture.py +179 -0
- package/skills/pr-converge/scripts/test_check_convergence_gates.py +84 -68
- package/skills/pr-converge/scripts/test_check_convergence_thread_gates.py +24 -0
- package/skills/pr-converge/test_step5_host_branch.py +106 -0
- package/skills/pr-loop-cloud-transport/SKILL.md +2 -0
- package/skills/reviewer-gates/SKILL.md +7 -5
- package/skills/team-advisor/SKILL.md +7 -7
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
"""Behavior tests for the Write/Edit payload PII evaluation."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import json
|
|
6
|
+
import subprocess
|
|
7
|
+
import sys
|
|
8
|
+
from pathlib import Path
|
|
9
|
+
|
|
10
|
+
import pytest
|
|
11
|
+
|
|
12
|
+
_HOOK_DIR = Path(__file__).parent
|
|
13
|
+
_HOOKS_DIR = _HOOK_DIR.parent
|
|
14
|
+
|
|
15
|
+
try:
|
|
16
|
+
from pii_payload_scan import evaluate_post_body_texts, evaluate_write_edit_payload
|
|
17
|
+
except ImportError:
|
|
18
|
+
for each_bootstrap_directory in (str(_HOOK_DIR), str(_HOOKS_DIR)):
|
|
19
|
+
if each_bootstrap_directory not in sys.path:
|
|
20
|
+
sys.path.insert(0, each_bootstrap_directory)
|
|
21
|
+
from pii_payload_scan import evaluate_post_body_texts, evaluate_write_edit_payload
|
|
22
|
+
|
|
23
|
+
_ALLOW_SLUG = "AllowOwner/allow-repo"
|
|
24
|
+
_OTHER_SLUG = "OtherOwner/other-repo"
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
def test_post_body_with_pii_is_blocked_by_default() -> None:
|
|
28
|
+
deny_reason = evaluate_post_body_texts([_assembled_fixture_email()])
|
|
29
|
+
assert deny_reason is not None
|
|
30
|
+
assert "post body" in deny_reason
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
def test_post_body_allowlisted_values_plumb_through_to_the_scan() -> None:
|
|
34
|
+
allowed_value = _assembled_fixture_email()
|
|
35
|
+
deny_reason = evaluate_post_body_texts(
|
|
36
|
+
[allowed_value], all_allowlisted_values=frozenset({allowed_value})
|
|
37
|
+
)
|
|
38
|
+
assert deny_reason is None
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
def test_post_body_unlisted_value_still_blocked_beside_allowlist() -> None:
|
|
42
|
+
deny_reason = evaluate_post_body_texts(
|
|
43
|
+
[_assembled_unlisted_email()],
|
|
44
|
+
all_allowlisted_values=frozenset({_assembled_fixture_email()}),
|
|
45
|
+
)
|
|
46
|
+
assert deny_reason is not None
|
|
47
|
+
assert "post body" in deny_reason
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _assembled_fixture_email() -> str:
|
|
51
|
+
return "owner.fixture" + "@" + "acme-corp" + ".example" + ".io"
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def _assembled_unlisted_email() -> str:
|
|
55
|
+
return "other.person" + "@" + "different-co" + ".example" + ".io"
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def _write_allowlist_identity(identity_path: Path, slug: str, value: str) -> None:
|
|
59
|
+
identity_path.write_text(
|
|
60
|
+
json.dumps({"pii_allowlisted_values": {slug: [value]}}),
|
|
61
|
+
encoding="utf-8",
|
|
62
|
+
)
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def _init_repo_with_github_origin(repository_root: Path, origin_slug: str) -> None:
|
|
66
|
+
repository_root.mkdir(parents=True, exist_ok=True)
|
|
67
|
+
subprocess.run(["git", "init", "-q"], cwd=repository_root, check=True)
|
|
68
|
+
origin_url = "https://github.com/" + origin_slug + ".git"
|
|
69
|
+
subprocess.run(
|
|
70
|
+
["git", "remote", "add", "origin", origin_url],
|
|
71
|
+
cwd=repository_root,
|
|
72
|
+
check=True,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
def _write_deny_reason(target_path: Path, value: str) -> str | None:
|
|
77
|
+
content = "owner email " + value + "\n"
|
|
78
|
+
return evaluate_write_edit_payload(
|
|
79
|
+
"Write", {"file_path": str(target_path), "content": content}
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def test_write_payload_with_a_fixture_email_is_flagged(tmp_path: Path) -> None:
|
|
84
|
+
deny_reason = _write_deny_reason(tmp_path / "notes.md", _assembled_fixture_email())
|
|
85
|
+
assert deny_reason is not None
|
|
86
|
+
assert "email" in deny_reason
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def test_allowlisted_value_under_the_allowlisted_repo_is_allowed(
|
|
90
|
+
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
|
91
|
+
) -> None:
|
|
92
|
+
allowed_value = _assembled_fixture_email()
|
|
93
|
+
identity_path = tmp_path / "local-identity.json"
|
|
94
|
+
_write_allowlist_identity(identity_path, _ALLOW_SLUG, allowed_value)
|
|
95
|
+
monkeypatch.setenv("CLAUDE_LOCAL_IDENTITY_PATH", str(identity_path))
|
|
96
|
+
repository_root = tmp_path / "repo"
|
|
97
|
+
_init_repo_with_github_origin(repository_root, _ALLOW_SLUG)
|
|
98
|
+
assert _write_deny_reason(repository_root / "notes.md", allowed_value) is None
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def test_allowlisted_value_in_a_different_repo_is_blocked(
|
|
102
|
+
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
|
103
|
+
) -> None:
|
|
104
|
+
allowed_value = _assembled_fixture_email()
|
|
105
|
+
identity_path = tmp_path / "local-identity.json"
|
|
106
|
+
_write_allowlist_identity(identity_path, _ALLOW_SLUG, allowed_value)
|
|
107
|
+
monkeypatch.setenv("CLAUDE_LOCAL_IDENTITY_PATH", str(identity_path))
|
|
108
|
+
repository_root = tmp_path / "repo"
|
|
109
|
+
_init_repo_with_github_origin(repository_root, _OTHER_SLUG)
|
|
110
|
+
deny_reason = _write_deny_reason(repository_root / "notes.md", allowed_value)
|
|
111
|
+
assert deny_reason is not None
|
|
112
|
+
assert "email" in deny_reason
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def test_unlisted_value_in_the_allowlisted_repo_is_blocked(
|
|
116
|
+
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
|
117
|
+
) -> None:
|
|
118
|
+
identity_path = tmp_path / "local-identity.json"
|
|
119
|
+
_write_allowlist_identity(identity_path, _ALLOW_SLUG, _assembled_fixture_email())
|
|
120
|
+
monkeypatch.setenv("CLAUDE_LOCAL_IDENTITY_PATH", str(identity_path))
|
|
121
|
+
repository_root = tmp_path / "repo"
|
|
122
|
+
_init_repo_with_github_origin(repository_root, _ALLOW_SLUG)
|
|
123
|
+
deny_reason = _write_deny_reason(
|
|
124
|
+
repository_root / "notes.md", _assembled_unlisted_email()
|
|
125
|
+
)
|
|
126
|
+
assert deny_reason is not None
|
|
127
|
+
assert "email" in deny_reason
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def test_clean_write_payload_skips_repository_resolution(
|
|
131
|
+
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
|
132
|
+
) -> None:
|
|
133
|
+
identity_path = tmp_path / "local-identity.json"
|
|
134
|
+
_write_allowlist_identity(identity_path, _ALLOW_SLUG, _assembled_fixture_email())
|
|
135
|
+
monkeypatch.setenv("CLAUDE_LOCAL_IDENTITY_PATH", str(identity_path))
|
|
136
|
+
repository_root = tmp_path / "repo"
|
|
137
|
+
_init_repo_with_github_origin(repository_root, _ALLOW_SLUG)
|
|
138
|
+
|
|
139
|
+
def _fail_if_called(_working_directory: str | None) -> None:
|
|
140
|
+
raise AssertionError(
|
|
141
|
+
"resolve_repository_root should not run for a payload with no PII"
|
|
142
|
+
)
|
|
143
|
+
|
|
144
|
+
monkeypatch.setattr(
|
|
145
|
+
sys.modules["pii_payload_scan"], "resolve_repository_root", _fail_if_called
|
|
146
|
+
)
|
|
147
|
+
deny_reason = evaluate_write_edit_payload(
|
|
148
|
+
"Write",
|
|
149
|
+
{
|
|
150
|
+
"file_path": str(repository_root / "notes.md"),
|
|
151
|
+
"content": "nothing sensitive here\n",
|
|
152
|
+
},
|
|
153
|
+
)
|
|
154
|
+
assert deny_reason is None
|
|
155
|
+
|
|
156
|
+
|
|
157
|
+
def test_allowlisted_value_under_missing_nested_parent_is_allowed(
|
|
158
|
+
monkeypatch: pytest.MonkeyPatch, tmp_path: Path
|
|
159
|
+
) -> None:
|
|
160
|
+
allowed_value = _assembled_fixture_email()
|
|
161
|
+
identity_path = tmp_path / "local-identity.json"
|
|
162
|
+
_write_allowlist_identity(identity_path, _ALLOW_SLUG, allowed_value)
|
|
163
|
+
monkeypatch.setenv("CLAUDE_LOCAL_IDENTITY_PATH", str(identity_path))
|
|
164
|
+
repository_root = tmp_path / "repo"
|
|
165
|
+
_init_repo_with_github_origin(repository_root, _ALLOW_SLUG)
|
|
166
|
+
nested_target = repository_root / "new_dir" / "deeper" / "notes.md"
|
|
167
|
+
assert not nested_target.parent.exists()
|
|
168
|
+
assert _write_deny_reason(nested_target, allowed_value) is None
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
"""End-to-end tests for the absent-but-tracked restore exemption.
|
|
2
|
+
|
|
3
|
+
Drives the real ``tdd_enforcer.py`` entry hook over a Write payload in a temp
|
|
4
|
+
git repository, proving a remove-then-Write rewrite of committed code is allowed
|
|
5
|
+
even when the paired test's freshness window has lapsed, while new untracked
|
|
6
|
+
code and real edits to a present file stay gated.
|
|
7
|
+
"""
|
|
8
|
+
|
|
9
|
+
import json
|
|
10
|
+
import os
|
|
11
|
+
import subprocess
|
|
12
|
+
import sys
|
|
13
|
+
from pathlib import Path
|
|
14
|
+
|
|
15
|
+
SCRIPT_PATH = Path(__file__).parent / "tdd_enforcer.py"
|
|
16
|
+
STALE_MTIME_OFFSET_SECONDS = 700
|
|
17
|
+
DISABLE_EPHEMERAL_ENVIRONMENT = {"CLAUDE_CODE_RULES_DISABLE_EPHEMERAL_EXEMPT": "1"}
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def _run_hook(payload: dict) -> subprocess.CompletedProcess[str]:
|
|
21
|
+
return subprocess.run(
|
|
22
|
+
[sys.executable, str(SCRIPT_PATH)],
|
|
23
|
+
input=json.dumps(payload),
|
|
24
|
+
text=True,
|
|
25
|
+
capture_output=True,
|
|
26
|
+
check=False,
|
|
27
|
+
env={**os.environ, **DISABLE_EPHEMERAL_ENVIRONMENT},
|
|
28
|
+
)
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def _write_payload(file_path: Path, content: str) -> dict:
|
|
32
|
+
return {"tool_name": "Write", "tool_input": {"file_path": str(file_path), "content": content}}
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def _decision_from(completed: subprocess.CompletedProcess[str]) -> str | None:
|
|
36
|
+
if not completed.stdout:
|
|
37
|
+
return None
|
|
38
|
+
hook_output = json.loads(completed.stdout).get("hookSpecificOutput", {})
|
|
39
|
+
return hook_output.get("permissionDecision")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def _run_git(repository_root: Path, *arguments: str) -> None:
|
|
43
|
+
subprocess.run(
|
|
44
|
+
["git", *arguments],
|
|
45
|
+
cwd=str(repository_root),
|
|
46
|
+
check=True,
|
|
47
|
+
capture_output=True,
|
|
48
|
+
env={**os.environ, "GIT_CONFIG_GLOBAL": os.devnull, "GIT_CONFIG_SYSTEM": os.devnull},
|
|
49
|
+
)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def _init_git_repository(repository_root: Path) -> None:
|
|
53
|
+
_run_git(repository_root, "init", "-q")
|
|
54
|
+
_run_git(repository_root, "config", "user.email", "test@example.com")
|
|
55
|
+
_run_git(repository_root, "config", "user.name", "Test")
|
|
56
|
+
_run_git(repository_root, "config", "commit.gpgsign", "false")
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _commit_tracked_service(repository_root: Path) -> tuple[Path, Path]:
|
|
60
|
+
production_file = repository_root / "service.py"
|
|
61
|
+
production_file.write_text("def serve(): return 1\n")
|
|
62
|
+
sibling_test = repository_root / "test_service.py"
|
|
63
|
+
sibling_test.write_text("def test_serve(): pass\n")
|
|
64
|
+
_run_git(repository_root, "add", "-A")
|
|
65
|
+
_run_git(repository_root, "commit", "-q", "-m", "init")
|
|
66
|
+
return production_file, sibling_test
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
def test_should_allow_restore_write_of_absent_but_tracked_file_despite_stale_window(
|
|
70
|
+
tmp_path: Path,
|
|
71
|
+
) -> None:
|
|
72
|
+
repository_root = tmp_path / "repo"
|
|
73
|
+
repository_root.mkdir()
|
|
74
|
+
_init_git_repository(repository_root)
|
|
75
|
+
production_file, sibling_test = _commit_tracked_service(repository_root)
|
|
76
|
+
stale_timestamp = os.stat(sibling_test).st_mtime - STALE_MTIME_OFFSET_SECONDS
|
|
77
|
+
os.utime(sibling_test, (stale_timestamp, stale_timestamp))
|
|
78
|
+
production_file.unlink()
|
|
79
|
+
|
|
80
|
+
completed = _run_hook(_write_payload(production_file, "def serve(): return 2\n"))
|
|
81
|
+
|
|
82
|
+
assert _decision_from(completed) == "allow"
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def test_should_still_deny_write_of_absent_untracked_file_without_fresh_test(
|
|
86
|
+
tmp_path: Path,
|
|
87
|
+
) -> None:
|
|
88
|
+
repository_root = tmp_path / "repo"
|
|
89
|
+
repository_root.mkdir()
|
|
90
|
+
_init_git_repository(repository_root)
|
|
91
|
+
untracked_file = repository_root / "brand_new.py"
|
|
92
|
+
|
|
93
|
+
completed = _run_hook(_write_payload(untracked_file, "def fresh(): return 1\n"))
|
|
94
|
+
|
|
95
|
+
assert _decision_from(completed) == "deny"
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def test_should_still_deny_write_of_present_file_with_stale_test(tmp_path: Path) -> None:
|
|
99
|
+
repository_root = tmp_path / "repo"
|
|
100
|
+
repository_root.mkdir()
|
|
101
|
+
_init_git_repository(repository_root)
|
|
102
|
+
production_file, sibling_test = _commit_tracked_service(repository_root)
|
|
103
|
+
stale_timestamp = os.stat(sibling_test).st_mtime - STALE_MTIME_OFFSET_SECONDS
|
|
104
|
+
os.utime(sibling_test, (stale_timestamp, stale_timestamp))
|
|
105
|
+
|
|
106
|
+
completed = _run_hook(_write_payload(production_file, "def serve(): return 2\n"))
|
|
107
|
+
|
|
108
|
+
assert _decision_from(completed) == "deny"
|
|
@@ -69,6 +69,36 @@ def _write_sidecar(agent_transcript_file: pathlib.Path, agent_type: str) -> None
|
|
|
69
69
|
)
|
|
70
70
|
|
|
71
71
|
|
|
72
|
+
def _write_verdict_transcript(agent_transcript_file: pathlib.Path, verdict_body: str) -> None:
|
|
73
|
+
verdict_text = f"ok\n```verdict\n{verdict_body}\n```\n"
|
|
74
|
+
agent_transcript_file.write_text(
|
|
75
|
+
json.dumps(
|
|
76
|
+
{
|
|
77
|
+
"type": "assistant",
|
|
78
|
+
"message": {"content": [{"type": "text", "text": verdict_text}]},
|
|
79
|
+
}
|
|
80
|
+
)
|
|
81
|
+
+ "\n",
|
|
82
|
+
encoding="utf-8",
|
|
83
|
+
)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _make_repo(tmp_path: pathlib.Path) -> pathlib.Path:
|
|
87
|
+
repo_root = tmp_path / "repo"
|
|
88
|
+
repo_root.mkdir()
|
|
89
|
+
return repo_root
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def _mint_payload(
|
|
93
|
+
agent_transcript_file: pathlib.Path, repo_root: pathlib.Path, agent_id: str
|
|
94
|
+
) -> dict[str, str]:
|
|
95
|
+
return {
|
|
96
|
+
"agent_transcript_path": str(agent_transcript_file),
|
|
97
|
+
"cwd": str(repo_root),
|
|
98
|
+
"agent_id": agent_id,
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
|
|
72
102
|
def test_resolves_subagent_type_from_sidecar(tmp_path: pathlib.Path) -> None:
|
|
73
103
|
agent_transcript = tmp_path / "agent-7.jsonl"
|
|
74
104
|
agent_transcript.write_text("", encoding="utf-8")
|
|
@@ -141,7 +171,7 @@ def test_verifier_type_without_a_verdict_mints_nothing(tmp_path: pathlib.Path) -
|
|
|
141
171
|
assert mint_for_payload(payload) is None
|
|
142
172
|
|
|
143
173
|
|
|
144
|
-
def
|
|
174
|
+
def _init_repo_with_upstream(repo_root: pathlib.Path) -> None:
|
|
145
175
|
subprocess.run(["git", "-C", str(repo_root), "init", "-q"], check=True)
|
|
146
176
|
subprocess.run(
|
|
147
177
|
["git", "-C", str(repo_root), "config", "user.email", "verifier@test"], check=True
|
|
@@ -151,37 +181,20 @@ def _init_repo_with_upstream_and_edit(repo_root: pathlib.Path) -> None:
|
|
|
151
181
|
subprocess.run(["git", "-C", str(repo_root), "add", "-A"], check=True)
|
|
152
182
|
subprocess.run(["git", "-C", str(repo_root), "commit", "-qm", "init"], check=True)
|
|
153
183
|
subprocess.run(["git", "-C", str(repo_root), "branch", "-f", "origin/main", "HEAD"], check=True)
|
|
184
|
+
|
|
185
|
+
|
|
186
|
+
def _init_repo_with_upstream_and_edit(repo_root: pathlib.Path) -> None:
|
|
187
|
+
_init_repo_with_upstream(repo_root)
|
|
154
188
|
(repo_root / "module.py").write_text("answer = 2\n", encoding="utf-8")
|
|
155
189
|
|
|
156
190
|
|
|
157
191
|
def test_clean_verifier_verdict_mints_a_verdict_file(tmp_path: pathlib.Path) -> None:
|
|
158
|
-
repo_root = tmp_path
|
|
159
|
-
repo_root.mkdir()
|
|
192
|
+
repo_root = _make_repo(tmp_path)
|
|
160
193
|
_init_repo_with_upstream_and_edit(repo_root)
|
|
161
194
|
agent_transcript = tmp_path / "agent-7.jsonl"
|
|
162
|
-
agent_transcript
|
|
163
|
-
json.dumps(
|
|
164
|
-
{
|
|
165
|
-
"type": "assistant",
|
|
166
|
-
"message": {
|
|
167
|
-
"content": [
|
|
168
|
-
{
|
|
169
|
-
"type": "text",
|
|
170
|
-
"text": 'ok\n```verdict\n{"all_pass": true, "findings": []}\n```\n',
|
|
171
|
-
}
|
|
172
|
-
]
|
|
173
|
-
},
|
|
174
|
-
}
|
|
175
|
-
)
|
|
176
|
-
+ "\n",
|
|
177
|
-
encoding="utf-8",
|
|
178
|
-
)
|
|
195
|
+
_write_verdict_transcript(agent_transcript, '{"all_pass": true, "findings": []}')
|
|
179
196
|
_write_sidecar(agent_transcript, MINTING_AGENT_TYPE)
|
|
180
|
-
payload =
|
|
181
|
-
"agent_transcript_path": str(agent_transcript),
|
|
182
|
-
"cwd": str(repo_root),
|
|
183
|
-
"agent_id": "a02b9583eedc74093",
|
|
184
|
-
}
|
|
197
|
+
payload = _mint_payload(agent_transcript, repo_root, "a02b9583eedc74093")
|
|
185
198
|
verdict_path = mint_for_payload(payload)
|
|
186
199
|
try:
|
|
187
200
|
assert verdict_path is not None
|
|
@@ -199,7 +212,8 @@ def _deny_rules() -> list[str]:
|
|
|
199
212
|
|
|
200
213
|
|
|
201
214
|
def test_settings_deny_verdict_directory_write() -> None:
|
|
202
|
-
assert "
|
|
215
|
+
assert "Edit($HOME/.claude/verification/**)" in _deny_rules()
|
|
216
|
+
assert "Write($HOME/.claude/verification/**)" not in _deny_rules()
|
|
203
217
|
|
|
204
218
|
|
|
205
219
|
def test_settings_deny_verdict_directory_edit() -> None:
|
|
@@ -209,87 +223,28 @@ def test_settings_deny_verdict_directory_edit() -> None:
|
|
|
209
223
|
def test_minter_refuses_when_attested_hash_equals_empty_surface_hash(
|
|
210
224
|
tmp_path: pathlib.Path,
|
|
211
225
|
) -> None:
|
|
212
|
-
repo_root = tmp_path
|
|
213
|
-
repo_root.mkdir()
|
|
226
|
+
repo_root = _make_repo(tmp_path)
|
|
214
227
|
_init_repo_with_upstream_and_edit(repo_root)
|
|
215
228
|
attested_empty = empty_surface_hash()
|
|
216
229
|
verdict_fence = json.dumps(
|
|
217
230
|
{"all_pass": True, "findings": [], "manifest_sha256": attested_empty}
|
|
218
231
|
)
|
|
219
232
|
agent_transcript = tmp_path / "agent-7.jsonl"
|
|
220
|
-
agent_transcript
|
|
221
|
-
json.dumps(
|
|
222
|
-
{
|
|
223
|
-
"type": "assistant",
|
|
224
|
-
"message": {
|
|
225
|
-
"content": [
|
|
226
|
-
{
|
|
227
|
-
"type": "text",
|
|
228
|
-
"text": f"ok\n```verdict\n{verdict_fence}\n```\n",
|
|
229
|
-
}
|
|
230
|
-
]
|
|
231
|
-
},
|
|
232
|
-
}
|
|
233
|
-
)
|
|
234
|
-
+ "\n",
|
|
235
|
-
encoding="utf-8",
|
|
236
|
-
)
|
|
233
|
+
_write_verdict_transcript(agent_transcript, verdict_fence)
|
|
237
234
|
_write_sidecar(agent_transcript, MINTING_AGENT_TYPE)
|
|
238
|
-
payload =
|
|
239
|
-
"agent_transcript_path": str(agent_transcript),
|
|
240
|
-
"cwd": str(repo_root),
|
|
241
|
-
"agent_id": "empty-surface-1",
|
|
242
|
-
}
|
|
235
|
+
payload = _mint_payload(agent_transcript, repo_root, "empty-surface-1")
|
|
243
236
|
assert mint_for_payload(payload) is None
|
|
244
237
|
|
|
245
238
|
|
|
246
239
|
def test_minter_refuses_when_recomputed_surface_is_empty(
|
|
247
240
|
tmp_path: pathlib.Path,
|
|
248
241
|
) -> None:
|
|
249
|
-
repo_root = tmp_path
|
|
250
|
-
repo_root
|
|
251
|
-
subprocess.run(["git", "-C", str(repo_root), "init", "-q"], check=True)
|
|
252
|
-
subprocess.run(
|
|
253
|
-
["git", "-C", str(repo_root), "config", "user.email", "verifier@test"],
|
|
254
|
-
check=True,
|
|
255
|
-
)
|
|
256
|
-
subprocess.run(
|
|
257
|
-
["git", "-C", str(repo_root), "config", "user.name", "verifier"],
|
|
258
|
-
check=True,
|
|
259
|
-
)
|
|
260
|
-
(repo_root / "module.py").write_text("answer = 1\n", encoding="utf-8")
|
|
261
|
-
subprocess.run(["git", "-C", str(repo_root), "add", "-A"], check=True)
|
|
262
|
-
subprocess.run(
|
|
263
|
-
["git", "-C", str(repo_root), "commit", "-qm", "init"], check=True
|
|
264
|
-
)
|
|
265
|
-
subprocess.run(
|
|
266
|
-
["git", "-C", str(repo_root), "branch", "-f", "origin/main", "HEAD"],
|
|
267
|
-
check=True,
|
|
268
|
-
)
|
|
242
|
+
repo_root = _make_repo(tmp_path)
|
|
243
|
+
_init_repo_with_upstream(repo_root)
|
|
269
244
|
agent_transcript = tmp_path / "agent-7.jsonl"
|
|
270
|
-
agent_transcript
|
|
271
|
-
json.dumps(
|
|
272
|
-
{
|
|
273
|
-
"type": "assistant",
|
|
274
|
-
"message": {
|
|
275
|
-
"content": [
|
|
276
|
-
{
|
|
277
|
-
"type": "text",
|
|
278
|
-
"text": 'ok\n```verdict\n{"all_pass": true, "findings": []}\n```\n',
|
|
279
|
-
}
|
|
280
|
-
]
|
|
281
|
-
},
|
|
282
|
-
}
|
|
283
|
-
)
|
|
284
|
-
+ "\n",
|
|
285
|
-
encoding="utf-8",
|
|
286
|
-
)
|
|
245
|
+
_write_verdict_transcript(agent_transcript, '{"all_pass": true, "findings": []}')
|
|
287
246
|
_write_sidecar(agent_transcript, MINTING_AGENT_TYPE)
|
|
288
|
-
payload =
|
|
289
|
-
"agent_transcript_path": str(agent_transcript),
|
|
290
|
-
"cwd": str(repo_root),
|
|
291
|
-
"agent_id": "empty-recompute-1",
|
|
292
|
-
}
|
|
247
|
+
payload = _mint_payload(agent_transcript, repo_root, "empty-recompute-1")
|
|
293
248
|
assert mint_for_payload(payload) is None
|
|
294
249
|
|
|
295
250
|
|
|
@@ -297,23 +252,7 @@ def test_resolves_none_when_sidecar_absent_even_with_verdict_fence(
|
|
|
297
252
|
tmp_path: pathlib.Path,
|
|
298
253
|
) -> None:
|
|
299
254
|
agent_transcript = tmp_path / "agent-7.jsonl"
|
|
300
|
-
agent_transcript
|
|
301
|
-
json.dumps(
|
|
302
|
-
{
|
|
303
|
-
"type": "assistant",
|
|
304
|
-
"message": {
|
|
305
|
-
"content": [
|
|
306
|
-
{
|
|
307
|
-
"type": "text",
|
|
308
|
-
"text": 'ok\n```verdict\n{"all_pass": true, "findings": []}\n```\n',
|
|
309
|
-
}
|
|
310
|
-
]
|
|
311
|
-
},
|
|
312
|
-
}
|
|
313
|
-
)
|
|
314
|
-
+ "\n",
|
|
315
|
-
encoding="utf-8",
|
|
316
|
-
)
|
|
255
|
+
_write_verdict_transcript(agent_transcript, '{"all_pass": true, "findings": []}')
|
|
317
256
|
payload = {"agent_transcript_path": str(agent_transcript)}
|
|
318
257
|
assert resolved_subagent_type(payload) is None
|
|
319
258
|
|
|
@@ -321,67 +260,25 @@ def test_resolves_none_when_sidecar_absent_even_with_verdict_fence(
|
|
|
321
260
|
def test_does_not_mint_when_sidecar_absent_but_transcript_has_verdict(
|
|
322
261
|
tmp_path: pathlib.Path,
|
|
323
262
|
) -> None:
|
|
324
|
-
repo_root = tmp_path
|
|
325
|
-
repo_root.mkdir()
|
|
263
|
+
repo_root = _make_repo(tmp_path)
|
|
326
264
|
_init_repo_with_upstream_and_edit(repo_root)
|
|
327
265
|
agent_transcript = tmp_path / "agent-7.jsonl"
|
|
328
|
-
agent_transcript
|
|
329
|
-
|
|
330
|
-
{
|
|
331
|
-
"type": "assistant",
|
|
332
|
-
"message": {
|
|
333
|
-
"content": [
|
|
334
|
-
{
|
|
335
|
-
"type": "text",
|
|
336
|
-
"text": 'ok\n```verdict\n{"all_pass": true, "findings": []}\n```\n',
|
|
337
|
-
}
|
|
338
|
-
]
|
|
339
|
-
},
|
|
340
|
-
}
|
|
341
|
-
)
|
|
342
|
-
+ "\n",
|
|
343
|
-
encoding="utf-8",
|
|
344
|
-
)
|
|
345
|
-
payload = {
|
|
346
|
-
"agent_transcript_path": str(agent_transcript),
|
|
347
|
-
"cwd": str(repo_root),
|
|
348
|
-
"agent_id": "no-sidecar-1",
|
|
349
|
-
}
|
|
266
|
+
_write_verdict_transcript(agent_transcript, '{"all_pass": true, "findings": []}')
|
|
267
|
+
payload = _mint_payload(agent_transcript, repo_root, "no-sidecar-1")
|
|
350
268
|
assert mint_for_payload(payload) is None
|
|
351
269
|
|
|
352
270
|
|
|
353
271
|
def test_attested_manifest_hash_binds_over_cwd_surface(tmp_path: pathlib.Path) -> None:
|
|
354
|
-
repo_root = tmp_path
|
|
355
|
-
repo_root.mkdir()
|
|
272
|
+
repo_root = _make_repo(tmp_path)
|
|
356
273
|
_init_repo_with_upstream_and_edit(repo_root)
|
|
357
274
|
attested_hash = "c" * 64
|
|
358
|
-
agent_transcript = tmp_path / "agent-7.jsonl"
|
|
359
275
|
verdict_fence = json.dumps(
|
|
360
276
|
{"all_pass": True, "findings": [], "manifest_sha256": attested_hash}
|
|
361
277
|
)
|
|
362
|
-
agent_transcript.
|
|
363
|
-
|
|
364
|
-
{
|
|
365
|
-
"type": "assistant",
|
|
366
|
-
"message": {
|
|
367
|
-
"content": [
|
|
368
|
-
{
|
|
369
|
-
"type": "text",
|
|
370
|
-
"text": f"ok\n```verdict\n{verdict_fence}\n```\n",
|
|
371
|
-
}
|
|
372
|
-
]
|
|
373
|
-
},
|
|
374
|
-
}
|
|
375
|
-
)
|
|
376
|
-
+ "\n",
|
|
377
|
-
encoding="utf-8",
|
|
378
|
-
)
|
|
278
|
+
agent_transcript = tmp_path / "agent-7.jsonl"
|
|
279
|
+
_write_verdict_transcript(agent_transcript, verdict_fence)
|
|
379
280
|
_write_sidecar(agent_transcript, MINTING_AGENT_TYPE)
|
|
380
|
-
payload =
|
|
381
|
-
"agent_transcript_path": str(agent_transcript),
|
|
382
|
-
"cwd": str(repo_root),
|
|
383
|
-
"agent_id": "attest-1",
|
|
384
|
-
}
|
|
281
|
+
payload = _mint_payload(agent_transcript, repo_root, "attest-1")
|
|
385
282
|
verdict_path = mint_for_payload(payload)
|
|
386
283
|
try:
|
|
387
284
|
assert verdict_path is not None
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""Put the blocking and hooks directories on sys.path for the blocking tests."""
|
|
2
|
+
|
|
3
|
+
import sys
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
|
|
6
|
+
_blocking_directory = str(Path(__file__).resolve().parents[1])
|
|
7
|
+
_hooks_directory = str(Path(__file__).resolve().parents[2])
|
|
8
|
+
for each_bootstrap_directory in (_blocking_directory, _hooks_directory):
|
|
9
|
+
if each_bootstrap_directory not in sys.path:
|
|
10
|
+
sys.path.insert(0, each_bootstrap_directory)
|