From 1df93a9fe6cde7bfef65ca5fc4520275e79b4e36 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 1 May 2026 00:15:08 +0000 Subject: [PATCH 1/2] Initial plan From cf35dab0cdf479ed37022068ee0207e8f2299feb Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 1 May 2026 00:18:00 +0000 Subject: [PATCH 2/2] feat: add NLBoot release archive inspection (sourceosctl release inspect-archive) Agent-Logs-Url: https://github.com/SourceOS-Linux/sourceos-devtools/sessions/869cea59-b85d-44d6-997e-233335b9e3ae Co-authored-by: mdheller <21163552+mdheller@users.noreply.github.com> --- README.md | 2 + fixtures/nlboot_release_invalid/Cargo.lock | 6 +++ fixtures/nlboot_release_invalid/nlboot-client | 1 + fixtures/nlboot_release_valid/Cargo.lock | 7 ++++ .../nlboot_release_valid/cargo-metadata.json | 10 +++++ fixtures/nlboot_release_valid/nlboot-client | 1 + .../release-manifest.json | 17 ++++++++ fixtures/nlboot_release_valid/sbom.spdx.json | 8 ++++ sourceosctl/cli.py | 9 +++++ sourceosctl/commands/release.py | 40 +++++++++++++++++++ tests/test_cli.py | 35 ++++++++++++++++ 11 files changed, 136 insertions(+) create mode 100644 fixtures/nlboot_release_invalid/Cargo.lock create mode 100644 fixtures/nlboot_release_invalid/nlboot-client create mode 100644 fixtures/nlboot_release_valid/Cargo.lock create mode 100644 fixtures/nlboot_release_valid/cargo-metadata.json create mode 100644 fixtures/nlboot_release_valid/nlboot-client create mode 100644 fixtures/nlboot_release_valid/release-manifest.json create mode 100644 fixtures/nlboot_release_valid/sbom.spdx.json diff --git a/README.md b/README.md index 36a841e..364c0d2 100644 --- a/README.md +++ b/README.md @@ -54,6 +54,7 @@ sourceosctl [--version] [] [options] | `sourceosctl nlboot evidence inspect --validate ` | Inspect and validate a NLBoot evidence file against its bundled schema (read-only) | | `sourceosctl nlboot evidence validate ` | Validate a NLBoot evidence file against its bundled JSON Schema (read-only) | | `sourceosctl release inspect ` | Inspect a release artifact JSON file (read-only) | +| `sourceosctl release inspect-archive ` | Inspect a NLBoot release archive directory for required files (read-only) | | `sourceosctl fingerprint collect --dry-run` | Print environment fingerprint fields (dry-run only) | | `sourceosctl ai labs list` | List available AI labs (read-only) | | `sourceosctl agents sandbox plan --dry-run` | Print agent sandbox plan (dry-run only) | @@ -68,6 +69,7 @@ python3 bin/sourceosctl nlboot evidence inspect fixtures/sample_nlboot_evidence. python3 bin/sourceosctl nlboot evidence inspect --validate fixtures/sample_nlboot_evidence.json python3 bin/sourceosctl nlboot evidence validate fixtures/sample_nlboot_evidence.json python3 bin/sourceosctl release inspect fixtures/sample_release.json +python3 bin/sourceosctl release inspect-archive fixtures/nlboot_release_valid python3 bin/sourceosctl fingerprint collect --dry-run python3 bin/sourceosctl ai labs list python3 bin/sourceosctl agents sandbox plan --dry-run diff --git a/fixtures/nlboot_release_invalid/Cargo.lock b/fixtures/nlboot_release_invalid/Cargo.lock new file mode 100644 index 0000000..e8bb04c --- /dev/null +++ b/fixtures/nlboot_release_invalid/Cargo.lock @@ -0,0 +1,6 @@ +# This file is a stub Cargo.lock for fixture/inspection purposes only. +version = 3 + +[[package]] +name = "nlboot-client" +version = "0.1.0" diff --git a/fixtures/nlboot_release_invalid/nlboot-client b/fixtures/nlboot_release_invalid/nlboot-client new file mode 100644 index 0000000..316e4f1 --- /dev/null +++ b/fixtures/nlboot_release_invalid/nlboot-client @@ -0,0 +1 @@ +# stub nlboot-client binary placeholder (not executable; read-only fixture only) diff --git a/fixtures/nlboot_release_valid/Cargo.lock b/fixtures/nlboot_release_valid/Cargo.lock new file mode 100644 index 0000000..0717fbe --- /dev/null +++ b/fixtures/nlboot_release_valid/Cargo.lock @@ -0,0 +1,7 @@ +# This file is a stub Cargo.lock for fixture/inspection purposes only. +# It does not represent a real build and must not be used for dependency resolution. +version = 3 + +[[package]] +name = "nlboot-client" +version = "0.1.0" diff --git a/fixtures/nlboot_release_valid/cargo-metadata.json b/fixtures/nlboot_release_valid/cargo-metadata.json new file mode 100644 index 0000000..4c6f6de --- /dev/null +++ b/fixtures/nlboot_release_valid/cargo-metadata.json @@ -0,0 +1,10 @@ +{ + "note": "stub cargo-metadata for fixture/inspection purposes only", + "packages": [ + { + "name": "nlboot-client", + "version": "0.1.0", + "id": "nlboot-client 0.1.0 (path+file:///stub)" + } + ] +} diff --git a/fixtures/nlboot_release_valid/nlboot-client b/fixtures/nlboot_release_valid/nlboot-client new file mode 100644 index 0000000..316e4f1 --- /dev/null +++ b/fixtures/nlboot_release_valid/nlboot-client @@ -0,0 +1 @@ +# stub nlboot-client binary placeholder (not executable; read-only fixture only) diff --git a/fixtures/nlboot_release_valid/release-manifest.json b/fixtures/nlboot_release_valid/release-manifest.json new file mode 100644 index 0000000..5bea120 --- /dev/null +++ b/fixtures/nlboot_release_valid/release-manifest.json @@ -0,0 +1,17 @@ +{ + "schemaVersion": "nlboot-release.v1", + "name": "nlboot-client", + "version": "0.1.0", + "channel": "stable", + "note": "stub release-manifest for fixture/inspection purposes only", + "artifacts": [ + "nlboot-client", + "Cargo.lock", + "cargo-metadata.json", + "sbom.spdx.json" + ], + "metadata": { + "gitRef": "refs/heads/main", + "builtAt": "2025-01-01T00:00:00Z" + } +} diff --git a/fixtures/nlboot_release_valid/sbom.spdx.json b/fixtures/nlboot_release_valid/sbom.spdx.json new file mode 100644 index 0000000..6651799 --- /dev/null +++ b/fixtures/nlboot_release_valid/sbom.spdx.json @@ -0,0 +1,8 @@ +{ + "SPDXID": "SPDXRef-DOCUMENT", + "spdxVersion": "SPDX-2.3", + "name": "nlboot-client-sbom", + "dataLicense": "CC0-1.0", + "documentNamespace": "https://example.invalid/nlboot-client-stub", + "note": "stub SBOM for fixture/inspection purposes only" +} diff --git a/sourceosctl/cli.py b/sourceosctl/cli.py index a659011..1a02450 100644 --- a/sourceosctl/cli.py +++ b/sourceosctl/cli.py @@ -74,6 +74,15 @@ def build_parser() -> argparse.ArgumentParser: release_inspect_p.add_argument("path", help="Path to release artifact JSON file") release_inspect_p.set_defaults(func=release.inspect) + release_inspect_archive_p = release_sub.add_parser( + "inspect-archive", + help="Inspect a NLBoot release archive directory for required files", + ) + release_inspect_archive_p.add_argument( + "path", help="Path to unpacked NLBoot release archive directory" + ) + release_inspect_archive_p.set_defaults(func=release.inspect_archive) + # --- fingerprint --- fingerprint_p = sub.add_parser("fingerprint", help="Environment fingerprint utilities") fingerprint_sub = fingerprint_p.add_subparsers( diff --git a/sourceosctl/commands/release.py b/sourceosctl/commands/release.py index b7b36d2..1cadb85 100644 --- a/sourceosctl/commands/release.py +++ b/sourceosctl/commands/release.py @@ -4,6 +4,46 @@ import pathlib import sys +# Files that must be present in a valid NLBoot release archive directory. +_NLBOOT_REQUIRED_FILES = [ + "nlboot-client", + "Cargo.lock", + "cargo-metadata.json", + "sbom.spdx.json", + "release-manifest.json", +] + + +def inspect_archive(args) -> int: + """Inspect a NLBoot release archive directory for required files. Read-only.""" + path = pathlib.Path(args.path) + if not path.exists(): + print(f"error: path not found: {path}", file=sys.stderr) + return 1 + if not path.is_dir(): + print(f"error: not a directory: {path}", file=sys.stderr) + return 1 + + print(f"NLBoot release archive: {path}") + missing = [] + for name in _NLBOOT_REQUIRED_FILES: + entry = path / name + if entry.exists(): + print(f" ok {name}") + else: + print(f" MISSING {name}") + missing.append(name) + + if missing: + print( + f"error: {len(missing)} required file(s) missing: {', '.join(missing)}", + file=sys.stderr, + ) + return 1 + + print("ok: all required NLBoot release files present") + return 0 + def inspect(args) -> int: """Inspect a release artifact. Read-only.""" diff --git a/tests/test_cli.py b/tests/test_cli.py index 6516253..d85b4ea 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -175,6 +175,41 @@ def test_release_inspect_via_main(self): self.assertEqual(rc, 0) +class TestReleaseArchive(unittest.TestCase): + def test_inspect_archive_valid(self): + path = FIXTURES / "nlboot_release_valid" + args = _Args(path=str(path)) + result = release.inspect_archive(args) + self.assertEqual(result, 0) + + def test_inspect_archive_invalid(self): + path = FIXTURES / "nlboot_release_invalid" + args = _Args(path=str(path)) + result = release.inspect_archive(args) + self.assertEqual(result, 1) + + def test_inspect_archive_missing_dir(self): + args = _Args(path="/nonexistent/nlboot_release") + result = release.inspect_archive(args) + self.assertEqual(result, 1) + + def test_inspect_archive_not_a_directory(self): + path = FIXTURES / "sample_release.json" + args = _Args(path=str(path)) + result = release.inspect_archive(args) + self.assertEqual(result, 1) + + def test_inspect_archive_valid_via_main(self): + path = FIXTURES / "nlboot_release_valid" + rc = main(["release", "inspect-archive", str(path)]) + self.assertEqual(rc, 0) + + def test_inspect_archive_invalid_via_main(self): + path = FIXTURES / "nlboot_release_invalid" + rc = main(["release", "inspect-archive", str(path)]) + self.assertEqual(rc, 1) + + class TestFingerprint(unittest.TestCase): def test_collect_dry_run(self): args = _Args(dry_run=True)