SBOM Scope Property Registry v1
ID: CONTRACT-SBOM-SCOPE-PROPERTIES-V1 Status: Published Last Updated: 2026-06-21 Source of truth: src/Scanner/__Libraries/StellaOps.Scanner.Core/Contracts/DependencyScope.cs, src/Scanner/__Libraries/StellaOps.Scanner.Analyzers.Lang/Core/LanguageComponentMapper.cs, src/Scanner/__Libraries/StellaOps.Scanner.Emit/.../CycloneDxComposer.cs (MapScope).
Registers the stellaops.scope.* CycloneDX properties namespace that Stella analyzers emit on every language component, encoding the declared → deployed scope axis (the layer between “found/declared in an SBOM” and “deployed/reachable at runtime”). These properties are additive and back-compatible: an analyzer that does not resolve scope emits none of them, and consumers that do not understand them ignore them.
See also: Canonical SBOM Identifier v1, Scanner architecture §3.1 (component model), and the
stella sbom checkreference (the primary consumer of these properties).
Why a property namespace (not just CycloneDX scope)
CycloneDX component.scope has only three values — required / optional / excluded — which is too coarse to carry the nine canonical Stella scopes and cannot distinguish “proven dev” from “no information.” Stella therefore emits both:
- the standard CycloneDX
component.scope(so generic CycloneDX consumers get a usable signal), and - the precise
stellaops.scope.*properties below (so Stella’s own gate is exact).
The mapping from canonical scope to CycloneDX scope (CycloneDxComposer.MapScope):
Canonical (stellaops.scope.effective) | CycloneDX component.scope |
|---|---|
runtime, bundled, provided | required |
optional, peer | optional |
dev, test, build | excluded |
unknown (or absent) | (no scope claim — the consumer’s fail-safe re-applies) |
A proven non-deployed scope is never silently re-promoted to required; only a genuine unknown produces a null CycloneDX scope.
Registered properties
| Property name | Value domain | Cardinality | Meaning |
|---|---|---|---|
stellaops.scope.effective | runtime | bundled | provided | optional | peer | build | test | dev | unknown | 0…1 per component | The graph-wide effective scope: the most-deployed scope under which the component is reachable from any project root, after transitive propagation. This is the emitted/filtered value. |
stellaops.scope.declared | same domain as effective | 0…1 per component | The declared scope of the direct edge from a project root, if the component is a direct dependency; absent for transitive-only components. Retained for auditability and to explain how the effective scope was computed. |
stellaops.scope.deployed | true | false | 0…1 per component | The single gate bit: = effective.IsDeployed(). true for {runtime, bundled, provided, optional, peer, unknown}; false for {dev, test, build}. Downstream gates key off this bit so the partition is stable regardless of how many canonical scopes exist. |
stellaops.scope.resolution | see resolution-marker table | 0…1 per component | How the scope was determined (graph quality / provenance). An honest precision marker — derived / partial-graph / *-no-graph / *-degraded mean the deployed bit is a prediction or fail-safe, not a lockfile-authoritative fact. |
Deployed-set (the deployed=true cut)
deployed-set = { runtime, bundled, provided, optional, peer, unknown }
non-deployed-set = { dev, test, build }
unknown is deployed-side by the fail-safe rule (R2): any edge/component that cannot be resolved defaults to a deployed-equivalent treatment so a vulnerability is never hidden on missing data. unknown never lowers a concrete deployed scope.
stellaops.scope.resolution markers
| Marker | Emitted by | Meaning |
|---|---|---|
lockfile-authoritative | npm v2/v3, composer, pnpm, nuget, etc. with full graph + per-pkg flags | Scope is read directly from authoritative lockfile data. |
derived | yarn/pnpm/ruby/rust graph-derivation | Scope computed from the lockfile dependency graph (no per-node flag) — accurate but inferred. |
lockfile-v1-degraded | npm package-lock v1 | Degraded lockfile (no packages map); direct deps back-filled from package.json. |
yarn-classic-derived | yarn classic (v1) | Section scope derived from package.json + workspaces; entries with no root scope floor at unknown. |
partial-graph | any ecosystem with a severed edge | Component reachable only through an unresolvable link: / workspace: / peer path; floored at unknown (deployed, fail-safe). |
no-graph / requirements-no-graph | python requirements.txt, roots-only inputs | No usable transitive edge graph; scope is roots-only or unknown (deployed). |
Determinism
These properties are deterministic: the canonical token is fixed per scope, stellaops.scope.deployed is a pure function of effective, and the accumulator/composer emit properties in stable ordinal order. A re-run on identical inputs is byte-identical.
Consumers
stella sbom check— partitions findings into Section A (deployed, gated) and Section B (declared-only dev/test/build, informational) keyed offstellaops.scope.deployed; surfaceseffectiveScopeper finding and ascopeSummaryblock. See the CLI reference.- The CycloneDX
dependencies[]graph (populated alongside these properties) lets a consumer recompute the deployed partition independently.
