claude-dev-env 1.62.1 → 1.64.0
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/agents/code-advisor.md +22 -0
- package/agents/code-verifier.md +42 -0
- package/bin/install.mjs +1 -1
- package/hooks/blocking/code_rules_dead_argparse_argument.py +554 -0
- package/hooks/blocking/code_rules_enforcer.py +6 -0
- package/hooks/blocking/config/verified_commit_constants.py +16 -0
- package/hooks/blocking/test_code_rules_enforcer_dead_argparse_argument.py +534 -0
- package/hooks/blocking/test_verification_verdict_store.py +232 -0
- package/hooks/blocking/test_verified_commit_gate.py +43 -0
- package/hooks/blocking/test_verifier_verdict_minter.py +139 -0
- package/hooks/blocking/verification_verdict_store.py +165 -10
- package/hooks/blocking/verified_commit_gate.py +8 -2
- package/hooks/blocking/verifier_verdict_minter.py +59 -9
- package/hooks/hooks_constants/dead_argparse_argument_constants.py +28 -0
- package/package.json +1 -1
- package/skills/autoconverge/SKILL.md +26 -1
- package/skills/autoconverge/workflow/converge.contract.test.mjs +82 -18
- package/skills/autoconverge/workflow/converge.mjs +46 -18
- package/skills/verified-build/SKILL.md +38 -0
|
@@ -11,6 +11,8 @@ import pathlib
|
|
|
11
11
|
import subprocess
|
|
12
12
|
import sys
|
|
13
13
|
|
|
14
|
+
import pytest
|
|
15
|
+
|
|
14
16
|
_HOOK_DIR = pathlib.Path(__file__).parent
|
|
15
17
|
if str(_HOOK_DIR) not in sys.path:
|
|
16
18
|
sys.path.insert(0, str(_HOOK_DIR))
|
|
@@ -28,6 +30,10 @@ resolve_merge_base = store_module.resolve_merge_base
|
|
|
28
30
|
branch_surface_manifest = store_module.branch_surface_manifest
|
|
29
31
|
manifest_sha256 = store_module.manifest_sha256
|
|
30
32
|
workflow_verdict_covers_surface = store_module.workflow_verdict_covers_surface
|
|
33
|
+
minted_verdict_covers_surface = store_module.minted_verdict_covers_surface
|
|
34
|
+
write_verdict = store_module.write_verdict
|
|
35
|
+
worktree_path_for_branch = store_module.worktree_path_for_branch
|
|
36
|
+
empty_surface_hash = store_module.empty_surface_hash
|
|
31
37
|
|
|
32
38
|
constants_spec = importlib.util.spec_from_file_location(
|
|
33
39
|
"verified_commit_constants",
|
|
@@ -38,6 +44,8 @@ assert constants_spec.loader is not None
|
|
|
38
44
|
constants_module = importlib.util.module_from_spec(constants_spec)
|
|
39
45
|
constants_spec.loader.exec_module(constants_module)
|
|
40
46
|
CORRECTIVE_MESSAGE = constants_module.CORRECTIVE_MESSAGE
|
|
47
|
+
EMPTY_SURFACE_GUARD_MESSAGE = constants_module.EMPTY_SURFACE_GUARD_MESSAGE
|
|
48
|
+
BRANCH_WORKTREE_ABSENT_MESSAGE = constants_module.BRANCH_WORKTREE_ABSENT_MESSAGE
|
|
41
49
|
|
|
42
50
|
PRODUCTION_SOURCE = "def add(left: int, right: int) -> int:\n return left + right\n"
|
|
43
51
|
TEST_SOURCE = "def test_add() -> None:\n assert 1 + 1 == 2\n"
|
|
@@ -488,3 +496,227 @@ def test_manifest_hash_cli_prints_live_surface_hash(tmp_path: pathlib.Path) -> N
|
|
|
488
496
|
text=True,
|
|
489
497
|
)
|
|
490
498
|
assert completed_process.stdout.strip() == expected_hash
|
|
499
|
+
|
|
500
|
+
|
|
501
|
+
def _isolate_home(monkeypatch: pytest.MonkeyPatch, fake_home: pathlib.Path) -> None:
|
|
502
|
+
home_text = str(fake_home)
|
|
503
|
+
monkeypatch.setenv("HOME", home_text)
|
|
504
|
+
monkeypatch.setenv("USERPROFILE", home_text)
|
|
505
|
+
monkeypatch.delenv("HOMEDRIVE", raising=False)
|
|
506
|
+
monkeypatch.delenv("HOMEPATH", raising=False)
|
|
507
|
+
|
|
508
|
+
|
|
509
|
+
def test_minted_verdict_covers_surface_matches_other_worktree_by_hash(
|
|
510
|
+
monkeypatch: pytest.MonkeyPatch, tmp_path: pathlib.Path
|
|
511
|
+
) -> None:
|
|
512
|
+
fake_home = tmp_path / "home"
|
|
513
|
+
fake_home.mkdir()
|
|
514
|
+
_isolate_home(monkeypatch, fake_home)
|
|
515
|
+
write_verdict(
|
|
516
|
+
str(tmp_path / "other" / "worktree"),
|
|
517
|
+
MATCHING_MANIFEST_SHA256,
|
|
518
|
+
True,
|
|
519
|
+
[],
|
|
520
|
+
"agent-x",
|
|
521
|
+
)
|
|
522
|
+
assert minted_verdict_covers_surface(MATCHING_MANIFEST_SHA256) is True
|
|
523
|
+
|
|
524
|
+
|
|
525
|
+
def test_minted_verdict_covers_surface_false_for_other_hash(
|
|
526
|
+
monkeypatch: pytest.MonkeyPatch, tmp_path: pathlib.Path
|
|
527
|
+
) -> None:
|
|
528
|
+
fake_home = tmp_path / "home"
|
|
529
|
+
fake_home.mkdir()
|
|
530
|
+
_isolate_home(monkeypatch, fake_home)
|
|
531
|
+
write_verdict(
|
|
532
|
+
str(tmp_path / "other" / "worktree"),
|
|
533
|
+
OTHER_MANIFEST_SHA256,
|
|
534
|
+
True,
|
|
535
|
+
[],
|
|
536
|
+
"agent-x",
|
|
537
|
+
)
|
|
538
|
+
assert minted_verdict_covers_surface(MATCHING_MANIFEST_SHA256) is False
|
|
539
|
+
|
|
540
|
+
|
|
541
|
+
def test_minted_verdict_covers_surface_false_for_failing_verdict(
|
|
542
|
+
monkeypatch: pytest.MonkeyPatch, tmp_path: pathlib.Path
|
|
543
|
+
) -> None:
|
|
544
|
+
fake_home = tmp_path / "home"
|
|
545
|
+
fake_home.mkdir()
|
|
546
|
+
_isolate_home(monkeypatch, fake_home)
|
|
547
|
+
write_verdict(
|
|
548
|
+
str(tmp_path / "other" / "worktree"),
|
|
549
|
+
MATCHING_MANIFEST_SHA256,
|
|
550
|
+
False,
|
|
551
|
+
[{"severity": "P0", "summary": "boom"}],
|
|
552
|
+
"agent-x",
|
|
553
|
+
)
|
|
554
|
+
assert minted_verdict_covers_surface(MATCHING_MANIFEST_SHA256) is False
|
|
555
|
+
|
|
556
|
+
|
|
557
|
+
def test_minted_verdict_covers_surface_false_when_directory_absent(
|
|
558
|
+
monkeypatch: pytest.MonkeyPatch, tmp_path: pathlib.Path
|
|
559
|
+
) -> None:
|
|
560
|
+
fake_home = tmp_path / "home"
|
|
561
|
+
fake_home.mkdir()
|
|
562
|
+
_isolate_home(monkeypatch, fake_home)
|
|
563
|
+
assert minted_verdict_covers_surface(MATCHING_MANIFEST_SHA256) is False
|
|
564
|
+
|
|
565
|
+
|
|
566
|
+
def _make_repo_with_branch_worktree(
|
|
567
|
+
tmp_path: pathlib.Path, branch_name: str
|
|
568
|
+
) -> tuple[pathlib.Path, pathlib.Path]:
|
|
569
|
+
"""Create a repo with a branch checked out in a separate worktree.
|
|
570
|
+
|
|
571
|
+
Returns:
|
|
572
|
+
A tuple of (main worktree path, branch worktree path).
|
|
573
|
+
"""
|
|
574
|
+
empty_hooks_dir = tmp_path / "nohooks"
|
|
575
|
+
empty_hooks_dir.mkdir()
|
|
576
|
+
|
|
577
|
+
main_dir = tmp_path / "main"
|
|
578
|
+
main_dir.mkdir()
|
|
579
|
+
_run_git(main_dir, "init", "--initial-branch=main")
|
|
580
|
+
_run_git(main_dir, "config", "user.email", "tests@example.com")
|
|
581
|
+
_run_git(main_dir, "config", "user.name", "Worktree Tests")
|
|
582
|
+
_run_git(main_dir, "config", "core.hooksPath", str(empty_hooks_dir))
|
|
583
|
+
(main_dir / "app.py").write_text(PRODUCTION_SOURCE, encoding="utf-8")
|
|
584
|
+
_run_git(main_dir, "add", "-A")
|
|
585
|
+
_run_git(main_dir, "commit", "-m", "base")
|
|
586
|
+
|
|
587
|
+
origin_dir = tmp_path / "origin.git"
|
|
588
|
+
subprocess.run(
|
|
589
|
+
["git", "init", "--bare", "--initial-branch=main", str(origin_dir)],
|
|
590
|
+
check=True,
|
|
591
|
+
capture_output=True,
|
|
592
|
+
text=True,
|
|
593
|
+
)
|
|
594
|
+
_run_git(main_dir, "remote", "add", "origin", str(origin_dir))
|
|
595
|
+
_run_git(main_dir, "push", "-u", "origin", "main")
|
|
596
|
+
|
|
597
|
+
_run_git(main_dir, "branch", branch_name)
|
|
598
|
+
|
|
599
|
+
branch_worktree_dir = tmp_path / "branch-worktree"
|
|
600
|
+
_run_git(main_dir, "worktree", "add", str(branch_worktree_dir), branch_name)
|
|
601
|
+
|
|
602
|
+
return main_dir, branch_worktree_dir
|
|
603
|
+
|
|
604
|
+
|
|
605
|
+
def test_worktree_path_for_branch_returns_path_when_branch_present(
|
|
606
|
+
tmp_path: pathlib.Path,
|
|
607
|
+
) -> None:
|
|
608
|
+
_main_dir, branch_worktree_dir = _make_repo_with_branch_worktree(
|
|
609
|
+
tmp_path, "feature-x"
|
|
610
|
+
)
|
|
611
|
+
resolved_path = worktree_path_for_branch(str(branch_worktree_dir), "feature-x")
|
|
612
|
+
assert resolved_path is not None
|
|
613
|
+
assert pathlib.Path(resolved_path).resolve() == branch_worktree_dir.resolve()
|
|
614
|
+
|
|
615
|
+
|
|
616
|
+
def test_worktree_path_for_branch_returns_none_when_branch_absent(
|
|
617
|
+
tmp_path: pathlib.Path,
|
|
618
|
+
) -> None:
|
|
619
|
+
main_dir, _branch_worktree_dir = _make_repo_with_branch_worktree(
|
|
620
|
+
tmp_path, "feature-x"
|
|
621
|
+
)
|
|
622
|
+
resolved_path = worktree_path_for_branch(str(main_dir), "branch-never-checked-out")
|
|
623
|
+
assert resolved_path is None
|
|
624
|
+
|
|
625
|
+
|
|
626
|
+
def test_empty_surface_hash_equals_hash_of_empty_string() -> None:
|
|
627
|
+
assert empty_surface_hash() == manifest_sha256("")
|
|
628
|
+
|
|
629
|
+
|
|
630
|
+
def test_manifest_hash_cli_empty_surface_writes_guard_message_to_stderr(
|
|
631
|
+
tmp_path: pathlib.Path,
|
|
632
|
+
) -> None:
|
|
633
|
+
work_dir = _make_repo_with_origin(tmp_path)
|
|
634
|
+
completed_process = subprocess.run(
|
|
635
|
+
[
|
|
636
|
+
sys.executable,
|
|
637
|
+
str(_HOOK_DIR / "verification_verdict_store.py"),
|
|
638
|
+
"--manifest-hash",
|
|
639
|
+
str(work_dir),
|
|
640
|
+
],
|
|
641
|
+
capture_output=True,
|
|
642
|
+
text=True,
|
|
643
|
+
)
|
|
644
|
+
assert completed_process.returncode != 0
|
|
645
|
+
assert completed_process.stdout.strip() == ""
|
|
646
|
+
lowered_stderr = completed_process.stderr.lower()
|
|
647
|
+
assert "wrong work tree" in lowered_stderr or "empty" in lowered_stderr
|
|
648
|
+
|
|
649
|
+
|
|
650
|
+
def test_manifest_hash_cli_empty_surface_prints_nothing_on_stdout(
|
|
651
|
+
tmp_path: pathlib.Path,
|
|
652
|
+
) -> None:
|
|
653
|
+
work_dir = _make_repo_with_origin(tmp_path)
|
|
654
|
+
completed_process = subprocess.run(
|
|
655
|
+
[
|
|
656
|
+
sys.executable,
|
|
657
|
+
str(_HOOK_DIR / "verification_verdict_store.py"),
|
|
658
|
+
"--manifest-hash",
|
|
659
|
+
str(work_dir),
|
|
660
|
+
],
|
|
661
|
+
capture_output=True,
|
|
662
|
+
text=True,
|
|
663
|
+
)
|
|
664
|
+
assert completed_process.stdout.strip() == ""
|
|
665
|
+
|
|
666
|
+
|
|
667
|
+
def test_manifest_hash_for_branch_cli_prints_same_hash_as_explicit_dir(
|
|
668
|
+
tmp_path: pathlib.Path,
|
|
669
|
+
) -> None:
|
|
670
|
+
_main_dir, branch_worktree_dir = _make_repo_with_branch_worktree(
|
|
671
|
+
tmp_path, "feature-branch"
|
|
672
|
+
)
|
|
673
|
+
(branch_worktree_dir / "app.py").write_text(
|
|
674
|
+
"def add(left: int, right: int) -> int:\n return left - right\n",
|
|
675
|
+
encoding="utf-8",
|
|
676
|
+
)
|
|
677
|
+
direct_process = subprocess.run(
|
|
678
|
+
[
|
|
679
|
+
sys.executable,
|
|
680
|
+
str(_HOOK_DIR / "verification_verdict_store.py"),
|
|
681
|
+
"--manifest-hash",
|
|
682
|
+
str(branch_worktree_dir),
|
|
683
|
+
],
|
|
684
|
+
capture_output=True,
|
|
685
|
+
text=True,
|
|
686
|
+
check=True,
|
|
687
|
+
)
|
|
688
|
+
branch_process = subprocess.run(
|
|
689
|
+
[
|
|
690
|
+
sys.executable,
|
|
691
|
+
str(_HOOK_DIR / "verification_verdict_store.py"),
|
|
692
|
+
"--manifest-hash-for-branch",
|
|
693
|
+
"feature-branch",
|
|
694
|
+
],
|
|
695
|
+
capture_output=True,
|
|
696
|
+
text=True,
|
|
697
|
+
check=True,
|
|
698
|
+
cwd=str(branch_worktree_dir),
|
|
699
|
+
)
|
|
700
|
+
assert direct_process.stdout.strip() == branch_process.stdout.strip()
|
|
701
|
+
assert direct_process.stdout.strip() != ""
|
|
702
|
+
|
|
703
|
+
|
|
704
|
+
def test_manifest_hash_for_branch_cli_returns_nonzero_when_branch_absent(
|
|
705
|
+
tmp_path: pathlib.Path,
|
|
706
|
+
) -> None:
|
|
707
|
+
main_dir, _branch_worktree_dir = _make_repo_with_branch_worktree(
|
|
708
|
+
tmp_path, "feature-branch"
|
|
709
|
+
)
|
|
710
|
+
completed_process = subprocess.run(
|
|
711
|
+
[
|
|
712
|
+
sys.executable,
|
|
713
|
+
str(_HOOK_DIR / "verification_verdict_store.py"),
|
|
714
|
+
"--manifest-hash-for-branch",
|
|
715
|
+
"branch-never-checked-out",
|
|
716
|
+
],
|
|
717
|
+
capture_output=True,
|
|
718
|
+
text=True,
|
|
719
|
+
cwd=str(main_dir),
|
|
720
|
+
)
|
|
721
|
+
assert completed_process.returncode != 0
|
|
722
|
+
assert completed_process.stdout.strip() == ""
|
|
@@ -525,3 +525,46 @@ def test_verification_bypass_marker_allows_an_otherwise_gated_commit(
|
|
|
525
525
|
assert "VERIFIED_COMMIT_GATE" in capsys.readouterr().out
|
|
526
526
|
_run_gate_main(monkeypatch, "git commit -m x # verify-skip", work_dir)
|
|
527
527
|
assert capsys.readouterr().out == ""
|
|
528
|
+
|
|
529
|
+
|
|
530
|
+
def test_minted_verdict_from_other_worktree_allows_commit_by_hash(
|
|
531
|
+
monkeypatch: pytest.MonkeyPatch, tmp_path: pathlib.Path
|
|
532
|
+
) -> None:
|
|
533
|
+
fake_home = tmp_path / "home"
|
|
534
|
+
fake_home.mkdir()
|
|
535
|
+
_isolate_home(monkeypatch, fake_home)
|
|
536
|
+
work_dir = _make_gated_repo(tmp_path)
|
|
537
|
+
live_surface_hash = _live_surface_hash(work_dir)
|
|
538
|
+
store_module.write_verdict(
|
|
539
|
+
str(tmp_path / "sibling" / "worktree"),
|
|
540
|
+
live_surface_hash,
|
|
541
|
+
True,
|
|
542
|
+
[],
|
|
543
|
+
"agent-x",
|
|
544
|
+
)
|
|
545
|
+
transcript_path = tmp_path / "projects" / "demo" / "sess1.jsonl"
|
|
546
|
+
transcript_path.parent.mkdir(parents=True)
|
|
547
|
+
transcript_path.write_text("", encoding="utf-8")
|
|
548
|
+
assert deny_reason_for_directory(str(work_dir), str(transcript_path)) is None
|
|
549
|
+
|
|
550
|
+
|
|
551
|
+
def test_minted_verdict_from_other_worktree_with_wrong_hash_denies(
|
|
552
|
+
monkeypatch: pytest.MonkeyPatch, tmp_path: pathlib.Path
|
|
553
|
+
) -> None:
|
|
554
|
+
fake_home = tmp_path / "home"
|
|
555
|
+
fake_home.mkdir()
|
|
556
|
+
_isolate_home(monkeypatch, fake_home)
|
|
557
|
+
work_dir = _make_gated_repo(tmp_path)
|
|
558
|
+
store_module.write_verdict(
|
|
559
|
+
str(tmp_path / "sibling" / "worktree"),
|
|
560
|
+
"d" * 64,
|
|
561
|
+
True,
|
|
562
|
+
[],
|
|
563
|
+
"agent-x",
|
|
564
|
+
)
|
|
565
|
+
transcript_path = tmp_path / "projects" / "demo" / "sess1.jsonl"
|
|
566
|
+
transcript_path.parent.mkdir(parents=True)
|
|
567
|
+
transcript_path.write_text("", encoding="utf-8")
|
|
568
|
+
deny_reason = deny_reason_for_directory(str(work_dir), str(transcript_path))
|
|
569
|
+
assert deny_reason is not None
|
|
570
|
+
assert "VERIFIED_COMMIT_GATE" in deny_reason
|
|
@@ -37,6 +37,16 @@ minter_spec.loader.exec_module(minter_module)
|
|
|
37
37
|
mint_for_payload = minter_module.mint_for_payload
|
|
38
38
|
resolved_subagent_type = minter_module.resolved_subagent_type
|
|
39
39
|
|
|
40
|
+
store_spec = importlib.util.spec_from_file_location(
|
|
41
|
+
"verification_verdict_store",
|
|
42
|
+
_HOOK_DIR / "verification_verdict_store.py",
|
|
43
|
+
)
|
|
44
|
+
assert store_spec is not None
|
|
45
|
+
assert store_spec.loader is not None
|
|
46
|
+
store_module = importlib.util.module_from_spec(store_spec)
|
|
47
|
+
store_spec.loader.exec_module(store_module)
|
|
48
|
+
empty_surface_hash = store_module.empty_surface_hash
|
|
49
|
+
|
|
40
50
|
constants_spec = importlib.util.spec_from_file_location(
|
|
41
51
|
"verified_commit_constants",
|
|
42
52
|
_HOOK_DIR / "config" / "verified_commit_constants.py",
|
|
@@ -191,3 +201,132 @@ def test_settings_deny_verdict_directory_write() -> None:
|
|
|
191
201
|
|
|
192
202
|
def test_settings_deny_verdict_directory_edit() -> None:
|
|
193
203
|
assert "Edit($HOME/.claude/verification/**)" in _deny_rules()
|
|
204
|
+
|
|
205
|
+
|
|
206
|
+
def test_minter_refuses_when_attested_hash_equals_empty_surface_hash(
|
|
207
|
+
tmp_path: pathlib.Path,
|
|
208
|
+
) -> None:
|
|
209
|
+
repo_root = tmp_path / "repo"
|
|
210
|
+
repo_root.mkdir()
|
|
211
|
+
_init_repo_with_upstream_and_edit(repo_root)
|
|
212
|
+
attested_empty = empty_surface_hash()
|
|
213
|
+
verdict_fence = json.dumps(
|
|
214
|
+
{"all_pass": True, "findings": [], "manifest_sha256": attested_empty}
|
|
215
|
+
)
|
|
216
|
+
agent_transcript = tmp_path / "agent-7.jsonl"
|
|
217
|
+
agent_transcript.write_text(
|
|
218
|
+
json.dumps(
|
|
219
|
+
{
|
|
220
|
+
"type": "assistant",
|
|
221
|
+
"message": {
|
|
222
|
+
"content": [
|
|
223
|
+
{
|
|
224
|
+
"type": "text",
|
|
225
|
+
"text": f"ok\n```verdict\n{verdict_fence}\n```\n",
|
|
226
|
+
}
|
|
227
|
+
]
|
|
228
|
+
},
|
|
229
|
+
}
|
|
230
|
+
)
|
|
231
|
+
+ "\n",
|
|
232
|
+
encoding="utf-8",
|
|
233
|
+
)
|
|
234
|
+
_write_sidecar(agent_transcript, MINTING_AGENT_TYPE)
|
|
235
|
+
payload = {
|
|
236
|
+
"agent_transcript_path": str(agent_transcript),
|
|
237
|
+
"cwd": str(repo_root),
|
|
238
|
+
"agent_id": "empty-surface-1",
|
|
239
|
+
}
|
|
240
|
+
assert mint_for_payload(payload) is None
|
|
241
|
+
|
|
242
|
+
|
|
243
|
+
def test_minter_refuses_when_recomputed_surface_is_empty(
|
|
244
|
+
tmp_path: pathlib.Path,
|
|
245
|
+
) -> None:
|
|
246
|
+
repo_root = tmp_path / "repo"
|
|
247
|
+
repo_root.mkdir()
|
|
248
|
+
subprocess.run(["git", "-C", str(repo_root), "init", "-q"], check=True)
|
|
249
|
+
subprocess.run(
|
|
250
|
+
["git", "-C", str(repo_root), "config", "user.email", "verifier@test"],
|
|
251
|
+
check=True,
|
|
252
|
+
)
|
|
253
|
+
subprocess.run(
|
|
254
|
+
["git", "-C", str(repo_root), "config", "user.name", "verifier"],
|
|
255
|
+
check=True,
|
|
256
|
+
)
|
|
257
|
+
(repo_root / "module.py").write_text("answer = 1\n", encoding="utf-8")
|
|
258
|
+
subprocess.run(["git", "-C", str(repo_root), "add", "-A"], check=True)
|
|
259
|
+
subprocess.run(
|
|
260
|
+
["git", "-C", str(repo_root), "commit", "-qm", "init"], check=True
|
|
261
|
+
)
|
|
262
|
+
subprocess.run(
|
|
263
|
+
["git", "-C", str(repo_root), "branch", "-f", "origin/main", "HEAD"],
|
|
264
|
+
check=True,
|
|
265
|
+
)
|
|
266
|
+
agent_transcript = tmp_path / "agent-7.jsonl"
|
|
267
|
+
agent_transcript.write_text(
|
|
268
|
+
json.dumps(
|
|
269
|
+
{
|
|
270
|
+
"type": "assistant",
|
|
271
|
+
"message": {
|
|
272
|
+
"content": [
|
|
273
|
+
{
|
|
274
|
+
"type": "text",
|
|
275
|
+
"text": 'ok\n```verdict\n{"all_pass": true, "findings": []}\n```\n',
|
|
276
|
+
}
|
|
277
|
+
]
|
|
278
|
+
},
|
|
279
|
+
}
|
|
280
|
+
)
|
|
281
|
+
+ "\n",
|
|
282
|
+
encoding="utf-8",
|
|
283
|
+
)
|
|
284
|
+
_write_sidecar(agent_transcript, MINTING_AGENT_TYPE)
|
|
285
|
+
payload = {
|
|
286
|
+
"agent_transcript_path": str(agent_transcript),
|
|
287
|
+
"cwd": str(repo_root),
|
|
288
|
+
"agent_id": "empty-recompute-1",
|
|
289
|
+
}
|
|
290
|
+
assert mint_for_payload(payload) is None
|
|
291
|
+
|
|
292
|
+
|
|
293
|
+
def test_attested_manifest_hash_binds_over_cwd_surface(tmp_path: pathlib.Path) -> None:
|
|
294
|
+
repo_root = tmp_path / "repo"
|
|
295
|
+
repo_root.mkdir()
|
|
296
|
+
_init_repo_with_upstream_and_edit(repo_root)
|
|
297
|
+
attested_hash = "c" * 64
|
|
298
|
+
agent_transcript = tmp_path / "agent-7.jsonl"
|
|
299
|
+
verdict_fence = json.dumps(
|
|
300
|
+
{"all_pass": True, "findings": [], "manifest_sha256": attested_hash}
|
|
301
|
+
)
|
|
302
|
+
agent_transcript.write_text(
|
|
303
|
+
json.dumps(
|
|
304
|
+
{
|
|
305
|
+
"type": "assistant",
|
|
306
|
+
"message": {
|
|
307
|
+
"content": [
|
|
308
|
+
{
|
|
309
|
+
"type": "text",
|
|
310
|
+
"text": f"ok\n```verdict\n{verdict_fence}\n```\n",
|
|
311
|
+
}
|
|
312
|
+
]
|
|
313
|
+
},
|
|
314
|
+
}
|
|
315
|
+
)
|
|
316
|
+
+ "\n",
|
|
317
|
+
encoding="utf-8",
|
|
318
|
+
)
|
|
319
|
+
_write_sidecar(agent_transcript, MINTING_AGENT_TYPE)
|
|
320
|
+
payload = {
|
|
321
|
+
"agent_transcript_path": str(agent_transcript),
|
|
322
|
+
"cwd": str(repo_root),
|
|
323
|
+
"agent_id": "attest-1",
|
|
324
|
+
}
|
|
325
|
+
verdict_path = mint_for_payload(payload)
|
|
326
|
+
try:
|
|
327
|
+
assert verdict_path is not None
|
|
328
|
+
verdict_record = json.loads(verdict_path.read_text(encoding="utf-8"))
|
|
329
|
+
assert verdict_record["manifest_sha256"] == attested_hash
|
|
330
|
+
finally:
|
|
331
|
+
if verdict_path is not None and verdict_path.exists():
|
|
332
|
+
verdict_path.unlink()
|