8000 minimal sbom + blueprint: session/task SBOM (CycloneDX 1.5), extended to a CycloneDX 2.0 Blueprint · Issue #700 · gominimal/minimal · GitHub
[go: up one dir, main page]

Skip to content

minimal sbom + blueprint: session/task SBOM (CycloneDX 1.5), extended to a CycloneDX 2.0 Blueprint #700

Open

Description

@bryan-minimal

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:

  1. 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 thin Serializer seam (D10).
  2. 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 shared minimal/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_data per-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 + Serializer trait 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_sha256 via the signed-index reader, digest_gap for local/non-redistributable
  • 4. CLI min sbom declared-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): GetSessionComposition RPC, 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::Task never 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=launcher property, 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

  1. bryan-minimal commented on Jul 10, 2026

    @bryan-minimal
    MemberAuthor

    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) and compose_user_contribution(...) -> WireContribution (policy-gated)
    • activate composes loadouts client-side before opening the daemon (lib.rs ~:811), daemon Phase-4 Composition remains the full-session truth (loadouts ∪ [session] ∪ package env_state_wiring), all Source-tagged — feat(sessions): loadouts, config, and debug commands #686's per-session content logging already prints exactly this data

    The mapping (one pure module, three input tiers)

    loadout construct SBOM/blueprint output
    packages() sbom::Component per package (versionless pkg:minimal/... purl v1; version/provenance enrichment daemon-side later)
    patches() host Data asset (source) + sandbox Data asset (dest) + Flow{Data, "patch"} — a trust-boundary crossing per patch
    vars() with VarValue::Inherit / InheritWithDefault host Env asset + Flow{Env, "inherit"} into the sandbox
    vars() with specified values sandbox Env asset only (no boundary crossing — visually distinct from inherits)
    lifecycle_hooks() (on_activate/on_destroy/on_failure) Process asset + Behavior{session:hook:onActivate|onDestroy|onFailure}, acknowledgment = declared; inline script content is hashable for integrity later
    (fixed) zones host / sandbox; the session subject is the metadata.component

    Input tiers (same mapping fn, three sources)

    1. Vec<Loadout> (client-only, no daemon): ships as minimal loadout blueprint <name> [--format mermaid|cyclonedx|spdx] the moment feat(sessions): loadouts, config, and debug commands #686 lands. Pure function, trivially testable.
    2. WireContribution (post-policy-gate: what survived). Caveat: feat(sessions): loadouts, config, and debug commands #686 pre-resolves Inherit vars from the process env at compose time — inherit-ness must be captured from the Loadout (tier 1) or the wire Source provenance, not from the resolved value.
    3. Daemon Composition (the full session incl. [session] + package contributions): the real minimal session blueprint <id>; Source provenance maps to per-asset properties.

    Placement decision to make in the PR

    The mapping module needs sessions types. Options: (a) in crates/minimal (CLI layer) building sbom/blueprint model structs — keeps both new crates dep-lean; (b) a sessions feature on blueprint. 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.

  2. bryan-minimal commented on Jul 10, 2026

    @bryan-minimal
    MemberAuthor

    Consolidation: #433 is closed in lieu of this issue — the crates/sbom extraction 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).

  3. added
    kind:featureA new feature or capability. Applied by the inbox-route classifier.
    priority:nice-to-haveNice to have. Applied by the inbox-route classifier or a human.
    on Jul 23, 2026
  4. changed the title [-]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[/+] on Jul 24, 2026
  5. bryan-minimal commented on Jul 30, 2026

    @bryan-minimal
    MemberAuthor

    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, min CLI surface has no conflicting subcommand). Plan stands, with three upgrades from what merged in between:

    1. CatalogPin = the closure snapshot reference. feat(rcache): closure-union index reads behind index_source="closure" #995 landed index_source = "closure" reads: mfile::commit_closure_object() derives the key, and <commit>.closure.shisha is the signed catalog. CatalogPin becomes {repo, commit, closure_object} — component digests chain to a Sigstore-verifiable object, not an abstract index.
    2. Digest source is merged: RemoteCache::sha256 (feat(rcache): expose RemoteCache::sha256 accessor #284) — slice 3's accessor exists at tip.
    3. Attribution alignment: component licenses source from the same license_spdx graph attrs the sealed per-commit attribution manifest uses (build-servers#194), so a session SBOM and the sealed <commit>.attribution.json are 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, Serializer trait + both formats, SerialPolicy::ContentDerived (serials/namespaces/created derived 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 created source (proposal: the pinned commit's committer timestamp), and digest_gap as a two-variant enum (built-locally vs non-redistributable — the distinction is load-bearing since inbox#290).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

kind:featureA new feature or capability. Applied by the inbox-route classifier.priority:nice-to-haveNice to have. Applied by the inbox-route classifier or a human.

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions

    0