Spaces:
Running
Running
Sync GitHub source 230f393a334d767a9d19960ee19ee03ea2e4e770
Browse filesCanonical source: https://github.com/noqt/Lumi-Eggcracker/commit/230f393a334d767a9d19960ee19ee03ea2e4e770
- HUGGINGFACE_MANIFEST.json +13 -13
- HUGGINGFACE_SYNC.json +3 -3
- README.md +20 -1
- docs/DETECTION_RECEIPT_EXPORT.md +19 -6
- index.html +2 -2
- src/lumi_eggcracker/brokered/compare_detection_receipts.py +6 -2
- src/lumi_eggcracker/cli.py +9 -0
- tests/test_cli.py +123 -0
HUGGINGFACE_MANIFEST.json
CHANGED
|
@@ -37,8 +37,8 @@
|
|
| 37 |
"size": 10537
|
| 38 |
},
|
| 39 |
"README.md": {
|
| 40 |
-
"sha256": "
|
| 41 |
-
"size":
|
| 42 |
},
|
| 43 |
"RELEASE_NOTES.md": {
|
| 44 |
"sha256": "64d330d0f297b15720fc65e6347ececc1b99a917a597d405ebfac04baf3222ea",
|
|
@@ -77,8 +77,8 @@
|
|
| 77 |
"size": 14858
|
| 78 |
},
|
| 79 |
"docs/DETECTION_RECEIPT_EXPORT.md": {
|
| 80 |
-
"sha256": "
|
| 81 |
-
"size":
|
| 82 |
},
|
| 83 |
"docs/HUGGINGFACE_DISTRIBUTION.md": {
|
| 84 |
"sha256": "b68000aa30407ef1837bc4ca7e6eabe50645911a76250b14570847c507b916e1",
|
|
@@ -189,7 +189,7 @@
|
|
| 189 |
"size": 4101
|
| 190 |
},
|
| 191 |
"index.html": {
|
| 192 |
-
"sha256": "
|
| 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": "
|
| 429 |
-
"size":
|
| 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": "
|
| 441 |
-
"size":
|
| 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": "
|
| 593 |
-
"size":
|
| 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": "
|
| 831 |
-
"source_url": "https://github.com/noqt/Lumi-Eggcracker/commit/
|
| 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": "
|
| 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": "
|
| 69 |
-
"source_url": "https://github.com/noqt/Lumi-Eggcracker/commit/
|
| 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 |
-
> [`
|
| 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 |
-
|
| 128 |
-
|
|
|
|
|
|
|
|
|
|
| 129 |
|
| 130 |
```sh
|
| 131 |
-
|
| 132 |
--before ./before-export.json \
|
| 133 |
--after ./after-export.json
|
| 134 |
```
|
| 135 |
|
| 136 |
-
|
| 137 |
-
|
|
|
|
|
|
|
| 138 |
|
| 139 |
```sh
|
| 140 |
-
|
| 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="
|
| 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 ·
|
| 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(
|
|
|
|
|
|
|
|
|
|
|
|
|
| 690 |
parser = _QuietArgumentParser(
|
| 691 |
-
prog=
|
| 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,
|