Repository navigation
Description
Rewritten 2026-07-24 for trackability (previous body in edit history). A detailed implementation plan against main tip 3a05252c exists — "SBOM/Blueprint implementation plan" on Notion; this issue tracks goal, slices, and decisions.
Goal
A min sbom command on the new CLI (crates/minimal, NOT mip) emitting the supply-chain + posture story of a session / task+loadout:
- SBOM: components + dependency graph — CycloneDX 1.5 (per crates: extract SBOM generation into a shared
minimal/crates/sbom(out of minimal-supply-chain) #433's close-out: nothing we generate needs 1.6-only attributes; the forward path is 2.0). SPDX deferred behind a thinSerializerseam (D10). - Blueprint: the CycloneDX 2.0 draft threat-modeling schema — the session's posture: assets, behaviors, flows, zones. Declared-spec first; live-session later.
Two architectural consumers of the same model: this CLI, and build infra emitting per-package SBOMs at publish time (the unified architecture makes Built Package SBOM + its signature first-class) — so crates/sbom must stay lean (no clap/daemon/rcache deps in the default build).
Where this stands
- Greenfield at tip: no
crates/sbom/crates/blueprint, no CDX/SPDX/purl deps in the lock. crates: extract SBOM generation into a sharedminimal/crates/sbom(out of minimal-supply-chain) #433 is closed (superseded here); its resolved decisions carry over. - Source material: ~1,500 portable LOC in minimal-supply-chain (CDX types, build-scan, store, purl subset) + rustmachineguard's working schema-gated CDX 2.0 Blueprint emitter (vendored-schema conformance-test pattern to port; its string-parsing identity resolution is the anti-pattern to avoid — we have structured provenance end-to-end).
- Axis reality at tip (full ✓/△/✗ table in the plan): components/assets/behaviors/most flows ✓ (the
carries_user_dataper-var taint bit is a ready-made data-flow marker); network zones △ (declared, unenforced — emit with a caveat property);file_exports,github_imports+scopes,loadout_mode, otel endpoints,vpn_network_names✗ — schema-TODOs, never fabricated. - Emitted vocabulary follows the Sandbox Spec (
network.type = none|host_ip|no_ip,file_imports/exports,machine.*), so documents survive the Composition→Spec naming convergence.
PR slices (each lands green; 1–3 platform-neutral, 4+ Linux-lane testing)
- 1+2 (PR-1, one PR):
crates/sbom(portable subset from minimal-supply-chain, lean deps) + neutral IR +Serializertrait with CycloneDX 1.5 (full) and SPDX 2.3 (minimal-valid) +SerialPolicy::ContentDerived+CatalogPin={repo, commit, closure_object}(the signed<commit>.closure.shisha, per feat(rcache): closure-union index reads behind index_source="closure" #995 — replaces uuid-v4 + wall clock); golden fixtures both formats; CDX schema conformance test. See the 2026-07-30 re-validation comment. - 3. Graph typed accessors + adapter: components from the closure walk, real dependency edges,
artifact_sha256via the signed-index reader,digest_gapfor local/non-redistributable - 4. CLI
min sbomdeclared-spec mode (--task/--loadout,--format): first end-to-end value; zero daemon changes - 5.
crates/blueprint+--format cdx-blueprint: rmg-derived model, vendored-schema gate (re-pin to current 2.0-dev head; re-vendor at finalization, milestone 2026-08-31) - 6. Live-session mode (deferrable; gated on D2):
GetSessionCompositionRPC, observed acknowledgment, minvmd resources
Open decisions (recommendations in the plan doc)
- D2 live-session in v1? → declared-only v1; slice 6 follows once the daemon wire layer settles
- D4 "blueprint" naming collision (minimal.toml is already "the project blueprint") → crate name fine; user-facing
--format cdx-blueprint; decide before slice 4 - D5 persist policy-gate verdicts at FinalizeSession? → yes; cheap now, unreconstructible later
- D6 legacy
mfile::Tasknever produces a Composition → v1 reads graph side for packages, sessions side for posture; converging Task onto sessions primitives is the real fix (separate work) - D7 launcher-injected packages (baseline + bash/socat) → emit with
minimal:added-by=launcherproperty, don't omit (an SBOM missing packages present in every environment is dishonest) - D9 authority when a sealed pkg-SBOM and a regenerated graph SBOM disagree → graph is authoritative for metadata, sealed for build-time facts; both carry CatalogPin so divergence is diffable
- D11 Sandbox-Spec vocabulary conflicts → settle before slice 2's golden fixtures land (renames churn every checked-in BOM)
D1 (CDX 1.5) applied per #433's close-out. D10 revised 2026-07-30: minimal SPDX 2.3 ships in PR-1 alongside CDX (the serializer seam shouldn't land with one implementation); full SPDX profile depth stays deferred.
Refs
Prior art: rustmachineguard (blueprint emitter + ADBOM taxonomy — evaluate for agent-running sessions, follow-up). Signing/verifiability: the per-commit signed index (#912 reader; build-servers#86/#185) — session-SBOM component digests chain to it. Usage axis (STATIC/DYNAMIC link tiers): future, model field reserved.
Activity
bryan-minimal commentedon Jul 10, 2026
Adapter prep: loadout → blueprint, reasoned against #686's actual seams
#686 (loadouts/config/debug commands) provides exactly the hooks the slice-4 adapter needs. Mapping written down now so the adapter PR is mechanical later.
The seams #686 ships
sessions::client::disk:read_loadout_file(path) -> Loadout,list_loadouts(dir) -> Vec<LoadoutEntry>(per-entry errors — malformed files don't abort)minimal::loadouts:resolve_active_loadouts(cfg, flags) -> Vec<Loadout>(--loadout/--no-loadouts/config defaults) andcompose_user_contribution(...) -> WireContribution(policy-gated)lib.rs~:811), daemon Phase-4Compositionremains the full-session truth (loadouts ∪[session]∪ packageenv_state_wiring), allSource-tagged — feat(sessions): loadouts, config, and debug commands #686's per-session content logging already prints exactly this dataThe mapping (one pure module, three input tiers)
packages()sbom::Componentper package (versionlesspkg:minimal/...purl v1; version/provenance enrichment daemon-side later)patches()Dataasset (source) + sandboxDataasset (dest) +Flow{Data, "patch"}— a trust-boundary crossing per patchvars()withVarValue::Inherit/InheritWithDefaultEnvasset +Flow{Env, "inherit"}into the sandboxvars()with specified valuesEnvasset only (no boundary crossing — visually distinct from inherits)lifecycle_hooks()(on_activate/on_destroy/on_failure)Processasset +Behavior{session:hook:onActivate|onDestroy|onFailure},acknowledgment = declared; inline script content is hashable for integrity laterhost/sandbox; the session subject is themetadata.componentInput tiers (same mapping fn, three sources)
Vec<Loadout>(client-only, no daemon): ships asminimal loadout blueprint <name> [--format mermaid|cyclonedx|spdx]the moment feat(sessions): loadouts, config, and debug commands #686 lands. Pure function, trivially testable.WireContribution(post-policy-gate: what survived). Caveat: feat(sessions): loadouts, config, and debug commands #686 pre-resolvesInheritvars from the process env at compose time — inherit-ness must be captured from theLoadout(tier 1) or the wireSourceprovenance, not from the resolved value.Composition(the full session incl.[session]+ package contributions): the realminimal session blueprint <id>;Sourceprovenance maps to per-asset properties.Placement decision to make in the PR
The mapping module needs
sessionstypes. Options: (a) incrates/minimal(CLI layer) buildingsbom/blueprintmodel structs — keeps both new crates dep-lean; (b) asessionsfeature onblueprint. Leaning (a).Interop note: nothing here depends on #686 merging in a particular order vs the sbom/blueprint crates PR — file overlap is Cargo.toml/lock workspace lists only.
bryan-minimal commentedon Jul 10, 2026
Consolidation: #433 is closed in lieu of this issue — the
crates/sbomextraction it scoped is PR-slice 1 here (unchanged in design, with one amendment: CycloneDX 1.5 for v1 to match minimal-supply-chain's current output; the future format axis is 2.0 blueprints, not 1.6 extensions).[-]minimal sbom + blueprint: session posture as CycloneDX/SPDX SBOM, extended to a CycloneDX 2.0 Blueprint[/-][+]minimal sbom + blueprint: session/task SBOM (CycloneDX 1.5), extended to a CycloneDX 2.0 Blueprint[/+]bryan-minimal commentedon Jul 30, 2026
Plan re-validated against tip
063d4785(2026-07-30)Greenfield holds: no sbom/blueprint crates, no cyclonedx/spdx/purl deps in the lock. 82 commits since the plan tip (
3a05252c); none touch the plan's inputs (sessions vocabulary unchanged,minCLI surface has no conflicting subcommand). Plan stands, with three upgrades from what merged in between:CatalogPin= the closure snapshot reference. feat(rcache): closure-union index reads behind index_source="closure" #995 landedindex_source = "closure"reads:mfile::commit_closure_object()derives the key, and<commit>.closure.shishais the signed catalog. CatalogPin becomes{repo, commit, closure_object}— component digests chain to a Sigstore-verifiable object, not an abstract index.RemoteCache::sha256(feat(rcache): expose RemoteCache::sha256 accessor #284) — slice 3's accessor exists at tip.licensessource from the samelicense_spdxgraph attrs the sealed per-commit attribution manifest uses (build-servers#194), so a session SBOM and the sealed<commit>.attribution.jsonare cross-checkable via the shared CatalogPin. Consistency contract, not new scope.PR-1 = slices 1+2 in one PR, and it includes a minimal-but-valid SPDX 2.3 serializer alongside full CycloneDX 1.5 (revising D10's thin-skeleton deferral: the serializer seam shouldn't land with a single implementation). Contents:
crates/sbom(serde/serde_json/thiserror only), neutral IR + CatalogPin,Serializertrait + both formats,SerialPolicy::ContentDerived(serials/namespaces/createdderived from content + pin — byte-identical regeneration), golden fixtures both formats, CDX 1.5 vendored-schema conformance test. Platform-neutral, no CLI/daemon/graph changes. Note the D11 fixture risk mostly defers: PR-1 fixtures cover component/dependency vocabulary only; posture vocabulary arrives with the blueprint slice.Decisions to settle in PR-1 review (both reversible): SPDX
createdsource (proposal: the pinned commit's committer timestamp), anddigest_gapas a two-variant enum (built-locallyvsnon-redistributable— the distinction is load-bearing since inbox#290).