personal memory agent
0

Configure Feed

Select the types of activity you want to include in your feed.

solstone / tests / test_journal_doctor.py
46 kB 1326 lines
1# SPDX-License-Identifier: AGPL-3.0-only 2# Copyright (c) 2026 sol pbc 3 4from __future__ import annotations 5 6import json 7import os 8import plistlib 9import shlex 10import subprocess 11import sys 12from pathlib import Path 13from types import SimpleNamespace 14 15import pytest 16 17from solstone.think import install_guard, service 18 19 20@pytest.fixture 21def doctor(): 22 from solstone.think import doctor as doctor_module 23 24 return doctor_module 25 26 27@pytest.fixture 28def home_root(monkeypatch, tmp_path): 29 home = tmp_path / "home" 30 home.mkdir() 31 monkeypatch.setattr(Path, "home", classmethod(lambda cls: home)) 32 return home 33 34 35def args(doctor): 36 return doctor.Args(verbose=False, json=False, jsonl=False, port=5015) 37 38 39MAINT_APP = "sol" 40MAINT_TASK = "000_migrate_agent_layout" 41MAINT_QUALIFIED = f"{MAINT_APP}:{MAINT_TASK}" 42 43 44def maint_state_file(journal: Path) -> Path: 45 path = journal / "maint" / MAINT_APP / f"{MAINT_TASK}.jsonl" 46 path.parent.mkdir(parents=True, exist_ok=True) 47 return path 48 49 50def write_maint_events(journal: Path, events: list[dict]) -> None: 51 maint_state_file(journal).write_text( 52 "".join(json.dumps(event) + "\n" for event in events), 53 encoding="utf-8", 54 ) 55 56 57def make_repo(tmp_path: Path, *, worktree: bool = False) -> Path: 58 repo = tmp_path / "repo" 59 repo.mkdir() 60 if worktree: 61 (repo / ".git").write_text("gitdir: /tmp/worktree\n", encoding="utf-8") 62 else: 63 (repo / ".git").mkdir() 64 return repo 65 66 67def make_alias(home_root: Path, binary: str, target: Path | str) -> Path: 68 alias = home_root / ".local" / "bin" / binary 69 alias.parent.mkdir(parents=True, exist_ok=True) 70 alias.symlink_to(target) 71 return alias 72 73 74def make_existing_target(path: Path) -> Path: 75 path.parent.mkdir(parents=True, exist_ok=True) 76 path.write_text("", encoding="utf-8") 77 return path 78 79 80def patch_alias_absent(doctor, monkeypatch): 81 monkeypatch.setattr( 82 doctor, 83 "import_install_guard", 84 lambda: (install_guard.AliasState, install_guard.check_alias), 85 ) 86 87 88def tree_snapshot(root: Path) -> list[tuple[str, str, str]]: 89 snapshot: list[tuple[str, str, str]] = [] 90 for path in sorted(root.rglob("*")): 91 rel = path.relative_to(root).as_posix() 92 if path.is_symlink(): 93 snapshot.append((rel, "symlink", os.readlink(path))) 94 elif path.is_file(): 95 snapshot.append((rel, "file", path.read_text(encoding="utf-8"))) 96 elif path.is_dir(): 97 snapshot.append((rel, "dir", "")) 98 return snapshot 99 100 101class _FakeUids: 102 def __init__(self, real: int): 103 self.real = real 104 105 106class _FakeProcess: 107 def __init__(self, *, pid: int = 99, uid: int = 501, exe: str = "/tmp/other"): 108 self.pid = pid 109 self._uid = uid 110 self._exe = exe 111 112 def uids(self) -> _FakeUids: 113 return _FakeUids(self._uid) 114 115 def exe(self) -> str: 116 return self._exe 117 118 119def force_darwin_supervisor_reader( 120 doctor, 121 monkeypatch, 122 *, 123 launchctl: subprocess.CompletedProcess, 124 processes: list[_FakeProcess] | None = None, 125) -> None: 126 monkeypatch.setattr(doctor, "platform_tag", lambda: "darwin") 127 monkeypatch.setattr(service.sys, "platform", "darwin") 128 monkeypatch.setattr(service.os, "getuid", lambda: 501) 129 monkeypatch.setattr(service.subprocess, "run", lambda *args, **kwargs: launchctl) 130 monkeypatch.setattr( 131 service.psutil, 132 "process_iter", 133 lambda: [] if processes is None else processes, 134 ) 135 136 137def write_legacy_plist(home_root: Path, argv: list[str]) -> Path: 138 plist_path = home_root / "Library" / "LaunchAgents" / "org.solpbc.solstone.plist" 139 plist_path.parent.mkdir(parents=True) 140 plist_path.write_bytes( 141 plistlib.dumps( 142 { 143 "Label": "org.solpbc.solstone", 144 "ProgramArguments": argv, 145 } 146 ) 147 ) 148 return plist_path 149 150 151def write_foreign_plist( 152 home_root: Path, 153 filename: str, 154 *, 155 label: str, 156 keep_alive=True, 157 program: str | None = None, 158 program_arguments: list[str] | None = None, 159) -> Path: 160 plist_path = home_root / "Library" / "LaunchAgents" / filename 161 plist_path.parent.mkdir(parents=True, exist_ok=True) 162 data = { 163 "Label": label, 164 "KeepAlive": keep_alive, 165 } 166 if program is not None: 167 data["Program"] = program 168 if program_arguments is not None: 169 data["ProgramArguments"] = program_arguments 170 plist_path.write_bytes(plistlib.dumps(data)) 171 return plist_path 172 173 174def install_router_skill_links(doctor, journal: Path) -> None: 175 sources = doctor._discover_project_sources(doctor.ROOT) 176 for rel_dir in [Path(".claude/skills"), Path(".agents/skills")]: 177 skills_dir = journal / rel_dir 178 skills_dir.mkdir(parents=True) 179 for source in sources: 180 link = skills_dir / source.name 181 link.symlink_to(os.path.relpath(source, skills_dir)) 182 183 184def test_service_running_ok(doctor, monkeypatch): 185 monkeypatch.setattr(doctor, "service_is_installed", lambda: True) 186 monkeypatch.setattr(doctor, "fetch_supervisor_status", lambda: {"crashed": []}) 187 188 result = doctor.service_running_check(args(doctor)) 189 190 assert result.status == "ok" 191 assert result.detail == "journal service is running" 192 193 194def test_service_running_stopped_warns(doctor, monkeypatch): 195 monkeypatch.setattr(doctor, "service_is_installed", lambda: True) 196 monkeypatch.setattr(doctor, "fetch_supervisor_status", lambda: None) 197 monkeypatch.setattr(doctor, "service_is_failed", lambda: False) 198 199 result = doctor.service_running_check(args(doctor)) 200 201 assert result.status == "warn" 202 assert result.detail == "service installed but not running" 203 assert result.fix == "run journal service start" 204 205 206def test_service_running_failed_unit_fails(doctor, monkeypatch): 207 monkeypatch.setattr(doctor, "service_is_installed", lambda: True) 208 monkeypatch.setattr(doctor, "fetch_supervisor_status", lambda: None) 209 monkeypatch.setattr(doctor, "service_is_failed", lambda: True) 210 211 result = doctor.service_running_check(args(doctor)) 212 213 assert result.status == "fail" 214 assert result.detail == "journal service unit is failed" 215 216 217def test_observer_ingest_health_warns(doctor, monkeypatch): 218 monkeypatch.setattr( 219 "solstone.apps.observer.utils.list_observers", 220 lambda: [ 221 { 222 "name": "fedora", 223 "enabled": True, 224 "health": { 225 "ingest_rejection": { 226 "version": "0.3.1", 227 "summary": "screen.jsonl:2: value is not of type 'number'", 228 "active_count": 2, 229 "first_ts": 1700000000000, 230 } 231 }, 232 } 233 ], 234 ) 235 236 result = doctor.observer_ingest_health_check(args(doctor)) 237 238 assert result.status == "warn" 239 assert "fedora" in result.detail 240 assert "screen.jsonl:2" in result.detail 241 assert "2x" in result.detail 242 assert "2023-11-14" in result.detail 243 244 245def test_observer_ingest_health_ok_and_skip(doctor, monkeypatch): 246 monkeypatch.setattr( 247 "solstone.apps.observer.utils.list_observers", 248 lambda: [{"name": "fedora", "enabled": True}], 249 ) 250 251 result = doctor.observer_ingest_health_check(args(doctor)) 252 253 assert result.status == "ok" 254 255 monkeypatch.setattr("solstone.apps.observer.utils.list_observers", lambda: []) 256 257 result = doctor.observer_ingest_health_check(args(doctor)) 258 259 assert result.status == "skip" 260 261 262def test_orphan_segment_pdf_check_warns_for_pdf_without_transcript( 263 doctor, monkeypatch, tmp_path 264): 265 monkeypatch.setattr(doctor, "get_journal_info", lambda: (str(tmp_path), "source")) 266 segment = tmp_path / "chronicle" / "20250101" / "import.document" / "120000_0" 267 segment.mkdir(parents=True) 268 (segment / "original.pdf").write_bytes(b"%PDF-1.4 synthetic") 269 270 result = doctor.orphan_segment_pdf_check(args(doctor)) 271 272 assert result.status == "warn" 273 assert "1 raw PDF original" in result.detail 274 assert "without a readable document transcript" in result.detail 275 assert "chronicle/20250101/import.document/120000_0/original.pdf" in result.detail 276 assert result.fix is not None 277 assert "journal maint --force settings:007_migrate_pdf_extractions" in result.fix 278 279 280def test_orphan_segment_pdf_check_warns_for_uppercase_pdf( 281 doctor, monkeypatch, tmp_path 282): 283 monkeypatch.setattr(doctor, "get_journal_info", lambda: (str(tmp_path), "source")) 284 segment = tmp_path / "chronicle" / "20250101" / "import.document" / "120000_0" 285 segment.mkdir(parents=True) 286 (segment / "ORIGINAL.PDF").write_bytes(b"%PDF-1.4 synthetic") 287 288 result = doctor.orphan_segment_pdf_check(args(doctor)) 289 290 assert result.status == "warn" 291 assert "chronicle/20250101/import.document/120000_0/ORIGINAL.PDF" in result.detail 292 293 294def test_orphan_segment_pdf_check_ignores_pdf_with_transcript( 295 doctor, monkeypatch, tmp_path 296): 297 monkeypatch.setattr(doctor, "get_journal_info", lambda: (str(tmp_path), "source")) 298 segment = tmp_path / "chronicle" / "20250101" / "import.document" / "120000_0" 299 segment.mkdir(parents=True) 300 (segment / "original.pdf").write_bytes(b"%PDF-1.4 synthetic") 301 (segment / "document_transcript.md").write_text("ready", encoding="utf-8") 302 303 result = doctor.orphan_segment_pdf_check(args(doctor)) 304 305 assert result.status == "ok" 306 assert result.detail == "all raw PDF originals have readable document transcripts" 307 308 309def test_journal_maint_tasks_failed_state_fails(doctor, monkeypatch, tmp_path): 310 monkeypatch.setattr(doctor, "get_journal_info", lambda: (str(tmp_path), "source")) 311 write_maint_events( 312 tmp_path, 313 [ 314 {"event": "exec", "ts": 1000}, 315 {"event": "exit", "ts": 2000, "exit_code": 2}, 316 ], 317 ) 318 319 result = doctor.journal_maint_tasks_check(args(doctor)) 320 321 assert result.status == "fail" 322 assert MAINT_QUALIFIED in result.detail 323 assert "exit 2" in result.detail 324 assert result.fix is not None 325 assert "journal maint <task>" in result.fix 326 assert "journal maint --force <task>" in result.fix 327 328 329def test_journal_maint_tasks_stale_in_progress_warns(doctor, monkeypatch, tmp_path): 330 now = 10_000_000 331 monkeypatch.setattr(doctor, "get_journal_info", lambda: (str(tmp_path), "source")) 332 monkeypatch.setattr(doctor, "now_ms", lambda: now) 333 write_maint_events( 334 tmp_path, 335 [{"event": "exec", "ts": now - doctor._MAINT_STALE_MS - 1}], 336 ) 337 338 result = doctor.journal_maint_tasks_check(args(doctor)) 339 340 assert result.status == "warn" 341 assert "started, no exit" in result.detail 342 assert MAINT_QUALIFIED in result.detail 343 assert result.fix is not None 344 assert "journal maint --force <task>" in result.fix 345 346 347def test_journal_maint_tasks_pending_is_ok(doctor, monkeypatch, tmp_path): 348 monkeypatch.setattr(doctor, "get_journal_info", lambda: (str(tmp_path), "source")) 349 350 result = doctor.journal_maint_tasks_check(args(doctor)) 351 352 assert result.status == "ok" 353 assert result.detail == "no unresolved maint tasks" 354 355 356def test_journal_maint_tasks_exec_less_state_warns(doctor, monkeypatch, tmp_path): 357 monkeypatch.setattr(doctor, "get_journal_info", lambda: (str(tmp_path), "source")) 358 write_maint_events( 359 tmp_path, 360 [{"event": "line", "ts": 1000, "line": "started but no exec"}], 361 ) 362 363 result = doctor.journal_maint_tasks_check(args(doctor)) 364 365 assert result.status == "warn" 366 assert result.detail.startswith("couldn't fully determine") 367 assert MAINT_QUALIFIED in result.detail 368 369 370def test_journal_maint_tasks_no_local_journal_skips(doctor, monkeypatch, tmp_path): 371 missing = tmp_path / "missing-journal" 372 monkeypatch.setattr(doctor, "get_journal_info", lambda: (str(missing), "source")) 373 374 result = doctor.journal_maint_tasks_check(args(doctor)) 375 376 assert result.status == "skip" 377 assert result.detail == "no local journal" 378 379 380def test_service_running_crash_loop_fails(doctor, monkeypatch): 381 monkeypatch.setattr(doctor, "service_is_installed", lambda: True) 382 monkeypatch.setattr( 383 doctor, 384 "fetch_supervisor_status", 385 lambda: {"crashed": [{"name": "cortex", "restart_attempts": 3}]}, 386 ) 387 388 result = doctor.service_running_check(args(doctor)) 389 390 assert result.status == "fail" 391 assert result.detail == "crash-loop: cortex (3 restart attempts)" 392 assert result.fix == "run journal service logs" 393 394 395def test_service_identity_not_installed_skips(doctor, monkeypatch): 396 monkeypatch.setattr( 397 doctor, 398 "check_service_target_identity", 399 lambda: SimpleNamespace( 400 installed=False, 401 target="", 402 matches_current_install=False, 403 detail="service not installed", 404 ), 405 ) 406 407 result = doctor.service_identity_check(args(doctor)) 408 409 assert result.status == "skip" 410 assert result.detail == "no local journal service" 411 412 413def test_service_identity_malformed_fails(doctor, monkeypatch): 414 monkeypatch.setattr( 415 doctor, 416 "check_service_target_identity", 417 lambda: SimpleNamespace( 418 installed=True, 419 target="", 420 matches_current_install=False, 421 detail="service config invalid", 422 ), 423 ) 424 425 result = doctor.service_identity_check(args(doctor)) 426 427 assert result.status == "fail" 428 assert result.detail == "service config invalid" 429 assert result.fix == "run journal setup to reinstall the service" 430 431 432def test_service_identity_mismatch_fails_with_force_fix(doctor, monkeypatch): 433 monkeypatch.setattr( 434 doctor, 435 "check_service_target_identity", 436 lambda: SimpleNamespace( 437 installed=True, 438 target="/tmp/old/journal", 439 matches_current_install=False, 440 detail="service target mismatch", 441 ), 442 ) 443 444 result = doctor.service_identity_check(args(doctor)) 445 446 assert result.status == "fail" 447 assert "journal setup --force" in (result.fix or "") 448 449 450def test_service_identity_match_ok(doctor, monkeypatch): 451 monkeypatch.setattr( 452 doctor, 453 "check_service_target_identity", 454 lambda: SimpleNamespace( 455 installed=True, 456 target="/tmp/current/journal", 457 matches_current_install=True, 458 detail="service target matches current install", 459 ), 460 ) 461 462 result = doctor.service_identity_check(args(doctor)) 463 464 assert result.status == "ok" 465 assert result.detail == "service target matches current install" 466 467 468SUPERVISOR_CONFLICT_GRID = [ 469 ("present", "loaded", "running", "fail", ()), 470 ("present", "loaded", "absent", "ok", ()), 471 ("present", "loaded", "unknown", "warn", ("app",)), 472 ("present", "unloaded", "running", "fail", ()), 473 ("present", "unloaded", "absent", "ok", ()), 474 ("present", "unloaded", "unknown", "warn", ("app",)), 475 ("present", "unknown", "running", "fail", ()), 476 ("present", "unknown", "absent", "warn", ("label",)), 477 ("present", "unknown", "unknown", "warn", ("label", "app")), 478 ("malformed", "loaded", "running", "fail", ()), 479 ("malformed", "loaded", "absent", "ok", ()), 480 ("malformed", "loaded", "unknown", "warn", ("app",)), 481 ("malformed", "unloaded", "running", "fail", ()), 482 ("malformed", "unloaded", "absent", "ok", ()), 483 ("malformed", "unloaded", "unknown", "warn", ("app",)), 484 ("malformed", "unknown", "running", "fail", ()), 485 ("malformed", "unknown", "absent", "warn", ("label",)), 486 ("malformed", "unknown", "unknown", "warn", ("label", "app")), 487 ("absent", "loaded", "running", "fail", ()), 488 ("absent", "loaded", "absent", "ok", ()), 489 ("absent", "loaded", "unknown", "warn", ("app",)), 490 ("absent", "unloaded", "running", "ok", ()), 491 ("absent", "unloaded", "absent", "ok", ()), 492 ("absent", "unloaded", "unknown", "warn", ("app",)), 493 ("absent", "unknown", "running", "warn", ("label",)), 494 ("absent", "unknown", "absent", "warn", ("label",)), 495 ("absent", "unknown", "unknown", "warn", ("label", "app")), 496 ("unknown", "loaded", "running", "fail", ()), 497 ("unknown", "loaded", "absent", "warn", ("plist",)), 498 ("unknown", "loaded", "unknown", "warn", ("plist", "app")), 499 ("unknown", "unloaded", "running", "warn", ("plist",)), 500 ("unknown", "unloaded", "absent", "warn", ("plist",)), 501 ("unknown", "unloaded", "unknown", "warn", ("plist", "app")), 502 ("unknown", "unknown", "running", "warn", ("plist", "label")), 503 ("unknown", "unknown", "absent", "warn", ("plist", "label")), 504 ("unknown", "unknown", "unknown", "warn", ("plist", "label", "app")), 505] 506 507 508@pytest.mark.parametrize( 509 ("plist_state", "label_state", "app_state", "expected_status", "unknown_axes"), 510 SUPERVISOR_CONFLICT_GRID, 511) 512def test_supervisor_conflict_grid( 513 doctor, 514 monkeypatch, 515 plist_state, 516 label_state, 517 app_state, 518 expected_status, 519 unknown_axes, 520): 521 plist_path = "/tmp/Library/LaunchAgents/org.solpbc.solstone.plist" 522 label_pid = 12345 if label_state == "loaded" else None 523 app_pid = 2468 if app_state == "running" else None 524 app_executable = ( 525 "/private/var/folders/xx/AppTranslocation/ABC/d/" 526 "journal.app/Contents/MacOS/journal" 527 if app_pid is not None 528 else None 529 ) 530 evidence = service.SupervisorConflictEvidence( 531 plist_path=plist_path, 532 plist_state=plist_state, 533 label_state=label_state, 534 label_pid=label_pid, 535 app_state=app_state, 536 app_pid=app_pid, 537 app_executable=app_executable, 538 detail=( 539 f"plist_path={plist_path} plist_state={plist_state}; " 540 f"launchd_label={label_state} pid={label_pid or 'none'}; " 541 f"journal_app={app_state} pid={app_pid or 'none'} " 542 f"exe={app_executable or 'none'}" 543 ), 544 foreign=service.ForeignLauncherScan(matches=(), incomplete_paths=()), 545 ) 546 monkeypatch.setattr(doctor, "inspect_supervisor_conflict", lambda: evidence) 547 548 result = doctor.supervisor_conflict_check(args(doctor)) 549 550 assert result.status == expected_status 551 if expected_status == "fail": 552 assert result.fix == "journal service uninstall" 553 assert "macOS supervisor conflict" in result.detail 554 elif expected_status == "warn": 555 assert result.fix is None 556 for axis in unknown_axes: 557 assert axis in result.detail 558 else: 559 assert result.fix is None 560 assert "no macOS supervisor conflict" in result.detail 561 562 563def test_supervisor_conflict_grid_counts(): 564 counts = { 565 status: sum( 566 1 567 for *_states, row_status, _axes in SUPERVISOR_CONFLICT_GRID 568 if row_status == status 569 ) 570 for status in {"fail", "ok", "warn"} 571 } 572 573 assert counts == {"fail": 8, "ok": 7, "warn": 21} 574 575 576def test_foreign_launcher_remedy_renders_single_match_example(doctor): 577 match = service.ForeignLauncherMatch( 578 label="com.example.solstone-watchdog", 579 plist_path="/Users/.../com.example.solstone-watchdog.plist", 580 service_target="gui/501/com.example.solstone-watchdog", 581 ) 582 583 assert doctor._foreign_launcher_remedy((match,)) == ( 584 "remove foreign launchers targeting /Applications/solstone.app: " 585 "launchctl bootout gui/501/com.example.solstone-watchdog; " 586 "rm /Users/.../com.example.solstone-watchdog.plist; " 587 "then rerun journal doctor" 588 ) 589 590 591def test_foreign_launcher_incident_regression_blocks_without_legacy_or_app( 592 doctor, monkeypatch, home_root 593): 594 """Incident regression: a hand-written user LaunchAgent with KeepAlive=True 595 relaunches /Applications/solstone.app and must block journal doctor even 596 when the legacy service is absent and journal.app is not currently running. 597 This is the standing reality proxy for macOS-only behavior that cannot be 598 validated on this Linux host. 599 """ 600 601 plist_path = write_foreign_plist( 602 home_root, 603 "com.example.solstone-watchdog.plist", 604 label="com.example.solstone-watchdog", 605 program_arguments=["/usr/bin/open", "-a", "/Applications/solstone.app"], 606 ) 607 force_darwin_supervisor_reader( 608 doctor, 609 monkeypatch, 610 launchctl=subprocess.CompletedProcess( 611 args=["launchctl"], 612 returncode=113, 613 stdout="", 614 stderr="Bootstrap lookup failed: service not found", 615 ), 616 ) 617 before = tree_snapshot(home_root) 618 619 results = doctor.run_checks( 620 args(doctor), 621 checks=[(doctor.SUPERVISOR_CONFLICT_CHECK, doctor.supervisor_conflict_check)], 622 ) 623 624 assert tree_snapshot(home_root) == before 625 result = results[0] 626 assert result.status == "fail" 627 assert "1 foreign KeepAlive launcher targets /Applications/solstone.app" in ( 628 result.detail 629 ) 630 assert "foreign_launchers=1" in result.detail 631 assert f"com.example.solstone-watchdog@{plist_path}" in result.detail 632 assert result.fix == ( 633 "remove foreign launchers targeting /Applications/solstone.app: " 634 "launchctl bootout gui/501/com.example.solstone-watchdog; " 635 f"rm {plist_path}; then rerun journal doctor" 636 ) 637 638 639def test_foreign_launcher_fix_shell_quotes_single_line_adversarial_label( 640 doctor, monkeypatch, home_root 641): 642 label = "com.example; rm -rf ~ `tick` $(echo hi) space 'quote" 643 write_foreign_plist( 644 home_root, 645 "shell-safe.plist", 646 label=label, 647 program_arguments=["/usr/bin/open", "-a", "/Applications/solstone.app"], 648 ) 649 force_darwin_supervisor_reader( 650 doctor, 651 monkeypatch, 652 launchctl=subprocess.CompletedProcess( 653 args=["launchctl"], 654 returncode=113, 655 stdout="", 656 stderr="service not found", 657 ), 658 ) 659 660 result = doctor.supervisor_conflict_check(args(doctor)) 661 662 assert result.status == "fail" 663 assert result.fix is not None 664 assert "\n" not in result.fix 665 target = f"gui/501/{label}" 666 quoted_target = shlex.quote(target) 667 assert result.fix.count(quoted_target) == 1 668 assert shlex.split(quoted_target) == [target] 669 670 671@pytest.mark.parametrize( 672 "unsafe_char", 673 ["\u0085", "\u2028", "\u2029", "\u202e"], 674) 675def test_unsafe_unicode_foreign_label_warns_without_remedy( 676 doctor, monkeypatch, home_root, unsafe_char 677): 678 write_foreign_plist( 679 home_root, 680 f"unsafe-label-{ord(unsafe_char):x}.plist", 681 label=f"com.example.bad{unsafe_char}label", 682 program_arguments=["/usr/bin/open", "-a", "/Applications/solstone.app"], 683 ) 684 force_darwin_supervisor_reader( 685 doctor, 686 monkeypatch, 687 launchctl=subprocess.CompletedProcess( 688 args=["launchctl"], 689 returncode=113, 690 stdout="", 691 stderr="service not found", 692 ), 693 ) 694 695 result = doctor.supervisor_conflict_check(args(doctor)) 696 697 assert result.status == "warn" 698 assert result.fix is None 699 assert "foreign launcher scan incomplete" in result.detail 700 assert unsafe_char not in result.detail 701 702 703def test_surrogateescape_foreign_filename_warns_without_remedy( 704 doctor, monkeypatch, home_root 705): 706 if not sys.platform.startswith("linux"): 707 pytest.skip("surrogateescape filename creation is only exercised on Linux") 708 write_foreign_plist( 709 home_root, 710 os.fsdecode(b"bad\xffname.plist"), 711 label="com.example.solstone-watchdog", 712 program_arguments=["/usr/bin/open", "-a", "/Applications/solstone.app"], 713 ) 714 force_darwin_supervisor_reader( 715 doctor, 716 monkeypatch, 717 launchctl=subprocess.CompletedProcess( 718 args=["launchctl"], 719 returncode=113, 720 stdout="", 721 stderr="service not found", 722 ), 723 ) 724 725 result = doctor.supervisor_conflict_check(args(doctor)) 726 727 assert result.status == "warn" 728 assert result.fix is None 729 assert "\udcff" not in result.detail 730 assert "bad?name.plist" in result.detail 731 732 733def test_printable_unicode_foreign_label_with_shell_metacharacters_matches( 734 doctor, monkeypatch, home_root 735): 736 label = "com.example.cafe-\u00e9.\u6771\u4eac; $(echo hi) space 'quote" 737 write_foreign_plist( 738 home_root, 739 "printable-unicode-shell-safe.plist", 740 label=label, 741 program_arguments=["/usr/bin/open", "-a", "/Applications/solstone.app"], 742 ) 743 force_darwin_supervisor_reader( 744 doctor, 745 monkeypatch, 746 launchctl=subprocess.CompletedProcess( 747 args=["launchctl"], 748 returncode=113, 749 stdout="", 750 stderr="service not found", 751 ), 752 ) 753 754 result = doctor.supervisor_conflict_check(args(doctor)) 755 756 assert result.status == "fail" 757 assert result.fix is not None 758 assert "foreign_launchers=1" in result.detail 759 target = f"gui/501/{label}" 760 quoted_target = shlex.quote(target) 761 assert result.fix.count(quoted_target) == 1 762 assert shlex.split(quoted_target) == [target] 763 764 765def test_printable_unicode_foreign_plist_path_with_shell_metacharacters_matches( 766 doctor, monkeypatch, home_root 767): 768 plist_path = write_foreign_plist( 769 home_root, 770 "cafeé 東京; $(echo hi) it's.plist", 771 label="com.example.solstone-watchdog", 772 program_arguments=["/usr/bin/open", "-a", "/Applications/solstone.app"], 773 ) 774 force_darwin_supervisor_reader( 775 doctor, 776 monkeypatch, 777 launchctl=subprocess.CompletedProcess( 778 args=["launchctl"], returncode=113, stdout="", stderr="service not found" 779 ), 780 ) 781 782 result = doctor.supervisor_conflict_check(args(doctor)) 783 784 assert result.status == "fail" 785 assert result.fix is not None 786 assert "foreign_launchers=1" in result.detail 787 assert str(plist_path) in result.detail 788 assert "foreign_incomplete" not in result.detail 789 lex = shlex.shlex(result.fix, posix=True, punctuation_chars=True) 790 lex.whitespace_split = True 791 tokens = list(lex) 792 assert tokens.count("rm") == 1 793 assert tokens[tokens.index("rm") + 1] == str(plist_path) 794 795 796def test_control_character_foreign_label_warns_without_remedy( 797 doctor, monkeypatch, home_root 798): 799 plist_path = write_foreign_plist( 800 home_root, 801 "control-label.plist", 802 label="com.example.bad\nlabel", 803 program_arguments=["/usr/bin/open", "-a", "/Applications/solstone.app"], 804 ) 805 force_darwin_supervisor_reader( 806 doctor, 807 monkeypatch, 808 launchctl=subprocess.CompletedProcess( 809 args=["launchctl"], 810 returncode=113, 811 stdout="", 812 stderr="service not found", 813 ), 814 ) 815 816 evidence = service.inspect_supervisor_conflict() 817 result = doctor.supervisor_conflict_check(args(doctor)) 818 819 assert evidence.foreign.matches == () 820 assert evidence.foreign.incomplete_paths == (str(plist_path),) 821 assert result.status == "warn" 822 assert result.fix is None 823 assert "foreign_incomplete=" in result.detail 824 assert "\n" not in result.detail 825 826 827def test_combined_exact_and_foreign_conflict_fix_contains_both_actions( 828 doctor, monkeypatch, home_root 829): 830 write_legacy_plist(home_root, ["/tmp/legacy-journal", "start"]) 831 foreign_path = write_foreign_plist( 832 home_root, 833 "com.example.foreign.plist", 834 label="com.example.foreign", 835 program_arguments=["/usr/bin/open", "-a", "/Applications/solstone.app"], 836 ) 837 force_darwin_supervisor_reader( 838 doctor, 839 monkeypatch, 840 launchctl=subprocess.CompletedProcess( 841 args=["launchctl"], 842 returncode=0, 843 stdout="\tpid = 12345\n", 844 stderr="", 845 ), 846 processes=[ 847 _FakeProcess( 848 pid=4242, 849 exe="/Applications/journal.app/Contents/MacOS/journal", 850 ) 851 ], 852 ) 853 854 result = doctor.supervisor_conflict_check(args(doctor)) 855 856 assert result.status == "fail" 857 assert result.fix == ( 858 "journal service uninstall; " 859 "remove foreign launchers targeting /Applications/solstone.app: " 860 "launchctl bootout gui/501/com.example.foreign; " 861 f"rm {foreign_path}; then rerun journal doctor" 862 ) 863 864 865def test_foreign_match_with_incomplete_scan_fails_with_match_remedy_only( 866 doctor, monkeypatch, home_root 867): 868 foreign_path = write_foreign_plist( 869 home_root, 870 "com.example.foreign.plist", 871 label="com.example.foreign", 872 program_arguments=["/usr/bin/open", "-a", "/Applications/solstone.app"], 873 ) 874 bad_path = home_root / "Library" / "LaunchAgents" / "broken.plist" 875 bad_path.write_text("not a plist", encoding="utf-8") 876 force_darwin_supervisor_reader( 877 doctor, 878 monkeypatch, 879 launchctl=subprocess.CompletedProcess( 880 args=["launchctl"], 881 returncode=113, 882 stdout="", 883 stderr="service not found", 884 ), 885 ) 886 887 result = doctor.supervisor_conflict_check(args(doctor)) 888 889 assert result.status == "fail" 890 assert result.fix is not None 891 assert str(foreign_path) in result.fix 892 assert str(bad_path) not in result.fix 893 assert "foreign launcher scan incomplete" in result.detail 894 assert f"foreign_incomplete={bad_path}" in result.detail 895 896 897def test_incomplete_foreign_scan_warns_without_remedy(doctor, monkeypatch, home_root): 898 bad_path = home_root / "Library" / "LaunchAgents" / "broken.plist" 899 bad_path.parent.mkdir(parents=True) 900 bad_path.write_text("not a plist", encoding="utf-8") 901 force_darwin_supervisor_reader( 902 doctor, 903 monkeypatch, 904 launchctl=subprocess.CompletedProcess( 905 args=["launchctl"], 906 returncode=113, 907 stdout="", 908 stderr="service not found", 909 ), 910 ) 911 912 result = doctor.supervisor_conflict_check(args(doctor)) 913 914 assert result.status == "warn" 915 assert result.fix is None 916 assert "foreign launcher scan incomplete" in result.detail 917 assert f"foreign_incomplete={bad_path}" in result.detail 918 919 920def test_conflict_report_suppresses_orthogonal_upgrade_fix( 921 doctor, monkeypatch, home_root 922): 923 plist_path = write_legacy_plist(home_root, ["/tmp/legacy-journal", "start"]) 924 app_executable = ( 925 "/private/var/folders/xx/AppTranslocation/ABC/d/" 926 "journal.app/Contents/MacOS/journal" 927 ) 928 force_darwin_supervisor_reader( 929 doctor, 930 monkeypatch, 931 launchctl=subprocess.CompletedProcess( 932 args=["launchctl"], 933 returncode=0, 934 stdout="\tpid = 12345\n", 935 stderr="", 936 ), 937 processes=[_FakeProcess(pid=4242, exe=app_executable)], 938 ) 939 monkeypatch.setattr( 940 doctor, 941 "_installed_packaging_versions", 942 lambda: { 943 "solstone": "2.0.0", 944 "solstone-journal": "1.0.0", 945 "solstone-journal-cuda": None, 946 "solstone-journal-host": None, 947 }, 948 ) 949 before = tree_snapshot(home_root) 950 951 results = doctor.run_checks( 952 args(doctor), 953 checks=[ 954 (doctor.SUPERVISOR_CONFLICT_CHECK, doctor.supervisor_conflict_check), 955 ( 956 doctor.JOURNAL_PACKAGE_VERSION_CHECK, 957 doctor.journal_package_version_check, 958 ), 959 ], 960 ) 961 962 assert tree_snapshot(home_root) == before 963 by_name = {result.name: result for result in results} 964 assert by_name["supervisor_conflict"].status == "fail" 965 assert by_name["supervisor_conflict"].fix == "journal service uninstall" 966 assert str(plist_path) in by_name["supervisor_conflict"].detail 967 assert "plist_state=present" in by_name["supervisor_conflict"].detail 968 assert "launchd_label=loaded pid=12345" in by_name["supervisor_conflict"].detail 969 assert ( 970 f"journal_app=running pid=4242 exe={app_executable}" 971 in by_name["supervisor_conflict"].detail 972 ) 973 drift = by_name["journal_package_version"] 974 assert drift.status == "fail" 975 assert "journal package version mismatch" in drift.detail 976 assert "solstone 2.0.0" in drift.detail 977 assert drift.fix == doctor._SUPERVISOR_CONFLICT_FIX_POINTER 978 fixes = {result.fix for result in results if result.fix} 979 assert fixes == { 980 "journal service uninstall", 981 doctor._SUPERVISOR_CONFLICT_FIX_POINTER, 982 } 983 fix_text = "\n".join(fixes) 984 assert "pip install --upgrade" not in fix_text 985 assert "journal setup" not in fix_text 986 assert "journal service install" not in fix_text 987 assert "journal service start" not in fix_text 988 assert "journal service restart" not in fix_text 989 990 991def test_conflict_full_journal_report_suppresses_every_other_fix( 992 doctor, monkeypatch, tmp_path, home_root 993): 994 from solstone.think import pipeline_health 995 996 journal = tmp_path / "journal" 997 journal.mkdir() 998 write_legacy_plist(home_root, ["/tmp/missing-journal", "start"]) 999 force_darwin_supervisor_reader( 1000 doctor, 1001 monkeypatch, 1002 launchctl=subprocess.CompletedProcess( 1003 args=["launchctl"], 1004 returncode=0, 1005 stdout="\tpid = 12345\n", 1006 stderr="", 1007 ), 1008 processes=[ 1009 _FakeProcess( 1010 pid=4242, 1011 exe="/Applications/journal.app/Contents/MacOS/journal", 1012 ) 1013 ], 1014 ) 1015 monkeypatch.setattr(doctor, "get_journal_info", lambda: (str(journal), "test")) 1016 monkeypatch.setattr(doctor, "_host_module_present", lambda _module: True) 1017 monkeypatch.setattr(doctor, "service_is_installed", lambda: True) 1018 monkeypatch.setattr( 1019 doctor, 1020 "check_service_target_identity", 1021 lambda: SimpleNamespace( 1022 installed=True, 1023 target="/tmp/current/journal", 1024 matches_current_install=True, 1025 detail="service target matches current install", 1026 ), 1027 ) 1028 monkeypatch.setattr( 1029 doctor, 1030 "fetch_supervisor_status", 1031 lambda: {"crashed": [], "tasks": []}, 1032 ) 1033 monkeypatch.setattr( 1034 doctor, 1035 "_installed_packaging_versions", 1036 lambda: { 1037 "solstone": "2.0.0", 1038 "solstone-journal": "1.0.0", 1039 "solstone-journal-cuda": None, 1040 "solstone-journal-host": None, 1041 }, 1042 ) 1043 monkeypatch.setattr( 1044 doctor, 1045 "check_journal_sync", 1046 lambda: SimpleNamespace(is_conflict=False), 1047 ) 1048 monkeypatch.setattr(doctor, "format_doctor_report", lambda _result: "synced") 1049 monkeypatch.setattr( 1050 pipeline_health, 1051 "read_backlog_view", 1052 lambda: SimpleNamespace( 1053 days=[], 1054 errors=[], 1055 pending_days=0, 1056 stuck_days=0, 1057 oldest_pending_day=None, 1058 ), 1059 ) 1060 monkeypatch.setattr("solstone.apps.observer.utils.list_observers", lambda: []) 1061 1062 results = doctor.run_checks(args(doctor), checks=doctor.JOURNAL_CHECKS) 1063 1064 by_name = {result.name: result for result in results} 1065 assert by_name["supervisor_conflict"].status == "fail" 1066 for result in results: 1067 if result.name == "supervisor_conflict": 1068 continue 1069 assert result.fix in {None, doctor._SUPERVISOR_CONFLICT_FIX_POINTER} 1070 suppressed = [ 1071 result.name 1072 for result in results 1073 if result.name != "supervisor_conflict" 1074 and result.fix == doctor._SUPERVISOR_CONFLICT_FIX_POINTER 1075 ] 1076 assert suppressed, "no fix was suppressed; the test proves nothing" 1077 1078 fix_text = "\n".join(result.fix or "" for result in results) 1079 for token in doctor._UNSAFE_SERVICE_ACTIONS: 1080 assert token not in fix_text 1081 assert "rm " not in fix_text 1082 assert "pip install --upgrade" not in fix_text 1083 1084 1085def test_unknown_topology_suppresses_only_service_lifecycle_fixes( 1086 doctor, monkeypatch, home_root 1087): 1088 write_legacy_plist(home_root, ["/tmp/missing-journal", "start"]) 1089 force_darwin_supervisor_reader( 1090 doctor, 1091 monkeypatch, 1092 launchctl=subprocess.CompletedProcess( 1093 args=["launchctl"], 1094 returncode=5, 1095 stdout="", 1096 stderr="opaque launchctl failure", 1097 ), 1098 ) 1099 monkeypatch.setattr( 1100 service.psutil, 1101 "process_iter", 1102 lambda: (_ for _ in ()).throw(service.psutil.Error("boom")), 1103 ) 1104 monkeypatch.setattr( 1105 doctor, 1106 "_installed_packaging_versions", 1107 lambda: { 1108 "solstone": "2.0.0", 1109 "solstone-journal": "1.0.0", 1110 "solstone-journal-cuda": None, 1111 "solstone-journal-host": None, 1112 }, 1113 ) 1114 before = tree_snapshot(home_root) 1115 1116 results = doctor.run_checks( 1117 args(doctor), 1118 checks=[ 1119 (doctor.SUPERVISOR_CONFLICT_CHECK, doctor.supervisor_conflict_check), 1120 (doctor.LAUNCHD_STALE_PLIST_CHECK, doctor.launchd_stale_plist_check), 1121 ( 1122 doctor.JOURNAL_PACKAGE_VERSION_CHECK, 1123 doctor.journal_package_version_check, 1124 ), 1125 ], 1126 ) 1127 1128 assert tree_snapshot(home_root) == before 1129 by_name = {result.name: result for result in results} 1130 assert by_name["supervisor_conflict"].status == "warn" 1131 assert "unknown axis(es): label, app" in by_name["supervisor_conflict"].detail 1132 stale = by_name["launchd_stale_plist"] 1133 assert stale.status == "fail" 1134 assert "plist points to missing executable" in stale.detail 1135 assert stale.fix == doctor._SUPERVISOR_TOPOLOGY_WARN_POINTER 1136 drift = by_name["journal_package_version"] 1137 assert drift.status == "fail" 1138 assert "journal package version mismatch" in drift.detail 1139 assert "pip install --upgrade solstone-journal" in (drift.fix or "") 1140 1141 fix_text = "\n".join(result.fix or "" for result in results) 1142 assert "journal service uninstall" not in fix_text 1143 for token in doctor._UNSAFE_SERVICE_ACTIONS: 1144 assert token not in fix_text 1145 1146 1147def test_unsafe_service_action_match_does_not_match_uninstall(doctor): 1148 assert not doctor._fix_mentions_unsafe_service_action("journal service uninstall") 1149 assert doctor._fix_mentions_unsafe_service_action("journal service install") 1150 assert doctor._fix_mentions_unsafe_service_action("journal service start") 1151 assert doctor._fix_mentions_unsafe_service_action("journal service restart") 1152 assert doctor._fix_mentions_unsafe_service_action("journal setup") 1153 1154 1155def test_role_skip_without_local_journal(doctor, monkeypatch, tmp_path, home_root): 1156 journal = tmp_path / "missing-journal" 1157 monkeypatch.setattr(doctor, "get_journal_info", lambda: (str(journal), "env")) 1158 monkeypatch.setattr(doctor, "_host_module_present", lambda _module: True) 1159 monkeypatch.setattr(doctor, "service_is_installed", lambda: False) 1160 monkeypatch.setattr( 1161 doctor, 1162 "check_journal_sync", 1163 lambda: pytest.fail("journal_sync should be role-skipped"), 1164 ) 1165 patch_alias_absent(doctor, monkeypatch) 1166 monkeypatch.setattr(doctor, "ROOT", make_repo(tmp_path)) 1167 1168 results = doctor.run_checks(args(doctor), checks=doctor.JOURNAL_CHECKS) 1169 by_name = {result.name: result for result in results} 1170 1171 assert by_name["journal_dir_writable"].status == "skip" 1172 assert by_name["journal_sync"].status == "skip" 1173 assert by_name["service_identity"].status == "skip" 1174 assert by_name["service_running"].status == "skip" 1175 assert by_name["skill_state"].status == "skip" 1176 assert by_name["host_dependencies"].status == "ok" 1177 assert by_name["disk_space"].status in {"ok", "warn"} 1178 assert by_name["config_dir_readable"].status == "ok" 1179 assert by_name["feature:pdf-import"].status in {"ok", "warn"} 1180 assert by_name["feature:pdf-export"].status in {"ok", "warn"} 1181 1182 1183def test_skill_state_no_local_journal_skips(doctor, monkeypatch, tmp_path): 1184 journal = tmp_path / "missing-journal" 1185 monkeypatch.setattr(doctor, "get_journal_info", lambda: (str(journal), "env")) 1186 monkeypatch.setattr(doctor, "is_packaged_install", lambda: False) 1187 1188 result = doctor.skill_state_check(args(doctor)) 1189 1190 assert result.status == "skip" 1191 assert result.detail == "no local journal" 1192 1193 1194def test_skill_state_current_router_links_ok(doctor, monkeypatch, tmp_path): 1195 journal = tmp_path / "journal" 1196 journal.mkdir() 1197 install_router_skill_links(doctor, journal) 1198 monkeypatch.setattr(doctor, "get_journal_info", lambda: (str(journal), "env")) 1199 monkeypatch.setattr(doctor, "is_packaged_install", lambda: False) 1200 1201 result = doctor.skill_state_check(args(doctor)) 1202 1203 assert result.status == "ok" 1204 assert result.detail == "router skills sol, journal are installed and current" 1205 1206 1207def test_skill_state_warns_for_stale_and_missing_links_without_writing( 1208 doctor, monkeypatch, tmp_path 1209): 1210 journal = tmp_path / "journal" 1211 skills_dir = journal / ".claude" / "skills" 1212 skills_dir.mkdir(parents=True) 1213 sources = { 1214 source.name: source for source in doctor._discover_project_sources(doctor.ROOT) 1215 } 1216 (skills_dir / "journal").symlink_to(os.path.relpath(sources["journal"], skills_dir)) 1217 (skills_dir / "entities").symlink_to( 1218 "../../../solstone/apps/entities/talent/entities" 1219 ) 1220 before = tree_snapshot(journal) 1221 monkeypatch.setattr(doctor, "get_journal_info", lambda: (str(journal), "env")) 1222 monkeypatch.setattr(doctor, "is_packaged_install", lambda: False) 1223 1224 result = doctor.skill_state_check(args(doctor)) 1225 1226 assert result.status == "warn" 1227 assert f"missing router sol at {skills_dir / 'sol'}" in result.detail 1228 assert f"stale skill link entities at {skills_dir / 'entities'}" in result.detail 1229 assert result.fix is not None 1230 assert "journal setup" in result.fix 1231 assert f"sol skills install --project {journal} --agent all" in result.fix 1232 assert tree_snapshot(journal) == before 1233 1234 1235class TestJournalAlias: 1236 @pytest.fixture(autouse=True) 1237 def isolated_legacy_backups(self, doctor, monkeypatch, tmp_path): 1238 backup_dir = tmp_path / "legacy-backups" 1239 backup_dir.mkdir() 1240 monkeypatch.setattr(install_guard, "_legacy_backup_dir", lambda: backup_dir) 1241 self.backup_dir = backup_dir 1242 1243 def test_journal_only_absent_ok_even_if_sol_is_foreign( 1244 self, doctor, monkeypatch, home_root, tmp_path 1245 ): 1246 patch_alias_absent(doctor, monkeypatch) 1247 repo = make_repo(tmp_path) 1248 sol_target = make_existing_target(tmp_path / "other" / ".venv" / "bin" / "sol") 1249 make_alias(home_root, "sol", sol_target) 1250 monkeypatch.setattr(doctor, "ROOT", repo) 1251 1252 result = doctor.stale_alias_symlink_check(args(doctor), binary="journal") 1253 1254 assert result.status == "ok" 1255 1256 def test_journal_uv_tool_reports_only_journal( 1257 self, doctor, monkeypatch, home_root, tmp_path 1258 ): 1259 patch_alias_absent(doctor, monkeypatch) 1260 repo = make_repo(tmp_path) 1261 target = make_existing_target( 1262 home_root 1263 / ".local" 1264 / "share" 1265 / "uv" 1266 / "tools" 1267 / "solstone" 1268 / "bin" 1269 / "journal" 1270 ) 1271 alias = make_alias(home_root, "journal", target) 1272 original_target = alias.readlink() 1273 monkeypatch.setattr(doctor, "ROOT", repo) 1274 1275 result = doctor.stale_alias_symlink_check(args(doctor), binary="journal") 1276 1277 assert result.status == "warn" 1278 assert "uv-tool" in result.detail 1279 assert result.fix is not None 1280 assert "journal setup" in result.fix 1281 assert alias.is_symlink() 1282 assert alias.readlink() == original_target 1283 assert not (home_root / ".local" / "bin" / "sol").exists() 1284 assert list(self.backup_dir.glob("*.old-symlink-*")) == [] 1285 1286 1287class TestLaunchdStalePlist: 1288 def test_skip_on_linux(self, doctor, monkeypatch): 1289 monkeypatch.setattr(doctor, "platform_tag", lambda: "linux") 1290 result = doctor.launchd_stale_plist_check(args(doctor)) 1291 assert result.status == "skip" 1292 1293 def test_skip_when_absent(self, doctor, monkeypatch, home_root): 1294 monkeypatch.setattr(doctor, "platform_tag", lambda: "darwin") 1295 result = doctor.launchd_stale_plist_check(args(doctor)) 1296 assert result.status == "skip" 1297 1298 def test_fail_when_target_missing(self, doctor, monkeypatch, home_root): 1299 monkeypatch.setattr(doctor, "platform_tag", lambda: "darwin") 1300 plist_path = ( 1301 home_root / "Library" / "LaunchAgents" / "org.solpbc.solstone.plist" 1302 ) 1303 plist_path.parent.mkdir(parents=True) 1304 plist_path.write_bytes( 1305 plistlib.dumps({"ProgramArguments": ["/tmp/missing-sol"]}) 1306 ) 1307 result = doctor.launchd_stale_plist_check(args(doctor)) 1308 assert result.status == "fail" 1309 assert result.fix == ( 1310 "run journal service uninstall, then run journal service install " 1311 "separately to reinstall a headless background service" 1312 ) 1313 assert "journal setup" not in (result.fix or "") 1314 assert "&&" not in (result.fix or "") 1315 1316 def test_ok_when_target_exists(self, doctor, monkeypatch, home_root, tmp_path): 1317 monkeypatch.setattr(doctor, "platform_tag", lambda: "darwin") 1318 exe = tmp_path / "sol" 1319 exe.write_text("", encoding="utf-8") 1320 plist_path = ( 1321 home_root / "Library" / "LaunchAgents" / "org.solpbc.solstone.plist" 1322 ) 1323 plist_path.parent.mkdir(parents=True) 1324 plist_path.write_bytes(plistlib.dumps({"ProgramArguments": [str(exe)]})) 1325 result = doctor.launchd_stale_plist_check(args(doctor)) 1326 assert result.status == "ok"