@@ -399,6 +399,197 @@ def test_antigravity_is_gui_surface_with_availability_only_candidates(probe, sch
399399 assert result ["version_probe" ]["state" ] == "skipped"
400400
401401
402+ def _fake_version_process (exit_code : int = 0 ) -> mock .Mock :
403+ process = mock .Mock ()
404+ process .poll .return_value = exit_code
405+ process .pid = 6161
406+ return process
407+
408+
409+ def test_hermes_version_probe_records_version_and_platform_receipt (probe , schema , fixtures ) -> None :
410+ fixture = next (item for item in fixtures if item ["harness" ]["id" ] == "hermes" )
411+ with (
412+ mock .patch .object (probe .shutil , "which" , return_value = "/fake/bin/hermes" ),
413+ mock .patch .object (probe , "_popen_probe_process" , return_value = _fake_version_process ()),
414+ mock .patch .object (
415+ probe ,
416+ "_collect_bounded_output" ,
417+ return_value = (b"hermes 0.3.1\n " , False , None ),
418+ ),
419+ ):
420+ result = probe .probe_fixture (fixture , schema , run_version = True , timeout_seconds = 1.0 )
421+ version_probe = result ["version_probe" ]
422+ assert version_probe ["state" ] == "observed"
423+ assert version_probe ["command" ] == "hermes"
424+ assert version_probe ["version" ] == "hermes 0.3.1"
425+ assert version_probe ["platform" ] == sys .platform
426+ assert "hermes 0.3.1" in version_probe ["output" ]
427+
428+
429+ def test_hermes_missing_binary_stays_externally_blocked_binary_not_found (probe , schema , fixtures ) -> None :
430+ fixture = next (item for item in fixtures if item ["harness" ]["id" ] == "hermes" )
431+ with mock .patch .object (probe .shutil , "which" , return_value = None ):
432+ result = probe .probe_fixture (fixture , schema , run_version = True , timeout_seconds = 1.0 )
433+ assert result ["availability" ]["state" ] == "externally_blocked"
434+ assert result ["availability" ]["reason" ] == "binary_not_found"
435+ assert result ["version_probe" ]["state" ] == "externally_blocked"
436+ assert result ["version_probe" ]["reason" ] == "binary_not_found"
437+
438+
439+ def test_hermes_home_directory_only_never_counts_as_runtime_conformance (
440+ probe , schema , fixtures , monkeypatch , tmp_path : Path
441+ ) -> None :
442+ fixture = next (item for item in fixtures if item ["harness" ]["id" ] == "hermes" )
443+ home = tmp_path / "home"
444+ (home / ".hermes" ).mkdir (parents = True )
445+ (home / ".config" / "hermes" ).mkdir (parents = True )
446+ monkeypatch .setenv ("HOME" , str (home ))
447+ with mock .patch .object (probe .shutil , "which" , return_value = None ):
448+ result = probe .probe_fixture (fixture , schema , run_version = True , timeout_seconds = 1.0 )
449+ for section in (result ["availability" ], result ["version_probe" ]):
450+ assert section ["state" ] == "externally_blocked"
451+ assert section ["reason" ] == "binary_not_found"
452+
453+
454+ def test_antigravity_version_probe_records_version_and_platform_receipt (probe , schema , fixtures ) -> None :
455+ fixture = next (item for item in fixtures if item ["harness" ]["id" ] == "antigravity" )
456+ assert fixture ["binary" ] == {"command" : "agy" , "version_args" : ["--version" ]}
457+ with (
458+ mock .patch .object (
459+ probe .shutil ,
460+ "which" ,
461+ side_effect = lambda name : "/fake/bin/agy" if name == "agy" else None ,
462+ ),
463+ mock .patch .object (probe , "_popen_probe_process" , return_value = _fake_version_process ()),
464+ mock .patch .object (
465+ probe ,
466+ "_collect_bounded_output" ,
467+ return_value = (b"agy 1.4.0\n " , False , None ),
468+ ),
469+ ):
470+ result = probe .probe_fixture (fixture , schema , run_version = True , timeout_seconds = 1.0 )
471+ version_probe = result ["version_probe" ]
472+ assert version_probe ["state" ] == "observed"
473+ assert version_probe ["command" ] == "agy"
474+ assert version_probe ["version" ] == "agy 1.4.0"
475+ assert version_probe ["platform" ] == sys .platform
476+ assert "agy 1.4.0" in version_probe ["output" ]
477+
478+
479+ def test_antigravity_version_probe_falls_back_to_declared_availability_candidates (probe , schema , fixtures ) -> None :
480+ # Greptile P1 on PR 450: an install with only the `antigravity` launcher
481+ # must not report availability "available" alongside a blocked version
482+ # probe; the probe resolves whichever declared candidate is present.
483+ fixture = next (item for item in fixtures if item ["harness" ]["id" ] == "antigravity" )
484+ with (
485+ mock .patch .object (
486+ probe .shutil ,
487+ "which" ,
488+ side_effect = lambda name : "/fake/bin/antigravity" if name == "antigravity" else None ,
489+ ),
490+ mock .patch .object (probe , "_popen_probe_process" , return_value = _fake_version_process ()),
491+ mock .patch .object (
492+ probe ,
493+ "_collect_bounded_output" ,
494+ return_value = (b"antigravity 2.0.1\n " , False , None ),
495+ ),
496+ ):
497+ result = probe .probe_fixture (fixture , schema , run_version = True , timeout_seconds = 1.0 )
498+ assert result ["availability" ]["state" ] == "available"
499+ version_probe = result ["version_probe" ]
500+ assert version_probe ["state" ] == "observed"
501+ assert version_probe ["command" ] == "antigravity"
502+ assert version_probe ["version" ] == "antigravity 2.0.1"
503+ assert version_probe ["platform" ] == sys .platform
504+
505+
506+ def test_version_receipt_skips_banner_lines_without_version_numbers (probe , schema , fixtures ) -> None :
507+ # Greptile P2 on PR 450: a warning or banner printed before the version
508+ # must not be reported as the version evidence.
509+ fixture = next (item for item in fixtures if item ["harness" ]["id" ] == "hermes" )
510+ bannered_output = b"WARNING: deprecated config key detected\n Build channel: stable\n hermes 0.3.1\n "
511+ with (
512+ mock .patch .object (probe .shutil , "which" , return_value = "/fake/bin/hermes" ),
513+ mock .patch .object (probe , "_popen_probe_process" , return_value = _fake_version_process ()),
514+ mock .patch .object (
515+ probe ,
516+ "_collect_bounded_output" ,
517+ return_value = (bannered_output , False , None ),
518+ ),
519+ ):
520+ result = probe .probe_fixture (fixture , schema , run_version = True , timeout_seconds = 1.0 )
521+ version_probe = result ["version_probe" ]
522+ assert version_probe ["state" ] == "observed"
523+ assert version_probe ["version" ] == "hermes 0.3.1"
524+
525+
526+ def test_version_receipt_reports_none_when_no_version_line_present (probe , schema , fixtures ) -> None :
527+ fixture = next (item for item in fixtures if item ["harness" ]["id" ] == "hermes" )
528+ with (
529+ mock .patch .object (probe .shutil , "which" , return_value = "/fake/bin/hermes" ),
530+ mock .patch .object (probe , "_popen_probe_process" , return_value = _fake_version_process ()),
531+ mock .patch .object (
532+ probe ,
533+ "_collect_bounded_output" ,
534+ return_value = (b"build channel: stable\n " , False , None ),
535+ ),
536+ ):
537+ result = probe .probe_fixture (fixture , schema , run_version = True , timeout_seconds = 1.0 )
538+ version_probe = result ["version_probe" ]
539+ assert version_probe ["state" ] == "observed"
540+ assert version_probe ["version" ] is None
541+
542+
543+ def test_antigravity_missing_binary_stays_externally_blocked_binary_not_found (probe , schema , fixtures ) -> None :
544+ fixture = next (item for item in fixtures if item ["harness" ]["id" ] == "antigravity" )
545+ with mock .patch .object (probe .shutil , "which" , return_value = None ):
546+ result = probe .probe_fixture (fixture , schema , run_version = True , timeout_seconds = 1.0 )
547+ assert result ["availability" ]["state" ] == "externally_blocked"
548+ assert result ["availability" ]["reason" ] == "binary_not_found"
549+ assert result ["version_probe" ]["state" ] == "externally_blocked"
550+ assert result ["version_probe" ]["reason" ] == "binary_not_found"
551+
552+
553+ def test_antigravity_home_directory_only_never_counts_as_runtime_conformance (
554+ probe , schema , fixtures , monkeypatch , tmp_path : Path
555+ ) -> None :
556+ fixture = next (item for item in fixtures if item ["harness" ]["id" ] == "antigravity" )
557+ home = tmp_path / "home"
558+ (home / ".antigravity" ).mkdir (parents = True )
559+ (home / ".config" / "antigravity" ).mkdir (parents = True )
560+ monkeypatch .setenv ("HOME" , str (home ))
561+ with mock .patch .object (probe .shutil , "which" , return_value = None ):
562+ result = probe .probe_fixture (fixture , schema , run_version = True , timeout_seconds = 1.0 )
563+ for section in (result ["availability" ], result ["version_probe" ]):
564+ assert section ["state" ] == "externally_blocked"
565+ assert section ["reason" ] == "binary_not_found"
566+
567+
568+ def test_antigravity_version_args_gate_still_blocks_unsafe_args (probe , fixtures ) -> None :
569+ fixture = json .loads (json .dumps (next (item for item in fixtures if item ["harness" ]["id" ] == "antigravity" )))
570+ fixture ["binary" ]["version_args" ] = ["--full" ]
571+ with mock .patch .object (probe , "_popen_probe_process" ) as popen :
572+ result = probe .run_version_probe (fixture , timeout_seconds = 1.0 )
573+ assert result ["state" ] == "externally_blocked"
574+ assert result ["reason" ] == "unsafe_version_arguments"
575+ popen .assert_not_called ()
576+
577+
578+ def test_gui_surface_without_binary_declaration_remains_version_limited (probe , schema ) -> None :
579+ fixture = {
580+ "schema" : "harness-contract.v1" ,
581+ "harness" : {"id" : "gui-without-binary" , "surface" : "gui" },
582+ "availability" : {"command_candidates" : ["example-gui" ]},
583+ "capabilities" : _minimal_capabilities (),
584+ "deep_probes" : _minimal_deep_probes (),
585+ }
586+ with mock .patch .object (probe , "_popen_probe_process" ) as popen :
587+ result = probe .probe_fixture (fixture , schema , run_version = True , timeout_seconds = 1.0 )
588+ assert result ["version_probe" ]["state" ] == "externally_blocked"
589+ assert result ["version_probe" ]["reason" ] == "version_execution_limited_to_cli_surface"
590+ popen .assert_not_called ()
591+
592+
402593def test_deep_probes_are_returned_not_executed (probe , schema , fixtures ) -> None :
403594 result = probe .probe_fixture (fixtures [0 ], schema , run_version = False , timeout_seconds = 1.0 )
404595 assert result ["deep_probes" ] == fixtures [0 ]["deep_probes" ]
0 commit comments