up
Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Mirror Thin Bundle Sign & Verify / mirror-sign (push) Has been cancelled
api-governance / spectral-lint (push) Has been cancelled

This commit is contained in:
StellaOps Bot
2025-11-24 07:52:25 +02:00
parent 5970f0d9bd
commit 150b3730ef
215 changed files with 8119 additions and 740 deletions

View File

@@ -0,0 +1,6 @@
# Keys and Issuers (DOCS-ATTEST-74-001)
- Maintain issuer registry (KMS IDs, key IDs, allowed predicates).
- Rotate keys with overlap; publish fingerprints and validity in registry file.
- Offline operation: bundle registry with bootstrap; no remote fetch.
- Each attestation must include issuer ID and key ID; verify against registry.

View File

@@ -0,0 +1,9 @@
# Attestor Overview (DOCS-ATTEST-73-001)
High-level description of the Attestor service and its contracts.
- Purpose: verify DSSE/attestations, supply transparency info, and expose attestation APIs without deriving verdicts.
- Components: WebService, Worker, KMS integration, Transparency log (optional), Evidence links.
- Rule banner: aggregation-only; no policy decisions.
- Tenancy: all attestations scoped per tenant; cross-tenant reads forbidden.
- Offline posture: allow offline verification using bundled trust roots and Rekor checkpoints when available.

View File

@@ -1,48 +1,29 @@
# Attestor Payload Reference
# Attestor Payloads (DOCS-ATTEST-73-002)
StellaOps evidence predicates must remain reproducible, explainable, and portable across online and fully air-gapped deployments. This guide lists each predicate type, indicates where the canonical JSON Schema lives, highlights the producing service, and links to the matching golden samples.
Schemas/examples for attestations handled by Attestor.
## Quick Reference
## DSSE payload
```json
{
"_type": "https://in-toto.io/Statement/v1",
"subject": [{"name": "sha256:...", "digest": {"sha256": "..."}}],
"predicateType": "stella.ops/vexObservation@v1",
"predicate": {
"observationId": "vex:obs:sha256:...",
"tenant": "default",
"providerId": "ubuntu-csaf",
"createdAt": "2025-11-23T23:10:00Z"
}
}
```
| Type ID | Predicate URI | Schema file | Produced by | Primary consumers |
| --- | --- | --- | --- | --- |
| StellaOps.BuildProvenance@1 | https://schemas.stella-ops.org/attestations/build-provenance@1 | src/Attestor/StellaOps.Attestor.Types/schemas/stellaops-build-provenance.v1.schema.json | Build pipelines, Scanner SBOM bake stage | Attestor, Export Center, Policy Engine |
| StellaOps.SBOMAttestation@1 | https://schemas.stella-ops.org/attestations/sbom-attestation@1 | src/Attestor/StellaOps.Attestor.Types/schemas/stellaops-sbom-attestation.v1.schema.json | Scanner.Worker SBOM composer | Policy Engine, CLI, Export Center |
| StellaOps.ScanResults@1 | https://schemas.stella-ops.org/attestations/scan-results@1 | src/Attestor/StellaOps.Attestor.Types/schemas/stellaops-scan-results.v1.schema.json | Scanner.Worker analyzers | Policy Engine, CLI, Orchestrator |
| StellaOps.PolicyEvaluation@1 | https://schemas.stella-ops.org/attestations/policy-evaluation@1 | src/Attestor/StellaOps.Attestor.Types/schemas/stellaops-policy-evaluation.v1.schema.json | Policy Engine explain pipeline | CLI, Notify, Export Center |
| StellaOps.VEXAttestation@1 | https://schemas.stella-ops.org/attestations/vex-attestation@1 | src/Attestor/StellaOps.Attestor.Types/schemas/stellaops-vex-attestation.v1.schema.json | Excititor consensus service | Policy Engine, CLI, Console |
| StellaOps.RiskProfileEvidence@1 | https://schemas.stella-ops.org/attestations/risk-profile@1 | src/Attestor/StellaOps.Attestor.Types/schemas/stellaops-risk-profile.v1.schema.json | Policy Engine risk pipeline | Console, Notify, Export Center |
| StellaOps.CustomEvidence@1 | https://schemas.stella-ops.org/attestations/custom-evidence@1 | src/Attestor/StellaOps.Attestor.Types/schemas/stellaops-custom-evidence.v1.schema.json | CLI custom evidence workflows and partner integrations | Policy Engine (policy hooks), Export Center |
## Evidence links
- Each payload references evidence hashes (VEX observations/linksets) and optional timeline event IDs.
- Keep payloads aggregation-only; no verdict fields.
Golden JSON fixtures that double as contract tests live under `src/Attestor/StellaOps.Attestor.Types/fixtures/v1/<predicate>.sample.json`. TypeScript and Go clients consume the generated sources in `src/Attestor/StellaOps.Attestor.Types/generated/ts` and `src/Attestor/StellaOps.Attestor.Types/generated/go`.
## Hashing/signing
- Canonicalize JSON (RFC 8785) before signing.
- Use SHA-256 digests; include in envelope metadata.
## Envelope Conventions
- DSSE envelopes are signed over canonical JSON (sorted keys, UTF-8, no insignificant whitespace).
- The `subject` array must include at least one SHA-256 digest and may attach annotations such as `oci.reference` or `stellaops.asset`.
- `predicateType` uses the URI shown in the table; `predicate.typeId` mirrors the short identifier.
- `predicate.schemaVersion` follows semantic versioning. Consumers reject mismatched major versions.
- Optional `metadata` and `materials` sections follow the in-toto Statement format to maximise provenance portability.
## Predicate Highlights
- **StellaOps.BuildProvenance@1** records builder identity, config source, materials, reproducibility flags, and the resulting artifact digests. Outputs must match the DSSE subject.
- **StellaOps.SBOMAttestation@1** links an artifact digest to a CycloneDX 1.6 or SBOM 3.0.0 document, tracking inventory counts and the generator metadata. Component graph hashes reference CAS entries emitted by Scanner.Worker.
- **StellaOps.ScanResults@1** captures deterministic findings from OS, language, and native analyzers. It reports summary counts, per-finding metadata (PURL, severity, exploitability), and the layer digests inspected.
- **StellaOps.PolicyEvaluation@1** documents lattice-based policy outcomes, including decision traces and evidence digests consumed during evaluation.
- **StellaOps.VEXAttestation@1** mirrors OpenVEX-aligned statements with justification, scope narrowing (package coordinates or component IDs), and issue timestamps.
- **StellaOps.RiskProfileEvidence@1** summarises exploitability, ticketing load, runtime coverage, and maturity for downstream dashboards.
- **StellaOps.CustomEvidence@1** allows regulated tenants to attach organisation-specific payloads referenced by a CAS-hosted schema while preserving provenance and retention controls.
## Validation and Tooling
- Run `npm install` once, then `npm run docs:attestor:validate` to validate JSON fixtures against their schemas, execute the generated TypeScript tests (`npm test`), and run `go test ./...` for the Go SDK. The command fails fast when any schema, fixture, or generated SDK drifts.
- Regenerate schemas and SDKs after edits with `dotnet run --project src/Attestor/StellaOps.Attestor.Types/Tools/StellaOps.Attestor.Types.Generator`.
- Offline Kit builds (`ops/devops/offline-kit/`) mirror schemas, fixtures, and SDK bundles so air-gapped operators can run the same validation stack.
## Related Material
- `docs/modules/attestor/architecture.md` — service topology, Rekor integration, caching model.
- `docs/modules/platform/architecture-overview.md` — cross-module data flows and tenant boundaries.
- `docs/ingestion/aggregation-only-contract.md` — guardrails for advisory feeds consumed by policy evaluation.
- `src/Attestor/StellaOps.Attestor.Types/samples/README.md` — directory map for the golden evidence set referenced here.
## Examples
- Place sample payloads in `docs/samples/attestor/payloads/` (add when available).

View File

@@ -0,0 +1,12 @@
# Attestor Policies (DOCS-ATTEST-73-003)
Guidance on verification policies applied by Attestor.
- Scope: DSSE envelope validation, subject hash matching, optional transparency checks.
- Policy fields:
- allowed issuers / key IDs
- required predicates (e.g., `stella.ops/vexObservation@v1`)
- transparency requirements (allow/require/skip)
- freshness window for attestations
- Determinism: policies must be pure; no external lookups in sealed mode.
- Versioning: include `policyVersion` and hash; store alongside attestation records.

View File

@@ -0,0 +1,6 @@
# Transparency (DOCS-ATTEST-74-002)
- Optional Rekor/witness integration.
- In sealed mode, use bundled checkpoints and disable live witness fetch.
- Verification: compare embedded checkpoint with bundled; log discrepancies.
- Record transparency fields on verification result: `{uuid, logIndex, checkpointHash}`.

View File

@@ -1,247 +1,9 @@
# Attestor Verification Workflows
# Attestor Workflows (DOCS-ATTEST-73-004)
> How StellaOps turns DSSE bundles into verifiable evidence, how the verification API reports outcomes, and how explainability signals surface in UI/CLI flows.
Sequence of ingest, verify, and bulk operations.
> ⚠️ **2025-11-01 coordination note:** `StellaOps.Attestor.WebService` is failing to compile until downstream fixes land (`Contracts/AttestationBundleContracts.cs` null-coalescing update and scope/token variables restored in `Program.cs`). Verification flows ship in infrastructure/tests, but the WebService hand-off stays blocked — track via `ATTESTOR-73-002` (see Attestor task board).
## 1. Verification flow (API and service contract)
- **Entry point.** `POST /api/v1/rekor/verify` deserialises to `AttestorVerificationRequest`.
- **Resolution order.** The service tries `uuid`, then canonicalised `bundle`, then `artifactSha256`. At least one selector must be present (`invalid_query` otherwise).
- **Optional proof refresh.** `refreshProof=true` forces a Rekor lookup before returning. Proofs are cached in Mongo.
- **Signature replay.** Supplying `bundle` lets the service recompute the canonical hash and re-run signature checks; omitting the bundle skips those steps but still validates Merkle proofs and cached policy decisions.
- **Auth scopes.** Endpoints demand `attestor.verify` (write scope is also accepted); read-only detail/list APIs require `attestor.read` at minimum.
### 1.1 Request properties
| Field | Type | Required | Purpose |
|-------|------|----------|---------|
| `uuid` | string | optional | Rekor V2 UUID to verify and (optionally) refresh. |
| `bundle` | object | optional | DSSE envelope (same shape as submission) for signature re-verification. |
| `artifactSha256` | string | optional | Resolve the most recent entry for an attestable artefact digest. |
| `subject` | string | optional | Logical subject identifier used for cache/telemetry tagging; defaults to the stored artifact digest. |
| `envelopeId` | string | optional | Stable identifier for the DSSE bundle (typically the canonical hash); enables cache lookups. |
| `policyVersion` | string | optional | Policy digest/version driving verification; feeds cache keys and observability dimensions. |
| `refreshProof` | bool | optional (default `false`) | Pull the current inclusion proof and checkpoint from Rekor before evaluating. |
All selectors are mutually compatible; if more than one is set the service uses the first match (`uuid``bundle``artifactSha256`).
### 1.2 Response schema (`AttestorVerificationResult`)
| Field | Type | Description |
|-------|------|-------------|
| `ok` | bool | `true` when the entry status is `included` **and** no issues were recorded. |
| `uuid` | string | Rekor UUID that satisfied the query. Useful for follow-up fetches. |
| `index` | number (int64) | Rekor log index, when supplied by the backend. |
| `logUrl` | string | Fully-qualified Rekor entry URL for operators and auditors. |
| `status` | string | Transparency-log status seen in Mongo (`included`, `pending`, `failed`, …). |
| `checkedAt` | string (ISO-8601 UTC) | Timestamp emitted when the response is created. |
| `issues` | array[string] | Machine-readable explainability codes. Empty when `ok=true`. |
> **Note:** `checkedAt` is recomputed each call; cache hits do not recycle previous timestamps.
### 1.3 Success criteria
`ok=true` requires:
1. Entry exists and status equals `included`.
2. Canonical DSSE hash matches the stored bundle hash.
3. Signature re-verification (when a bundle is supplied) succeeds.
4. Inclusion proof validates against the cached or refreshed checkpoint.
Any deviation records at least one issue and flips `ok` to `false`. Consumers **must** inspect `issues` rather than inferring from `status` alone.
## 2. Verification report schema
`AttestorVerificationResult` carries the flattened summary shown above. When callers request the detailed report (`GET /api/v1/rekor/entries/{uuid}?refresh=true` or via SDK) they receive a `VerificationReport` shaped as follows:
```json
{
"overallStatus": "pass",
"succeeded": true,
"policy": { ... },
"issuer": { ... },
"freshness": { ... },
"signatures": { ... },
"transparency": { ... },
"issues": [ "bundle_hash_mismatch" ]
}
```
| Field | Type | Description |
|-------|------|-------------|
| `overallStatus` | string (`pass`, `warn`, `fail`, `skipped`) | Aggregated verdict derived from the individual section statuses. |
| `succeeded` | bool | Convenience flag; `true` when `overallStatus ∈ {pass, warn}`. |
| `policy` | object | Results from policy evaluation (see below). |
| `issuer` | object | Identity/result of the signing entity. |
| `freshness` | object | Age analysis relative to policy settings. |
| `signatures` | object | Signature validation summary. |
| `transparency` | object | Inclusion proof / checkpoint evaluation summary. |
| `issues` | array[string] | De-duplicated set drawn from the sections; order is deterministic and stable. |
### 2.1 `policy`
| Field | Description |
|-------|-------------|
| `status` | Section verdict (`pass`, `warn`, `fail`, `skipped`). |
| `policyId` / `policyVersion` | DSL identifier and revision used for evaluation. |
| `verdict` | Policy outcome (`allow`, `challenge`, `deny`, etc.). |
| `issues` | Policy-specific explainability codes (e.g., `policy_rule_blocked`). |
| `attributes` | Key/value map emitted by the policy for downstream observability (e.g., applicable rules, matched waivers). |
### 2.2 `issuer`
| Field | Description |
|-------|-------------|
| `status` | Result of issuer validation. |
| `mode` | Signing mode detected (`keyless`, `kms`, `unknown`). |
| `issuer` | Distinguished name / issuer URI recorded during signing. |
| `subjectAlternativeName` | SAN pulled from the Fulcio certificate (keyless) or recorded KMS identity. |
| `keyId` | Logical key identifier associated with the signature. |
| `issues` | Issuer-specific issues (e.g., `issuer_trust_root_mismatch`, `signer_mode_unsupported:kid`). |
### 2.3 `freshness`
| Field | Description |
|-------|-------------|
| `status` | `fail` when the attestation exceeds `verification.freshnessMaxAgeMinutes`; `warn` when only the warning threshold is hit. |
| `createdAt` | Timestamp embedded in the attestation metadata. |
| `evaluatedAt` | Server-side timestamp used for age calculations. |
| `age` | ISO8601 duration of `evaluatedAt - createdAt`. |
| `maxAge` | Policy-driven ceiling (null when unchecked). |
| `issues` | `freshness_max_age_exceeded`, `freshness_warning`, etc. |
### 2.4 `signatures`
| Field | Description |
|-------|-------------|
| `status` | Signature validation verdict. |
| `bundleProvided` | `true` when canonical DSSE bytes were supplied. |
| `totalSignatures` | Count observed in the DSSE envelope. |
| `verifiedSignatures` | Number of signatures that validated against trusted keys. |
| `requiredSignatures` | Policy / configuration minimum enforced. |
| `issues` | Signature codes such as `bundle_payload_invalid_base64`, `signature_invalid`, `signer_mode_unknown`. |
### 2.5 `transparency`
| Field | Description |
|-------|-------------|
| `status` | Inclusion proof / checkpoint verdict. |
| `proofPresent` | Whether a proof document was available. |
| `checkpointPresent` | Indicates the Rekor checkpoint existed and parsed. |
| `inclusionPathPresent` | `true` when the Merkle path array contained nodes. |
| `issues` | Merkle/rekor codes (`proof_missing`, `proof_leafhash_mismatch`, `checkpoint_missing`, `proof_root_mismatch`). |
### 2.6 Issue catalogue (non-exhaustive)
| Code | Trigger | Notes |
|------|---------|-------|
| `bundle_hash_mismatch` | Canonical DSSE hash differs from stored value. | Often indicates tampering or inconsistent canonicalisation. |
| `bundle_payload_invalid_base64` | DSSE payload cannot be base64-decoded. | Validate producer pipeline; the attestation is unusable. |
| `signature_invalid` | At least one signature failed cryptographic verification. | Consider checking key rotation / revocation status. |
| `signer_mode_unknown` / `signer_mode_unsupported:<mode>` | Signing mode not configured for this installation. | Update `attestorOptions.security.signerIdentity.mode`. |
| `issuer_trust_root_mismatch` | Certificate chain does not terminate in configured Fulcio/KMS roots. | Check Fulcio bundle / KMS configuration. |
| `freshness_max_age_exceeded` | Attestation older than permitted maximum. | Regenerate attestation or extend policy window. |
| `proof_missing` | No inclusion proof stored or supplied. | When running offline, import bundles with proofs or allow warn-level policies. |
| `proof_root_mismatch` | Rebuilt Merkle root differs from checkpoint. | Proof may be stale or log compromised; escalate. |
| `checkpoint_missing` | No Rekor checkpoint available. | Configure `RequireCheckpoint=false` to downgrade severity. |
Downstream consumers (UI, CLI, policy studio) should render human-readable messages but must retain the exact issue codes for automation and audit replay.
## 3. Explainability signals
1. **Canonicalisation.** The service replays DSSE canonicalisation to derive `bundleSha256`. Failures surface as `bundle_hash_mismatch` or decoding errors.
2. **Signature checks.** Mode-aware handling:
- `kms` (HMAC) compares against configured shared secrets.
- `keyless` rebuilds the certificate chain, enforces Fulcio roots, SAN allow-lists, and verifies with the leaf certificate.
- Unknown modes emit `signer_mode_unknown` / `signer_mode_unsupported:<mode>`.
3. **Proof acquisition.** When `refreshProof` is requested the Rekor backend may contribute a textual issue (`Proof refresh failed: …`) without stopping evaluation.
4. **Merkle validation.** Structured helper ensures leaf hash, path orientation, and checkpoint root are consistent; each validation failure has a discrete issue code.
5. **Observability.** The meter `attestor.verify_total` increments with `result=ok|failed`; structured logs and traces carry the same `issues` vector for UI/CLI drill-down.
All issues are appended in detection order to simplify chronological replay in the Consoles chain-of-custody view.
## 3. Issue catalogue
| Code | Trigger | Operator guidance |
|------|---------|-------------------|
| `bundle_hash_mismatch` | Canonicalised DSSE hash differs from stored bundle hash. | Re-download artefact; investigate tampering or submission races. |
| `bundle_payload_invalid_base64` | Payload could not be base64-decoded. | Ensure bundle transport preserved payload; capture original DSSE for forensics. |
| `signature_invalid_kms` | HMAC verification failed for `mode=kms`. | Confirm shared secret alignment with Signer; rotate keys if drift detected. |
| `signer_mode_unknown` | Entry lacks signer mode metadata and bundle omitted it. | Re-ingest bundle or inspect submission pipeline metadata. |
| `signer_mode_unsupported:<mode>` | Signer mode is unsupported by the verifier. | Add support or block unsupported issuers in policy. |
| `kms_key_missing` | No configured KMS secrets to verify `mode=kms`. | Populate `security:signerIdentity:kmsKeys` in Attestor config before retry. |
| `signature_invalid_base64` | One or more signatures were not valid base64. | Bundle corruption; capture raw payload and re-submit. |
| `certificate_chain_missing` | `mode=keyless` bundle lacked any certificates. | Ensure Signer attaches Fulcio chain; review submission pipeline. |
| `certificate_chain_invalid` | Certificates could not be parsed. | Fetch original DSSE bundle for repair; confirm certificate encoding. |
| `certificate_chain_untrusted[:detail]` | Chain failed custom-root validation. | Import correct Fulcio roots or investigate potential impersonation. |
| `certificate_san_untrusted` | Leaf SAN not in configured allow-list. | Update allow-list or revoke offending issuer. |
| `signature_invalid` | No signature validated with supplied public keys. | Treat as tampering; trigger incident response. |
| `proof_missing` | No Merkle proof stored for the entry. | Re-run with `refreshProof=true`; check Rekor availability. |
| `bundle_hash_decode_failed` | Stored bundle hash could not be decoded. | Verify Mongo record integrity; re-enqueue submission if necessary. |
| `proof_inclusion_missing` | Inclusion section absent from proof. | Retry proof refresh; inspect Rekor health. |
| `proof_leafhash_decode_failed` | Leaf hash malformed. | Replay submission; inspect Rekor data corruption. |
| `proof_leafhash_mismatch` | Leaf hash differs from canonical bundle hash. | Raises tamper alert; reconcile Rekor entry vs stored bundle. |
| `proof_path_decode_failed` | Inclusion path entry malformed. | Same action as above; likely Rekor data corruption. |
| `proof_path_orientation_missing` | Inclusion path lacks left/right marker. | File Rekor bug; fallback to mirror log if configured. |
| `checkpoint_missing` | Proof lacks checkpoint metadata. | Retry refresh; ensure Rekor is configured to return checkpoints. |
| `checkpoint_root_decode_failed` | Checkpoint root hash malformed. | Investigate Rekor/mirror integrity before trusting log. |
| `proof_root_mismatch` | Computed root hash != checkpoint root. | Critical alert; assume inclusion proof compromised. |
| `Proof refresh failed: …` | Rekor fetch threw an exception. | Message includes upstream error; surface alongside telemetry for debugging. |
Future explainability flags must follow the same pattern: short, lowercase codes with optional suffix payload (`code:detail`).
## 4. Worked examples
### 4.1 Successful verification
```json
{
"ok": true,
"uuid": "0192fdb4-a82b-7f90-b894-6fd1dd918b85",
"index": 73421,
"logUrl": "https://rekor.stellaops.test/api/v2/log/entries/0192fdb4a82b7f90b8946fd1dd918b85",
"status": "included",
"checkedAt": "2025-11-01T17:06:52.182394Z",
"issues": []
}
```
This mirrors the happy-path asserted in `AttestorVerificationServiceTests.VerifyAsync_ReturnsOk_ForExistingUuid`, which replays the entire submission→verification loop.
### 4.2 Tampered bundle
```json
{
"ok": false,
"uuid": "0192fdb4-a82b-7f90-b894-6fd1dd918b85",
"index": 73421,
"logUrl": "https://rekor.stellaops.test/api/v2/log/entries/0192fdb4a82b7f90b8946fd1dd918b85",
"status": "included",
"checkedAt": "2025-11-01T17:09:05.443218Z",
"issues": [
"bundle_hash_mismatch",
"signature_invalid"
]
}
```
Derived from `AttestorVerificationServiceTests.VerifyAsync_FlagsTamperedBundle`, which flips the DSSE payload and expects both issues to surface. CLI and Console consumers should display these codes verbatim and provide remediation tips from the table above.
## 5. Validating the documentation
- Run `dotnet test src/Attestor/StellaOps.Attestor/StellaOps.Attestor.Tests` to exercise the scenarios behind the examples.
- API integrators can `curl` the verify endpoint and compare responses with the JSON above.
- UI/CLI teams should ensure explainability tooltips and runbooks reference the same issue catalogue.
Keeping the documentation aligned with the test suite guarantees explainability remains deterministic and audit-friendly.
## 6. Offline bundles & air-gapped verification
StellaOps Attestor now supports packaging attestations for sealed environments and rehydrating them without calling Rekor:
- **Export bundles.** `POST /api/v1/attestations:export` accepts either a list of Rekor UUIDs or filter criteria (`subject`, `type`, `issuer`, `scope`, `createdAfter|Before`, `limit`, `continuationToken`) and returns an `attestor.bundle.v1` document. Each item contains the attestation entry, canonical DSSE payload (base64), optional proof payload, and metadata. Responses include a `continuationToken` so callers can page through large result sets (limits default to 100 and are capped at 200). JSON content is required and requests are gated by the `attestor.read` scope.
- **Import bundles.** `POST /api/v1/attestations:import` ingests the bundle document, upserts attestation metadata, and restores the canonical DSSE/proof into the configured archive store. The S3 archive integration must be enabled; the response reports how many entries were imported versus updated, any skipped items, and issue codes (`bundle_payload_invalid_base64`, `bundle_hash_mismatch`, `archive_disabled`, …).
- **Offline verification.** When replaying verification without log connectivity, submit the DSSE bundle and set `offline=true` on `POST /api/v1/rekor/verify`. The service reuses imported proofs when present and surfaces deterministic explainability codes (`proof_missing`, `proof_inclusion_missing`, …) instead of attempting Rekor fetches.
Tests `AttestorBundleServiceTests.ExportAsync_AppliesFiltersAndContinuation`, `AttestationBundleEndpointsTests`, `AttestorVerificationServiceTests.VerifyAsync_OfflineSkipsProofRefreshWhenMissing`, and `AttestorVerificationServiceTests.VerifyAsync_OfflineUsesImportedProof` exercise the exporter/importer, API contracts, and the offline verification path with and without witness data.
1. **Ingest**: receive DSSE, validate schema, hash subjects, store envelope + metadata.
2. **Verify**: run policy checks (issuer, predicate, transparency optional), compute verification record.
3. **Persist**: store verification result with `verificationId`, `attestationId`, `policyVersion`, timestamps.
4. **Bulk ops**: batch verify envelopes; export results to timeline/audit logs.
5. **Audit**: expose read API for verification records; include determinism hash of inputs.

View File

@@ -0,0 +1,49 @@
# CLI Airgap Guide (DOCS-AIRGAP-57-003)
Offline/air-gapped usage patterns for the Stella CLI.
## Prerequisites
- CLI installed from offline bundle; `local-nugets/` and cached plugins available.
- Mirror/Bootstrap bundles staged locally; no external network required.
- Set `STELLA_OFFLINE=true` to prevent outbound fetches.
## Common commands
- Validate mirror bundle
```bash
stella airgap verify-bundle /mnt/media/mirror.tar \
--manifest /mnt/media/manifest.json \
--trust-root /opt/stella/trust/mirror-root.pem
```
- Import bundle into local registry
```bash
stella airgap import --bundle /mnt/media/mirror.tar --generation 12
```
- Check sealed mode status
```bash
stella airgap status
```
- List bundles and staleness
```bash
stella airgap list --format table
```
## Determinism & offline rules
- Commands must succeed without egress; any outbound attempt is a bug—report with logs.
- Hashes and signatures are verified locally using bundled trust roots; no OCSP/CRL.
- Outputs are stable JSON/NDJSON; timestamps use UTC.
## Exit codes
- `0` success
- `2` validation failed (hash/signature mismatch)
- `3` sealed-mode violation (unexpected egress attempted)
- `4` input/argument error
- `>4` unexpected error (inspect logs)
## Logs
- Default stderr structured JSON: includes `tenant`, `bundleId`, `mirrorGeneration`, `sealed` flag.
- For audits, use `--log-file /var/log/stella/airgap.log --log-format json`.
## Tips
- Keep bundles on read-only media to avoid hash drift.
- Use `--dry-run` to validate without writing to registries.
- Pair with `docs/airgap/overview.md` and `docs/airgap/sealing-and-egress.md` for policy context.

View File

@@ -0,0 +1,25 @@
# CLI Attest Guide (DOCS-ATTEST-74-004)
How to verify and inspect attestations via CLI.
## Verify DSSE
```bash
stella attest verify --envelope bundle.dsse.json --policy policy.json \
--root keys/root.pem --transparency-checkpoint checkpoints/rekor.json
```
- Offline verification uses bundled roots and checkpoints; transparency optional.
## List attestations
```bash
stella attest list --tenant default --issuer dev-kms --format table
```
## Show attestation
```bash
stella attest show --id a1b2c3 --output json
```
## Notes
- No network access required in sealed mode.
- All commands emit deterministic JSON; timestamps in UTC.
- Exit codes: 0 success, 2 verification failed, 4 input error.

View File

@@ -0,0 +1,39 @@
# Excititor Locker Manifest (OBS-53-001)
Defines the manifest for evidence snapshots stored in Evidence Locker / sealed-mode bundles.
## Manifest structure
```json
{
"tenant": "default",
"manifestId": "locker:excititor:2025-11-23:0001",
"createdAt": "2025-11-23T23:10:00Z",
"items": [
{
"observationId": "vex:obs:sha256:...",
"providerId": "ubuntu-csaf",
"contentHash": "sha256:...",
"linksetId": "CVE-2024-0001:pkg:maven/org.demo/app@1.2.3",
"dsseEnvelopeHash": "sha256:...",
"provenance": {
"source": "mirror|ingest",
"mirrorGeneration": 12,
"exportCenterManifest": "sha256:..."
}
}
],
"merkleRoot": "sha256:...", // over `items[*].contentHash`
"signature": null, // populated in OBS-54-001 (DSSE)
"metadata": {"sealed": true}
}
```
## Rules
- `items` sorted by `observationId`, then `providerId`.
- `merkleRoot` uses SHA-256 over concatenated item hashes (stable order above).
- `signature` is a DSSE envelope (hash recorded in `dsseEnvelopeHash`) when OBS-54-001 is enabled; otherwise `null`.
- Manifests are immutable; version using `manifestId` suffix.
## Storage and replay
- Store manifests alongside payloads in object storage; key prefix: `locker/excititor/<tenant>/<manifestId>`.
- Replay tools must verify `merkleRoot` before loading payloads; reject if mismatched.

View File

@@ -0,0 +1,43 @@
# Excititor Timeline Events (OBS-52-001)
Defines the event envelope for evidence timelines emitted by Excititor. All fields are aggregation-only; no consensus/merge logic.
## Envelope
```json
{
"type": "excititor.timeline.v1",
"tenant": "default",
"eventId": "urn:uuid:...",
"timestamp": "2025-11-23T23:10:00Z",
"traceId": "beefcafe...",
"spanId": "deadb33f...",
"source": "excititor.web",
"kind": "observation|linkset",
"action": "ingest|update|backfill|replay",
"observationId": "vex:obs:sha256:...",
"linksetId": "CVE-2024-0001:pkg:maven/org.demo/app@1.2.3",
"justifications": ["component_not_present"],
"conflicts": [
{"providerId": "suse-csaf", "status": "fixed", "justification": null}
],
"evidenceHash": "sha256:...", // content-addressed payload hash
"dsseEnvelopeHash": "sha256:...", // if attested (see OBS-54-001)
"metadata": {"connector": "ubuntu-csaf", "mirrorGeneration": 12}
}
```
## Semantics
- `eventId` is stable per write; retries reuse the same ID.
- `timestamp` must be UTC; derive from TimeProvider.
- `traceId`/`spanId` propagate ingestion traces; if tracing is disabled, set both to `null`.
- `kind` + `action` drive downstream storage and alerting.
- `evidenceHash` is the raw document hash; `dsseEnvelopeHash` appears only when OBS-54-001 is enabled.
## Determinism
- Sort `justifications` and `conflicts` ascending by providerId/status before emit.
- Emit at-most-once per storage write; idempotent consumers rely on `(eventId, tenant)`.
## Transport
- Default topic: `excititor.timeline.v1` (NATS/Redis). Subject includes tenant: `excititor.timeline.v1.<tenant>`.
- Payload size should stay <32 KiB; truncate conflict arrays with `truncated=true` flag if needed (keep hash counts deterministic).

View File

@@ -37,6 +37,24 @@ Excititors evidence APIs now emit first-class OpenTelemetry metrics so Lens,
3. **Alerting**: add rules for high guard violation rates, missing signatures, and abnormal chunk bytes/record counts. Tie alerts back to connectors via tenant metadata.
4. **Post-deploy checks**: after each release, verify metrics emit by curling `/v1/vex/observations/...` and `/v1/vex/evidence/chunks`, watching the console exporter (dev) or OTLP (prod).
## SLOs (Sprint 119 OBS-51-001)
The following SLOs apply to Excititor evidence read paths when telemetry is enabled. Record them in the shared SLO registry and alert via the platform alertmanager.
| Surface | SLI | Target | Window | Burn alert | Notes |
| --- | --- | --- | --- | --- | --- |
| `/v1/vex/observations` | p95 latency | ≤ 450ms | 7d | 2% over 1h | Measured on successful responses only; tenant scoped. |
| `/v1/vex/observations` | freshness | ≥ 99% within 5min of upstream ingest | 7d | 5% over 4h | Derived from arrival minus `createdAt`; requires ingest clocks in UTC. |
| `/v1/vex/observations` | signature presence | ≥ 98% statements with signature present | 7d | 3% over 24h | Use `excititor.vex.signature.status{status="missing"}`. |
| `/v1/vex/evidence/chunks` | p95 stream duration | ≤ 600ms | 7d | 2% over 1h | From request start to last NDJSON write; excludes client disconnects. |
| `/v1/vex/evidence/chunks` | truncation rate | ≤ 1% truncated streams | 7d | 1% over 1h | `excititor.vex.chunks.records` with `truncated=true`. |
| AOC guardrail | zero hard violations | 0 | continuous | immediate | Any `excititor.vex.aoc.guard_violations` with severity `error` pages ops. |
Implementation notes:
- Emit latency/freshness SLOs via OTEL views that pre-aggregate by tenant and route to the platform SLO backend; keep bucket boundaries aligned with 50/100/250/450/650/1000ms.
- Freshness SLI derived from ingest timestamps; ensure clocks are synchronized (NTP) and stored in UTC.
- For air-gapped deployments without OTEL sinks, scrape console exporter and push to offline Prometheus; same thresholds apply.
## Related documents
- `docs/modules/excititor/architecture.md` API contract, AOC guardrails, connector responsibilities.

View File

@@ -101,4 +101,29 @@ Response 200:
- Determinism: responses sorted by `vulnerabilityId`, then `productKey`; arrays sorted lexicographically.
## SDK generation
- Use this file plus `vex_observations.md` as the source of truth for SDK examples in EXCITITOR-LNM-21-203.
- Source of truth for EXCITITOR-LNM-21-203 SDK samples (TypeScript/Go/Python) and OpenAPI snippets.
- Suggested generation inputs:
- Schema: this doc + `docs/modules/excititor/vex_observations.md` for field semantics.
- Auth: bearer token + `X-Stella-Tenant` header (required).
- Pagination: `cursor` (opaque) + `limit` (default 200, max 500).
- Minimal client example (TypeScript, fetch):
```ts
const resp = await fetch(
`${baseUrl}/v1/vex/observations?` + new URLSearchParams({
vulnerabilityId: "CVE-2024-0001",
productKey: "pkg:maven/org.demo/app@1.2.3",
limit: "100"
}),
{
headers: {
Authorization: `Bearer ${token}`,
"X-Stella-Tenant": "default"
}
}
);
const body = await resp.json();
```
- Determinism requirements for SDKs:
- Preserve server ordering; do not resort items client-side.
- Treat `cursor` as opaque; echo it back for next page.
- Keep enums case-sensitive as returned by API.

View File

@@ -31,10 +31,10 @@ OCI=1 scripts/mirror/ci-sign.sh
```
## Temporary dev key (to unblock CI until production key is issued)
Use this throwaway Ed25519 key only for non-production runs. Generated 2025-11-23 to replace the previous placeholder; rotate TUF metadata immediately after swapping in the production key.
Use this throwaway Ed25519 key only for non-production runs. Generated 2025-11-24 to replace the previous placeholder; rotate TUF metadata immediately after swapping in the production key.
```
MIRROR_SIGN_KEY_B64=LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1DNENBUUF3QlFZREsyVndCQ0lFSURqb3pDRVdKVVFUdW1xZ2gyRmZXcVBaemlQbkdaSzRvOFZRTThGYkZCSEcKLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLQo=
MIRROR_SIGN_KEY_B64=LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1DNENBUUF3QlFZREsyVndCQ0lFSUxGdFMwbjBpMVVueE1maGt0cDNlY1N4WHVxYmcrVFJuaENhS05jaGtTbFIKLS0tLS1FTkQgUFJJVkFURSBLRVktLS0tLQo=
```
**Do not ship with this key.** Set `REQUIRE_PROD_SIGNING=1` for release/tag builds so they fail without the real key. Add the production key as a Gitea secret (`MIRROR_SIGN_KEY_B64`) and rerun the workflow; remove this temporary key block once rotated.

View File

@@ -208,8 +208,17 @@ All payloads are immutable and include analyzer fingerprints (`scanner.native@sh
- **Scopes:** Mutations require `policy:*` scopes corresponding to action; `effective:write` restricted to service identity.
- **Tenancy:** All queries filter by `tenant`. Service identity uses `tenant-global` for shared policies; cross-tenant reads prohibited unless `policy:tenant-admin` scope present.
- **Secrets:** Configuration loaded via environment variables or sealed secrets; runtime avoids writing secrets to logs.
- **Determinism guard:** Static analyzer prevents referencing forbidden namespaces; runtime guard intercepts `DateTime.Now`, `Random`, `Guid`, HTTP clients beyond allow-list.
- **Sealed mode:** Global flag disables outbound network except allow-listed internal hosts; watchers fail fast if unexpected egress attempted.
- **Determinism guard:** Static analyzer prevents referencing forbidden namespaces; runtime guard intercepts `DateTime.Now`, `Random`, `Guid`, HTTP clients beyond allow-list.
- **Sealed mode:** Global flag disables outbound network except allow-listed internal hosts; watchers fail fast if unexpected egress attempted.
### Determinism enforcement (DOCS-POLICY-DET-01)
- **Inputs are ordered and frozen:** Selector emits batches sorted deterministically by `(tenant, policyId, vulnerabilityId, productKey, source)` with stable cursors; workers must not resort.
- **No ambient randomness or wall clocks:** Policy code relies on injected `TimeProvider`/`IRandom` shims; guards block `DateTime.Now`, `Guid.NewGuid`, `Random` when not injected.
- **Immutable evidence:** SBOM/VEX inputs carry content hashes; evaluator treats payloads as read-only and surfaces hashes in logs for replay.
- **Side effects prohibited:** Evaluator cannot call external HTTP except allow-listed internal services (Authority, Storage) and must not write files outside temp workspace.
- **Replay hash:** Each batch computes `determinismHash = SHA256(policyVersion + batchCursor + inputsHash)`; included in logs and run exports.
- **Testing:** Determinism tests run the same batch twice with seeded clock/GUID providers and assert identical outputs + determinismHash; add a test per policy package.
---

View File

@@ -0,0 +1,29 @@
{
"knobs": [
{
"name": "ai_signal_weight",
"default_value": 1.2,
"min": 0.0,
"max": 2.0,
"step": 0.1,
"description": "Weight applied to Advisory AI signal scores"
},
{
"name": "reachability_boost",
"default_value": 0.25,
"min": 0.0,
"max": 1.0,
"step": 0.05,
"description": "Boost when asset is reachable"
},
{
"name": "time_decay_half_life_days",
"default_value": 45,
"min": 1,
"max": 365,
"step": 1,
"description": "Half-life in days for signal decay"
}
],
"profile_hash": "ADVISORYAIHASH"
}

View File

@@ -0,0 +1,16 @@
{
"job_id": "01HZX1QJP6Z3MNA0Q2T3VCPV5K",
"tenant_id": "acme",
"context_id": "ctx-2025-11-24T10:00:00Z",
"policy_profile_hash": "overlay-hash-123",
"priority": "high",
"requested_at": "2025-11-24T10:00:00Z",
"status": "queued",
"trace_ref": "4E5C2B5E22F928E846B0EFBC58AA53FC3218C8C172199FF52C7C09244E0C0D30",
"determinism_hash": "2C855E80F66D30D5E51C4D9A0441A63C5BB8F04DC1EC537D0ADB7B9357A4C713",
"batch_items": [
{ "component_purl": "pkg:npm/alpha@1.0.0", "advisory_id": "ADV-1" },
{ "component_purl": "pkg:npm/zeta@1.0.0", "advisory_id": "ADV-2" }
],
"callbacks": { "sse": "sse://events", "nats": "policy.jobs" }
}

View File

@@ -0,0 +1,11 @@
{
"tenant_id": "acme",
"policy_profile_hash": "overlay-hash-123",
"knobs_version": "knobs-v1",
"overlay_hash": "overlay-hash-123",
"items": [
{ "component_purl": "pkg:npm/lodash@4.17.21", "advisory_id": "ADV-2025-0001" },
{ "component_purl": "pkg:npm/left-pad@1.3.0", "advisory_id": "ADV-2025-0002" }
],
"options": { "include_reachability": true }
}

View File

@@ -0,0 +1,32 @@
{
"tenant_id": "acme",
"component_purl": "pkg:npm/alpha@1.0.0",
"advisory_id": "ADV-1",
"conflicts": [
{
"tenant_id": "acme",
"snapshot_id": "01HZX3GN4V6KBW1PXJ0K3VXEGT",
"component_purl": "pkg:npm/alpha@1.0.0",
"advisory_id": "ADV-1",
"severity_fused": "high",
"score": 0.900,
"sources": [
{ "source": "policy-engine", "weight": 1.050, "severity": "high", "score": 0.945 }
],
"reason_codes": ["weights-applied", "deterministic-fusion"]
},
{
"tenant_id": "acme",
"snapshot_id": "01HZX3GN4V6KBW1PXJ0K3VXEGT",
"component_purl": "pkg:npm/alpha@1.0.0",
"advisory_id": "ADV-1",
"severity_fused": "medium",
"score": 0.600,
"sources": [
{ "source": "policy-engine", "weight": 1.050, "severity": "medium", "score": 0.630 }
],
"reason_codes": ["weights-applied", "deterministic-fusion"]
}
],
"resolved_status": null
}

View File

@@ -0,0 +1,36 @@
{
"manifest": {
"export_id": "01HZX2KDRT9Q9K5AZXWPRH62VE",
"schema_version": "policy-ledger-export-v1",
"generated_at": "2025-11-24T15:00:00Z",
"record_count": 2,
"sha256": "D4B8C98A2F946D93AFBDE6C4DE6535853A223E108A4A2C389E2C2623D3761C1E"
},
"records": [
{
"tenant_id": "acme",
"job_id": "job-1",
"context_id": "ctx",
"component_purl": "pkg:npm/alpha@1.0.0",
"advisory_id": "ADV-1",
"status": "violation",
"trace_ref": "trace-a",
"occurred_at": "2025-11-24T15:00:00Z"
},
{
"tenant_id": "acme",
"job_id": "job-1",
"context_id": "ctx",
"component_purl": "pkg:npm/zeta@1.0.0",
"advisory_id": "ADV-2",
"status": "ok",
"trace_ref": "trace-b",
"occurred_at": "2025-11-24T15:00:00Z"
}
],
"lines": [
"{\"export_id\":\"01HZX2KDRT9Q9K5AZXWPRH62VE\",\"schema_version\":\"policy-ledger-export-v1\",\"generated_at\":\"2025-11-24T15:00:00Z\",\"record_count\":2,\"sha256\":\"D4B8C98A2F946D93AFBDE6C4DE6535853A223E108A4A2C389E2C2623D3761C1E\"}",
"{\"tenant_id\":\"acme\",\"job_id\":\"job-1\",\"context_id\":\"ctx\",\"component_purl\":\"pkg:npm/alpha@1.0.0\",\"advisory_id\":\"ADV-1\",\"status\":\"violation\",\"trace_ref\":\"trace-a\",\"occurred_at\":\"2025-11-24T15:00:00Z\"}",
"{\"tenant_id\":\"acme\",\"job_id\":\"job-1\",\"context_id\":\"ctx\",\"component_purl\":\"pkg:npm/zeta@1.0.0\",\"advisory_id\":\"ADV-2\",\"status\":\"ok\",\"trace_ref\":\"trace-b\",\"occurred_at\":\"2025-11-24T15:00:00Z\"}"
]
}

View File

@@ -0,0 +1,30 @@
{
"snapshot_id": "01HZX3GN4V6KBW1PXJ0K3VXEGT",
"tenant_id": "acme",
"ledger_export_id": "01HZX2KDRT9Q9K5AZXWPRH62VE",
"generated_at": "2025-11-24T16:00:00Z",
"overlay_hash": "overlay-1",
"status_counts": { "violation": 1, "ok": 1 },
"records": [
{
"tenant_id": "acme",
"job_id": "job-1",
"context_id": "ctx",
"component_purl": "pkg:npm/alpha@1.0.0",
"advisory_id": "ADV-1",
"status": "violation",
"trace_ref": "trace-a",
"occurred_at": "2025-11-24T15:00:00Z"
},
{
"tenant_id": "acme",
"job_id": "job-1",
"context_id": "ctx",
"component_purl": "pkg:npm/zeta@1.0.0",
"advisory_id": "ADV-2",
"status": "ok",
"trace_ref": "trace-b",
"occurred_at": "2025-11-24T15:00:00Z"
}
]
}

View File

@@ -0,0 +1,13 @@
{
"event_id": "E7A1F3B0D6F243B4868A6D4B3E7B2AB9",
"tenant_id": "acme",
"snapshot_id": "01HZX3GN4V6KBW1PXJ0K3VXEGT",
"policy_profile_hash": "overlay-hash-123",
"component_purl": "pkg:npm/alpha@1.0.0",
"advisory_id": "ADV-1",
"violation_code": "policy.violation.detected",
"severity": "high",
"status": "violation",
"trace_ref": "trace-a",
"occurred_at": "2025-11-24T16:00:00Z"
}

View File

@@ -0,0 +1,11 @@
{
"job_id": "01HZX1QJP6Z3MNA0Q2T3VCPV5K",
"worker_id": "worker-stub",
"started_at": "2025-11-24T13:00:00Z",
"completed_at": "2025-11-24T13:00:01Z",
"result_hash": "5E5A4EFA8C7E9952E4E5E5D9E2B9F3A5D46B13E44CB6E0D7292F7D5CB40CF182",
"results": [
{ "component_purl": "pkg:npm/alpha@1.0.0", "advisory_id": "ADV-1", "status": "violation", "trace_ref": "F5D9B8717EAB4B0252BE22325771C4F9F8ABAE4E7728F3221E15C5F24A8E8D9F" },
{ "component_purl": "pkg:npm/zeta@1.0.0", "advisory_id": "ADV-2", "status": "ok", "trace_ref": "3C75CC86A30B6E230D1DE2D5F08F9B0F5CF75AB1931E47372DC7AC2175BE3F6C" }
]
}

View File

@@ -0,0 +1,12 @@
{
"tenant_id": "acme",
"snapshot_id": "01HZX3GN4V6KBW1PXJ0K3VXEGT",
"component_purl": "pkg:npm/alpha@1.0.0",
"advisory_id": "ADV-1",
"severity_fused": "high",
"score": 0.900,
"sources": [
{ "source": "policy-engine", "weight": 1.050, "severity": "high", "score": 0.945 }
],
"reason_codes": ["weights-applied", "deterministic-fusion"]
}

View File

@@ -0,0 +1,23 @@
{
"weights": [
{
"source": "cartographer",
"weight": 1.000,
"justification": "default baseline",
"updated_at": "2025-11-23T12:00:00Z"
},
{
"source": "scanner",
"weight": 0.950,
"justification": "prefer curated SBOM sources",
"updated_at": "2025-11-23T12:00:00Z"
},
{
"source": "concelier",
"weight": 1.050,
"justification": "policy engine override",
"updated_at": "2025-11-23T12:00:00Z"
}
],
"profile_hash": "D1A5F0A0DEFAULTHASH"
}

View File

@@ -0,0 +1,27 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "advisory-ai-knobs@draft",
"type": "object",
"properties": {
"knobs": {
"type": "array",
"items": {
"type": "object",
"properties": {
"name": { "type": "string", "minLength": 1 },
"default_value": { "type": "number" },
"min": { "type": "number" },
"max": { "type": "number" },
"step": { "type": "number" },
"description": { "type": "string" }
},
"required": ["name", "default_value", "min", "max", "step", "description"],
"additionalProperties": false
},
"minItems": 1
},
"profile_hash": { "type": "string", "minLength": 1 }
},
"required": ["knobs", "profile_hash"],
"additionalProperties": false
}

View File

@@ -0,0 +1,51 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "orchestrator-job@draft",
"type": "object",
"properties": {
"job_id": { "type": "string", "minLength": 1 },
"tenant_id": { "type": "string", "minLength": 1 },
"context_id": { "type": "string", "minLength": 1 },
"policy_profile_hash": { "type": "string", "minLength": 1 },
"priority": { "type": "string", "enum": ["normal", "high", "emergency", "preview"] },
"requested_at": { "type": "string", "format": "date-time" },
"status": { "type": "string", "minLength": 1 },
"trace_ref": { "type": "string", "minLength": 1 },
"determinism_hash": { "type": "string", "minLength": 1 },
"completed_at": { "type": ["string", "null"], "format": "date-time" },
"result_hash": { "type": ["string", "null"] },
"batch_items": {
"type": "array",
"items": {
"type": "object",
"properties": {
"component_purl": { "type": "string", "minLength": 1 },
"advisory_id": { "type": "string", "minLength": 1 }
},
"required": ["component_purl", "advisory_id"],
"additionalProperties": false
},
"minItems": 1
},
"callbacks": {
"type": ["object", "null"],
"properties": {
"sse": { "type": ["string", "null"] },
"nats": { "type": ["string", "null"] }
},
"additionalProperties": false
}
},
"required": [
"job_id",
"tenant_id",
"context_id",
"policy_profile_hash",
"priority",
"requested_at",
"status",
"determinism_hash",
"batch_items"
],
"additionalProperties": false
}

View File

@@ -0,0 +1,41 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "policy-batch-context@draft",
"type": "object",
"properties": {
"tenant_id": { "type": "string", "minLength": 1 },
"policy_profile_hash": { "type": "string", "minLength": 1 },
"knobs_version": { "type": "string", "minLength": 1 },
"overlay_hash": { "type": "string", "minLength": 1 },
"items": {
"type": "array",
"items": {
"type": "object",
"properties": {
"component_purl": { "type": "string", "minLength": 1 },
"advisory_id": { "type": "string", "minLength": 1 }
},
"required": ["component_purl", "advisory_id"],
"additionalProperties": false
},
"minItems": 1
},
"options": {
"type": "object",
"properties": {
"include_reachability": { "type": "boolean" }
},
"required": ["include_reachability"],
"additionalProperties": false
}
},
"required": [
"tenant_id",
"policy_profile_hash",
"knobs_version",
"overlay_hash",
"items",
"options"
],
"additionalProperties": false
}

View File

@@ -0,0 +1,17 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "policy-conflict@draft",
"type": "object",
"properties": {
"tenant_id": { "type": "string", "minLength": 1 },
"component_purl": { "type": "string", "minLength": 1 },
"advisory_id": { "type": "string", "minLength": 1 },
"conflicts": {
"type": "array",
"items": { "$ref": "severity-fusion@draft.json" }
},
"resolved_status": { "type": ["string", "null"] }
},
"required": ["tenant_id", "component_purl", "advisory_id", "conflicts"],
"additionalProperties": false
}

View File

@@ -0,0 +1,40 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "policy-ledger-export@draft",
"type": "object",
"properties": {
"manifest": {
"type": "object",
"properties": {
"export_id": { "type": "string", "minLength": 1 },
"schema_version": { "type": "string", "minLength": 1 },
"generated_at": { "type": "string", "format": "date-time" },
"record_count": { "type": "integer", "minimum": 0 },
"sha256": { "type": "string", "minLength": 1 }
},
"required": ["export_id", "schema_version", "generated_at", "record_count", "sha256"],
"additionalProperties": false
},
"records": {
"type": "array",
"items": {
"type": "object",
"properties": {
"tenant_id": { "type": "string", "minLength": 1 },
"job_id": { "type": "string", "minLength": 1 },
"context_id": { "type": "string", "minLength": 1 },
"component_purl": { "type": "string", "minLength": 1 },
"advisory_id": { "type": "string", "minLength": 1 },
"status": { "type": "string", "minLength": 1 },
"trace_ref": { "type": "string", "minLength": 1 },
"occurred_at": { "type": "string", "format": "date-time" }
},
"required": ["tenant_id", "job_id", "context_id", "component_purl", "advisory_id", "status", "trace_ref", "occurred_at"],
"additionalProperties": false
}
},
"lines": { "type": "array", "items": { "type": "string" } }
},
"required": ["manifest", "records", "lines"],
"additionalProperties": false
}

View File

@@ -0,0 +1,33 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "policy-snapshot@draft",
"type": "object",
"properties": {
"snapshot_id": { "type": "string", "minLength": 1 },
"tenant_id": { "type": "string", "minLength": 1 },
"ledger_export_id": { "type": "string", "minLength": 1 },
"generated_at": { "type": "string", "format": "date-time" },
"overlay_hash": { "type": "string", "minLength": 1 },
"status_counts": { "type": "object", "additionalProperties": { "type": "integer" } },
"records": {
"type": "array",
"items": {
"type": "object",
"properties": {
"tenant_id": { "type": "string" },
"job_id": { "type": "string" },
"context_id": { "type": "string" },
"component_purl": { "type": "string" },
"advisory_id": { "type": "string" },
"status": { "type": "string" },
"trace_ref": { "type": "string" },
"occurred_at": { "type": "string", "format": "date-time" }
},
"required": ["tenant_id", "job_id", "context_id", "component_purl", "advisory_id", "status", "trace_ref", "occurred_at"],
"additionalProperties": false
}
}
},
"required": ["snapshot_id", "tenant_id", "ledger_export_id", "generated_at", "overlay_hash", "status_counts", "records"],
"additionalProperties": false
}

View File

@@ -0,0 +1,32 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "policy-violation-event@draft",
"type": "object",
"properties": {
"event_id": { "type": "string", "minLength": 1 },
"tenant_id": { "type": "string", "minLength": 1 },
"snapshot_id": { "type": "string", "minLength": 1 },
"policy_profile_hash": { "type": "string", "minLength": 1 },
"component_purl": { "type": "string", "minLength": 1 },
"advisory_id": { "type": "string", "minLength": 1 },
"violation_code": { "type": "string", "minLength": 1 },
"severity": { "type": "string", "minLength": 1 },
"status": { "type": "string", "minLength": 1 },
"trace_ref": { "type": "string", "minLength": 1 },
"occurred_at": { "type": "string", "format": "date-time" }
},
"required": [
"event_id",
"tenant_id",
"snapshot_id",
"policy_profile_hash",
"component_purl",
"advisory_id",
"violation_code",
"severity",
"status",
"trace_ref",
"occurred_at"
],
"additionalProperties": false
}

View File

@@ -0,0 +1,29 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "policy-worker-result@draft",
"type": "object",
"properties": {
"job_id": { "type": "string", "minLength": 1 },
"worker_id": { "type": "string", "minLength": 1 },
"started_at": { "type": "string", "format": "date-time" },
"completed_at": { "type": "string", "format": "date-time" },
"result_hash": { "type": "string", "minLength": 1 },
"results": {
"type": "array",
"items": {
"type": "object",
"properties": {
"component_purl": { "type": "string", "minLength": 1 },
"advisory_id": { "type": "string", "minLength": 1 },
"status": { "type": "string", "minLength": 1 },
"trace_ref": { "type": "string", "minLength": 1 }
},
"required": ["component_purl", "advisory_id", "status", "trace_ref"],
"additionalProperties": false
},
"minItems": 1
}
},
"required": ["job_id", "worker_id", "started_at", "completed_at", "result_hash", "results"],
"additionalProperties": false
}

View File

@@ -0,0 +1,30 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "severity-fusion@draft",
"type": "object",
"properties": {
"tenant_id": { "type": "string", "minLength": 1 },
"snapshot_id": { "type": "string", "minLength": 1 },
"component_purl": { "type": "string", "minLength": 1 },
"advisory_id": { "type": "string", "minLength": 1 },
"severity_fused": { "type": "string", "minLength": 1 },
"score": { "type": "number" },
"sources": {
"type": "array",
"items": {
"type": "object",
"properties": {
"source": { "type": "string", "minLength": 1 },
"weight": { "type": "number" },
"severity": { "type": "string", "minLength": 1 },
"score": { "type": "number" }
},
"required": ["source", "weight", "severity", "score"],
"additionalProperties": false
}
},
"reason_codes": { "type": "array", "items": { "type": "string" } }
},
"required": ["tenant_id", "snapshot_id", "component_purl", "advisory_id", "severity_fused", "score", "sources", "reason_codes"],
"additionalProperties": false
}

View File

@@ -0,0 +1,25 @@
{
"$schema": "http://json-schema.org/draft-07/schema#",
"title": "trust-weighting@draft",
"type": "object",
"properties": {
"weights": {
"type": "array",
"items": {
"type": "object",
"properties": {
"source": { "type": "string", "minLength": 1 },
"weight": { "type": "number" },
"justification": { "type": ["string", "null"] },
"updated_at": { "type": "string", "format": "date-time" }
},
"required": ["source", "weight", "updated_at"],
"additionalProperties": false
},
"minItems": 1
},
"profile_hash": { "type": "string", "minLength": 1 }
},
"required": ["weights", "profile_hash"],
"additionalProperties": false
}

View File

@@ -44,8 +44,16 @@ Operational rules:
- `GET /console/sboms` — Console catalog with filters (artifact, license, scope, asset tags), cursor pagination, evaluation metadata, immutable JSON projection for drawer views.
- `GET /components/lookup?purl=...` — component neighborhood for global search/Graph overlays; returns caches hints + tenant enforcement.
- `POST /entrypoints` / `GET /entrypoints` — manage entrypoint/service node overrides feeding Cartographer relevance; deterministic defaults when unset.
- `GET /sboms/{snapshotId}/projection` — Link-Not-Merge v1 projection returning hashes plus asset metadata (criticality, owner, environment, exposure flags, tags) alongside package/component graph.
- `GET /internal/sbom/events` — internal diagnostics endpoint returning the in-memory event outbox for validation.
- `POST /internal/sbom/events/backfill` — replays existing projections into the event stream; deterministic ordering, clock abstraction for tests.
- `GET /internal/sbom/asset-events` — diagnostics endpoint returning emitted `sbom.asset.updated` envelopes for validation and air-gap parity checks.
- `GET/POST /internal/orchestrator/sources` — list/register orchestrator ingest/index sources (deterministic seeds; idempotent on artifactDigest+sourceType).
- `GET/POST /internal/orchestrator/control` — manage pause/throttle/backpressure signals per tenant; metrics emitted for control updates.
- `GET/POST /internal/orchestrator/watermarks` — fetch/set backfill watermarks for reconciliation and deterministic replays.
- `GET /internal/sbom/resolver-feed` — list resolver candidates (artifact, purl, version, paths, scope, runtime_flag, nearest_safe_version).
- `POST /internal/sbom/resolver-feed/backfill` — clear and repopulate resolver feed from current projections.
- `GET /internal/sbom/resolver-feed/export` — NDJSON export of resolver candidates for air-gap delivery.
## 4) Ingestion & orchestrator integration
- Ingest sources: Scanner pipeline (preferred) or uploaded SPDX 3.0.1/CycloneDX 1.6 bundles.
@@ -70,7 +78,8 @@ Operational rules:
- Input validation: schema-validate incoming SBOMs; reject oversized/unsupported media types early.
## 8) Observability
- Metrics: `sbom_projection_seconds`, `sbom_projection_size_bytes`, `sbom_paths_latency_seconds`, `sbom_paths_cache_hit_ratio`, `sbom_events_backlog`.
- Metrics: `sbom_projection_seconds`, `sbom_projection_size_bytes`, `sbom_projection_queries_total`, `sbom_paths_latency_seconds`, `sbom_paths_cache_hit_ratio`, `sbom_events_backlog`.
- Tracing: ActivitySource `StellaOps.SbomService` (entrypoints, component lookup, console catalog, projections, events).
- Traces: wrap ingest, projection build, and API handlers; propagate orchestrator job IDs.
- Logs: structured, include tenant + artifact digest + sbomVersion; classify ingest failures (schema, storage, orchestrator, validation).
- Alerts: backlog thresholds for outbox/event delivery; high latency on path/timeline endpoints.

View File

@@ -1 +1 @@
[{"snapshotId":"snap-001","tenantId":"tenant-a","projection":{"purl":"pkg:npm/lodash@4.17.21","paths":[],"metadata":{"schemaVersion":"1.0.0"}}}]
[{"snapshotId":"snap-001","tenantId":"tenant-a","projection":{"purl":"pkg:npm/lodash@4.17.21","paths":[],"metadata":{"schemaVersion":"1.0.0","asset":{"criticality":"high","owner":"team-console","environment":"prod","exposure":["internet","pci"],"tags":{"tier":"1","service":"sample-api"}}}}}]