From 5cf4828ac64cd7641ed815d4d692818f49b9e22a Mon Sep 17 00:00:00 2001 From: Solomon Neas Date: Sun, 26 Jul 2026 16:37:21 -0400 Subject: [PATCH 1/2] fix(evidence): index captured verify receipts Co-Authored-By: Codex --- docs/technical-guide.md | 6 +- src/brigade/receipts_cmd.py | 162 ++++++++++++++---- .../templates/skills/brigade-work/SKILL.md | 2 +- src/brigade/work_cmd/verification.py | 23 +++ tests/test_receipts_cmd.py | 33 +++- tests/test_work_cmd_verification.py | 106 +++++++++++- 6 files changed, 293 insertions(+), 39 deletions(-) diff --git a/docs/technical-guide.md b/docs/technical-guide.md index 7d3892e7..c2256033 100644 --- a/docs/technical-guide.md +++ b/docs/technical-guide.md @@ -1041,7 +1041,9 @@ For scheduled local indexing, use the built-in pipeline: brigade receipts export miseledger --target . --new-only --import ``` -Put that export on your own timer when you want the loop to run without thinking about it. Brigade does not install the timer. The export sends new verify-run and Brigade-run receipts to MiseLedger, and the next `brigade run` can fetch them back as a compact evidence brief through the same read-only `miseledger evidence ... --source brigade --json` path. The direct command is: +`brigade work verify run --capture ` also runs that new-only export/import automatically after outcome capture, so routine verification closes the receipts-to-MiseLedger loop without a separate command. Indexing is fail-open: a missing `miseledger` binary or import failure prints an explicit status, leaves pending receipts retryable, and does not change the verification exit code. Use the manual export command above for backlog catch-up or fleet targets (`.brigade/repos.toml` with `--fleet --json`). + +The export sends new verify-run and Brigade-run receipts to MiseLedger, and the next `brigade run` can fetch them back as a compact evidence brief through the same read-only `miseledger evidence ... --source brigade --json` path. The direct command is: ```bash brigade work import context --from-miseledger "auth receipts" --target . @@ -1051,7 +1053,7 @@ The brief contains only bounded evidence lines: run id, status, code-graph delta MiseLedger evidence is fail-open. Missing `miseledger`, a nonzero exit, timeout, malformed JSON, or zero usable results does not block `brigade run` or `brigade work import context --from-miseledger`. Run prompts proceed without the brief, and the import command reports the absence instead of writing a broken context note. -`--new-only` stores exported `raw.hash` values in `.brigade/work/miseledger-export-cursor.json`, so later runs only write receipt items that have not already been exported. The cursor is an optimization, not the identity boundary. MiseLedger uses content-hash identity for adapter items, so double-importing the same receipt file remains harmless if the cursor is deleted, copied late, or skipped. +`--new-only` stores exported `raw.hash` values in `.brigade/work/miseledger-export-cursor.json`, so later runs only write receipt items that have not already been exported. With `--import`, the cursor advances only after a successful import; failed imports leave receipts pending for the next run. Export-only runs advance the cursor after a successful write. The export is deterministic and idempotent for unchanged receipts: records are sorted newest first, external ids derive from receipt ids, hashes derive from stored receipt digests or deterministic fallbacks, and JSONL rendering uses stable key order. When a receipt carries `digests.signature` and `digests.key_id`, the export includes them at `item.metadata.digest_signature`; unsigned receipts omit that metadata field. Re-importing the same file should update or skip the same MiseLedger adapter items rather than create duplicates. diff --git a/src/brigade/receipts_cmd.py b/src/brigade/receipts_cmd.py index 6bbaced4..f5da0de0 100644 --- a/src/brigade/receipts_cmd.py +++ b/src/brigade/receipts_cmd.py @@ -31,6 +31,7 @@ MISELEDGER_CURSOR_REL = Path(".brigade") / "work" / "miseledger-export-cursor.json" MISELEDGER_EXPORT_RESULT_SCHEMA = "brigade.miseledger_export_result.v1" MISELEDGER_FLEET_EXPORT_RESULT_SCHEMA = "brigade.miseledger_fleet_export_result.v1" +MISELEDGER_INDEX_RESULT_SCHEMA = "brigade.miseledger_index_result.v1" _FLEET_STATUS_PRECEDENCE = ("failed", "exported", "nothing-new", "empty") _CODE_REFERENCE_LIMIT = 100 _COMPACT_CODE_REFERENCE_NODE_LIMIT = 20 @@ -1405,6 +1406,17 @@ def _print_export_result(payload: dict[str, Any]) -> None: print(json.dumps(payload, sort_keys=True, separators=(",", ":"))) +def _advance_miseledger_cursor(target: Path, cursor_hashes: set[str], written_hashes: list[str]) -> int: + if not written_hashes: + return 0 + try: + _write_miseledger_cursor_hashes(target, cursor_hashes | set(written_hashes)) + except OSError as exc: + print(f"error: could not write cursor {_miseledger_cursor_path(target)}: {exc}", file=sys.stderr) + return 1 + return 0 + + def _finalize_repo_cursors( repo_results: list[dict[str, Any]], *, @@ -1423,14 +1435,71 @@ def _finalize_repo_cursors( cursor_hashes = result.get("cursor_hashes") if not isinstance(cursor_hashes, set): cursor_hashes = set() - try: - _write_miseledger_cursor_hashes(target, cursor_hashes | set(written_hashes)) - except OSError as exc: - print(f"error: could not write cursor {_miseledger_cursor_path(target)}: {exc}", file=sys.stderr) - return 1 + exit_code = _advance_miseledger_cursor(target, cursor_hashes, written_hashes) + if exit_code != 0: + return exit_code return 0 +def _index_result_payload(result: dict[str, Any], *, status: str) -> dict[str, Any]: + return { + "schema": MISELEDGER_INDEX_RESULT_SCHEMA, + "status": status, + "candidate_count": int(result["candidate_count"]), + "exported_count": int(result["exported_count"]), + "skipped_count": int(result["skipped_count"]), + "error_count": int(result["error_count"]), + } + + +def index_miseledger_receipts(*, target: Path, quiet: bool = False) -> dict[str, Any]: + """Export and import pending local receipts via the new-only adapter path. + + Fail-open: always returns a status payload and never raises. + """ + result = _repository_export_result(target=target, limit=0, new_only=True) + lines = result["lines"] + candidate_count = int(result["candidate_count"]) + exported_count = int(result["exported_count"]) + skipped_count = int(result["skipped_count"]) + + if candidate_count == 0: + payload = _index_result_payload(result, status="empty") + payload["message"] = "miseledger indexing: no receipts" + return payload + + if not lines: + status = "nothing-new" if skipped_count > 0 else "empty" + payload = _index_result_payload(result, status=status) + payload["message"] = f"miseledger indexing: {status}" + return payload + + output_path = _temporary_miseledger_export_path(target) + exit_code, written_hashes = _write_miseledger_lines_to_path(output_path, lines) + if exit_code != 0: + payload = _index_result_payload(result, status="failed") + payload["message"] = "miseledger indexing: export write failed" + return payload + + attempted, import_failed = _import_miseledger_file(output_path, strict=False, quiet=quiet, failed_on_error=True) + if attempted and import_failed: + payload = _index_result_payload(result, status="failed") + payload["message"] = "miseledger indexing: import failed (receipts pending)" + return payload + + cursor_hashes = result.get("cursor_hashes") + if not isinstance(cursor_hashes, set): + cursor_hashes = set() + if _advance_miseledger_cursor(target, cursor_hashes, written_hashes) != 0: + payload = _index_result_payload(result, status="failed") + payload["message"] = "miseledger indexing: cursor write failed" + return payload + + payload = _index_result_payload(result, status="indexed") + payload["message"] = f"miseledger indexing: indexed {exported_count} receipt(s)" + return payload + + def _write_aggregate_lines(output_path: Path, lines: list[tuple[str, str]]) -> tuple[int, list[str]]: return _write_miseledger_lines_to_path(output_path, lines) @@ -1501,19 +1570,30 @@ def _export_miseledger_fleet( if result_written: written_hashes_by_target[result["target"]] = result_written - cursor_exit = _finalize_repo_cursors( - repo_results, new_only=new_only, written_hashes_by_target=written_hashes_by_target - ) + cursor_exit = 0 + if not import_miseledger: + cursor_exit = _finalize_repo_cursors( + repo_results, new_only=new_only, written_hashes_by_target=written_hashes_by_target + ) if cursor_exit != 0: payload = _fleet_export_payload(repo_results, status="failed") _print_export_result(payload) return 1 import_error_count = 0 + import_failed = False if import_miseledger and aggregate_lines: - attempted, failed = _import_miseledger_file(output_path, strict=True, quiet=True) - if attempted and failed: + attempted, import_failed = _import_miseledger_file(output_path, strict=True, quiet=True) + if attempted and import_failed: import_error_count = 1 + elif not import_failed: + cursor_exit = _finalize_repo_cursors( + repo_results, new_only=new_only, written_hashes_by_target=written_hashes_by_target + ) + if cursor_exit != 0: + payload = _fleet_export_payload(repo_results, status="failed") + _print_export_result(payload) + return 1 status = _fleet_export_status([str(result["status"]) for result in repo_results]) if import_error_count: @@ -1618,19 +1698,6 @@ def export_miseledger( if json_output: json_output_path = Path(out).expanduser() exit_code, written_hashes = _write_miseledger_lines_to_path(json_output_path, lines) - if new_only and written_hashes: - cursor_hashes = result["cursor_hashes"] - if not isinstance(cursor_hashes, set): - cursor_hashes = set() - try: - _write_miseledger_cursor_hashes(target, cursor_hashes | set(written_hashes)) - except OSError as exc: - print(f"error: could not write cursor {_miseledger_cursor_path(target)}: {exc}", file=sys.stderr) - failed_result = dict(result) - failed_result["status"] = "failed" - failed_result["error_count"] = int(failed_result["error_count"]) + 1 - _print_export_result(_single_repo_export_payload(failed_result)) - return 1 if exit_code != 0: failed_result = dict(result) failed_result["status"] = "failed" @@ -1638,11 +1705,42 @@ def export_miseledger( _print_export_result(_single_repo_export_payload(failed_result)) return 1 + import_error_count = 0 if import_miseledger: if lines: attempted, import_failed = _import_miseledger_file(json_output_path, strict=True, quiet=True) if attempted and import_failed: import_error_count = 1 + elif not import_failed and new_only and written_hashes: + cursor_hashes = result["cursor_hashes"] + if not isinstance(cursor_hashes, set): + cursor_hashes = set() + if _advance_miseledger_cursor(target, cursor_hashes, written_hashes) != 0: + failed_result = dict(result) + failed_result["status"] = "failed" + failed_result["error_count"] = int(failed_result["error_count"]) + 1 + _print_export_result(_single_repo_export_payload(failed_result)) + return 1 + elif new_only and written_hashes: + cursor_hashes = result["cursor_hashes"] + if not isinstance(cursor_hashes, set): + cursor_hashes = set() + if _advance_miseledger_cursor(target, cursor_hashes, written_hashes) != 0: + failed_result = dict(result) + failed_result["status"] = "failed" + failed_result["error_count"] = int(failed_result["error_count"]) + 1 + _print_export_result(_single_repo_export_payload(failed_result)) + return 1 + elif new_only and written_hashes: + cursor_hashes = result["cursor_hashes"] + if not isinstance(cursor_hashes, set): + cursor_hashes = set() + if _advance_miseledger_cursor(target, cursor_hashes, written_hashes) != 0: + failed_result = dict(result) + failed_result["status"] = "failed" + failed_result["error_count"] = int(failed_result["error_count"]) + 1 + _print_export_result(_single_repo_export_payload(failed_result)) + return 1 payload = _single_repo_export_payload(result) if import_error_count: @@ -1670,17 +1768,10 @@ def export_miseledger( else: output_path = _temporary_miseledger_export_path(target) if str(out) == "-" else Path(out).expanduser() exit_code, written_hashes = _write_miseledger_lines_to_path(output_path, lines) - if new_only and written_hashes: - cursor_hashes = result["cursor_hashes"] - if not isinstance(cursor_hashes, set): - cursor_hashes = set() - try: - _write_miseledger_cursor_hashes(target, cursor_hashes | set(written_hashes)) - except OSError as exc: - print(f"error: could not write cursor {_miseledger_cursor_path(target)}: {exc}", file=sys.stderr) - return 1 if exit_code != 0: return exit_code + + import_failed = False if import_miseledger and output_path is not None: if lines: attempted, import_failed = _import_miseledger_file(output_path, failed_on_error=True) @@ -1689,6 +1780,13 @@ def export_miseledger( else: print("nothing new; import skipped") return 0 + + if new_only and written_hashes and (not import_miseledger or not import_failed): + cursor_hashes = result["cursor_hashes"] + if not isinstance(cursor_hashes, set): + cursor_hashes = set() + if _advance_miseledger_cursor(target, cursor_hashes, written_hashes) != 0: + return 1 if int(result["error_count"]) > 0: return 1 return 0 diff --git a/src/brigade/templates/skills/brigade-work/SKILL.md b/src/brigade/templates/skills/brigade-work/SKILL.md index 5dd6a096..22ada172 100644 --- a/src/brigade/templates/skills/brigade-work/SKILL.md +++ b/src/brigade/templates/skills/brigade-work/SKILL.md @@ -49,7 +49,7 @@ This is the +1 (passed) or -1 (failed) the learning ratchet scores. Capture on f Do not invent a skill name you are not running just to have something to capture against. ### 4. Export receipts so the next run can reuse them (optional stations) -When GraphTrail and MiseLedger are installed, close the receipts-to-context loop so the next run sees measured evidence, not only a handoff: +When GraphTrail and MiseLedger are installed, close the receipts-to-context loop so the next run sees measured evidence, not only a handoff. `brigade work verify run --capture ` auto-exports and imports pending receipts after capture (fail-open when `miseledger` is absent). For backlog catch-up or fleet repos, run manually: ```bash brigade receipts export miseledger --target . --new-only --import # fail-open if miseledger is absent # next brigade run attaches a capped evidence brief from MiseLedger automatically diff --git a/src/brigade/work_cmd/verification.py b/src/brigade/work_cmd/verification.py index 5ce24b5d..9bc271b8 100644 --- a/src/brigade/work_cmd/verification.py +++ b/src/brigade/work_cmd/verification.py @@ -910,6 +910,27 @@ def verify_plan( return 0 if not blockers else 1 +def _attach_miseledger_indexing( + receipt: dict[str, object], + *, + target: Path, + json_output: bool, +) -> None: + import contextlib + import io + + from .. import receipts_cmd + + if json_output: + sink = io.StringIO() + with contextlib.redirect_stdout(sink), contextlib.redirect_stderr(sink): + indexing = receipts_cmd.index_miseledger_receipts(target=target, quiet=True) + else: + indexing = receipts_cmd.index_miseledger_receipts(target=target, quiet=False) + print(indexing["message"]) + receipt["miseledger_indexing"] = {key: value for key, value in indexing.items() if key != "message"} + + def verify_run( *, target: Path, @@ -976,6 +997,7 @@ def verify_run( run_id=receipt["run_id"], json_output=False, ) + _attach_miseledger_indexing(receipt, target=target, json_output=True) print(json.dumps(receipt, indent=2, sort_keys=True)) return rc print(f"work verify run: {target}") @@ -996,6 +1018,7 @@ def verify_run( run_id=receipt["run_id"], json_output=False, ) + _attach_miseledger_indexing(receipt, target=target, json_output=False) return rc diff --git a/tests/test_receipts_cmd.py b/tests/test_receipts_cmd.py index f30bf6ba..293f56cb 100644 --- a/tests/test_receipts_cmd.py +++ b/tests/test_receipts_cmd.py @@ -550,9 +550,8 @@ def partial_open(self, *args, **kwargs): assert "could not write output" in captured.err assert len(writes) == 2 - first_hash = json.loads(writes[0])["raw"]["hash"] - cursor = json.loads((tmp_path / ".brigade" / "work" / "miseledger-export-cursor.json").read_text()) - assert cursor["raw_hashes"] == [first_hash] + cursor_path = tmp_path / ".brigade" / "work" / "miseledger-export-cursor.json" + assert not cursor_path.exists() def test_receipts_export_miseledger_import_runs_fake_binary_and_prints_summary(tmp_path, monkeypatch, capsys): @@ -1091,6 +1090,32 @@ def test_receipts_export_miseledger_import_failure_is_failed_and_keeps_batch(tmp assert "miseledger import failed" in captured.err assert out_path.is_file() assert len(_jsonl(out_path.read_text())) == 1 + cursor_path = tmp_path / ".brigade" / "work" / "miseledger-export-cursor.json" + assert not cursor_path.exists() + + +def test_receipts_export_miseledger_new_only_import_failure_keeps_cursor_pending(tmp_path, monkeypatch, capsys): + _write_verify_export_receipt( + tmp_path, + "20260708-120000-work-verify-import-retry", + started_at="2026-07-08T12:00:00Z", + ) + marker = tmp_path / "import.json" + _write_fake_miseledger(tmp_path / "bin" / "miseledger", marker, exit_code=7) + monkeypatch.setenv("PATH", str(tmp_path / "bin")) + cursor_path = tmp_path / ".brigade" / "work" / "miseledger-export-cursor.json" + + assert cli.main(["receipts", "export", "miseledger", "--target", str(tmp_path), "--new-only", "--import"]) == 1 + captured = capsys.readouterr() + + assert "miseledger import failed" in captured.err + assert not cursor_path.exists() + + _write_fake_miseledger(tmp_path / "bin" / "miseledger", marker) + assert cli.main(["receipts", "export", "miseledger", "--target", str(tmp_path), "--new-only", "--import"]) == 0 + capsys.readouterr() + cursor = json.loads(cursor_path.read_text()) + assert len(cursor["raw_hashes"]) == 1 def test_receipts_export_miseledger_fleet_uses_configured_repo_outside_home_repos(tmp_path, capsys): @@ -1291,6 +1316,7 @@ def test_receipts_export_miseledger_fleet_preserves_failure_and_continues(tmp_pa str(fleet), "--fleet", "--json", + "--new-only", "--import", ] ) @@ -1402,6 +1428,7 @@ def test_receipts_export_miseledger_fleet_import_failure_does_not_count_as_repo_ assert payload["failed_count"] == 0 assert payload["repos"][0]["status"] == "exported" assert "miseledger import failed" in captured.err + assert not (repo / ".brigade" / "work" / "miseledger-export-cursor.json").exists() def test_receipts_export_miseledger_fleet_new_only_counts_are_idempotent(tmp_path, capsys): diff --git a/tests/test_work_cmd_verification.py b/tests/test_work_cmd_verification.py index e7b26a1c..2a7362fa 100644 --- a/tests/test_work_cmd_verification.py +++ b/tests/test_work_cmd_verification.py @@ -57,12 +57,116 @@ def test_verify_run_capture_records_outcome_in_one_step(tmp_path, capsys): target=tmp_path, commands=["python3 -c \"print('ok')\""], capture="skill-x", capture_kind="skill" ) assert rc == 0 - capsys.readouterr() + captured = capsys.readouterr() + assert "miseledger indexing:" in captured.out records = outcome_cmd.load_records(tmp_path) assert len(records) == 1 assert records[0].artifact_id == "skill-x" and records[0].signal_value == 1 +def _write_fake_miseledger_for_verify(path, marker, *, exit_code=0): + path.parent.mkdir(parents=True, exist_ok=True) + path.write_text( + f"""#!{sys.executable} +import json +import pathlib +import sys + +assert sys.argv[1:3] == ["import", "adapter"] +export_path = pathlib.Path(sys.argv[3]) +assert sys.argv[4:] == ["--source", "brigade", "--json"] +marker = pathlib.Path({str(marker)!r}) +marker.write_text(export_path.read_text()) +print(json.dumps({{"inserted_items": 1, "already_known": 0}})) +sys.exit({exit_code}) +""" + ) + path.chmod(0o755) + + +def test_verify_run_capture_auto_indexes_miseledger_receipts(tmp_path, monkeypatch, capsys): + _init_git_repo(tmp_path) + marker = tmp_path / "imported.jsonl" + _write_fake_miseledger_for_verify(tmp_path / "bin" / "miseledger", marker) + monkeypatch.setenv("PATH", f"{tmp_path / 'bin'}{os.pathsep}{os.environ['PATH']}") + + rc = work_cmd.verify_run( + target=tmp_path, + commands=["python3 -c \"print('ok')\""], + capture="skill-x", + capture_kind="skill", + ) + captured = capsys.readouterr() + + assert rc == 0 + assert "miseledger indexing: indexed" in captured.out + assert marker.is_file() + cursor_path = tmp_path / ".brigade" / "work" / "miseledger-export-cursor.json" + assert cursor_path.is_file() + + +def test_verify_run_capture_json_includes_miseledger_indexing_status(tmp_path, monkeypatch, capsys): + _init_git_repo(tmp_path) + marker = tmp_path / "imported.jsonl" + _write_fake_miseledger_for_verify(tmp_path / "bin" / "miseledger", marker) + monkeypatch.setenv("PATH", f"{tmp_path / 'bin'}{os.pathsep}{os.environ['PATH']}") + + rc = work_cmd.verify_run( + target=tmp_path, + commands=["python3 -c \"print('ok')\""], + capture="skill-x", + json_output=True, + ) + captured = capsys.readouterr() + + assert rc == 0 + payload = json.loads(captured.out) + indexing = payload["miseledger_indexing"] + assert indexing["schema"] == "brigade.miseledger_index_result.v1" + assert indexing["status"] == "indexed" + assert indexing["exported_count"] >= 1 + assert captured.err == "" + + +def test_verify_run_capture_miseledger_failure_is_fail_open(tmp_path, monkeypatch, capsys): + _init_git_repo(tmp_path) + empty_path = tmp_path / "empty-path" + empty_path.mkdir() + monkeypatch.setenv("PATH", f"{empty_path}{os.pathsep}{os.environ['PATH']}") + + rc = work_cmd.verify_run( + target=tmp_path, + commands=["python3 -c \"print('ok')\""], + capture="skill-x", + ) + captured = capsys.readouterr() + + assert rc == 0 + assert "miseledger indexing: import failed" in captured.out + cursor_path = tmp_path / ".brigade" / "work" / "miseledger-export-cursor.json" + assert not cursor_path.exists() + + +def test_verify_run_capture_json_miseledger_failure_preserves_exit_code(tmp_path, monkeypatch, capsys): + _init_git_repo(tmp_path) + empty_path = tmp_path / "empty-path" + empty_path.mkdir() + monkeypatch.setenv("PATH", f"{empty_path}{os.pathsep}{os.environ['PATH']}") + + rc = work_cmd.verify_run( + target=tmp_path, + commands=['python3 -c "raise SystemExit(3)"'], + capture="skill-x", + json_output=True, + ) + captured = capsys.readouterr() + + assert rc == 3 + payload = json.loads(captured.out) + assert payload["miseledger_indexing"]["status"] == "failed" + assert captured.err == "" + + def test_verify_run_stamps_valid_claude_session_fingerprint(tmp_path, capsys, monkeypatch): from brigade.claude_hooks.runtime import _session_fingerprint From 311597acb654057dfdbcbeb2b4857a84146f37bc Mon Sep 17 00:00:00 2001 From: Solomon Neas Date: Sun, 26 Jul 2026 17:03:47 -0400 Subject: [PATCH 2/2] fix(ci): preserve manual receipt import coverage Co-Authored-By: Codex --- scripts/windows-native-acceptance.ps1 | 4 ++++ tests/test_ci_workflow.py | 3 +++ 2 files changed, 7 insertions(+) diff --git a/scripts/windows-native-acceptance.ps1 b/scripts/windows-native-acceptance.ps1 index 8e23976f..fa7c98b2 100644 --- a/scripts/windows-native-acceptance.ps1 +++ b/scripts/windows-native-acceptance.ps1 @@ -691,6 +691,10 @@ def call_greet(): & brigade work verify run --target $workRepo --command "python $verifyScriptName" --capture brigade-work if ($LASTEXITCODE -ne 0) { throw "work verify run failed" } + Write-Step "brigade work verify run (manual export)" + & brigade work verify run --target $workRepo --command "python $verifyScriptName" --no-reuse + if ($LASTEXITCODE -ne 0) { throw "manual export verify run failed" } + $exportPath = Join-Path $acceptRoot "receipts.jsonl" Write-Step "receipts export miseledger" & brigade receipts export miseledger --target $workRepo --out $exportPath --new-only diff --git a/tests/test_ci_workflow.py b/tests/test_ci_workflow.py index cdc7672f..b17fa3c8 100644 --- a/tests/test_ci_workflow.py +++ b/tests/test_ci_workflow.py @@ -295,6 +295,9 @@ def test_ci_windows_native_acceptance_script_covers_required_flow(): assert 'if ($callersOutput -notmatch "call_greet")' in text assert "brigadewinacceptance" in text assert "brigade work verify run" in text + assert "brigade work verify run (manual export)" in text + assert "--capture brigade-work" in text + assert "--no-reuse" in text assert "brigade receipts export miseledger" in text assert "import adapter" in text assert "$importPayload.inserted_items" in text