Repository navigation
Add bootintel submit to fetch server-side artifacts - #37
Merged
Merged
Conversation
The CLI could reach server analysis but not anything the server builds from it. `scan --api` posts to `/analysis/scan`, which the server documents as "stateless inline log analysis without device_id/database persistence" and which returns a freshly minted uuid as its scan_id, naming no row. The SBOM, evidence pack, PDF and JSON report are all built from a real ScanSession, so there was nothing for the CLI to export from. SBOM-in-CI is the canonical use case for the format and it was the one place the product could not do it. `bootintel submit <log>` creates the persisted scan and downloads whichever artifacts are asked for: --sbom, --evidence, --pdf, --json-report, with --json printing the scan id and written paths for scripting. A separate subcommand rather than a flag on `scan`, because it costs something `scan` does not: a saved scan consumes the account's monthly quota. The notice is printed immediately before that spend rather than at the top of the run, since printing it first meant a 403 from the device lookup was preceded by a claim about a charge that never happened. Requires PRO, and the two gates are easy to conflate. The export endpoints are gated at Researcher, but get_current_user refuses any X-API-Key request below Pro, so a Researcher can download these from the dashboard and not from here. The help text first said "Researcher or higher" by quoting the endpoint's own gate, which would have told a Researcher this command works for them. An existing device with the same name is reused instead of creating one per run. POST /devices/ accepts duplicates, so a nightly CI job would otherwise add a device a day and nothing server-side would have complained. Verified end to end against the live API with a real Pro account, through a loopback forwarder because the droplet's /etc/hosts points bootintel.com at a local nginx whose origin certificate the system store does not trust. All four artifacts came back genuine: a 25-page PDF with an intact %PDF-1.4 header, a valid zip containing manifest/findings/analysis/raw-log, and a CycloneDX 1.6 SBOM naming the real board with 6 components and 73 vulnerabilities that validates clean against the vendored upstream schema. A second run produced two scans and one device, confirming reuse. 403, missing-key and plaintext-base all exit 77 with the server's own reason shown. Seven integration tests drive the compiled binary against a mock server, since the exit code, the request sequence and byte fidelity are properties of the process. The binary-artifact test ships bytes that are not valid UTF-8, because a String round-trip would corrupt a PDF silently and a corrupt PDF still looks like a file on disk. 391 tests pass; fmt and clippy clean. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This was referenced Oct 7, 2026
Zenofex
added a commit
that referenced
this pull request
Oct 7, 2026
#37 shipped a race, and it blocked #38. Every test in `submit_cli.rs` wrote the same `tmpdir()/boot.log`. Rust runs tests in parallel threads, and `fs::write` truncates before it writes, so one test's spawned process could read the file in the instant another had emptied it. The binary then did exactly what it should: ``` Error: /tmp/submit_cli/boot.log is empty; nothing to submit ``` It passed 7/7 locally and passed on main's own CI run, then failed `test (default-features)` on the next branch. That is the shape of flake that survives: red often enough to erode trust in the suite, green often enough that nobody can reproduce it. Each test now writes its own `<tag>.log`. Verified with 15 consecutive runs of the file — 0 failures — and seven distinct files on disk. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
Zenofex
added a commit
that referenced
this pull request
Oct 7, 2026
`bootintel submit` (#37) is merged but sits under [Unreleased], and the workspace is still 0.13.0, which is also the latest published release. So nobody can install the command: a `brew install` or a release download today gets 0.13.0 without it. A merged feature no user can run is not shipped. The repo's own policy makes this MINOR: "new detector; new subcommand; new flag; new output format". Same four files the 0.13.0 release touched: the workspace version, the detectors pin in crates/cli, Cargo.lock, and the CHANGELOG heading. Nothing here publishes anything by itself -- cli-release.yml is workflow_dispatch only, with an explicit version input and a dry_run option -- so the release remains a deliberate manual act after this lands. Verified the built binary reports 0.14.0 and still carries `submit`. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Zenofex
added a commit
that referenced
this pull request
Oct 7, 2026
`bootintel submit` (#37) is merged but sits under `[Unreleased]`, and the workspace is still `0.13.0` — which is also the latest published release. So **nobody can install the command**: a `brew install` or a release download today gets 0.13.0 without it. A merged feature no user can run is not shipped. The repo's own policy makes this MINOR: *"new detector; new subcommand; new flag; new output format"*. ## What this changes The same four files the 0.13.0 release touched: - workspace version `0.13.0` → `0.14.0` - the `bootintel-detectors` pin in `crates/cli/Cargo.toml` - `Cargo.lock` - `CHANGELOG.md` — `[Unreleased]` closed out into a dated heading ## This does not publish anything `cli-release.yml` is `workflow_dispatch` only, with an explicit `version` input and a `dry_run` option. Merging this prepares the release; cutting it stays a deliberate manual dispatch afterwards. Verified the built binary reports `bootintel 0.14.0` and still carries `submit`. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The CLI could reach server analysis but not anything the server builds from it.
scan --apiposts to/analysis/scan, which the server documents as "stateless inline log analysis without device_id/database persistence" and which returns a freshly minted uuid as itsscan_id, naming no row. The SBOM, evidence pack, PDF and JSON report are all built from a realScanSession, so there was nothing for the CLI to export from. SBOM-in-CI is the canonical use case for the format, and it was the one place the product could not do it.bootintel submit <log>creates the persisted scan and downloads whichever artifacts are asked for:--sbom,--evidence,--pdf,--json-report, with--jsonprinting the scan id and written paths for scripting.Why a separate subcommand
It costs something
scandoes not: a saved scan consumes the account's monthly quota. The notice prints immediately before that spend rather than at the top of the run — printing it first meant a 403 from the device lookup was preceded by a claim about a charge that never happened.Requires Pro, not Researcher
The two gates are easy to conflate. The export endpoints are gated at Researcher, but
get_current_userrefuses anyX-API-Keyrequest below Pro, so a Researcher can download these from the dashboard and not from here. The help text first said "Researcher or higher" by quoting the endpoint's own gate, which would have told a Researcher this command works for them.Device reuse
An existing device with the same name is reused instead of creating one per run.
POST /devices/accepts duplicates, so a nightly CI job would otherwise add a device a day and nothing server-side would have complained.Verification
End to end against the live API with a real Pro account, through a loopback forwarder because the droplet's
/etc/hostspoints bootintel.com at a local nginx whose origin certificate the system store does not trust. All four artifacts came back genuine:%PDF-1.4headerA second run produced two scans and one device, confirming reuse. 403, missing-key and plaintext-base all exit 77 with the server's own reason shown.
Seven integration tests drive the compiled binary against a mock server, since the exit code, the request sequence and byte fidelity are properties of the process. The binary-artifact test ships bytes that are not valid UTF-8, because a String round-trip would corrupt a PDF silently and a corrupt PDF still looks like a file on disk.
391 tests pass;
cargo fmtandcargo clippy --all-targetsclean.🤖 Generated with Claude Code