noqt commited on
Commit
98dbd80
·
verified ·
1 Parent(s): 39e90d0

Sync GitHub source 230f393a334d767a9d19960ee19ee03ea2e4e770

Browse files

Canonical source: https://github.com/noqt/Lumi-Eggcracker/commit/230f393a334d767a9d19960ee19ee03ea2e4e770

HUGGINGFACE_MANIFEST.json CHANGED
@@ -37,8 +37,8 @@
37
  "size": 10537
38
  },
39
  "README.md": {
40
- "sha256": "590c33938d8109de2320d46b2aa0d4c981ff53f7b57877f78e666dff49ea8a83",
41
- "size": 29143
42
  },
43
  "RELEASE_NOTES.md": {
44
  "sha256": "64d330d0f297b15720fc65e6347ececc1b99a917a597d405ebfac04baf3222ea",
@@ -77,8 +77,8 @@
77
  "size": 14858
78
  },
79
  "docs/DETECTION_RECEIPT_EXPORT.md": {
80
- "sha256": "a97307fd0648918f84d56c5875e2edaa482689272032cdaad2b377b5335eebe3",
81
- "size": 9096
82
  },
83
  "docs/HUGGINGFACE_DISTRIBUTION.md": {
84
  "sha256": "b68000aa30407ef1837bc4ca7e6eabe50645911a76250b14570847c507b916e1",
@@ -189,7 +189,7 @@
189
  "size": 4101
190
  },
191
  "index.html": {
192
- "sha256": "75d7d87b0f617cf5e50030fc939a7ad0d2b68ee0c9f38f02bf3517b636bd0f87",
193
  "size": 22790
194
  },
195
  "integrations/prometheus/eggcracker.rules.yml": {
@@ -425,8 +425,8 @@
425
  "size": 549
426
  },
427
  "src/lumi_eggcracker/brokered/compare_detection_receipts.py": {
428
- "sha256": "f31d84d46d5587776fb2fdef83ed0da6809ef59cb3d7c4441df092970c8e8f0e",
429
- "size": 24272
430
  },
431
  "src/lumi_eggcracker/brokered/linux_ipc.py": {
432
  "sha256": "6c26fd8b79f013bbbec931aa30f7c0b6c1a7de70368fcaf6121de1db39f6cff2",
@@ -437,8 +437,8 @@
437
  "size": 55591
438
  },
439
  "src/lumi_eggcracker/cli.py": {
440
- "sha256": "d350bf345d5e05fd56970b152252e8aaefa6256654c501a9c276e5c55b7ce8ed",
441
- "size": 10117
442
  },
443
  "src/lumi_eggcracker/client.py": {
444
  "sha256": "0ac5dd4fdf564b2b7fd9e74de66d9ff083ac153864867b69fc078da9d66bbc66",
@@ -589,8 +589,8 @@
589
  "size": 7176
590
  },
591
  "tests/test_cli.py": {
592
- "sha256": "50b1fed650b68c6648cc9934a24b3ea5e203a04dcabce11bb417ada8238932aa",
593
- "size": 6908
594
  },
595
  "tests/test_collect_supported_path_receipt.py": {
596
  "sha256": "79ae57bf3d78cac5fbe1b9d7db60462328f70cb17214bc75a5b5e7eff3ea0eef",
@@ -827,6 +827,6 @@
827
  },
828
  "schema": "noqt.huggingface_manifest.v1",
829
  "source_repository": "https://github.com/noqt/Lumi-Eggcracker",
830
- "source_revision": "ad43fa130460ba5e2743524a12e4907aa5cc3731",
831
- "source_url": "https://github.com/noqt/Lumi-Eggcracker/commit/ad43fa130460ba5e2743524a12e4907aa5cc3731"
832
  }
 
37
  "size": 10537
38
  },
39
  "README.md": {
40
+ "sha256": "50186f966a36057d967a5d65b05d880452f5dd072de7c02d9d6f641e9df37a6e",
41
+ "size": 29887
42
  },
43
  "RELEASE_NOTES.md": {
44
  "sha256": "64d330d0f297b15720fc65e6347ececc1b99a917a597d405ebfac04baf3222ea",
 
77
  "size": 14858
78
  },
79
  "docs/DETECTION_RECEIPT_EXPORT.md": {
80
+ "sha256": "19917c226177fb93ddf91d0b0c4a4ff7e6b9af792a70837617d1bf3e69f959da",
81
+ "size": 9564
82
  },
83
  "docs/HUGGINGFACE_DISTRIBUTION.md": {
84
  "sha256": "b68000aa30407ef1837bc4ca7e6eabe50645911a76250b14570847c507b916e1",
 
189
  "size": 4101
190
  },
191
  "index.html": {
192
+ "sha256": "01c0357580b6da52284b953952bd825b24ba71c388fca1a0bd0ece70808fa431",
193
  "size": 22790
194
  },
195
  "integrations/prometheus/eggcracker.rules.yml": {
 
425
  "size": 549
426
  },
427
  "src/lumi_eggcracker/brokered/compare_detection_receipts.py": {
428
+ "sha256": "c9952a0226354b1aa76b84c5dcc5dac30a7b782869435254c23caf1018fa344d",
429
+ "size": 24308
430
  },
431
  "src/lumi_eggcracker/brokered/linux_ipc.py": {
432
  "sha256": "6c26fd8b79f013bbbec931aa30f7c0b6c1a7de70368fcaf6121de1db39f6cff2",
 
437
  "size": 55591
438
  },
439
  "src/lumi_eggcracker/cli.py": {
440
+ "sha256": "ca7675de60a8a58d303f924f4a0f2f3a89346f29cf02cd3dba52bd8112c722d4",
441
+ "size": 10518
442
  },
443
  "src/lumi_eggcracker/client.py": {
444
  "sha256": "0ac5dd4fdf564b2b7fd9e74de66d9ff083ac153864867b69fc078da9d66bbc66",
 
589
  "size": 7176
590
  },
591
  "tests/test_cli.py": {
592
+ "sha256": "b03872e0b0ebffaeb964eceb85320115b76a6c5ece5b0bce64d79a9a575b7db1",
593
+ "size": 12128
594
  },
595
  "tests/test_collect_supported_path_receipt.py": {
596
  "sha256": "79ae57bf3d78cac5fbe1b9d7db60462328f70cb17214bc75a5b5e7eff3ea0eef",
 
827
  },
828
  "schema": "noqt.huggingface_manifest.v1",
829
  "source_repository": "https://github.com/noqt/Lumi-Eggcracker",
830
+ "source_revision": "230f393a334d767a9d19960ee19ee03ea2e4e770",
831
+ "source_url": "https://github.com/noqt/Lumi-Eggcracker/commit/230f393a334d767a9d19960ee19ee03ea2e4e770"
832
  }
HUGGINGFACE_SYNC.json CHANGED
@@ -1,6 +1,6 @@
1
  {
2
  "manifest": "HUGGINGFACE_MANIFEST.json",
3
- "manifest_sha256": "a36c3105950b5fadc687451666db75490b12e1dad2fdf5fb9ab5e3a1be796ffd",
4
  "mirrored_files": 191,
5
  "mode": "DERIVED_READ_ONLY_MIRROR",
6
  "release_reference": {
@@ -65,7 +65,7 @@
65
  },
66
  "schema": "noqt.huggingface_sync.v1",
67
  "source_repository": "https://github.com/noqt/Lumi-Eggcracker",
68
- "source_revision": "ad43fa130460ba5e2743524a12e4907aa5cc3731",
69
- "source_url": "https://github.com/noqt/Lumi-Eggcracker/commit/ad43fa130460ba5e2743524a12e4907aa5cc3731",
70
  "target": "https://huggingface.co/spaces/noqt/eggcracker"
71
  }
 
1
  {
2
  "manifest": "HUGGINGFACE_MANIFEST.json",
3
+ "manifest_sha256": "c3daf4ec06f808bc036393a7f1ce77892da6d8d05ed1bf542314761e31b181c3",
4
  "mirrored_files": 191,
5
  "mode": "DERIVED_READ_ONLY_MIRROR",
6
  "release_reference": {
 
65
  },
66
  "schema": "noqt.huggingface_sync.v1",
67
  "source_repository": "https://github.com/noqt/Lumi-Eggcracker",
68
+ "source_revision": "230f393a334d767a9d19960ee19ee03ea2e4e770",
69
+ "source_url": "https://github.com/noqt/Lumi-Eggcracker/commit/230f393a334d767a9d19960ee19ee03ea2e4e770",
70
  "target": "https://huggingface.co/spaces/noqt/eggcracker"
71
  }
README.md CHANGED
@@ -11,7 +11,7 @@ short_description: Native Linux kill switch for supported local-AI workloads
11
  ---
12
 
13
  > Official noqt Hugging Face distribution, generated from GitHub commit
14
- > [`ad43fa130460`](https://github.com/noqt/Lumi-Eggcracker/commit/ad43fa130460ba5e2743524a12e4907aa5cc3731). This Space distributes the source for
15
  > discovery and cloning; Eggcracker itself runs on a supported native Linux
16
  > host, not on Hugging Face. A snapshot is called current only after the
17
  > release-linked sync job verifies its returned Hub commit and full manifest.
@@ -499,6 +499,25 @@ check is structural validation only, not authentication, privacy assurance,
499
  completeness, containment or incident truth. It does not certify that a bundle
500
  is safe to share; review the file yourself before attaching it.
501
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
502
  ## Install and remove
503
 
504
  The first-kill command is the recommended campaign path. A release is complete
 
11
  ---
12
 
13
  > Official noqt Hugging Face distribution, generated from GitHub commit
14
+ > [`230f393a334d`](https://github.com/noqt/Lumi-Eggcracker/commit/230f393a334d767a9d19960ee19ee03ea2e4e770). This Space distributes the source for
15
  > discovery and cloning; Eggcracker itself runs on a supported native Linux
16
  > host, not on Hugging Face. A snapshot is called current only after the
17
  > release-linked sync job verifies its returned Hub commit and full manifest.
 
499
  completeness, containment or incident truth. It does not certify that a bundle
500
  is safe to share; review the file yourself before attaching it.
501
 
502
+ ## Compare detection receipt exports
503
+
504
+ The published v1.0.10 package does not include this installed subcommand. After
505
+ building and installing a package or wheel from source that contains this
506
+ change, compare two redacted detection receipt exports with the offline CLI
507
+ command:
508
+
509
+ ```sh
510
+ eggcracker compare-detection-receipts \
511
+ --before ./before-export.json \
512
+ --after ./after-export.json
513
+ ```
514
+
515
+ Use `--output ./comparison.json` to write the bounded JSON report to a new
516
+ file. The command refuses to replace an existing output. It compares recorded
517
+ exports only; it does not authenticate them or verify a live workload. See
518
+ [the export and comparison guide](docs/DETECTION_RECEIPT_EXPORT.md) for input,
519
+ output, redaction and filesystem behavior.
520
+
521
  ## Install and remove
522
 
523
  The first-kill command is the recommended campaign path. A release is complete
docs/DETECTION_RECEIPT_EXPORT.md CHANGED
@@ -124,25 +124,38 @@ and filesystem operations.
124
 
125
  ## Comparing two redacted exports
126
 
127
- The offline comparator validates two existing exports and reports only the
128
- same bounded recorded-context comparison to stdout:
 
 
 
129
 
130
  ```sh
131
- python src/lumi_eggcracker/brokered/compare_detection_receipts.py \
132
  --before ./before-export.json \
133
  --after ./after-export.json
134
  ```
135
 
136
- The optional `--output FILE` writes those exact UTF-8 JSON bytes, including the
137
- final newline, to a new comparison file instead of writing JSON to stdout:
 
 
138
 
139
  ```sh
140
- python src/lumi_eggcracker/brokered/compare_detection_receipts.py \
141
  --before ./before-export.json \
142
  --after ./after-export.json \
143
  --output ./comparison.json
144
  ```
145
 
 
 
 
 
 
 
 
 
146
  On successful file output, stdout remains empty and the comparator prints only
147
  `comparison written` to stderr. With no `--output`, stdout behavior remains the
148
  original comparison JSON. Both exports are fully validated and the existing
 
124
 
125
  ## Comparing two redacted exports
126
 
127
+ A package or wheel built from source that contains this change exposes the
128
+ comparator through the installed `eggcracker` command as an additive, offline
129
+ subcommand. The published v1.0.10 package does not include this subcommand. It
130
+ validates exactly two existing exports and reports only the bounded
131
+ recorded-context comparison to stdout:
132
 
133
  ```sh
134
+ eggcracker compare-detection-receipts \
135
  --before ./before-export.json \
136
  --after ./after-export.json
137
  ```
138
 
139
+ Run `eggcracker compare-detection-receipts --help` to see the installed
140
+ command's options. The optional `--output FILE` writes those exact UTF-8 JSON
141
+ bytes, including the final newline, to a new comparison file instead of writing
142
+ JSON to stdout:
143
 
144
  ```sh
145
+ eggcracker compare-detection-receipts \
146
  --before ./before-export.json \
147
  --after ./after-export.json \
148
  --output ./comparison.json
149
  ```
150
 
151
+ The source checkout also retains direct invocation of the comparator module:
152
+
153
+ ```sh
154
+ python src/lumi_eggcracker/brokered/compare_detection_receipts.py \
155
+ --before ./before-export.json \
156
+ --after ./after-export.json
157
+ ```
158
+
159
  On successful file output, stdout remains empty and the comparator prints only
160
  `comparison written` to stderr. With no `--output`, stdout behavior remains the
161
  original comparison JSON. Both exports are fully validated and the existing
index.html CHANGED
@@ -4,7 +4,7 @@
4
  <meta charset="utf-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1">
6
  <meta name="description" content="Lumi Eggcracker is noqt's native Linux kill switch for unapproved supported local-AI workloads. Clone the complete source from Hugging Face.">
7
- <meta name="noqt-source-revision" content="ad43fa130460ba5e2743524a12e4907aa5cc3731">
8
  <meta name="theme-color" content="#090a0c">
9
  <title>Lumi Eggcracker — Native Linux AI kill switch</title>
10
  <style>
@@ -649,7 +649,7 @@ sudo /usr/bin/python3 -I -S scripts/first_kill.py \
649
  <section>
650
  <div class="shell source-bar">
651
  <div>
652
- <div class="source-id">SOURCE COMMIT · ad43fa130460</div>
653
  <h2>Inspect every line.</h2>
654
  <p>Complete source, tests, qualification documents and reproducible proof tooling are distributed in this repository.</p>
655
  </div>
 
4
  <meta charset="utf-8">
5
  <meta name="viewport" content="width=device-width, initial-scale=1">
6
  <meta name="description" content="Lumi Eggcracker is noqt's native Linux kill switch for unapproved supported local-AI workloads. Clone the complete source from Hugging Face.">
7
+ <meta name="noqt-source-revision" content="230f393a334d767a9d19960ee19ee03ea2e4e770">
8
  <meta name="theme-color" content="#090a0c">
9
  <title>Lumi Eggcracker — Native Linux AI kill switch</title>
10
  <style>
 
649
  <section>
650
  <div class="shell source-bar">
651
  <div>
652
+ <div class="source-id">SOURCE COMMIT · 230f393a334d</div>
653
  <h2>Inspect every line.</h2>
654
  <p>Complete source, tests, qualification documents and reproducible proof tooling are distributed in this repository.</p>
655
  </div>
src/lumi_eggcracker/brokered/compare_detection_receipts.py CHANGED
@@ -686,9 +686,13 @@ class _QuietArgumentParser(argparse.ArgumentParser):
686
  self.exit(2, f"{self.prog}: invalid command line\n")
687
 
688
 
689
- def main(argv: list[str] | None = None) -> int:
 
 
 
 
690
  parser = _QuietArgumentParser(
691
- prog="compare_detection_receipts.py",
692
  description="Compare two explicit redacted detection receipt exports.",
693
  )
694
  parser.add_argument("--before", required=True, metavar="FILE", help="first export file")
 
686
  self.exit(2, f"{self.prog}: invalid command line\n")
687
 
688
 
689
+ def main(
690
+ argv: list[str] | None = None,
691
+ *,
692
+ prog: str = "compare_detection_receipts.py",
693
+ ) -> int:
694
  parser = _QuietArgumentParser(
695
+ prog=prog,
696
  description="Compare two explicit redacted detection receipt exports.",
697
  )
698
  parser.add_argument("--before", required=True, metavar="FILE", help="first export file")
src/lumi_eggcracker/cli.py CHANGED
@@ -9,6 +9,7 @@ import sys
9
  from pathlib import Path
10
 
11
  from . import __version__
 
12
  from .client import request
13
  from .gate import main as gate_main
14
  from .jsonio import JsonInputError, write_new_json
@@ -111,6 +112,10 @@ def _parser() -> argparse.ArgumentParser:
111
  help="offline structural review of a support bundle",
112
  )
113
  validate_support.add_argument("FILE", type=Path)
 
 
 
 
114
  commands.add_parser("version", help="print the public version")
115
  return parser
116
 
@@ -121,6 +126,10 @@ def _command(argv: list[str]) -> list[str]:
121
 
122
  def main(argv: list[str] | None = None) -> int:
123
  values = sys.argv[1:] if argv is None else argv
 
 
 
 
124
  if values[:1] == ["validate-support-bundle"]:
125
  if values[1:] == ["--help"]:
126
  _parser().parse_args(values)
 
9
  from pathlib import Path
10
 
11
  from . import __version__
12
+ from .brokered.compare_detection_receipts import main as compare_detection_receipts_main
13
  from .client import request
14
  from .gate import main as gate_main
15
  from .jsonio import JsonInputError, write_new_json
 
112
  help="offline structural review of a support bundle",
113
  )
114
  validate_support.add_argument("FILE", type=Path)
115
+ commands.add_parser(
116
+ "compare-detection-receipts",
117
+ help="compare two redacted detection receipt exports",
118
+ )
119
  commands.add_parser("version", help="print the public version")
120
  return parser
121
 
 
126
 
127
  def main(argv: list[str] | None = None) -> int:
128
  values = sys.argv[1:] if argv is None else argv
129
+ if values[:1] == ["compare-detection-receipts"]:
130
+ return compare_detection_receipts_main(
131
+ values[1:], prog="eggcracker compare-detection-receipts"
132
+ )
133
  if values[:1] == ["validate-support-bundle"]:
134
  if values[1:] == ["--help"]:
135
  _parser().parse_args(values)
tests/test_cli.py CHANGED
@@ -1,8 +1,11 @@
1
  from __future__ import annotations
2
 
3
  import io
 
 
4
  import unittest
5
  from contextlib import redirect_stderr, redirect_stdout
 
6
  from unittest.mock import patch
7
 
8
  from lumi_eggcracker.cli import main
@@ -30,6 +33,7 @@ class CliTests(unittest.TestCase):
30
  "doctor",
31
  "support-bundle",
32
  "validate-support-bundle",
 
33
  "version",
34
  ):
35
  self.assertIn(command, help_text)
@@ -37,6 +41,125 @@ class CliTests(unittest.TestCase):
37
  self.assertNotIn("_supervisor", help_text)
38
  self.assertNotIn("network" + "-deny", help_text)
39
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
40
  def test_validate_support_bundle_dispatches_before_connected_commands(self) -> None:
41
  with (
42
  patch("lumi_eggcracker.cli.validate_support_bundle_main", return_value=0) as validate,
 
1
  from __future__ import annotations
2
 
3
  import io
4
+ import json
5
+ import tempfile
6
  import unittest
7
  from contextlib import redirect_stderr, redirect_stdout
8
+ from pathlib import Path
9
  from unittest.mock import patch
10
 
11
  from lumi_eggcracker.cli import main
 
33
  "doctor",
34
  "support-bundle",
35
  "validate-support-bundle",
36
+ "compare-detection-receipts",
37
  "version",
38
  ):
39
  self.assertIn(command, help_text)
 
41
  self.assertNotIn("_supervisor", help_text)
42
  self.assertNotIn("network" + "-deny", help_text)
43
 
44
+ def test_compare_detection_receipts_help_uses_installed_command(self) -> None:
45
+ output = io.StringIO()
46
+ with redirect_stdout(output), self.assertRaises(SystemExit) as raised:
47
+ main(["compare-detection-receipts", "--help"])
48
+ self.assertEqual(0, raised.exception.code)
49
+ self.assertIn("usage: eggcracker compare-detection-receipts", output.getvalue())
50
+ self.assertIn("--before FILE", output.getvalue())
51
+ self.assertIn("--after FILE", output.getvalue())
52
+
53
+ def test_compare_detection_receipts_forwards_arguments_before_connected_commands(
54
+ self,
55
+ ) -> None:
56
+ forwarded = [
57
+ "--before",
58
+ "before-export.json",
59
+ "--after",
60
+ "after-export.json",
61
+ "--output",
62
+ "comparison.json",
63
+ ]
64
+ with (
65
+ patch(
66
+ "lumi_eggcracker.cli.compare_detection_receipts_main", return_value=0
67
+ ) as compare,
68
+ patch("lumi_eggcracker.cli.request") as request,
69
+ patch("lumi_eggcracker.cli.supervisor_main") as supervisor,
70
+ patch("lumi_eggcracker.cli.gate_main") as gate,
71
+ patch("lumi_eggcracker.cli.watchdog_main") as watchdog,
72
+ ):
73
+ self.assertEqual(0, main(["compare-detection-receipts", *forwarded]))
74
+ compare.assert_called_once_with(
75
+ forwarded, prog="eggcracker compare-detection-receipts"
76
+ )
77
+ request.assert_not_called()
78
+ supervisor.assert_not_called()
79
+ gate.assert_not_called()
80
+ watchdog.assert_not_called()
81
+
82
+ def test_compare_detection_receipts_success_and_existing_output_refusal(self) -> None:
83
+ def synthetic_export(event_id: str) -> dict[str, object]:
84
+ return {
85
+ "export_schema": "lumi-eggcracker.redacted-detection-receipt-export.v1",
86
+ "source_sha256": "f" * 64,
87
+ "authentication": "NOT_AUTHENTICATED",
88
+ "live_verification": "NOT_PERFORMED",
89
+ "receipt": {
90
+ "schema_version": "lumi-eggcracker.detection-receipt.v2",
91
+ "event_id": event_id,
92
+ "source_commit": "a" * 40,
93
+ "version": "1.0.10",
94
+ "catalogue_sha256": "b" * 64,
95
+ "receipt_written_utc": "2026-10-02T00:00:00Z",
96
+ "detector": {
97
+ "profile": "content.gguf-llama",
98
+ "detection_path": "CONTENT",
99
+ "catalogue_schema": "lumi-eggcracker.detectors.v3",
100
+ },
101
+ "trigger": {"kind": "UNAPPROVED_AI_MATCH"},
102
+ "recorded_result": "CONTAINMENT_FAILED",
103
+ },
104
+ }
105
+
106
+ with tempfile.TemporaryDirectory(prefix="cli-receipt-comparison-") as directory:
107
+ root = Path(directory)
108
+ before = root / "before.json"
109
+ after = root / "after.json"
110
+ before.write_text(json.dumps(synthetic_export("0" * 24)), encoding="utf-8")
111
+ after.write_text(json.dumps(synthetic_export("1" * 24)), encoding="utf-8")
112
+ output = root / "existing-comparison.json"
113
+ original_output = b"preserve this synthetic report\n"
114
+ output.write_bytes(original_output)
115
+
116
+ stdout = io.StringIO()
117
+ stderr = io.StringIO()
118
+ with (
119
+ patch("lumi_eggcracker.cli.request") as request,
120
+ patch("lumi_eggcracker.cli.supervisor_main") as supervisor,
121
+ redirect_stdout(stdout),
122
+ redirect_stderr(stderr),
123
+ ):
124
+ self.assertEqual(
125
+ 0,
126
+ main(
127
+ [
128
+ "compare-detection-receipts",
129
+ "--before",
130
+ str(before),
131
+ "--after",
132
+ str(after),
133
+ ]
134
+ ),
135
+ )
136
+ comparison = json.loads(stdout.getvalue())
137
+ self.assertEqual("RECORDED", comparison["comparison_status"])
138
+ self.assertEqual("", stderr.getvalue())
139
+ stdout.seek(0)
140
+ stdout.truncate(0)
141
+ stderr.seek(0)
142
+ stderr.truncate(0)
143
+ self.assertEqual(
144
+ 1,
145
+ main(
146
+ [
147
+ "compare-detection-receipts",
148
+ "--before",
149
+ str(before),
150
+ "--after",
151
+ str(after),
152
+ "--output",
153
+ str(output),
154
+ ]
155
+ ),
156
+ )
157
+ self.assertEqual("", stdout.getvalue())
158
+ self.assertIn("OUTPUT_ALREADY_EXISTS", stderr.getvalue())
159
+ request.assert_not_called()
160
+ supervisor.assert_not_called()
161
+ self.assertEqual(original_output, output.read_bytes())
162
+
163
  def test_validate_support_bundle_dispatches_before_connected_commands(self) -> None:
164
  with (
165
  patch("lumi_eggcracker.cli.validate_support_bundle_main", return_value=0) as validate,