docs consolidation and others
This commit is contained in:
@@ -17,7 +17,7 @@ This contract defines the provenance tracking for runtime facts, callgraph stora
|
||||
| Schema | Location |
|
||||
|--------|----------|
|
||||
| Provenance Feed | `docs/schemas/provenance-feed.schema.json` |
|
||||
| Runtime Facts | `docs/signals/runtime-facts.md` |
|
||||
| Runtime Facts | `docs/modules/signals/guides/runtime-facts.md` |
|
||||
| Reachability Input | `docs/modules/policy/contracts/reachability-input-contract.md` |
|
||||
|
||||
## 3. CAS Storage Architecture
|
||||
|
||||
91
docs/modules/signals/events/README.md
Normal file
91
docs/modules/signals/events/README.md
Normal file
@@ -0,0 +1,91 @@
|
||||
# Event Envelope Schemas
|
||||
|
||||
Platform services publish strongly typed events; the JSON Schemas in this directory define those envelopes. File names follow `<event-name>@<version>.json` so producers and consumers can negotiate contracts explicitly.
|
||||
|
||||
## Catalog
|
||||
**Orchestrator envelopes (ORCH-SVC-38-101)**
|
||||
- `scanner.event.report.ready@1.json` — orchestrator event emitted when a signed report is persisted. Supersedes the legacy `scanner.report.ready@1` schema and adds versioning, idempotency keys, and trace context. Consumers: Orchestrator bus, Notifications Studio, UI timeline.
|
||||
- `scanner.event.scan.completed@1.json` — orchestrator event emitted when a scan run finishes. Supersedes the legacy `scanner.scan.completed@1` schema. Consumers: Orchestrator bus, Notifications Studio, Scheduler replay tooling.
|
||||
|
||||
**Legacy envelopes (Redis-backed)**
|
||||
- `scanner.report.ready@1.json` — legacy Redis stream event emitted once a signed report is persisted (kept for transitional compatibility).
|
||||
- `scanner.scan.completed@1.json` — legacy Redis stream event emitted alongside the signed report for automation.
|
||||
- `scheduler.rescan.delta@1.json` — emitted by Scheduler when BOM-Index diffs require fresh scans. Consumers: Notify, Policy Engine.
|
||||
- `scheduler.graph.job.completed@1.json` — emitted when a Cartographer graph build/overlay job finishes (`status = completed|failed|cancelled`). Consumers: Scheduler WebService (lag metrics/API), Cartographer cache warmers, UI overlay freshness indicators.
|
||||
- `attestor.logged@1.json` — emitted by Attestor after storing the Rekor inclusion proof. Consumers: UI attestation panel, Governance exports.
|
||||
|
||||
Additive payload changes (new optional fields) can stay within the same version. Any breaking change (removing a field, tightening validation, altering semantics) must increment the `@<version>` suffix and update downstream consumers. For full orchestrator guidance see [`orchestrator-scanner-events.md`](orchestrator-scanner-events.md).
|
||||
|
||||
## Envelope structure
|
||||
|
||||
### Orchestrator envelope (version 1)
|
||||
| Field | Type | Notes |
|
||||
|-------|------|-------|
|
||||
| `eventId` | `uuid` | Globally unique per occurrence. |
|
||||
| `kind` | `string` | e.g., `scanner.event.report.ready`. |
|
||||
| `version` | `integer` | Schema version (`1` for the initial release). |
|
||||
| `tenant` | `string` | Multi‑tenant isolation key; mirror the value recorded in queue/PostgreSQL metadata. |
|
||||
| `occurredAt` | `date-time` | RFC 3339 UTC timestamp describing when the state transition happened. |
|
||||
| `recordedAt` | `date-time` | RFC 3339 UTC timestamp for durable persistence (optional but recommended). |
|
||||
| `source` | `string` | Producer identifier (`scanner.webservice`). |
|
||||
| `idempotencyKey` | `string` | Deterministic dedupe key (`scanner.event.*:<tenant>:<report|scan>`). |
|
||||
| `correlationId` | `string` | Ties the event to the originating scan/API request. |
|
||||
| `traceId` / `spanId` | `string` | W3C trace context propagated into downstream telemetry. |
|
||||
| `scope` | `object` | Optional block with at least `repo` and `digest`. |
|
||||
| `payload` | `object` | Event-specific body; schemas embed the canonical report and DSSE envelope. |
|
||||
| `attributes` | `object` | Optional metadata bag (`string` keys/values) for downstream correlation. |
|
||||
|
||||
For Scanner orchestrator events, `links` include console and API deep links (`report.ui`, `report.api`, etc.) plus optional attestation references when a DSSE envelope is present. See [`orchestrator-scanner-events.md`](orchestrator-scanner-events.md) for details.
|
||||
|
||||
### Legacy Redis envelope
|
||||
| Field | Type | Notes |
|
||||
|-------|------|-------|
|
||||
| `eventId` | `uuid` | Must be globally unique per occurrence; producers log duplicates as fatal. |
|
||||
| `kind` | `string` | Fixed per schema (e.g., `scanner.report.ready`). Downstream services reject unknown kinds or versions. |
|
||||
| `tenant` | `string` | Multi‑tenant isolation key; mirror the value recorded in queue/PostgreSQL metadata. |
|
||||
| `ts` | `date-time` | RFC 3339 UTC timestamp. Use monotonic clocks or atomic offsets so ordering survives retries. |
|
||||
| `scope` | `object` | Optional block used when the event concerns a specific image or repository. See schema for required fields (e.g., `repo`, `digest`). |
|
||||
| `payload` | `object` | Event-specific body. Schemas allow additional properties so producers can add optional hints (e.g., `reportId`, `quietedFindingCount`) without breaking consumers. See `docs/runtime/SCANNER_RUNTIME_READINESS.md` for the runtime consumer checklist covering these hints. |
|
||||
| `attributes` | `object` | Optional metadata bag (`string` keys/values) for downstream correlation (e.g., pipeline identifiers). Omit when unused to keep payloads concise. |
|
||||
|
||||
When adding new optional fields, document the behaviour in the schema’s `description` block and update the consumer checklist in the next sprint sync.
|
||||
|
||||
## Canonical samples & validation
|
||||
Reference payloads live under `docs/events/samples/`, mirroring the schema version (`<event-name>@<version>.sample.json`). They illustrate common field combinations, including the optional attributes that downstream teams rely on for UI affordances and audit trails. Scanner samples reuse the exact DSSE envelope checked into `samples/api/reports/report-sample.dsse.json`, and unit tests (`ReportSamplesTests`, `PlatformEventSchemaValidationTests`) guard that payloads stay canonical and continue to satisfy the published schemas.
|
||||
|
||||
Run the following loop offline to validate both schemas and samples:
|
||||
|
||||
```bash
|
||||
# Validate schemas (same check as CI)
|
||||
for schema in docs/events/*.json; do
|
||||
npx ajv compile -c ajv-formats -s "$schema"
|
||||
done
|
||||
|
||||
# Validate canonical samples against their schemas
|
||||
for sample in docs/events/samples/*.sample.json; do
|
||||
schema="docs/events/$(basename "${sample%.sample.json}").json"
|
||||
npx ajv validate -c ajv-formats -s "$schema" -d "$sample"
|
||||
done
|
||||
```
|
||||
|
||||
Consumers can copy the samples into integration tests to guarantee backwards compatibility. When emitting new event versions, include a matching sample and update this README so air-gapped operators stay in sync.
|
||||
|
||||
## CI validation
|
||||
The Docs CI workflow (`.gitea/workflows/docs.yml`) installs `ajv-cli` and compiles every schema on pull requests. Run the same check locally before opening a PR:
|
||||
|
||||
```bash
|
||||
for schema in docs/events/*.json; do
|
||||
npx ajv compile -c ajv-formats -s "$schema"
|
||||
done
|
||||
```
|
||||
|
||||
Tip: run `npm install --no-save ajv ajv-cli ajv-formats` once per clone so `npx` can resolve the tooling offline.
|
||||
|
||||
If a schema references additional files, include `-r` flags so CI and local runs stay consistent.
|
||||
|
||||
## Working with schemas
|
||||
- Producers should validate outbound payloads using the matching schema during unit tests.
|
||||
- Consumers should pin to a specific version and log when encountering unknown versions to catch missing migrations early.
|
||||
- Store real payload samples under `docs/events/samples/` (mirrors the schema version) and mirror them into `samples/events/` when you need fixtures in integration repositories.
|
||||
|
||||
Contact the Platform Events group in Docs Guild if you need help shaping a new event or version strategy.
|
||||
@@ -0,0 +1,58 @@
|
||||
{
|
||||
"$id": "https://stella-ops.org/schemas/events/advisoryai.evidence.bundle@0.json",
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "AdvisoryAI evidence bundle (draft v0)",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["bundleId", "advisoryId", "tenant", "generatedAt", "observations"],
|
||||
"properties": {
|
||||
"bundleId": {"type": "string", "description": "Deterministic bundle identifier (UUID or ULID)."},
|
||||
"advisoryId": {"type": "string", "description": "Upstream advisory identifier (vendor or CVE-style)."},
|
||||
"tenant": {"type": "string", "description": "Owning tenant."},
|
||||
"generatedAt": {"type": "string", "format": "date-time", "description": "UTC timestamp when bundle was assembled."},
|
||||
"schemaVersion": {"type": "integer", "default": 0},
|
||||
"observations": {
|
||||
"type": "array",
|
||||
"minItems": 1,
|
||||
"items": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["observationId", "source"],
|
||||
"properties": {
|
||||
"observationId": {"type": "string"},
|
||||
"source": {"type": "string", "description": "Publisher or feed name."},
|
||||
"purl": {"type": "string", "description": "Optional package URL."},
|
||||
"cve": {"type": "string"},
|
||||
"severity": {"type": "string", "description": "Publisher-reported severity label."},
|
||||
"cvss": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"vector": {"type": "string"},
|
||||
"score": {"type": "number"}
|
||||
}
|
||||
},
|
||||
"summary": {"type": "string"},
|
||||
"evidence": {
|
||||
"type": "object",
|
||||
"description": "Raw upstream statement or excerpt.",
|
||||
"additionalProperties": true
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"signatures": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["signature", "keyId"],
|
||||
"properties": {
|
||||
"signature": {"type": "string", "description": "Base64 signature over canonical JSON."},
|
||||
"keyId": {"type": "string"},
|
||||
"algorithm": {"type": "string"}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,211 @@
|
||||
{
|
||||
"$schema": "https://json-schema.org/draft/2020-12/schema",
|
||||
"$id": "https://stellaops.org/schemas/events/advisoryai.evidence.bundle@1.schema.json",
|
||||
"title": "AdvisoryAI Evidence Bundle Schema v1",
|
||||
"description": "Schema for AdvisoryAI evidence bundles containing advisory observations with CVSS vectors and optional signatures. Used by ExportCenter and Timeline services for evidence aggregation.",
|
||||
"type": "object",
|
||||
"required": ["bundleId", "advisoryId", "tenant", "generatedAt", "schemaVersion"],
|
||||
"$defs": {
|
||||
"cvssVector": {
|
||||
"type": "object",
|
||||
"title": "CVSS Vector",
|
||||
"description": "Common Vulnerability Scoring System vector and score",
|
||||
"properties": {
|
||||
"vector": {
|
||||
"type": ["string", "null"],
|
||||
"description": "CVSS vector string (v2, v3.0, v3.1, or v4.0)",
|
||||
"examples": [
|
||||
"CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
|
||||
"CVSS:4.0/AV:N/AC:L/AT:N/PR:N/UI:N/VC:H/VI:H/VA:H/SC:N/SI:N/SA:N"
|
||||
]
|
||||
},
|
||||
"score": {
|
||||
"type": ["number", "null"],
|
||||
"minimum": 0,
|
||||
"maximum": 10,
|
||||
"description": "CVSS base score (0.0 to 10.0)"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"signatureInfo": {
|
||||
"type": "object",
|
||||
"title": "Signature Information",
|
||||
"description": "Cryptographic signature for bundle authentication",
|
||||
"required": ["signature", "keyId"],
|
||||
"properties": {
|
||||
"signature": {
|
||||
"type": "string",
|
||||
"description": "Base64-encoded cryptographic signature"
|
||||
},
|
||||
"keyId": {
|
||||
"type": "string",
|
||||
"description": "Identifier of the signing key",
|
||||
"examples": ["sha256:abc123...", "stellaops-prod-2025"]
|
||||
},
|
||||
"algorithm": {
|
||||
"type": ["string", "null"],
|
||||
"description": "Signature algorithm used",
|
||||
"examples": ["ECDSA-P256-SHA256", "RSA-PSS-SHA256", "Ed25519"]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"advisoryObservation": {
|
||||
"type": "object",
|
||||
"title": "Advisory Observation",
|
||||
"description": "An individual advisory observation within the bundle",
|
||||
"required": ["observationId", "source"],
|
||||
"properties": {
|
||||
"observationId": {
|
||||
"type": "string",
|
||||
"description": "Unique identifier for this observation",
|
||||
"minLength": 1
|
||||
},
|
||||
"source": {
|
||||
"type": "string",
|
||||
"description": "Source of the observation (e.g., scanner, user, vex-lens)",
|
||||
"examples": ["scanner", "manual", "vex-lens", "advisoryai", "concelier"]
|
||||
},
|
||||
"purl": {
|
||||
"type": ["string", "null"],
|
||||
"description": "Package URL identifying the affected component",
|
||||
"pattern": "^pkg:[a-z]+/",
|
||||
"examples": ["pkg:npm/lodash@4.17.21", "pkg:maven/org.apache.logging.log4j/log4j-core@2.14.1"]
|
||||
},
|
||||
"cve": {
|
||||
"type": ["string", "null"],
|
||||
"description": "CVE identifier",
|
||||
"pattern": "^CVE-[0-9]{4}-[0-9]+$",
|
||||
"examples": ["CVE-2021-44228", "CVE-2024-12345"]
|
||||
},
|
||||
"severity": {
|
||||
"type": ["string", "null"],
|
||||
"description": "Severity level",
|
||||
"enum": ["critical", "high", "medium", "low", "info", "unknown", null]
|
||||
},
|
||||
"cvss": {
|
||||
"oneOf": [
|
||||
{ "$ref": "#/$defs/cvssVector" },
|
||||
{ "type": "null" }
|
||||
],
|
||||
"description": "CVSS vector and score"
|
||||
},
|
||||
"summary": {
|
||||
"type": ["string", "null"],
|
||||
"description": "Brief summary of the observation"
|
||||
},
|
||||
"evidence": {
|
||||
"type": ["object", "null"],
|
||||
"additionalProperties": true,
|
||||
"description": "Arbitrary evidence data attached to the observation",
|
||||
"examples": [
|
||||
{
|
||||
"reachability": "reachable",
|
||||
"callPaths": ["main() -> vulnerable_func()"],
|
||||
"exploitMaturity": "poc"
|
||||
}
|
||||
]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"properties": {
|
||||
"bundleId": {
|
||||
"type": "string",
|
||||
"description": "Unique identifier for this evidence bundle",
|
||||
"minLength": 1,
|
||||
"examples": ["bundle-550e8400-e29b-41d4-a716-446655440000"]
|
||||
},
|
||||
"advisoryId": {
|
||||
"type": "string",
|
||||
"description": "Identifier of the related advisory or assessment",
|
||||
"minLength": 1,
|
||||
"examples": ["advisory-2025-001", "assessment-abc123"]
|
||||
},
|
||||
"tenant": {
|
||||
"type": "string",
|
||||
"description": "Tenant identifier (may be UUID or name)",
|
||||
"minLength": 1,
|
||||
"examples": ["00000000-0000-0000-0000-000000000001", "acme-corp"]
|
||||
},
|
||||
"generatedAt": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"description": "ISO 8601 timestamp when the bundle was generated"
|
||||
},
|
||||
"schemaVersion": {
|
||||
"type": "integer",
|
||||
"minimum": 0,
|
||||
"description": "Schema version number for this bundle format",
|
||||
"default": 1
|
||||
},
|
||||
"observations": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"$ref": "#/$defs/advisoryObservation"
|
||||
},
|
||||
"default": [],
|
||||
"description": "List of advisory observations in this bundle"
|
||||
},
|
||||
"signatures": {
|
||||
"type": ["array", "null"],
|
||||
"items": {
|
||||
"$ref": "#/$defs/signatureInfo"
|
||||
},
|
||||
"description": "Optional cryptographic signatures for bundle verification"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false,
|
||||
"examples": [
|
||||
{
|
||||
"bundleId": "bundle-550e8400-e29b-41d4-a716-446655440000",
|
||||
"advisoryId": "assessment-log4shell-2024",
|
||||
"tenant": "00000000-0000-0000-0000-000000000001",
|
||||
"generatedAt": "2025-12-07T10:30:00Z",
|
||||
"schemaVersion": 1,
|
||||
"observations": [
|
||||
{
|
||||
"observationId": "obs-001",
|
||||
"source": "scanner",
|
||||
"purl": "pkg:maven/org.apache.logging.log4j/log4j-core@2.14.1",
|
||||
"cve": "CVE-2021-44228",
|
||||
"severity": "critical",
|
||||
"cvss": {
|
||||
"vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:C/C:H/I:H/A:H",
|
||||
"score": 10.0
|
||||
},
|
||||
"summary": "Log4Shell RCE vulnerability detected in log4j-core",
|
||||
"evidence": {
|
||||
"reachability": "reachable",
|
||||
"callPaths": [
|
||||
"com.example.App.main() -> org.apache.logging.log4j.Logger.error()"
|
||||
],
|
||||
"exploitMaturity": "weaponized",
|
||||
"kevListed": true
|
||||
}
|
||||
},
|
||||
{
|
||||
"observationId": "obs-002",
|
||||
"source": "vex-lens",
|
||||
"purl": "pkg:maven/org.apache.logging.log4j/log4j-api@2.14.1",
|
||||
"cve": "CVE-2021-45105",
|
||||
"severity": "high",
|
||||
"cvss": {
|
||||
"vector": "CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:N/A:H",
|
||||
"score": 5.9
|
||||
},
|
||||
"summary": "Log4j2 infinite recursion DoS vulnerability"
|
||||
}
|
||||
],
|
||||
"signatures": [
|
||||
{
|
||||
"signature": "MEUCIQDx...",
|
||||
"keyId": "sha256:abc123def456...",
|
||||
"algorithm": "ECDSA-P256-SHA256"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
43
docs/modules/signals/events/attestor.logged@1.json
Normal file
43
docs/modules/signals/events/attestor.logged@1.json
Normal file
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"$id": "https://stella-ops.org/schemas/events/attestor.logged@1.json",
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"type": "object",
|
||||
"required": ["eventId", "kind", "tenant", "ts", "payload"],
|
||||
"properties": {
|
||||
"eventId": {"type": "string", "format": "uuid"},
|
||||
"kind": {"const": "attestor.logged"},
|
||||
"tenant": {"type": "string"},
|
||||
"ts": {"type": "string", "format": "date-time"},
|
||||
"payload": {
|
||||
"type": "object",
|
||||
"required": ["artifactSha256", "rekor", "subject"],
|
||||
"properties": {
|
||||
"artifactSha256": {"type": "string"},
|
||||
"rekor": {
|
||||
"type": "object",
|
||||
"required": ["uuid", "url"],
|
||||
"properties": {
|
||||
"uuid": {"type": "string"},
|
||||
"url": {"type": "string", "format": "uri"},
|
||||
"index": {"type": "integer", "minimum": 0}
|
||||
}
|
||||
},
|
||||
"subject": {
|
||||
"type": "object",
|
||||
"required": ["type", "name"],
|
||||
"properties": {
|
||||
"type": {"enum": ["sbom", "report", "vex-export"]},
|
||||
"name": {"type": "string"}
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": true
|
||||
},
|
||||
"attributes": {
|
||||
"type": "object",
|
||||
"description": "Optional event attributes for downstream correlation.",
|
||||
"additionalProperties": true
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
124
docs/modules/signals/events/orchestrator-scanner-events.md
Normal file
124
docs/modules/signals/events/orchestrator-scanner-events.md
Normal file
@@ -0,0 +1,124 @@
|
||||
# Scanner Orchestrator Events (ORCH-SVC-38-101)
|
||||
|
||||
Last updated: 2025-10-26
|
||||
|
||||
The Notifications Studio initiative (NOTIFY-SVC-38-001) and orchestrator backlog (ORCH-SVC-38-101) standardise how platform services emit lifecycle events. This document describes the Scanner WebService contract for the new **orchestrator envelopes** (`scanner.event.*`) and how they supersede the legacy Redis-backed `scanner.report.ready` / `scanner.scan.completed` events.
|
||||
|
||||
## 1. Envelope overview
|
||||
|
||||
Orchestrator events share a deterministic JSON envelope:
|
||||
|
||||
| Field | Type | Notes |
|
||||
|-------|------|-------|
|
||||
| `eventId` | `uuid` | Globally unique identifier generated per occurrence. |
|
||||
| `kind` | `string` | Event identifier; Scanner emits `scanner.event.report.ready` and `scanner.event.scan.completed`. |
|
||||
| `version` | `integer` | Schema version. Initial release uses `1`. |
|
||||
| `tenant` | `string` | Tenant that owns the scan/report. Mirrors Authority claims. |
|
||||
| `occurredAt` | `date-time` | UTC instant when the underlying state transition happened (e.g., report persisted). |
|
||||
| `recordedAt` | `date-time` | UTC instant when the event was durably written. Optional but recommended. |
|
||||
| `source` | `string` | Producer identifier (`scanner.webservice`). |
|
||||
| `idempotencyKey` | `string` | Deterministic key for duplicate suppression (see §4). |
|
||||
| `correlationId` | `string` | Maps back to the API request or scan identifier. |
|
||||
| `traceId` / `spanId` | `string` | W3C trace context propagated into downstream telemetry. |
|
||||
| `scope` | `object` | Describes the affected artefact. Requires `repo` and `digest`; optional `namespace`, `component`, `image`. |
|
||||
| `attributes` | `object` | Flat string map for frequently queried metadata (e.g., policy revision). |
|
||||
| `payload` | `object` | Event-specific body (see §2). |
|
||||
|
||||
Canonical schemas live under `docs/events/scanner.event.*@1.json`. Samples that round-trip through `NotifyCanonicalJsonSerializer` are stored in `docs/events/samples/`.
|
||||
|
||||
## 2. Event kinds and payloads
|
||||
|
||||
### 2.1 `scanner.event.report.ready`
|
||||
|
||||
Emitted once a signed report is persisted and attested. Payload highlights:
|
||||
|
||||
- `reportId` / `scanId` — identifiers for the persisted report and originating scan. Until Scan IDs are surfaced by the API, `scanId` mirrors `reportId` so downstream correlators can stabilise on a single key.
|
||||
- **Attributes:** `reportId`, `policyRevisionId`, `policyDigest`, `verdict` — pre-sorted for deterministic routing.
|
||||
- **Links:**
|
||||
- `report.ui` → `/ui/reports/{reportId}` on the current host.
|
||||
- `report.api` → `{apiBasePath}/{reportsSegment}/{reportId}` (defaults to `/api/v1/reports/{reportId}`).
|
||||
- `policy.ui` → `/ui/policy/revisions/{revisionId}` when a revision is present.
|
||||
- `policy.api` → `{apiBasePath}/{policySegment}/revisions/{revisionId}` when a revision is present.
|
||||
- `attestation.ui` → `/ui/attestations/{reportId}` when a DSSE envelope is included.
|
||||
- `attestation.api` → `{apiBasePath}/{reportsSegment}/{reportId}/attestation` when a DSSE envelope is included.
|
||||
- UI routes honour the configurable `scanner:console` options (`basePath`, `reportsSegment`, `policySegment`, `attestationsSegment`) so operators can move links under `/console` without code changes.
|
||||
- `imageDigest` — OCI image digest associated with the analysis.
|
||||
- `generatedAt` — report generation timestamp (ISO-8601 UTC).
|
||||
- `verdict` — `pass`, `warn`, or `fail` after policy evaluation.
|
||||
- `summary` — blocked/warned/ignored/quieted counters (all non-negative integers).
|
||||
- `delta` — newly critical/high counts and optional `kev` array.
|
||||
- `quietedFindingCount` — mirrors `summary.quieted`.
|
||||
- `policy` — revision metadata (`digest`, `revisionId`) surfaced for routing.
|
||||
- `links` — UI/report/policy URLs suitable for operators.
|
||||
- `dsse` — embedded DSSE envelope (payload, type, signature list).
|
||||
- `report` — canonical report document; identical to the DSSE payload.
|
||||
|
||||
Schema: `docs/events/scanner.event.report.ready@1.json`
|
||||
Sample: `docs/events/samples/scanner.event.report.ready@1.sample.json`
|
||||
|
||||
### 2.2 `scanner.event.scan.completed`
|
||||
|
||||
Emitted after scan execution finishes (success or policy failure). Payload highlights:
|
||||
|
||||
- `reportId` / `scanId` / `imageDigest` — identifiers mirroring the report-ready event. As with the report-ready payload, `scanId` currently mirrors `reportId` as a temporary shim.
|
||||
- **Attributes:** `reportId`, `policyRevisionId`, `policyDigest`, `verdict`.
|
||||
- **Links:** same as above (`report.*`, `policy.*`) with `attestation.*` populated when DSSE metadata exists.
|
||||
- `verdict`, `summary`, `delta`, `policy` — same semantics as above.
|
||||
- `findings` — array of surfaced findings with `id`, `severity`, optional `cve`, `purl`, and `reachability`.
|
||||
- `links`, `dsse`, `report` — same structure as §2.1 (allows Notifier to reuse signatures).
|
||||
|
||||
Schema: `docs/events/scanner.event.scan.completed@1.json`
|
||||
Sample: `docs/events/samples/scanner.event.scan.completed@1.sample.json`
|
||||
|
||||
### 2.3 Relationship to legacy events
|
||||
|
||||
| Legacy Redis event | Replacement orchestrator event | Notes |
|
||||
|--------------------|-------------------------------|-------|
|
||||
| `scanner.report.ready` | `scanner.event.report.ready` | Adds versioning, idempotency, trace context. Payload is a superset of the legacy fields. |
|
||||
| `scanner.scan.completed` | `scanner.event.scan.completed` | Same data plus explicit scan identifiers and orchestrator metadata. |
|
||||
|
||||
Legacy schemas remain for backwards-compatibility during migration, but new integrations **must** target the orchestrator variants.
|
||||
|
||||
## 3. Deterministic serialization
|
||||
|
||||
- Producers must serialise events using `NotifyCanonicalJsonSerializer` to guarantee consistent key ordering and whitespace.
|
||||
- Timestamps (`occurredAt`, `recordedAt`, `payload.generatedAt`) use `DateTimeOffset.UtcDateTime.ToString("O")`.
|
||||
- Payload arrays (`delta.kev`, `findings`) should be pre-sorted (e.g., alphabetical CVE order) so hash-based consumers remain stable.
|
||||
- Optional fields are omitted rather than emitted as `null`.
|
||||
|
||||
## 4. Idempotency and correlation
|
||||
|
||||
Idempotency keys dedupe repeated publishes and align with the orchestrator’s outbox pattern:
|
||||
|
||||
| Event kind | Idempotency key template |
|
||||
|------------|-------------------------|
|
||||
| `scanner.event.report.ready` | `scanner.event.report.ready:<tenant>:<reportId>` |
|
||||
| `scanner.event.scan.completed` | `scanner.event.scan.completed:<tenant>:<scanId>` |
|
||||
|
||||
Keys are ASCII lowercase; components should be trimmed and validated before concatenation. Retries must reuse the same key.
|
||||
|
||||
`correlationId` should match the scan identifier that appears in REST responses (`scanId`). Re-using the same value across the pair of events allows Notifier and orchestrator analytics to stitch lifecycle data together.
|
||||
|
||||
## 5. Versioning and evolution
|
||||
|
||||
- Increment the `version` field and the `@<version>` suffix for **breaking** changes (field removals, type changes, semantic shifts).
|
||||
- Additive optional fields may remain within version 1; update the JSON schema and samples accordingly.
|
||||
- When introducing `@2`, keep the `@1` schema/docs in place until orchestrator subscribers confirm migration.
|
||||
|
||||
## 6. Consumer checklist
|
||||
|
||||
1. Validate incoming payloads against the schema for the targeted version.
|
||||
2. Use `idempotencyKey` for dedupe, not `eventId`.
|
||||
3. Map `traceId`/`spanId` into telemetry spans to preserve causality.
|
||||
4. Prefer `payload.report` → `policy.revisionId` when populating templates; the top-level `attributes` are convenience duplicates for quick routing.
|
||||
5. Reserve the legacy Redis events for transitional compatibility only; downstream systems should subscribe to the orchestrator bus exposed by ORCH-SVC-38-101.
|
||||
|
||||
## 7. Implementation status and next actions
|
||||
|
||||
- **Scanner WebService** — `SCANNER-EVENTS-16-301` (blocked) and `SCANNER-EVENTS-16-302` (done) track the production of these envelopes. Dispatcher link customisation landed and samples updated; full `dotnet test` suite now succeeds after Surface cache ctor drift was patched and DSSE fixtures re-synced (2025-11-06).
|
||||
- **Gateway/Notifier consumers** — subscribe to the orchestrator stream documented in ORCH-SVC-38-101. When the Scanner tasks unblock, regenerate notifier contract tests against the sample events included here.
|
||||
- **Docs cadence** — update this file and the matching JSON schemas whenever payload fields change. Use the rehearsal checklist in `docs/modules/devops/runbooks/launch-cutover.md` to confirm downstream validation before the production cutover. Record gaps or newly required fields in `docs/modules/devops/runbooks/launch-readiness.md` so they land in the launch checklist.
|
||||
|
||||
---
|
||||
|
||||
**Imposed rule reminder:** work of this type or tasks of this type on this component must also be applied everywhere else it should be applied.
|
||||
@@ -0,0 +1,32 @@
|
||||
{
|
||||
"bundleId": "19bd7cf7-c7a6-4c1c-9b9c-6f2f794e9b1a",
|
||||
"advisoryId": "CVE-2025-12345",
|
||||
"tenant": "demo-tenant",
|
||||
"generatedAt": "2025-11-18T12:00:00Z",
|
||||
"schemaVersion": 0,
|
||||
"observations": [
|
||||
{
|
||||
"observationId": "obs-001",
|
||||
"source": "vendor.psirt",
|
||||
"purl": "pkg:maven/org.example/app@1.2.3",
|
||||
"cve": "CVE-2025-12345",
|
||||
"severity": "critical",
|
||||
"cvss": {
|
||||
"vector": "CVSS:3.1/AV:N/AC:L/PR:N/UI:N/S:U/C:H/I:H/A:H",
|
||||
"score": 9.8
|
||||
},
|
||||
"summary": "Remote code execution via deserialization of untrusted data.",
|
||||
"evidence": {
|
||||
"statement": "Vendor confirms unauthenticated RCE in versions <1.2.4",
|
||||
"references": ["https://example.com/advisory"]
|
||||
}
|
||||
}
|
||||
],
|
||||
"signatures": [
|
||||
{
|
||||
"signature": "MEQCID...==",
|
||||
"keyId": "authority-root-1",
|
||||
"algorithm": "ecdsa-p256-sha256"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
{
|
||||
"eventId": "1fdcaa1a-7a27-4154-8bac-cf813d8f4f6f",
|
||||
"kind": "attestor.logged",
|
||||
"tenant": "tenant-acme-solar",
|
||||
"ts": "2025-10-18T15:45:27+00:00",
|
||||
"payload": {
|
||||
"artifactSha256": "sha256:8927d9151ad3f44e61a9c647511f9a31af2b4d245e7e031fe5cb4a0e8211c5d9",
|
||||
"dsseEnvelopeDigest": "sha256:51c1dd189d5f16cfe87e82841d67b4fbc27d6fa9f5a09af0cd7e18945fb4c2a9",
|
||||
"rekor": {
|
||||
"index": 563421,
|
||||
"url": "https://rekor.example/api/v1/log/entries/d6d0f897e7244edc9cb0bb2c68b05c96",
|
||||
"uuid": "d6d0f897e7244edc9cb0bb2c68b05c96"
|
||||
},
|
||||
"signer": "cosign-stellaops",
|
||||
"subject": {
|
||||
"name": "scanner/report/sha256-0f0a8de5c1f93d6716b7249f6f4ea3a8",
|
||||
"type": "report"
|
||||
}
|
||||
},
|
||||
"attributes": {}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
{
|
||||
"eventId": "6d2d1b77-f3c3-4f70-8a9d-6f2d0c8801ab",
|
||||
"kind": "scanner.event.report.ready",
|
||||
"version": 1,
|
||||
"tenant": "tenant-alpha",
|
||||
"occurredAt": "2025-10-19T12:34:56+00:00",
|
||||
"recordedAt": "2025-10-19T12:34:57+00:00",
|
||||
"source": "scanner.webservice",
|
||||
"idempotencyKey": "scanner.event.report.ready:tenant-alpha:report-abc",
|
||||
"correlationId": "report-abc",
|
||||
"traceId": "0af7651916cd43dd8448eb211c80319c",
|
||||
"spanId": "b7ad6b7169203331",
|
||||
"scope": {
|
||||
"namespace": "acme/edge",
|
||||
"repo": "api",
|
||||
"digest": "sha256:feedface"
|
||||
},
|
||||
"payload": {
|
||||
"reportId": "report-abc",
|
||||
"scanId": "report-abc",
|
||||
"imageDigest": "sha256:feedface",
|
||||
"generatedAt": "2025-10-19T12:34:56+00:00",
|
||||
"verdict": "fail",
|
||||
"summary": {
|
||||
"total": 1,
|
||||
"blocked": 1,
|
||||
"warned": 0,
|
||||
"ignored": 0,
|
||||
"quieted": 0
|
||||
},
|
||||
"delta": {
|
||||
"newCritical": 1,
|
||||
"kev": [
|
||||
"CVE-2024-9999"
|
||||
]
|
||||
},
|
||||
"quietedFindingCount": 0,
|
||||
"policy": {
|
||||
"revisionId": "rev-42",
|
||||
"digest": "digest-123"
|
||||
},
|
||||
"links": {
|
||||
"report": {
|
||||
"ui": "https://scanner.example/ui/reports/report-abc",
|
||||
"api": "https://scanner.example/api/v1/reports/report-abc"
|
||||
},
|
||||
"policy": {
|
||||
"ui": "https://scanner.example/ui/policy/revisions/rev-42",
|
||||
"api": "https://scanner.example/api/v1/policy/revisions/rev-42"
|
||||
},
|
||||
"attestation": {
|
||||
"ui": "https://scanner.example/ui/attestations/report-abc",
|
||||
"api": "https://scanner.example/api/v1/reports/report-abc/attestation"
|
||||
}
|
||||
},
|
||||
"dsse": {
|
||||
"payloadType": "application/vnd.stellaops.report+json",
|
||||
"payload": "eyJyZXBvcnRJZCI6InJlcG9ydC1hYmMiLCJpbWFnZURpZ2VzdCI6InNoYTI1NjpmZWVkZmFjZSIsImdlbmVyYXRlZEF0IjoiMjAyNS0xMC0xOVQxMjozNDo1NiswMDowMCIsInZlcmRpY3QiOiJibG9ja2VkIiwicG9saWN5Ijp7InJldmlzaW9uSWQiOiJyZXYtNDIiLCJkaWdlc3QiOiJkaWdlc3QtMTIzIn0sInN1bW1hcnkiOnsidG90YWwiOjEsImJsb2NrZWQiOjEsIndhcm5lZCI6MCwiaWdub3JlZCI6MCwicXVpZXRlZCI6MH0sInZlcmRpY3RzIjpbeyJmaW5kaW5nSWQiOiJmaW5kaW5nLTEiLCJyZWFjaGFiaWxpdHkiOiJydW50aW1lIiwic2NvcmUiOjQ3LjUsInNvdXJjZVRydXN0IjoiTlZEIiwic3RhdHVzIjoiQmxvY2tlZCJ9XSwiaXNzdWVzIjpbXSwic3VyZmFjZSI6eyJ0ZW5hbnQiOiJ0ZW5hbnQtYWxwaGEiLCJnZW5lcmF0ZWRBdCI6IjIwMjUtMTAtMTlUMTI6MzQ6NTYrMDA6MDAiLCJtYW5pZmVzdERpZ2VzdCI6InNoYTI1Njo0ZmVlODdkMTg2MjkxZGRmYmJjYzJjNTZjOGVkMGU4Mjg1MjBiOGY1MmUxY2RlMGUxM2JiYTA4MmYxMDkxOGQ3IiwibWFuaWZlc3RVcmkiOiJjYXM6Ly9zY2FubmVyLWFydGlmYWN0cy9zY2FubmVyL3N1cmZhY2UvbWFuaWZlc3RzL3RlbmFudC1hbHBoYS9zaGEyNTYvNGYvZWUvNGZlZTg3ZDE4NjI5MWRkZmJiY2MyYzU2YzhlZDBlODI4NTIwYjhmNTJlMWNkZTBlMTNiYmEwODJmMTA5MThkNy5qc29uIiwibWFuaWZlc3QiOnsic2NoZW1hIjoic3RlbGxhb3BzLnN1cmZhY2UubWFuaWZlc3RAMSIsInRlbmFudCI6InRlbmFudC1hbHBoYSIsImltYWdlRGlnZXN0Ijoic2hhMjU2OmZlZWRmYWNlIiwiZ2VuZXJhdGVkQXQiOiIyMDI1LTEwLTE5VDEyOjM0OjU2KzAwOjAwIiwiYXJ0aWZhY3RzIjpbeyJraW5kIjoiZW50cnktdHJhY2UiLCJ1cmkiOiJjYXM6Ly9zY2FubmVyLWFydGlmYWN0cy9zY2FubmVyL2VudHJ5LXRyYWNlL2YwZjBmMGYwZjBmMGYwZjBmMGYwZjBmMGYwZjBmMGYwZjBmMGYwZjBmMGYwZjBmMGYwZjBmMGYwZjBmMGYwL2VudHJ5LXRyYWNlLmpzb24iLCJkaWdlc3QiOiJzaGEyNTY6ZjBmMGYwZjBmMGYwZjBmMGYwZjBmMGYwZjBmMGYwZjBmMGYwZjBmMGYwZjBmMGYwZjBmMGYwZjBmMGYwZjBmMCIsIm1lZGlhVHlwZSI6ImFwcGxpY2F0aW9uL2pzb24iLCJmb3JtYXQiOiJqc29uIiwic2l6ZUJ5dGVzIjo0MDk2fSx7ImtpbmQiOiJzYm9tLWludmVudG9yeSIsInVyaSI6ImNhczovL3NjYW5uZXItYXJ0aWZhY3RzL3NjYW5uZXIvaW1hZ2VzL2ZlZWRmYWNlL3Nib20uY2R4Lmpzb24iLCJkaWdlc3QiOiJzaGEyNTY6MTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMSIsIm1lZGlhVHlwZSI6ImFwcGxpY2F0aW9uL3ZuZC5jeWNsb25lZHgranNvbjt2ZXJzaW9uPTEuNjt2aWV3PWludmVudG9yeSIsImZvcm1hdCI6ImNkeC1qc29uIiwic2l6ZUJ5dGVzIjoyNDU3NiwidmlldyI6ImludmVudG9yeSJ9LHsia2luZCI6InNib20tdXNhZ2UiLCJ1cmkiOiJjYXM6Ly9zY2FubmVyLWFydGlmYWN0cy9zY2FubmVyL2ltYWdlcy9mZWVkZmFjZS9zYm9tLXVzYWdlLmNkeC5qc29uIiwiZGlnZXN0Ijoic2hhMjU2OjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIiLCJtZWRpYVR5cGUiOiJhcHBsaWNhdGlvbi92bmQuY3ljbG9uZWR4K2pzb247dmVyc2lvbj0xLjY7dmlldz11c2FnZSIsImZvcm1hdCI6ImNkeC1qc29uIiwic2l6ZUJ5dGVzIjoxNjM4NCwidmlldyI6InVzYWdlIn1dfX19",
|
||||
"signatures": [
|
||||
{
|
||||
"keyId": "test-key",
|
||||
"algorithm": "hs256",
|
||||
"signature": "signature-value"
|
||||
}
|
||||
]
|
||||
},
|
||||
"report": {
|
||||
"reportId": "report-abc",
|
||||
"imageDigest": "sha256:feedface",
|
||||
"generatedAt": "2025-10-19T12:34:56+00:00",
|
||||
"verdict": "blocked",
|
||||
"policy": {
|
||||
"revisionId": "rev-42",
|
||||
"digest": "digest-123"
|
||||
},
|
||||
"summary": {
|
||||
"total": 1,
|
||||
"blocked": 1,
|
||||
"warned": 0,
|
||||
"ignored": 0,
|
||||
"quieted": 0
|
||||
},
|
||||
"verdicts": [
|
||||
{
|
||||
"findingId": "finding-1",
|
||||
"reachability": "runtime",
|
||||
"score": 47.5,
|
||||
"sourceTrust": "NVD",
|
||||
"status": "Blocked"
|
||||
}
|
||||
],
|
||||
"issues": [],
|
||||
"surface": {
|
||||
"tenant": "tenant-alpha",
|
||||
"generatedAt": "2025-10-19T12:34:56+00:00",
|
||||
"manifestDigest": "sha256:4fee87d186291ddfbbcc2c56c8ed0e828520b8f52e1cde0e13bba082f10918d7",
|
||||
"manifestUri": "cas://scanner-artifacts/scanner/surface/manifests/tenant-alpha/sha256/4f/ee/4fee87d186291ddfbbcc2c56c8ed0e828520b8f52e1cde0e13bba082f10918d7.json",
|
||||
"manifest": {
|
||||
"schema": "stellaops.surface.manifest@1",
|
||||
"tenant": "tenant-alpha",
|
||||
"imageDigest": "sha256:feedface",
|
||||
"generatedAt": "2025-10-19T12:34:56+00:00",
|
||||
"artifacts": [
|
||||
{
|
||||
"kind": "entry-trace",
|
||||
"uri": "cas://scanner-artifacts/scanner/entry-trace/f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0/entry-trace.json",
|
||||
"digest": "sha256:f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0",
|
||||
"mediaType": "application/json",
|
||||
"format": "json",
|
||||
"sizeBytes": 4096
|
||||
},
|
||||
{
|
||||
"kind": "sbom-inventory",
|
||||
"uri": "cas://scanner-artifacts/scanner/images/feedface/sbom.cdx.json",
|
||||
"digest": "sha256:1111111111111111111111111111111111111111111111111111111111111111",
|
||||
"mediaType": "application/vnd.cyclonedx+json;version=1.6;view=inventory",
|
||||
"format": "cdx-json",
|
||||
"sizeBytes": 24576,
|
||||
"view": "inventory"
|
||||
},
|
||||
{
|
||||
"kind": "sbom-usage",
|
||||
"uri": "cas://scanner-artifacts/scanner/images/feedface/sbom-usage.cdx.json",
|
||||
"digest": "sha256:2222222222222222222222222222222222222222222222222222222222222222",
|
||||
"mediaType": "application/vnd.cyclonedx+json;version=1.6;view=usage",
|
||||
"format": "cdx-json",
|
||||
"sizeBytes": 16384,
|
||||
"view": "usage"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"attributes": {
|
||||
"policyDigest": "digest-123",
|
||||
"policyRevisionId": "rev-42",
|
||||
"reportId": "report-abc",
|
||||
"verdict": "blocked"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
{
|
||||
"eventId": "08a6de24-4a94-4d14-8432-9d14f36f6da3",
|
||||
"kind": "scanner.event.scan.completed",
|
||||
"version": 1,
|
||||
"tenant": "tenant-alpha",
|
||||
"occurredAt": "2025-10-19T12:34:56+00:00",
|
||||
"recordedAt": "2025-10-19T12:34:57+00:00",
|
||||
"source": "scanner.webservice",
|
||||
"idempotencyKey": "scanner.event.scan.completed:tenant-alpha:report-abc",
|
||||
"correlationId": "report-abc",
|
||||
"traceId": "4bf92f3577b34da6a3ce929d0e0e4736",
|
||||
"scope": {
|
||||
"namespace": "acme/edge",
|
||||
"repo": "api",
|
||||
"digest": "sha256:feedface"
|
||||
},
|
||||
"payload": {
|
||||
"reportId": "report-abc",
|
||||
"scanId": "report-abc",
|
||||
"imageDigest": "sha256:feedface",
|
||||
"verdict": "fail",
|
||||
"summary": {
|
||||
"total": 1,
|
||||
"blocked": 1,
|
||||
"warned": 0,
|
||||
"ignored": 0,
|
||||
"quieted": 0
|
||||
},
|
||||
"delta": {
|
||||
"newCritical": 1,
|
||||
"kev": [
|
||||
"CVE-2024-9999"
|
||||
]
|
||||
},
|
||||
"policy": {
|
||||
"revisionId": "rev-42",
|
||||
"digest": "digest-123"
|
||||
},
|
||||
"findings": [
|
||||
{
|
||||
"id": "finding-1",
|
||||
"severity": "Critical",
|
||||
"cve": "CVE-2024-9999",
|
||||
"purl": "pkg:docker/acme/edge-api@sha256-feedface",
|
||||
"reachability": "runtime"
|
||||
}
|
||||
],
|
||||
"links": {
|
||||
"report": {
|
||||
"ui": "https://scanner.example/ui/reports/report-abc",
|
||||
"api": "https://scanner.example/api/v1/reports/report-abc"
|
||||
},
|
||||
"policy": {
|
||||
"ui": "https://scanner.example/ui/policy/revisions/rev-42",
|
||||
"api": "https://scanner.example/api/v1/policy/revisions/rev-42"
|
||||
},
|
||||
"attestation": {
|
||||
"ui": "https://scanner.example/ui/attestations/report-abc",
|
||||
"api": "https://scanner.example/api/v1/reports/report-abc/attestation"
|
||||
}
|
||||
},
|
||||
"dsse": {
|
||||
"payloadType": "application/vnd.stellaops.report+json",
|
||||
"payload": "eyJyZXBvcnRJZCI6InJlcG9ydC1hYmMiLCJpbWFnZURpZ2VzdCI6InNoYTI1NjpmZWVkZmFjZSIsImdlbmVyYXRlZEF0IjoiMjAyNS0xMC0xOVQxMjozNDo1NiswMDowMCIsInZlcmRpY3QiOiJibG9ja2VkIiwicG9saWN5Ijp7InJldmlzaW9uSWQiOiJyZXYtNDIiLCJkaWdlc3QiOiJkaWdlc3QtMTIzIn0sInN1bW1hcnkiOnsidG90YWwiOjEsImJsb2NrZWQiOjEsIndhcm5lZCI6MCwiaWdub3JlZCI6MCwicXVpZXRlZCI6MH0sInZlcmRpY3RzIjpbeyJmaW5kaW5nSWQiOiJmaW5kaW5nLTEiLCJyZWFjaGFiaWxpdHkiOiJydW50aW1lIiwic2NvcmUiOjQ3LjUsInNvdXJjZVRydXN0IjoiTlZEIiwic3RhdHVzIjoiQmxvY2tlZCJ9XSwiaXNzdWVzIjpbXSwic3VyZmFjZSI6eyJ0ZW5hbnQiOiJ0ZW5hbnQtYWxwaGEiLCJnZW5lcmF0ZWRBdCI6IjIwMjUtMTAtMTlUMTI6MzQ6NTYrMDA6MDAiLCJtYW5pZmVzdERpZ2VzdCI6InNoYTI1Njo0ZmVlODdkMTg2MjkxZGRmYmJjYzJjNTZjOGVkMGU4Mjg1MjBiOGY1MmUxY2RlMGUxM2JiYTA4MmYxMDkxOGQ3IiwibWFuaWZlc3RVcmkiOiJjYXM6Ly9zY2FubmVyLWFydGlmYWN0cy9zY2FubmVyL3N1cmZhY2UvbWFuaWZlc3RzL3RlbmFudC1hbHBoYS9zaGEyNTYvNGYvZWUvNGZlZTg3ZDE4NjI5MWRkZmJiY2MyYzU2YzhlZDBlODI4NTIwYjhmNTJlMWNkZTBlMTNiYmEwODJmMTA5MThkNy5qc29uIiwibWFuaWZlc3QiOnsic2NoZW1hIjoic3RlbGxhb3BzLnN1cmZhY2UubWFuaWZlc3RAMSIsInRlbmFudCI6InRlbmFudC1hbHBoYSIsImltYWdlRGlnZXN0Ijoic2hhMjU2OmZlZWRmYWNlIiwiZ2VuZXJhdGVkQXQiOiIyMDI1LTEwLTE5VDEyOjM0OjU2KzAwOjAwIiwiYXJ0aWZhY3RzIjpbeyJraW5kIjoiZW50cnktdHJhY2UiLCJ1cmkiOiJjYXM6Ly9zY2FubmVyLWFydGlmYWN0cy9zY2FubmVyL2VudHJ5LXRyYWNlL2YwZjBmMGYwZjBmMGYwZjBmMGYwZjBmMGYwZjBmMGYwZjBmMGYwZjBmMGYwZjBmMGYwZjBmMGYwZjBmMGYwL2VudHJ5LXRyYWNlLmpzb24iLCJkaWdlc3QiOiJzaGEyNTY6ZjBmMGYwZjBmMGYwZjBmMGYwZjBmMGYwZjBmMGYwZjBmMGYwZjBmMGYwZjBmMGYwZjBmMGYwZjBmMGYwZjBmMCIsIm1lZGlhVHlwZSI6ImFwcGxpY2F0aW9uL2pzb24iLCJmb3JtYXQiOiJqc29uIiwic2l6ZUJ5dGVzIjo0MDk2fSx7ImtpbmQiOiJzYm9tLWludmVudG9yeSIsInVyaSI6ImNhczovL3NjYW5uZXItYXJ0aWZhY3RzL3NjYW5uZXIvaW1hZ2VzL2ZlZWRmYWNlL3Nib20uY2R4Lmpzb24iLCJkaWdlc3QiOiJzaGEyNTY6MTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMTExMSIsIm1lZGlhVHlwZSI6ImFwcGxpY2F0aW9uL3ZuZC5jeWNsb25lZHgranNvbjt2ZXJzaW9uPTEuNjt2aWV3PWludmVudG9yeSIsImZvcm1hdCI6ImNkeC1qc29uIiwic2l6ZUJ5dGVzIjoyNDU3NiwidmlldyI6ImludmVudG9yeSJ9LHsia2luZCI6InNib20tdXNhZ2UiLCJ1cmkiOiJjYXM6Ly9zY2FubmVyLWFydGlmYWN0cy9zY2FubmVyL2ltYWdlcy9mZWVkZmFjZS9zYm9tLXVzYWdlLmNkeC5qc29uIiwiZGlnZXN0Ijoic2hhMjU2OjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIyMjIiLCJtZWRpYVR5cGUiOiJhcHBsaWNhdGlvbi92bmQuY3ljbG9uZWR4K2pzb247dmVyc2lvbj0xLjY7dmlldz11c2FnZSIsImZvcm1hdCI6ImNkeC1qc29uIiwic2l6ZUJ5dGVzIjoxNjM4NCwidmlldyI6InVzYWdlIn1dfX19",
|
||||
"signatures": [
|
||||
{
|
||||
"keyId": "test-key",
|
||||
"algorithm": "hs256",
|
||||
"signature": "signature-value"
|
||||
}
|
||||
]
|
||||
},
|
||||
"report": {
|
||||
"reportId": "report-abc",
|
||||
"imageDigest": "sha256:feedface",
|
||||
"generatedAt": "2025-10-19T12:34:56+00:00",
|
||||
"verdict": "blocked",
|
||||
"policy": {
|
||||
"revisionId": "rev-42",
|
||||
"digest": "digest-123"
|
||||
},
|
||||
"summary": {
|
||||
"total": 1,
|
||||
"blocked": 1,
|
||||
"warned": 0,
|
||||
"ignored": 0,
|
||||
"quieted": 0
|
||||
},
|
||||
"verdicts": [
|
||||
{
|
||||
"findingId": "finding-1",
|
||||
"reachability": "runtime",
|
||||
"score": 47.5,
|
||||
"sourceTrust": "NVD",
|
||||
"status": "Blocked"
|
||||
}
|
||||
],
|
||||
"issues": [],
|
||||
"surface": {
|
||||
"tenant": "tenant-alpha",
|
||||
"generatedAt": "2025-10-19T12:34:56+00:00",
|
||||
"manifestDigest": "sha256:4fee87d186291ddfbbcc2c56c8ed0e828520b8f52e1cde0e13bba082f10918d7",
|
||||
"manifestUri": "cas://scanner-artifacts/scanner/surface/manifests/tenant-alpha/sha256/4f/ee/4fee87d186291ddfbbcc2c56c8ed0e828520b8f52e1cde0e13bba082f10918d7.json",
|
||||
"manifest": {
|
||||
"schema": "stellaops.surface.manifest@1",
|
||||
"tenant": "tenant-alpha",
|
||||
"imageDigest": "sha256:feedface",
|
||||
"generatedAt": "2025-10-19T12:34:56+00:00",
|
||||
"artifacts": [
|
||||
{
|
||||
"kind": "entry-trace",
|
||||
"uri": "cas://scanner-artifacts/scanner/entry-trace/f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0/entry-trace.json",
|
||||
"digest": "sha256:f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0f0",
|
||||
"mediaType": "application/json",
|
||||
"format": "json",
|
||||
"sizeBytes": 4096
|
||||
},
|
||||
{
|
||||
"kind": "sbom-inventory",
|
||||
"uri": "cas://scanner-artifacts/scanner/images/feedface/sbom.cdx.json",
|
||||
"digest": "sha256:1111111111111111111111111111111111111111111111111111111111111111",
|
||||
"mediaType": "application/vnd.cyclonedx+json;version=1.6;view=inventory",
|
||||
"format": "cdx-json",
|
||||
"sizeBytes": 24576,
|
||||
"view": "inventory"
|
||||
},
|
||||
{
|
||||
"kind": "sbom-usage",
|
||||
"uri": "cas://scanner-artifacts/scanner/images/feedface/sbom-usage.cdx.json",
|
||||
"digest": "sha256:2222222222222222222222222222222222222222222222222222222222222222",
|
||||
"mediaType": "application/vnd.cyclonedx+json;version=1.6;view=usage",
|
||||
"format": "cdx-json",
|
||||
"sizeBytes": 16384,
|
||||
"view": "usage"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"attributes": {
|
||||
"policyDigest": "digest-123",
|
||||
"policyRevisionId": "rev-42",
|
||||
"reportId": "report-abc",
|
||||
"verdict": "blocked"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
{
|
||||
"eventId": "6d2d1b77-f3c3-4f70-8a9d-6f2d0c8801ab",
|
||||
"kind": "scanner.report.ready",
|
||||
"tenant": "tenant-alpha",
|
||||
"ts": "2025-10-19T12:34:56+00:00",
|
||||
"scope": {
|
||||
"namespace": "acme/edge",
|
||||
"repo": "api",
|
||||
"digest": "sha256:feedface",
|
||||
"labels": {},
|
||||
"attributes": {}
|
||||
},
|
||||
"payload": {
|
||||
"delta": {
|
||||
"kev": ["CVE-2024-9999"],
|
||||
"newCritical": 1
|
||||
},
|
||||
"dsse": {
|
||||
"payload": "eyJyZXBvcnRJZCI6InJlcG9ydC1hYmMiLCJpbWFnZURpZ2VzdCI6InNoYTI1NjpmZWVkZmFjZSIsImdlbmVyYXRlZEF0IjoiMjAyNS0xMC0xOVQxMjozNDo1NiswMDowMCIsInZlcmRpY3QiOiJibG9ja2VkIiwicG9saWN5Ijp7InJldmlzaW9uSWQiOiJyZXYtNDIiLCJkaWdlc3QiOiJkaWdlc3QtMTIzIn0sInN1bW1hcnkiOnsidG90YWwiOjEsImJsb2NrZWQiOjEsIndhcm5lZCI6MCwiaWdub3JlZCI6MCwicXVpZXRlZCI6MH0sInZlcmRpY3RzIjpbeyJmaW5kaW5nSWQiOiJmaW5kaW5nLTEiLCJzdGF0dXMiOiJCbG9ja2VkIiwic2NvcmUiOjQ3LjUsInNvdXJjZVRydXN0IjoiTlZEIiwicmVhY2hhYmlsaXR5IjoicnVudGltZSJ9XSwiaXNzdWVzIjpbXX0=",
|
||||
"payloadType": "application/vnd.stellaops.report\u002Bjson",
|
||||
"signatures": [{
|
||||
"algorithm": "hs256",
|
||||
"keyId": "test-key",
|
||||
"signature": "signature-value"
|
||||
}]
|
||||
},
|
||||
"generatedAt": "2025-10-19T12:34:56+00:00",
|
||||
"links": {
|
||||
"ui": "https://scanner.example/ui/reports/report-abc"
|
||||
},
|
||||
"quietedFindingCount": 0,
|
||||
"report": {
|
||||
"generatedAt": "2025-10-19T12:34:56+00:00",
|
||||
"imageDigest": "sha256:feedface",
|
||||
"issues": [],
|
||||
"policy": {
|
||||
"digest": "digest-123",
|
||||
"revisionId": "rev-42"
|
||||
},
|
||||
"reportId": "report-abc",
|
||||
"summary": {
|
||||
"blocked": 1,
|
||||
"ignored": 0,
|
||||
"quieted": 0,
|
||||
"total": 1,
|
||||
"warned": 0
|
||||
},
|
||||
"verdict": "blocked",
|
||||
"verdicts": [
|
||||
{
|
||||
"findingId": "finding-1",
|
||||
"status": "Blocked",
|
||||
"score": 47.5,
|
||||
"sourceTrust": "NVD",
|
||||
"reachability": "runtime"
|
||||
}
|
||||
]
|
||||
},
|
||||
"reportId": "report-abc",
|
||||
"summary": {
|
||||
"blocked": 1,
|
||||
"ignored": 0,
|
||||
"quieted": 0,
|
||||
"total": 1,
|
||||
"warned": 0
|
||||
},
|
||||
"verdict": "fail"
|
||||
},
|
||||
"attributes": {}
|
||||
}
|
||||
@@ -0,0 +1,78 @@
|
||||
{
|
||||
"eventId": "08a6de24-4a94-4d14-8432-9d14f36f6da3",
|
||||
"kind": "scanner.scan.completed",
|
||||
"tenant": "tenant-alpha",
|
||||
"ts": "2025-10-19T12:34:56+00:00",
|
||||
"scope": {
|
||||
"namespace": "acme/edge",
|
||||
"repo": "api",
|
||||
"digest": "sha256:feedface",
|
||||
"labels": {},
|
||||
"attributes": {}
|
||||
},
|
||||
"payload": {
|
||||
"delta": {
|
||||
"kev": ["CVE-2024-9999"],
|
||||
"newCritical": 1
|
||||
},
|
||||
"digest": "sha256:feedface",
|
||||
"dsse": {
|
||||
"payload": "eyJyZXBvcnRJZCI6InJlcG9ydC1hYmMiLCJpbWFnZURpZ2VzdCI6InNoYTI1NjpmZWVkZmFjZSIsImdlbmVyYXRlZEF0IjoiMjAyNS0xMC0xOVQxMjozNDo1NiswMDowMCIsInZlcmRpY3QiOiJibG9ja2VkIiwicG9saWN5Ijp7InJldmlzaW9uSWQiOiJyZXYtNDIiLCJkaWdlc3QiOiJkaWdlc3QtMTIzIn0sInN1bW1hcnkiOnsidG90YWwiOjEsImJsb2NrZWQiOjEsIndhcm5lZCI6MCwiaWdub3JlZCI6MCwicXVpZXRlZCI6MH0sInZlcmRpY3RzIjpbeyJmaW5kaW5nSWQiOiJmaW5kaW5nLTEiLCJzdGF0dXMiOiJCbG9ja2VkIiwic2NvcmUiOjQ3LjUsInNvdXJjZVRydXN0IjoiTlZEIiwicmVhY2hhYmlsaXR5IjoicnVudGltZSJ9XSwiaXNzdWVzIjpbXX0=",
|
||||
"payloadType": "application/vnd.stellaops.report\u002Bjson",
|
||||
"signatures": [{
|
||||
"algorithm": "hs256",
|
||||
"keyId": "test-key",
|
||||
"signature": "signature-value"
|
||||
}]
|
||||
},
|
||||
"findings": [
|
||||
{
|
||||
"cve": "CVE-2024-9999",
|
||||
"id": "finding-1",
|
||||
"reachability": "runtime",
|
||||
"severity": "Critical"
|
||||
}
|
||||
],
|
||||
"policy": {
|
||||
"digest": "digest-123",
|
||||
"revisionId": "rev-42"
|
||||
},
|
||||
"report": {
|
||||
"generatedAt": "2025-10-19T12:34:56+00:00",
|
||||
"imageDigest": "sha256:feedface",
|
||||
"issues": [],
|
||||
"policy": {
|
||||
"digest": "digest-123",
|
||||
"revisionId": "rev-42"
|
||||
},
|
||||
"reportId": "report-abc",
|
||||
"summary": {
|
||||
"blocked": 1,
|
||||
"ignored": 0,
|
||||
"quieted": 0,
|
||||
"total": 1,
|
||||
"warned": 0
|
||||
},
|
||||
"verdict": "blocked",
|
||||
"verdicts": [
|
||||
{
|
||||
"findingId": "finding-1",
|
||||
"status": "Blocked",
|
||||
"score": 47.5,
|
||||
"sourceTrust": "NVD",
|
||||
"reachability": "runtime"
|
||||
}
|
||||
]
|
||||
},
|
||||
"reportId": "report-abc",
|
||||
"summary": {
|
||||
"blocked": 1,
|
||||
"ignored": 0,
|
||||
"quieted": 0,
|
||||
"total": 1,
|
||||
"warned": 0
|
||||
},
|
||||
"verdict": "fail"
|
||||
},
|
||||
"attributes": {}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
{
|
||||
"eventId": "4d33c19c-1c8a-44d1-9954-1d5e98b2af71",
|
||||
"kind": "scheduler.graph.job.completed",
|
||||
"tenant": "tenant-alpha",
|
||||
"ts": "2025-10-26T12:00:45Z",
|
||||
"payload": {
|
||||
"jobType": "build",
|
||||
"status": "completed",
|
||||
"occurredAt": "2025-10-26T12:00:45Z",
|
||||
"job": {
|
||||
"schemaVersion": "scheduler.graph-build-job@1",
|
||||
"id": "gbj_20251026a",
|
||||
"tenantId": "tenant-alpha",
|
||||
"sbomId": "sbom_20251026",
|
||||
"sbomVersionId": "sbom_ver_20251026",
|
||||
"sbomDigest": "sha256:0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef",
|
||||
"graphSnapshotId": "graph_snap_20251026",
|
||||
"status": "completed",
|
||||
"trigger": "sbom-version",
|
||||
"attempts": 1,
|
||||
"cartographerJobId": "carto_job_42",
|
||||
"correlationId": "evt_svc_987",
|
||||
"createdAt": "2025-10-26T12:00:00+00:00",
|
||||
"startedAt": "2025-10-26T12:00:05+00:00",
|
||||
"completedAt": "2025-10-26T12:00:45+00:00",
|
||||
"metadata": {
|
||||
"sbomEventId": "sbom_evt_20251026"
|
||||
}
|
||||
},
|
||||
"resultUri": "oras://cartographer/offline/tenant-alpha/graph_snap_20251026"
|
||||
},
|
||||
"attributes": {
|
||||
"cartographerCluster": "offline-kit",
|
||||
"plannerShard": "graph-builders-01"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"eventId": "51d0ef8d-3a17-4af3-b2d7-4ad3db3d9d2c",
|
||||
"kind": "scheduler.rescan.delta",
|
||||
"tenant": "tenant-acme-solar",
|
||||
"ts": "2025-10-18T15:40:11+00:00",
|
||||
"payload": {
|
||||
"impactedDigests": [
|
||||
"sha256:0f0a8de5c1f93d6716b7249f6f4ea3a8db451dc3f3c3ff823f53c9cbde5d5e8a",
|
||||
"sha256:ab921f9679dd8d0832f3710a4df75dbadbd58c2d95f26a4d4efb2fa8c3d9b4ce"
|
||||
],
|
||||
"reason": "policy-change:scoring/v2",
|
||||
"scheduleId": "rescan-weekly-critical",
|
||||
"summary": {
|
||||
"newCritical": 0,
|
||||
"newHigh": 1,
|
||||
"total": 4
|
||||
}
|
||||
},
|
||||
"attributes": {}
|
||||
}
|
||||
173
docs/modules/signals/events/scanner.event.report.ready@1.json
Normal file
173
docs/modules/signals/events/scanner.event.report.ready@1.json
Normal file
@@ -0,0 +1,173 @@
|
||||
{
|
||||
"$id": "https://stella-ops.org/schemas/events/scanner.event.report.ready@1.json",
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "Scanner orchestrator event – report ready (v1)",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"eventId",
|
||||
"kind",
|
||||
"version",
|
||||
"tenant",
|
||||
"occurredAt",
|
||||
"source",
|
||||
"idempotencyKey",
|
||||
"payload"
|
||||
],
|
||||
"properties": {
|
||||
"eventId": {
|
||||
"type": "string",
|
||||
"format": "uuid",
|
||||
"description": "Globally unique identifier for this occurrence."
|
||||
},
|
||||
"kind": {
|
||||
"const": "scanner.event.report.ready",
|
||||
"description": "Event kind identifier consumed by orchestrator subscribers."
|
||||
},
|
||||
"version": {
|
||||
"const": 1,
|
||||
"description": "Schema version for orchestrator envelopes."
|
||||
},
|
||||
"tenant": {
|
||||
"type": "string",
|
||||
"description": "Tenant that owns the scan/report."
|
||||
},
|
||||
"occurredAt": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"description": "Timestamp (UTC) when the report transitioned to ready."
|
||||
},
|
||||
"recordedAt": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"description": "Timestamp (UTC) when the event was persisted. Optional."
|
||||
},
|
||||
"source": {
|
||||
"type": "string",
|
||||
"description": "Producer identifier, e.g. `scanner.webservice`."
|
||||
},
|
||||
"idempotencyKey": {
|
||||
"type": "string",
|
||||
"minLength": 8,
|
||||
"description": "Deterministic key used to deduplicate events downstream."
|
||||
},
|
||||
"correlationId": {
|
||||
"type": "string",
|
||||
"description": "Correlation identifier that ties this event to a request or workflow."
|
||||
},
|
||||
"traceId": {
|
||||
"type": "string",
|
||||
"description": "W3C trace ID (32 hex chars) for distributed tracing."
|
||||
},
|
||||
"spanId": {
|
||||
"type": "string",
|
||||
"description": "Optional span identifier associated with traceId."
|
||||
},
|
||||
"scope": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["repo", "digest"],
|
||||
"properties": {
|
||||
"namespace": {"type": "string"},
|
||||
"repo": {"type": "string"},
|
||||
"digest": {"type": "string"},
|
||||
"component": {"type": "string"},
|
||||
"image": {"type": "string"}
|
||||
}
|
||||
},
|
||||
"attributes": {
|
||||
"type": "object",
|
||||
"description": "String attributes for downstream correlation (policy revision, scan id, etc.).",
|
||||
"additionalProperties": {"type": "string"}
|
||||
},
|
||||
"payload": {
|
||||
"type": "object",
|
||||
"additionalProperties": true,
|
||||
"required": ["reportId", "verdict", "summary", "links", "report"],
|
||||
"properties": {
|
||||
"reportId": {"type": "string"},
|
||||
"scanId": {"type": "string"},
|
||||
"imageDigest": {"type": "string"},
|
||||
"generatedAt": {"type": "string", "format": "date-time"},
|
||||
"verdict": {"enum": ["pass", "warn", "fail"]},
|
||||
"summary": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["total", "blocked", "warned", "ignored", "quieted"],
|
||||
"properties": {
|
||||
"total": {"type": "integer", "minimum": 0},
|
||||
"blocked": {"type": "integer", "minimum": 0},
|
||||
"warned": {"type": "integer", "minimum": 0},
|
||||
"ignored": {"type": "integer", "minimum": 0},
|
||||
"quieted": {"type": "integer", "minimum": 0}
|
||||
}
|
||||
},
|
||||
"delta": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"newCritical": {"type": "integer", "minimum": 0},
|
||||
"newHigh": {"type": "integer", "minimum": 0},
|
||||
"kev": {
|
||||
"type": "array",
|
||||
"items": {"type": "string"}
|
||||
}
|
||||
}
|
||||
},
|
||||
"quietedFindingCount": {
|
||||
"type": "integer",
|
||||
"minimum": 0
|
||||
},
|
||||
"policy": {
|
||||
"type": "object",
|
||||
"description": "Policy revision metadata surfaced alongside the report."
|
||||
},
|
||||
"links": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"report": {"$ref": "#/definitions/linkTarget"},
|
||||
"policy": {"$ref": "#/definitions/linkTarget"},
|
||||
"attestation": {"$ref": "#/definitions/linkTarget"}
|
||||
}
|
||||
},
|
||||
"dsse": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["payloadType", "payload", "signatures"],
|
||||
"properties": {
|
||||
"payloadType": {"type": "string"},
|
||||
"payload": {"type": "string"},
|
||||
"signatures": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["keyId", "algorithm", "signature"],
|
||||
"properties": {
|
||||
"keyId": {"type": "string"},
|
||||
"algorithm": {"type": "string"},
|
||||
"signature": {"type": "string"}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"report": {
|
||||
"type": "object",
|
||||
"description": "Canonical scanner report document that aligns with the DSSE payload."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"definitions": {
|
||||
"linkTarget": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"ui": {"type": "string", "format": "uri"},
|
||||
"api": {"type": "string", "format": "uri"}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
183
docs/modules/signals/events/scanner.event.scan.completed@1.json
Normal file
183
docs/modules/signals/events/scanner.event.scan.completed@1.json
Normal file
@@ -0,0 +1,183 @@
|
||||
{
|
||||
"$id": "https://stella-ops.org/schemas/events/scanner.event.scan.completed@1.json",
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "Scanner orchestrator event – scan completed (v1)",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"eventId",
|
||||
"kind",
|
||||
"version",
|
||||
"tenant",
|
||||
"occurredAt",
|
||||
"source",
|
||||
"idempotencyKey",
|
||||
"payload"
|
||||
],
|
||||
"properties": {
|
||||
"eventId": {
|
||||
"type": "string",
|
||||
"format": "uuid",
|
||||
"description": "Globally unique identifier for this occurrence."
|
||||
},
|
||||
"kind": {
|
||||
"const": "scanner.event.scan.completed",
|
||||
"description": "Event kind identifier consumed by orchestrator subscribers."
|
||||
},
|
||||
"version": {
|
||||
"const": 1,
|
||||
"description": "Schema version for orchestrator envelopes."
|
||||
},
|
||||
"tenant": {
|
||||
"type": "string",
|
||||
"description": "Tenant that owns the scan."
|
||||
},
|
||||
"occurredAt": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"description": "Timestamp (UTC) when the scan completed."
|
||||
},
|
||||
"recordedAt": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"description": "Timestamp (UTC) when the event was persisted. Optional."
|
||||
},
|
||||
"source": {
|
||||
"type": "string",
|
||||
"description": "Producer identifier, e.g. `scanner.webservice`."
|
||||
},
|
||||
"idempotencyKey": {
|
||||
"type": "string",
|
||||
"minLength": 8,
|
||||
"description": "Deterministic key used to deduplicate events downstream."
|
||||
},
|
||||
"correlationId": {
|
||||
"type": "string",
|
||||
"description": "Correlation identifier tying this event to a request or workflow."
|
||||
},
|
||||
"traceId": {
|
||||
"type": "string",
|
||||
"description": "W3C trace ID (32 hex chars) for distributed tracing."
|
||||
},
|
||||
"spanId": {
|
||||
"type": "string",
|
||||
"description": "Optional span identifier associated with traceId."
|
||||
},
|
||||
"scope": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["repo", "digest"],
|
||||
"properties": {
|
||||
"namespace": {"type": "string"},
|
||||
"repo": {"type": "string"},
|
||||
"digest": {"type": "string"},
|
||||
"component": {"type": "string"},
|
||||
"image": {"type": "string"}
|
||||
}
|
||||
},
|
||||
"attributes": {
|
||||
"type": "object",
|
||||
"description": "String attributes for downstream correlation (policy revision, scan id, etc.).",
|
||||
"additionalProperties": {"type": "string"}
|
||||
},
|
||||
"payload": {
|
||||
"type": "object",
|
||||
"additionalProperties": true,
|
||||
"required": ["reportId", "scanId", "imageDigest", "verdict", "summary", "report"],
|
||||
"properties": {
|
||||
"reportId": {"type": "string"},
|
||||
"scanId": {"type": "string"},
|
||||
"imageDigest": {"type": "string"},
|
||||
"verdict": {"enum": ["pass", "warn", "fail"]},
|
||||
"summary": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["total", "blocked", "warned", "ignored", "quieted"],
|
||||
"properties": {
|
||||
"total": {"type": "integer", "minimum": 0},
|
||||
"blocked": {"type": "integer", "minimum": 0},
|
||||
"warned": {"type": "integer", "minimum": 0},
|
||||
"ignored": {"type": "integer", "minimum": 0},
|
||||
"quieted": {"type": "integer", "minimum": 0}
|
||||
}
|
||||
},
|
||||
"delta": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"newCritical": {"type": "integer", "minimum": 0},
|
||||
"newHigh": {"type": "integer", "minimum": 0},
|
||||
"kev": {
|
||||
"type": "array",
|
||||
"items": {"type": "string"}
|
||||
}
|
||||
}
|
||||
},
|
||||
"policy": {
|
||||
"type": "object",
|
||||
"description": "Policy revision metadata surfaced alongside the report."
|
||||
},
|
||||
"findings": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["id"],
|
||||
"properties": {
|
||||
"id": {"type": "string"},
|
||||
"severity": {"type": "string"},
|
||||
"cve": {"type": "string"},
|
||||
"purl": {"type": "string"},
|
||||
"reachability": {"type": "string"}
|
||||
}
|
||||
}
|
||||
},
|
||||
"links": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"report": {"$ref": "#/definitions/linkTarget"},
|
||||
"policy": {"$ref": "#/definitions/linkTarget"},
|
||||
"attestation": {"$ref": "#/definitions/linkTarget"}
|
||||
}
|
||||
},
|
||||
"dsse": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["payloadType", "payload", "signatures"],
|
||||
"properties": {
|
||||
"payloadType": {"type": "string"},
|
||||
"payload": {"type": "string"},
|
||||
"signatures": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["keyId", "algorithm", "signature"],
|
||||
"properties": {
|
||||
"keyId": {"type": "string"},
|
||||
"algorithm": {"type": "string"},
|
||||
"signature": {"type": "string"}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"report": {
|
||||
"type": "object",
|
||||
"description": "Canonical scanner report document that aligns with the DSSE payload."
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
"definitions": {
|
||||
"linkTarget": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"properties": {
|
||||
"ui": {"type": "string", "format": "uri"},
|
||||
"api": {"type": "string", "format": "uri"}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
89
docs/modules/signals/events/scanner.report.ready@1.json
Normal file
89
docs/modules/signals/events/scanner.report.ready@1.json
Normal file
@@ -0,0 +1,89 @@
|
||||
{
|
||||
"$id": "https://stella-ops.org/schemas/events/scanner.report.ready@1.json",
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"type": "object",
|
||||
"required": ["eventId", "kind", "tenant", "ts", "scope", "payload"],
|
||||
"properties": {
|
||||
"eventId": {"type": "string", "format": "uuid"},
|
||||
"kind": {"const": "scanner.report.ready"},
|
||||
"tenant": {"type": "string"},
|
||||
"ts": {"type": "string", "format": "date-time"},
|
||||
"scope": {
|
||||
"type": "object",
|
||||
"required": ["repo", "digest"],
|
||||
"properties": {
|
||||
"namespace": {"type": "string"},
|
||||
"repo": {"type": "string"},
|
||||
"digest": {"type": "string"}
|
||||
}
|
||||
},
|
||||
"payload": {
|
||||
"type": "object",
|
||||
"required": ["verdict", "delta", "links"],
|
||||
"properties": {
|
||||
"reportId": {"type": "string"},
|
||||
"generatedAt": {"type": "string", "format": "date-time"},
|
||||
"verdict": {"enum": ["pass", "warn", "fail"]},
|
||||
"summary": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"total": {"type": "integer", "minimum": 0},
|
||||
"blocked": {"type": "integer", "minimum": 0},
|
||||
"warned": {"type": "integer", "minimum": 0},
|
||||
"ignored": {"type": "integer", "minimum": 0},
|
||||
"quieted": {"type": "integer", "minimum": 0}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"delta": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"newCritical": {"type": "integer", "minimum": 0},
|
||||
"newHigh": {"type": "integer", "minimum": 0},
|
||||
"kev": {"type": "array", "items": {"type": "string"}}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"links": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"ui": {"type": "string", "format": "uri"},
|
||||
"rekor": {"type": "string", "format": "uri"}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"quietedFindingCount": {"type": "integer", "minimum": 0},
|
||||
"report": {"type": "object"},
|
||||
"dsse": {
|
||||
"type": "object",
|
||||
"required": ["payloadType", "payload", "signatures"],
|
||||
"properties": {
|
||||
"payloadType": {"type": "string"},
|
||||
"payload": {"type": "string"},
|
||||
"signatures": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": ["keyId", "algorithm", "signature"],
|
||||
"properties": {
|
||||
"keyId": {"type": "string"},
|
||||
"algorithm": {"type": "string"},
|
||||
"signature": {"type": "string"}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": true
|
||||
},
|
||||
"attributes": {
|
||||
"type": "object",
|
||||
"description": "Optional event attributes for downstream correlation.",
|
||||
"additionalProperties": true
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
102
docs/modules/signals/events/scanner.scan.completed@1.json
Normal file
102
docs/modules/signals/events/scanner.scan.completed@1.json
Normal file
@@ -0,0 +1,102 @@
|
||||
{
|
||||
"$id": "https://stella-ops.org/schemas/events/scanner.scan.completed@1.json",
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"type": "object",
|
||||
"required": ["eventId", "kind", "tenant", "ts", "scope", "payload"],
|
||||
"properties": {
|
||||
"eventId": {"type": "string", "format": "uuid"},
|
||||
"kind": {"const": "scanner.scan.completed"},
|
||||
"tenant": {"type": "string"},
|
||||
"ts": {"type": "string", "format": "date-time"},
|
||||
"scope": {
|
||||
"type": "object",
|
||||
"required": ["repo", "digest"],
|
||||
"properties": {
|
||||
"namespace": {"type": "string"},
|
||||
"repo": {"type": "string"},
|
||||
"digest": {"type": "string"}
|
||||
}
|
||||
},
|
||||
"payload": {
|
||||
"type": "object",
|
||||
"required": ["reportId", "digest", "verdict", "summary"],
|
||||
"properties": {
|
||||
"reportId": {"type": "string"},
|
||||
"digest": {"type": "string"},
|
||||
"verdict": {"enum": ["pass", "warn", "fail"]},
|
||||
"summary": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"total": {"type": "integer", "minimum": 0},
|
||||
"blocked": {"type": "integer", "minimum": 0},
|
||||
"warned": {"type": "integer", "minimum": 0},
|
||||
"ignored": {"type": "integer", "minimum": 0},
|
||||
"quieted": {"type": "integer", "minimum": 0}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"delta": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"newCritical": {"type": "integer", "minimum": 0},
|
||||
"newHigh": {"type": "integer", "minimum": 0},
|
||||
"kev": {"type": "array", "items": {"type": "string"}}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"policy": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"revisionId": {"type": "string"},
|
||||
"digest": {"type": "string"}
|
||||
},
|
||||
"additionalProperties": false
|
||||
},
|
||||
"findings": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"id": {"type": "string"},
|
||||
"severity": {"type": "string"},
|
||||
"cve": {"type": "string"},
|
||||
"purl": {"type": "string"},
|
||||
"reachability": {"type": "string"}
|
||||
},
|
||||
"additionalProperties": true
|
||||
}
|
||||
},
|
||||
"report": {"type": "object"},
|
||||
"dsse": {
|
||||
"type": "object",
|
||||
"required": ["payloadType", "payload", "signatures"],
|
||||
"properties": {
|
||||
"payloadType": {"type": "string"},
|
||||
"payload": {"type": "string"},
|
||||
"signatures": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": ["keyId", "algorithm", "signature"],
|
||||
"properties": {
|
||||
"keyId": {"type": "string"},
|
||||
"algorithm": {"type": "string"},
|
||||
"signature": {"type": "string"}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
},
|
||||
"additionalProperties": true
|
||||
},
|
||||
"attributes": {
|
||||
"type": "object",
|
||||
"description": "Optional event attributes for downstream correlation.",
|
||||
"additionalProperties": true
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
196
docs/modules/signals/events/scheduler.graph.job.completed@1.json
Normal file
196
docs/modules/signals/events/scheduler.graph.job.completed@1.json
Normal file
@@ -0,0 +1,196 @@
|
||||
{
|
||||
"$id": "https://stella-ops.org/schemas/events/scheduler.graph.job.completed@1.json",
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"title": "Scheduler Graph Job Completed Event",
|
||||
"description": "Legacy scheduler event emitted when a graph build or overlay job reaches a terminal state. Consumers validate downstream caches and surface overlay freshness.",
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["eventId", "kind", "tenant", "ts", "payload"],
|
||||
"properties": {
|
||||
"eventId": {
|
||||
"type": "string",
|
||||
"format": "uuid",
|
||||
"description": "Globally unique identifier per event."
|
||||
},
|
||||
"kind": {
|
||||
"const": "scheduler.graph.job.completed"
|
||||
},
|
||||
"tenant": {
|
||||
"type": "string",
|
||||
"description": "Tenant identifier scoped to the originating job."
|
||||
},
|
||||
"ts": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"description": "UTC timestamp when the job reached a terminal state."
|
||||
},
|
||||
"payload": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": ["jobType", "job", "status", "occurredAt"],
|
||||
"properties": {
|
||||
"jobType": {
|
||||
"type": "string",
|
||||
"enum": ["build", "overlay"],
|
||||
"description": "Job flavour, matches the CLR type of the serialized job payload."
|
||||
},
|
||||
"status": {
|
||||
"type": "string",
|
||||
"enum": ["completed", "failed", "cancelled"],
|
||||
"description": "Terminal status recorded for the job."
|
||||
},
|
||||
"occurredAt": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"description": "UTC timestamp of the terminal transition, mirrors job.CompletedAt."
|
||||
},
|
||||
"job": {
|
||||
"oneOf": [
|
||||
{"$ref": "#/definitions/graphBuildJob"},
|
||||
{"$ref": "#/definitions/graphOverlayJob"}
|
||||
],
|
||||
"description": "Canonical serialized representation of the finished job."
|
||||
},
|
||||
"resultUri": {
|
||||
"type": "string",
|
||||
"description": "Optional URI pointing to Cartographer snapshot or overlay bundle (if available)."
|
||||
}
|
||||
}
|
||||
},
|
||||
"attributes": {
|
||||
"type": "object",
|
||||
"description": "Optional correlation bag for downstream consumers.",
|
||||
"additionalProperties": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"definitions": {
|
||||
"graphBuildJob": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"schemaVersion",
|
||||
"id",
|
||||
"tenantId",
|
||||
"sbomId",
|
||||
"sbomVersionId",
|
||||
"sbomDigest",
|
||||
"status",
|
||||
"trigger",
|
||||
"attempts",
|
||||
"createdAt"
|
||||
],
|
||||
"properties": {
|
||||
"schemaVersion": {
|
||||
"const": "scheduler.graph-build-job@1"
|
||||
},
|
||||
"id": {"type": "string"},
|
||||
"tenantId": {"type": "string"},
|
||||
"sbomId": {"type": "string"},
|
||||
"sbomVersionId": {"type": "string"},
|
||||
"sbomDigest": {
|
||||
"type": "string",
|
||||
"pattern": "^sha256:[a-f0-9]{64}$"
|
||||
},
|
||||
"graphSnapshotId": {"type": "string"},
|
||||
"status": {
|
||||
"type": "string",
|
||||
"enum": ["pending", "queued", "running", "completed", "failed", "cancelled"]
|
||||
},
|
||||
"trigger": {
|
||||
"type": "string",
|
||||
"enum": ["sbom-version", "backfill", "manual"]
|
||||
},
|
||||
"attempts": {
|
||||
"type": "integer",
|
||||
"minimum": 0
|
||||
},
|
||||
"cartographerJobId": {"type": "string"},
|
||||
"correlationId": {"type": "string"},
|
||||
"createdAt": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"startedAt": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"completedAt": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"error": {"type": "string"},
|
||||
"metadata": {
|
||||
"type": "object",
|
||||
"additionalProperties": {"type": "string"}
|
||||
}
|
||||
}
|
||||
},
|
||||
"graphOverlayJob": {
|
||||
"type": "object",
|
||||
"additionalProperties": false,
|
||||
"required": [
|
||||
"schemaVersion",
|
||||
"id",
|
||||
"tenantId",
|
||||
"graphSnapshotId",
|
||||
"overlayKind",
|
||||
"overlayKey",
|
||||
"status",
|
||||
"trigger",
|
||||
"attempts",
|
||||
"createdAt"
|
||||
],
|
||||
"properties": {
|
||||
"schemaVersion": {
|
||||
"const": "scheduler.graph-overlay-job@1"
|
||||
},
|
||||
"id": {"type": "string"},
|
||||
"tenantId": {"type": "string"},
|
||||
"graphSnapshotId": {"type": "string"},
|
||||
"buildJobId": {"type": "string"},
|
||||
"overlayKind": {
|
||||
"type": "string",
|
||||
"enum": ["policy", "advisory", "vex"]
|
||||
},
|
||||
"overlayKey": {"type": "string"},
|
||||
"subjects": {
|
||||
"type": "array",
|
||||
"items": {"type": "string"},
|
||||
"uniqueItems": true
|
||||
},
|
||||
"status": {
|
||||
"type": "string",
|
||||
"enum": ["pending", "queued", "running", "completed", "failed", "cancelled"]
|
||||
},
|
||||
"trigger": {
|
||||
"type": "string",
|
||||
"enum": ["policy", "advisory", "vex", "sbom-version", "manual"]
|
||||
},
|
||||
"attempts": {
|
||||
"type": "integer",
|
||||
"minimum": 0
|
||||
},
|
||||
"correlationId": {"type": "string"},
|
||||
"createdAt": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"startedAt": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"completedAt": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"error": {"type": "string"},
|
||||
"metadata": {
|
||||
"type": "object",
|
||||
"additionalProperties": {"type": "string"}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
38
docs/modules/signals/events/scheduler.rescan.delta@1.json
Normal file
38
docs/modules/signals/events/scheduler.rescan.delta@1.json
Normal file
@@ -0,0 +1,38 @@
|
||||
{
|
||||
"$id": "https://stella-ops.org/schemas/events/scheduler.rescan.delta@1.json",
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"type": "object",
|
||||
"required": ["eventId", "kind", "tenant", "ts", "payload"],
|
||||
"properties": {
|
||||
"eventId": {"type": "string", "format": "uuid"},
|
||||
"kind": {"const": "scheduler.rescan.delta"},
|
||||
"tenant": {"type": "string"},
|
||||
"ts": {"type": "string", "format": "date-time"},
|
||||
"payload": {
|
||||
"type": "object",
|
||||
"required": ["scheduleId", "impactedDigests", "summary"],
|
||||
"properties": {
|
||||
"scheduleId": {"type": "string"},
|
||||
"impactedDigests": {
|
||||
"type": "array",
|
||||
"items": {"type": "string"}
|
||||
},
|
||||
"summary": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"newCritical": {"type": "integer", "minimum": 0},
|
||||
"newHigh": {"type": "integer", "minimum": 0},
|
||||
"total": {"type": "integer", "minimum": 0}
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": true
|
||||
},
|
||||
"attributes": {
|
||||
"type": "object",
|
||||
"description": "Optional event attributes for downstream correlation.",
|
||||
"additionalProperties": true
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
355
docs/modules/signals/guides/callgraph-formats.md
Normal file
355
docs/modules/signals/guides/callgraph-formats.md
Normal file
@@ -0,0 +1,355 @@
|
||||
# Callgraph Schema Reference
|
||||
|
||||
This document describes the `stella.callgraph.v1` schema used for representing call graphs in StellaOps.
|
||||
|
||||
## Schema Version
|
||||
|
||||
**Current Version:** `stella.callgraph.v1`
|
||||
|
||||
All call graphs should include the `schema` field set to `stella.callgraph.v1`. Legacy call graphs without this field are automatically migrated on ingestion.
|
||||
|
||||
## Document Structure
|
||||
|
||||
A `CallgraphDocument` contains the following top-level fields:
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `schema` | string | Yes | Schema identifier: `stella.callgraph.v1` |
|
||||
| `scanKey` | string | No | Scan context identifier |
|
||||
| `language` | CallgraphLanguage | No | Primary language of the call graph |
|
||||
| `artifacts` | CallgraphArtifact[] | No | Artifacts included in the graph |
|
||||
| `nodes` | CallgraphNode[] | Yes | Graph nodes representing symbols |
|
||||
| `edges` | CallgraphEdge[] | Yes | Call edges between nodes |
|
||||
| `entrypoints` | CallgraphEntrypoint[] | No | Discovered entrypoints |
|
||||
| `metadata` | CallgraphMetadata | No | Graph-level metadata |
|
||||
| `id` | string | Yes | Unique graph identifier |
|
||||
| `component` | string | No | Component name |
|
||||
| `version` | string | No | Component version |
|
||||
| `ingestedAt` | DateTimeOffset | No | Ingestion timestamp (ISO 8601) |
|
||||
| `graphHash` | string | No | Content hash for deduplication |
|
||||
|
||||
### Legacy Fields
|
||||
|
||||
These fields are preserved for backward compatibility:
|
||||
|
||||
| Field | Type | Description |
|
||||
|-------|------|-------------|
|
||||
| `languageString` | string | Legacy language string |
|
||||
| `roots` | CallgraphRoot[] | Legacy root/entrypoint representation |
|
||||
| `schemaVersion` | string | Legacy schema version field |
|
||||
|
||||
## Enumerations
|
||||
|
||||
### CallgraphLanguage
|
||||
|
||||
Supported languages for call graph analysis:
|
||||
|
||||
| Value | Description |
|
||||
|-------|-------------|
|
||||
| `Unknown` | Language not determined |
|
||||
| `DotNet` | .NET (C#, F#, VB.NET) |
|
||||
| `Java` | Java and JVM languages |
|
||||
| `Node` | Node.js / JavaScript / TypeScript |
|
||||
| `Python` | Python |
|
||||
| `Go` | Go |
|
||||
| `Rust` | Rust |
|
||||
| `Ruby` | Ruby |
|
||||
| `Php` | PHP |
|
||||
| `Binary` | Native binary (ELF, PE) |
|
||||
| `Swift` | Swift |
|
||||
| `Kotlin` | Kotlin |
|
||||
|
||||
### SymbolVisibility
|
||||
|
||||
Access visibility levels for symbols:
|
||||
|
||||
| Value | Description |
|
||||
|-------|-------------|
|
||||
| `Unknown` | Visibility not determined |
|
||||
| `Public` | Publicly accessible |
|
||||
| `Internal` | Internal to assembly/module |
|
||||
| `Protected` | Protected (subclass accessible) |
|
||||
| `Private` | Private to containing type |
|
||||
|
||||
### EdgeKind
|
||||
|
||||
Edge classification based on analysis confidence:
|
||||
|
||||
| Value | Description | Confidence |
|
||||
|-------|-------------|------------|
|
||||
| `Static` | Statically determined call | High |
|
||||
| `Heuristic` | Heuristically inferred | Medium |
|
||||
| `Runtime` | Runtime-observed edge | Highest |
|
||||
|
||||
### EdgeReason
|
||||
|
||||
Reason codes explaining why an edge exists (critical for explainability):
|
||||
|
||||
| Value | Description | Typical Kind |
|
||||
|-------|-------------|--------------|
|
||||
| `DirectCall` | Direct method/function call | Static |
|
||||
| `VirtualCall` | Virtual/interface dispatch | Static |
|
||||
| `ReflectionString` | Reflection-based invocation | Heuristic |
|
||||
| `DiBinding` | Dependency injection binding | Heuristic |
|
||||
| `DynamicImport` | Dynamic import/require | Heuristic |
|
||||
| `NewObj` | Constructor/object instantiation | Static |
|
||||
| `DelegateCreate` | Delegate/function pointer creation | Static |
|
||||
| `AsyncContinuation` | Async/await continuation | Static |
|
||||
| `EventHandler` | Event handler subscription | Heuristic |
|
||||
| `GenericInstantiation` | Generic type instantiation | Static |
|
||||
| `NativeInterop` | Native interop (P/Invoke, JNI, FFI) | Static |
|
||||
| `RuntimeMinted` | Runtime-minted edge from execution | Runtime |
|
||||
| `Unknown` | Reason could not be determined | - |
|
||||
|
||||
### EntrypointKind
|
||||
|
||||
Types of entrypoints:
|
||||
|
||||
| Value | Description |
|
||||
|-------|-------------|
|
||||
| `Unknown` | Type not determined |
|
||||
| `Http` | HTTP endpoint |
|
||||
| `Grpc` | gRPC endpoint |
|
||||
| `Cli` | CLI command handler |
|
||||
| `Job` | Background job |
|
||||
| `Event` | Event handler |
|
||||
| `MessageQueue` | Message queue consumer |
|
||||
| `Timer` | Timer/scheduled task |
|
||||
| `Test` | Test method |
|
||||
| `Main` | Main entry point |
|
||||
| `ModuleInit` | Module initializer |
|
||||
| `StaticConstructor` | Static constructor |
|
||||
|
||||
### EntrypointFramework
|
||||
|
||||
Frameworks that expose entrypoints:
|
||||
|
||||
| Value | Description | Language |
|
||||
|-------|-------------|----------|
|
||||
| `Unknown` | Framework not determined | - |
|
||||
| `AspNetCore` | ASP.NET Core | DotNet |
|
||||
| `MinimalApi` | ASP.NET Core Minimal APIs | DotNet |
|
||||
| `Spring` | Spring Framework | Java |
|
||||
| `SpringBoot` | Spring Boot | Java |
|
||||
| `Express` | Express.js | Node |
|
||||
| `Fastify` | Fastify | Node |
|
||||
| `NestJs` | NestJS | Node |
|
||||
| `FastApi` | FastAPI | Python |
|
||||
| `Flask` | Flask | Python |
|
||||
| `Django` | Django | Python |
|
||||
| `Rails` | Ruby on Rails | Ruby |
|
||||
| `Gin` | Gin | Go |
|
||||
| `Echo` | Echo | Go |
|
||||
| `Actix` | Actix Web | Rust |
|
||||
| `Rocket` | Rocket | Rust |
|
||||
| `AzureFunctions` | Azure Functions | Multi |
|
||||
| `AwsLambda` | AWS Lambda | Multi |
|
||||
| `CloudFunctions` | Google Cloud Functions | Multi |
|
||||
|
||||
### EntrypointPhase
|
||||
|
||||
Execution phase for entrypoints:
|
||||
|
||||
| Value | Description |
|
||||
|-------|-------------|
|
||||
| `ModuleInit` | Module/assembly initialization |
|
||||
| `AppStart` | Application startup (Main) |
|
||||
| `Runtime` | Runtime request handling |
|
||||
| `Shutdown` | Shutdown/cleanup handlers |
|
||||
|
||||
## Node Structure
|
||||
|
||||
A `CallgraphNode` represents a symbol (method, function, type) in the call graph:
|
||||
|
||||
```json
|
||||
{
|
||||
"id": "n001",
|
||||
"nodeId": "n001",
|
||||
"name": "GetWeatherForecast",
|
||||
"kind": "method",
|
||||
"namespace": "SampleApi.Controllers",
|
||||
"file": "WeatherForecastController.cs",
|
||||
"line": 15,
|
||||
"symbolKey": "SampleApi.Controllers.WeatherForecastController::GetWeatherForecast()",
|
||||
"artifactKey": "SampleApi.dll",
|
||||
"visibility": "Public",
|
||||
"isEntrypointCandidate": true,
|
||||
"attributes": {
|
||||
"returnType": "IEnumerable<WeatherForecast>",
|
||||
"httpMethod": "GET",
|
||||
"route": "/weatherforecast"
|
||||
},
|
||||
"flags": 3
|
||||
}
|
||||
```
|
||||
|
||||
### Node Fields
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `id` | string | Yes | Unique identifier within the graph |
|
||||
| `nodeId` | string | No | Alias for id (v1 schema convention) |
|
||||
| `name` | string | Yes | Human-readable symbol name |
|
||||
| `kind` | string | Yes | Symbol kind (method, function, class) |
|
||||
| `namespace` | string | No | Namespace or module path |
|
||||
| `file` | string | No | Source file path |
|
||||
| `line` | int | No | Source line number |
|
||||
| `symbolKey` | string | No | Canonical symbol key (v1) |
|
||||
| `artifactKey` | string | No | Reference to containing artifact |
|
||||
| `visibility` | SymbolVisibility | No | Access visibility |
|
||||
| `isEntrypointCandidate` | bool | No | Whether node is an entrypoint candidate |
|
||||
| `purl` | string | No | Package URL for external packages |
|
||||
| `symbolDigest` | string | No | Content-addressed symbol digest |
|
||||
| `attributes` | object | No | Additional attributes |
|
||||
| `flags` | int | No | Bitmask for efficient filtering |
|
||||
|
||||
### Symbol Key Format
|
||||
|
||||
The `symbolKey` follows a canonical format:
|
||||
|
||||
```
|
||||
{Namespace}.{Type}[`Arity][+Nested]::{Method}[`Arity]({ParamTypes})
|
||||
```
|
||||
|
||||
Examples:
|
||||
- `System.String::Concat(string, string)`
|
||||
- `MyApp.Controllers.UserController::GetUser(int)`
|
||||
- `System.Collections.Generic.List`1::Add(T)`
|
||||
|
||||
## Edge Structure
|
||||
|
||||
A `CallgraphEdge` represents a call relationship between two symbols:
|
||||
|
||||
```json
|
||||
{
|
||||
"sourceId": "n001",
|
||||
"targetId": "n002",
|
||||
"from": "n001",
|
||||
"to": "n002",
|
||||
"type": "call",
|
||||
"kind": "Static",
|
||||
"reason": "DirectCall",
|
||||
"weight": 1.0,
|
||||
"offset": 42,
|
||||
"isResolved": true,
|
||||
"provenance": "static-analysis"
|
||||
}
|
||||
```
|
||||
|
||||
### Edge Fields
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `sourceId` | string | Yes | Source node ID (caller) |
|
||||
| `targetId` | string | Yes | Target node ID (callee) |
|
||||
| `from` | string | No | Alias for sourceId (v1) |
|
||||
| `to` | string | No | Alias for targetId (v1) |
|
||||
| `type` | string | No | Legacy edge type |
|
||||
| `kind` | EdgeKind | No | Edge classification |
|
||||
| `reason` | EdgeReason | No | Reason for edge existence |
|
||||
| `weight` | double | No | Confidence weight (0.0-1.0) |
|
||||
| `offset` | int | No | IL/bytecode offset |
|
||||
| `isResolved` | bool | No | Whether target was fully resolved |
|
||||
| `provenance` | string | No | Provenance information |
|
||||
| `candidates` | string[] | No | Virtual dispatch candidates |
|
||||
|
||||
## Entrypoint Structure
|
||||
|
||||
A `CallgraphEntrypoint` represents a discovered entrypoint:
|
||||
|
||||
```json
|
||||
{
|
||||
"nodeId": "n001",
|
||||
"kind": "Http",
|
||||
"route": "/api/users/{id}",
|
||||
"httpMethod": "GET",
|
||||
"framework": "AspNetCore",
|
||||
"source": "attribute",
|
||||
"phase": "Runtime",
|
||||
"order": 0
|
||||
}
|
||||
```
|
||||
|
||||
### Entrypoint Fields
|
||||
|
||||
| Field | Type | Required | Description |
|
||||
|-------|------|----------|-------------|
|
||||
| `nodeId` | string | Yes | Reference to the node |
|
||||
| `kind` | EntrypointKind | Yes | Type of entrypoint |
|
||||
| `route` | string | No | HTTP route pattern |
|
||||
| `httpMethod` | string | No | HTTP method (GET, POST, etc.) |
|
||||
| `framework` | EntrypointFramework | No | Framework exposing the entrypoint |
|
||||
| `source` | string | No | Discovery source |
|
||||
| `phase` | EntrypointPhase | No | Execution phase |
|
||||
| `order` | int | No | Deterministic ordering |
|
||||
|
||||
## Determinism Requirements
|
||||
|
||||
For reproducible analysis, call graphs must be deterministic:
|
||||
|
||||
1. **Stable Ordering**
|
||||
- Nodes must be sorted by `id` (ordinal string comparison)
|
||||
- Edges must be sorted by `sourceId`, then `targetId`
|
||||
- Entrypoints must be sorted by `order`
|
||||
|
||||
2. **Enum Serialization**
|
||||
- All enums serialize as camelCase strings
|
||||
- Example: `EdgeReason.DirectCall` → `"directCall"`
|
||||
|
||||
3. **Timestamps**
|
||||
- All timestamps must be UTC ISO 8601 format
|
||||
- Example: `2025-01-15T10:00:00Z`
|
||||
|
||||
4. **Content Hashing**
|
||||
- The `graphHash` field should contain a stable content hash
|
||||
- Hash algorithm: SHA-256
|
||||
- Format: `sha256:{hex-digest}`
|
||||
|
||||
## Schema Migration
|
||||
|
||||
Legacy call graphs without the `schema` field are automatically migrated:
|
||||
|
||||
1. **Schema Field**: Set to `stella.callgraph.v1`
|
||||
2. **Language Parsing**: String language converted to `CallgraphLanguage` enum
|
||||
3. **Visibility Inference**: Inferred from symbol key patterns:
|
||||
- Contains `.Internal.` → `Internal`
|
||||
- Contains `._` or `<` → `Private`
|
||||
- Default → `Public`
|
||||
4. **Edge Reason Inference**: Based on legacy `type` field:
|
||||
- `call`, `direct` → `DirectCall`
|
||||
- `virtual`, `callvirt` → `VirtualCall`
|
||||
- `newobj` → `NewObj`
|
||||
- etc.
|
||||
5. **Entrypoint Inference**: Built from legacy `roots` and candidate nodes
|
||||
6. **Symbol Key Generation**: Built from namespace and name if missing
|
||||
|
||||
## Validation Rules
|
||||
|
||||
Call graphs are validated against these rules:
|
||||
|
||||
1. All node `id` values must be unique
|
||||
2. All edge `sourceId` and `targetId` must reference existing nodes
|
||||
3. All entrypoint `nodeId` must reference existing nodes
|
||||
4. Edge `weight` must be between 0.0 and 1.0
|
||||
5. Artifacts referenced by nodes must exist in the `artifacts` list
|
||||
|
||||
## Golden Fixtures
|
||||
|
||||
Reference fixtures for testing are located at:
|
||||
`tests/reachability/fixtures/callgraph-schema-v1/`
|
||||
|
||||
| Fixture | Description |
|
||||
|---------|-------------|
|
||||
| `dotnet-aspnetcore-minimal.json` | ASP.NET Core application |
|
||||
| `java-spring-boot.json` | Spring Boot application |
|
||||
| `node-express-api.json` | Express.js API |
|
||||
| `go-gin-api.json` | Go Gin API |
|
||||
| `legacy-no-schema.json` | Legacy format for migration testing |
|
||||
| `all-edge-reasons.json` | All 13 edge reason codes |
|
||||
| `all-visibility-levels.json` | All 5 visibility levels |
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [Reachability Analysis Technical Reference](../reachability/README.md)
|
||||
- [Schema Migration Implementation](../../src/Signals/StellaOps.Signals/Parsing/CallgraphSchemaMigrator.cs)
|
||||
- [SPRINT_1100: CallGraph Schema Enhancement](../implplan/SPRINT_1100_0001_0001_callgraph_schema_enhancement.md)
|
||||
34
docs/modules/signals/guides/cas-promotion-24-002.md
Normal file
34
docs/modules/signals/guides/cas-promotion-24-002.md
Normal file
@@ -0,0 +1,34 @@
|
||||
# SIGNALS-24-002 · CAS promotion checklist (v1)
|
||||
|
||||
Purpose: unblock CAS promotion + signed manifest rollout for callgraph storage so SIGNALS-24-002 can move from BLOCKED to implementation.
|
||||
|
||||
## Preconditions
|
||||
- CAS bucket created for `signals-callgraphs` with write limited to Signals service principals.
|
||||
- Surface bundle mock hash recorded; real scanner cache ETA published.
|
||||
- Signed manifest tooling available (sigstore or in-house signer) with add-only policy.
|
||||
|
||||
## Steps
|
||||
1) Freeze manifest schema (fields: `graph_id`, `digest`, `language`, `source`, `created`, `signer`, `signature`).
|
||||
2) Generate manifests for existing callgraphs; store under `cas://signals/manifests/{graph_id}.json`.
|
||||
3) Sign each manifest; attach DSSE envelope; store under `cas://signals/manifests/{graph_id}.json.dsse`.
|
||||
4) Apply bucket policy: read-only for downstream, write for Signals service; deny deletes.
|
||||
5) Configure GC policy: retain manifests indefinitely; callgraph blobs keep 30d rolling unless referenced.
|
||||
6) Enable alerts for failed retrievals and missing manifest/DSSE pairs.
|
||||
7) Record hash list and signer key IDs in release notes.
|
||||
|
||||
## Deliverables
|
||||
- Policy document + proof of applied IAM
|
||||
- Manifest schema JSON
|
||||
- Signed manifest samples (see tests)
|
||||
- Hash list of all published callgraphs (sha256)
|
||||
|
||||
## Evidence locations (repo paths)
|
||||
- Policy & schema: `docs/modules/signals/guides/cas-promotion-24-002.md` (this file)
|
||||
- Sample manifest + DSSE: `tests/reachability/corpus/manifest.json` (already present) maps to expected structure.
|
||||
|
||||
## Owners
|
||||
- Signals Guild (implementation)
|
||||
- Platform Storage Guild (policy/approvals)
|
||||
|
||||
## Status
|
||||
- Checklist published 2025-11-19; awaiting Platform Storage approval to proceed.
|
||||
49
docs/modules/signals/guides/events-24-005.md
Normal file
49
docs/modules/signals/guides/events-24-005.md
Normal file
@@ -0,0 +1,49 @@
|
||||
# signals.fact.updated event contract (SIGNALS-24-005 prep)
|
||||
|
||||
**Purpose**: replace the in-memory logger used during Signals development with a real event bus contract so reachability caches can be invalidated and downstream consumers (Policy Engine, Notifications, Console) can subscribe deterministically.
|
||||
|
||||
## Topic / channel
|
||||
- Primary topic: `signals.fact.updated.v1`
|
||||
- Dead-letter topic: `signals.fact.updated.dlq`
|
||||
- Delivery: at-least-once; consumers must de-duplicate using `event_id`.
|
||||
|
||||
## Message envelope
|
||||
```jsonc
|
||||
{
|
||||
"event_id": "uuid-v4", // stable across retries; used for idempotency
|
||||
"emitted_at": "2025-11-20T00:00:00Z", // UTC, RFC3339
|
||||
"tenant": "acme", // required; lower-case
|
||||
"subject_key": "sbom:sha256:…" , // subject of facts (asset, sbom, host). Deterministic model key.
|
||||
"fact_kind": "callgraph" | "runtime" | "reachability" | "signal", // enums mapped from Signals domain
|
||||
"fact_version": 1, // monotonically increasing per subject_key + fact_kind
|
||||
"digest": "sha256:…", // CAS digest of canonical fact document
|
||||
"content_type": "application/json", // or application/vnd.stellaops.ndjson when chunked
|
||||
"producer": "StellaOps.Signals", // emitting service
|
||||
"source": {
|
||||
"pipeline": "signals", // consistent with Observability tags
|
||||
"release": "0.4.0-alpha" // optional
|
||||
},
|
||||
"trace": {
|
||||
"trace_id": "…", // pass-through if available
|
||||
"span_id": "…"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Routing / partitions
|
||||
- Partition key: `tenant` to keep per-tenant ordering.
|
||||
- Retry policy: exponential backoff up to 5 minutes; move to DLQ thereafter with `dlq_reason` header.
|
||||
|
||||
## Consumer expectations
|
||||
- De-duplicate on `event_id` and `digest`.
|
||||
- Fetch fact body from CAS using `digest`; avoid embedding large payloads in the message.
|
||||
- If consumer cannot resolve CAS, treat as transient and retry later (do not drop).
|
||||
|
||||
## Security / air-gap posture
|
||||
- No PII; tenant id only.
|
||||
- Works offline when bus is intra-cluster (e.g., NATS/Valkey Streams); external exporters disabled in sealed mode.
|
||||
|
||||
## Provenance
|
||||
- This contract supersedes the temporary log-based publisher referenced in Signals sprint 0143 Execution Log (2025-11-18). Aligns with `signals.fact.updated@v1` payload shape already covered by unit tests.
|
||||
- Implementation: `Signals.Events` defaults to Valkey Streams (`signals.fact.updated.v1` with `signals.fact.updated.dlq`), emitting envelopes that include `event_id`, `fact_version`, and deterministic `fact.digest` (sha256) generated by the reachability fact hasher.
|
||||
- Router transport: set `Signals.Events.Driver=router` to POST envelopes to the StellaOps Router gateway (`BaseUrl` + `Path`, default `/router/events/signals.fact.updated`) with optional API key/headers. This path should forward to downstream consumers registered in Router; Valkey remains mandatory for reachability cache but not for event fan-out when router is enabled.
|
||||
31
docs/modules/signals/guides/provenance-24-003.md
Normal file
31
docs/modules/signals/guides/provenance-24-003.md
Normal file
@@ -0,0 +1,31 @@
|
||||
# SIGNALS-24-003 · Provenance appendix checklist (v1)
|
||||
|
||||
Purpose: unblock provenance enrichment for runtime facts so SIGNALS-24-003 can advance once CAS promotion is approved.
|
||||
|
||||
## Required fields (per runtime fact)
|
||||
- `callgraph_id` (matches CAS manifest id)
|
||||
- `ingested_at` (UTC ISO-8601), `received_at`
|
||||
- `tenant`
|
||||
- `source` (host/service emitting facts)
|
||||
- `pipeline_version` (git SHA or build ID)
|
||||
- `provenance_hash` (sha256 of raw fact blob)
|
||||
- `signer` (key id) and optional `rekor_uuid` or `skip_reason: offline`
|
||||
|
||||
## Steps
|
||||
1) Freeze provenance JSON schema (`provenance.runtime.fact.v1`).
|
||||
2) Add enrichment stage writing provenance into CAS alongside runtime facts.
|
||||
3) Emit DSSE attestation per batch of runtime facts; store in CAS.
|
||||
4) Update `/signals/runtime-facts/ndjson` handler to return `provenance_hash` and `callgraph_id` when available.
|
||||
5) Add validation tests to ensure add-only evolution and deterministic ordering.
|
||||
|
||||
## Deliverables
|
||||
- Schema file: `docs/modules/signals/guides/provenance-24-003.md` (this file) with field list and invariants.
|
||||
- Test fixtures: reuse `tests/reachability/corpus/*/vex.openvex.json` provenance anchors; add `provenance_hash` coverage to `ReachabilityLatticeTests` when available.
|
||||
|
||||
## Owners
|
||||
- Signals Guild (implementation)
|
||||
- Runtime Guild (schema review)
|
||||
- Authority Guild (signing/attestation)
|
||||
|
||||
## Status
|
||||
- Checklist published 2025-11-19; awaiting schema/signing approval to proceed.
|
||||
16
docs/modules/signals/guides/reachability.md
Normal file
16
docs/modules/signals/guides/reachability.md
Normal file
@@ -0,0 +1,16 @@
|
||||
# Reachability Signals (outline)
|
||||
|
||||
## Pending Inputs
|
||||
- See sprint SPRINT_0309_0001_0009_docs_tasks_md_ix action tracker; inputs due 2025-12-09..12 from owning guilds.
|
||||
|
||||
## Determinism Checklist
|
||||
- [ ] Hash any inbound assets/payloads; place sums alongside artifacts (e.g., SHA256SUMS in this folder).
|
||||
- [ ] Keep examples offline-friendly and deterministic (fixed seeds, pinned versions, stable ordering).
|
||||
- [ ] Note source/approver for any provided captures or schemas.
|
||||
|
||||
## Sections to fill (once inputs arrive)
|
||||
- Purpose & scope (what “reachability” means across components).
|
||||
- States and scoring semantics.
|
||||
- Provenance and evidence sources.
|
||||
- Retention and TTL policy.
|
||||
- Sample payloads (with hashes recorded alongside).
|
||||
15
docs/modules/signals/guides/runtime-facts.md
Normal file
15
docs/modules/signals/guides/runtime-facts.md
Normal file
@@ -0,0 +1,15 @@
|
||||
# Runtime Facts (outline)
|
||||
|
||||
## Pending Inputs
|
||||
- See sprint SPRINT_0309_0001_0009_docs_tasks_md_ix action tracker; inputs due 2025-12-09..12 from owning guilds.
|
||||
|
||||
## Determinism Checklist
|
||||
- [ ] Hash any inbound assets/payloads; place sums alongside artifacts (e.g., SHA256SUMS in this folder).
|
||||
- [ ] Keep examples offline-friendly and deterministic (fixed seeds, pinned versions, stable ordering).
|
||||
- [ ] Note source/approver for any provided captures or schemas.
|
||||
|
||||
## Sections to fill (once inputs arrive)
|
||||
- Runtime agent capabilities captured.
|
||||
- Privacy safeguards and opt-in flags.
|
||||
- Payload schema and field descriptions.
|
||||
- Examples and hash listings for sample traces.
|
||||
383
docs/modules/signals/guides/unknowns-ranking.md
Normal file
383
docs/modules/signals/guides/unknowns-ranking.md
Normal file
@@ -0,0 +1,383 @@
|
||||
# Unknowns Ranking Algorithm Reference
|
||||
|
||||
This document describes the multi-factor scoring algorithm used to rank and triage unknowns in the StellaOps Signals module.
|
||||
|
||||
## Purpose
|
||||
|
||||
When reachability analysis encounters unresolved symbols, edges, or package identities, these are recorded as **unknowns**. The ranking algorithm prioritizes unknowns by computing a composite score from five factors, then assigns each to a triage band (HOT/WARM/COLD) that determines rescan scheduling and escalation policies.
|
||||
|
||||
## Scoring Formula
|
||||
|
||||
The composite score is computed as:
|
||||
|
||||
```
|
||||
Score = wP × P + wE × E + wU × U + wC × C + wS × S
|
||||
```
|
||||
|
||||
Where:
|
||||
- **P** = Popularity (deployment impact)
|
||||
- **E** = Exploit potential (CVE severity)
|
||||
- **U** = Uncertainty density (flag accumulation)
|
||||
- **C** = Centrality (graph position importance)
|
||||
- **S** = Staleness (evidence age)
|
||||
|
||||
All factors are normalized to [0.0, 1.0] before weighting. The final score is clamped to [0.0, 1.0].
|
||||
|
||||
### Default Weights
|
||||
|
||||
| Factor | Weight | Description |
|
||||
|--------|--------|-------------|
|
||||
| wP | 0.25 | Popularity weight |
|
||||
| wE | 0.25 | Exploit potential weight |
|
||||
| wU | 0.25 | Uncertainty density weight |
|
||||
| wC | 0.15 | Centrality weight |
|
||||
| wS | 0.10 | Staleness weight |
|
||||
|
||||
Weights must sum to 1.0 and are configurable via `Signals:UnknownsScoring` settings.
|
||||
|
||||
## Factor Details
|
||||
|
||||
### Factor P: Popularity (Deployment Impact)
|
||||
|
||||
Measures how widely the unknown's package is deployed across monitored environments.
|
||||
|
||||
**Formula:**
|
||||
```
|
||||
P = min(1, log10(1 + deploymentCount) / log10(1 + maxDeployments))
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- `deploymentCount`: Number of deployments referencing the package (from `deploy_refs` table)
|
||||
- `maxDeployments`: Normalization ceiling (default: 100)
|
||||
|
||||
**Rationale:** Logarithmic scaling prevents a single highly-deployed package from dominating scores while still prioritizing widely-used dependencies.
|
||||
|
||||
### Factor E: Exploit Potential (CVE Severity)
|
||||
|
||||
Estimates the consequence severity if the unknown resolves to a vulnerable component.
|
||||
|
||||
**Current Implementation:**
|
||||
- Returns 0.5 (medium potential) when no CVE association exists
|
||||
- Future: Integrate KEV lookup, EPSS scores, and exploit database references
|
||||
|
||||
**Planned Enhancements:**
|
||||
- CVE severity mapping (Critical=1.0, High=0.8, Medium=0.5, Low=0.2)
|
||||
- KEV (Known Exploited Vulnerabilities) flag boost
|
||||
- EPSS (Exploit Prediction Scoring System) integration
|
||||
|
||||
### Factor U: Uncertainty Density (Flag Accumulation)
|
||||
|
||||
Aggregates uncertainty signals from multiple sources. Each flag contributes a weighted penalty.
|
||||
|
||||
**Flag Weights:**
|
||||
|
||||
| Flag | Weight | Description |
|
||||
|------|--------|-------------|
|
||||
| `NoProvenanceAnchor` | 0.30 | Cannot verify package source |
|
||||
| `VersionRange` | 0.25 | Version specified as range, not exact |
|
||||
| `DynamicCallTarget` | 0.25 | Reflection, eval, or dynamic dispatch |
|
||||
| `ConflictingFeeds` | 0.20 | Contradictory info from different feeds |
|
||||
| `ExternalAssembly` | 0.20 | Assembly outside analysis scope |
|
||||
| `MissingVector` | 0.15 | No CVSS vector for severity assessment |
|
||||
| `UnreachableSourceAdvisory` | 0.10 | Source advisory URL unreachable |
|
||||
|
||||
**Formula:**
|
||||
```
|
||||
U = min(1.0, sum(activeFlags × flagWeight))
|
||||
```
|
||||
|
||||
**Example:**
|
||||
- NoProvenanceAnchor (0.30) + VersionRange (0.25) + MissingVector (0.15) = 0.70
|
||||
|
||||
### Factor C: Centrality (Graph Position Importance)
|
||||
|
||||
Measures the unknown's position importance in the call graph using betweenness centrality.
|
||||
|
||||
**Formula:**
|
||||
```
|
||||
C = min(1.0, betweenness / maxBetweenness)
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- `betweenness`: Raw betweenness centrality from graph analysis
|
||||
- `maxBetweenness`: Normalization ceiling (default: 1000)
|
||||
|
||||
**Rationale:** High-betweenness nodes appear on many shortest paths, meaning they're likely to be reached regardless of entry point.
|
||||
|
||||
**Related Metrics:**
|
||||
- `DegreeCentrality`: Number of incoming + outgoing edges (stored but not used in score)
|
||||
- `BetweennessCentrality`: Raw betweenness value (stored for debugging)
|
||||
|
||||
### Factor S: Staleness (Evidence Age)
|
||||
|
||||
Measures how old the evidence is since the last successful analysis attempt.
|
||||
|
||||
**Formula:**
|
||||
```
|
||||
S = min(1.0, daysSinceLastAnalysis / maxDays)
|
||||
```
|
||||
|
||||
With exponential decay enhancement (optional):
|
||||
```
|
||||
S = 1 - exp(-daysSinceLastAnalysis / tau)
|
||||
```
|
||||
|
||||
**Parameters:**
|
||||
- `daysSinceLastAnalysis`: Days since `LastAnalyzedAt` timestamp
|
||||
- `maxDays`: Staleness ceiling (default: 14 days)
|
||||
- `tau`: Decay constant for exponential model (default: 14)
|
||||
|
||||
**Special Cases:**
|
||||
- Never analyzed (`LastAnalyzedAt` is null): S = 1.0 (maximum staleness)
|
||||
|
||||
## Band Assignment
|
||||
|
||||
Based on the composite score, unknowns are assigned to triage bands:
|
||||
|
||||
| Band | Threshold | Rescan Policy | Description |
|
||||
|------|-----------|---------------|-------------|
|
||||
| **HOT** | Score >= 0.70 | 15 minutes | Immediate rescan + VEX escalation |
|
||||
| **WARM** | 0.40 <= Score < 0.70 | 24 hours | Scheduled rescan within 12-72h |
|
||||
| **COLD** | Score < 0.40 | 7 days | Weekly batch processing |
|
||||
|
||||
Thresholds are configurable:
|
||||
```yaml
|
||||
Signals:
|
||||
UnknownsScoring:
|
||||
HotThreshold: 0.70
|
||||
WarmThreshold: 0.40
|
||||
```
|
||||
|
||||
## Scheduler Integration
|
||||
|
||||
The `UnknownsRescanWorker` processes unknowns based on their band:
|
||||
|
||||
### HOT Band Processing
|
||||
- Poll interval: 1 minute
|
||||
- Batch size: 10 items
|
||||
- Action: Trigger immediate rescan via `IRescanOrchestrator`
|
||||
- On failure: Exponential backoff, max 3 retries before demotion to WARM
|
||||
|
||||
### WARM Band Processing
|
||||
- Poll interval: 5 minutes
|
||||
- Batch size: 50 items
|
||||
- Scheduled window: 12-72 hours based on score within band
|
||||
- On failure: Increment `RescanAttempts`, re-queue with delay
|
||||
|
||||
### COLD Band Processing
|
||||
- Schedule: Weekly on configurable day (default: Sunday)
|
||||
- Batch size: 500 items
|
||||
- Action: Batch rescan job submission
|
||||
- On failure: Log and retry next week
|
||||
|
||||
## Normalization Trace
|
||||
|
||||
Each scored unknown includes a `NormalizationTrace` for debugging and replay:
|
||||
|
||||
```json
|
||||
{
|
||||
"rawPopularity": 42,
|
||||
"normalizedPopularity": 0.65,
|
||||
"popularityFormula": "min(1, log10(1 + 42) / log10(1 + 100))",
|
||||
|
||||
"rawExploitPotential": 0.5,
|
||||
"normalizedExploitPotential": 0.5,
|
||||
|
||||
"rawUncertainty": 0.55,
|
||||
"normalizedUncertainty": 0.55,
|
||||
"activeFlags": ["NoProvenanceAnchor", "VersionRange"],
|
||||
|
||||
"rawCentrality": 250.0,
|
||||
"normalizedCentrality": 0.25,
|
||||
|
||||
"rawStaleness": 7,
|
||||
"normalizedStaleness": 0.5,
|
||||
|
||||
"weights": {
|
||||
"wP": 0.25,
|
||||
"wE": 0.25,
|
||||
"wU": 0.25,
|
||||
"wC": 0.15,
|
||||
"wS": 0.10
|
||||
},
|
||||
"finalScore": 0.52,
|
||||
"assignedBand": "Warm",
|
||||
"computedAt": "2025-12-15T10:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
**Replay Capability:** Given the trace, the exact score can be recomputed:
|
||||
```
|
||||
Score = 0.25×0.65 + 0.25×0.5 + 0.25×0.55 + 0.15×0.25 + 0.10×0.5
|
||||
= 0.1625 + 0.125 + 0.1375 + 0.0375 + 0.05
|
||||
= 0.5125 ≈ 0.52
|
||||
```
|
||||
|
||||
## API Endpoints
|
||||
|
||||
### Query Unknowns by Band
|
||||
|
||||
```
|
||||
GET /api/signals/unknowns?band=hot&limit=50&offset=0
|
||||
```
|
||||
|
||||
Response:
|
||||
```json
|
||||
{
|
||||
"items": [
|
||||
{
|
||||
"id": "unk-123",
|
||||
"subjectKey": "myapp|1.0.0",
|
||||
"purl": "pkg:npm/lodash@4.17.21",
|
||||
"score": 0.82,
|
||||
"band": "Hot",
|
||||
"flags": { "noProvenanceAnchor": true, "versionRange": true },
|
||||
"nextScheduledRescan": "2025-12-15T10:15:00Z"
|
||||
}
|
||||
],
|
||||
"total": 15,
|
||||
"hasMore": false
|
||||
}
|
||||
```
|
||||
|
||||
### Get Score Explanation
|
||||
|
||||
```
|
||||
GET /api/signals/unknowns/{id}/explain
|
||||
```
|
||||
|
||||
Response:
|
||||
```json
|
||||
{
|
||||
"unknown": { /* full UnknownSymbolDocument */ },
|
||||
"normalizationTrace": { /* trace object */ },
|
||||
"factorBreakdown": {
|
||||
"popularity": { "raw": 42, "normalized": 0.65, "weighted": 0.1625 },
|
||||
"exploitPotential": { "raw": 0.5, "normalized": 0.5, "weighted": 0.125 },
|
||||
"uncertainty": { "raw": 0.55, "normalized": 0.55, "weighted": 0.1375 },
|
||||
"centrality": { "raw": 250, "normalized": 0.25, "weighted": 0.0375 },
|
||||
"staleness": { "raw": 7, "normalized": 0.5, "weighted": 0.05 }
|
||||
},
|
||||
"bandThresholds": { "hot": 0.70, "warm": 0.40 }
|
||||
}
|
||||
```
|
||||
|
||||
## Configuration Reference
|
||||
|
||||
```yaml
|
||||
Signals:
|
||||
UnknownsScoring:
|
||||
# Factor weights (must sum to 1.0)
|
||||
WeightPopularity: 0.25
|
||||
WeightExploitPotential: 0.25
|
||||
WeightUncertainty: 0.25
|
||||
WeightCentrality: 0.15
|
||||
WeightStaleness: 0.10
|
||||
|
||||
# Popularity normalization
|
||||
PopularityMaxDeployments: 100
|
||||
|
||||
# Uncertainty flag weights
|
||||
FlagWeightNoProvenance: 0.30
|
||||
FlagWeightVersionRange: 0.25
|
||||
FlagWeightConflictingFeeds: 0.20
|
||||
FlagWeightMissingVector: 0.15
|
||||
FlagWeightUnreachableSource: 0.10
|
||||
FlagWeightDynamicTarget: 0.25
|
||||
FlagWeightExternalAssembly: 0.20
|
||||
|
||||
# Centrality normalization
|
||||
CentralityMaxBetweenness: 1000.0
|
||||
|
||||
# Staleness normalization
|
||||
StalenessMaxDays: 14
|
||||
StalenessTau: 14 # For exponential decay
|
||||
|
||||
# Band thresholds
|
||||
HotThreshold: 0.70
|
||||
WarmThreshold: 0.40
|
||||
|
||||
# Rescan scheduling
|
||||
HotRescanMinutes: 15
|
||||
WarmRescanHours: 24
|
||||
ColdRescanDays: 7
|
||||
|
||||
UnknownsDecay:
|
||||
# Nightly batch decay
|
||||
BatchEnabled: true
|
||||
MaxSubjectsPerBatch: 1000
|
||||
ColdBatchDay: Sunday
|
||||
```
|
||||
|
||||
## Determinism Requirements
|
||||
|
||||
The scoring algorithm is fully deterministic:
|
||||
|
||||
1. **Same inputs produce identical scores** - Given identical `UnknownSymbolDocument`, deployment counts, and graph metrics, the score will always be the same
|
||||
2. **Normalization trace enables replay** - The trace contains all raw values and weights needed to reproduce the score
|
||||
3. **Timestamps use UTC ISO 8601** - All `ComputedAt`, `LastAnalyzedAt`, and `NextScheduledRescan` timestamps are UTC
|
||||
4. **Weights logged per computation** - The trace includes the exact weights used, allowing audit of configuration changes
|
||||
|
||||
## Database Schema
|
||||
|
||||
```sql
|
||||
-- Unknowns table (enhanced)
|
||||
CREATE TABLE signals.unknowns (
|
||||
id UUID PRIMARY KEY,
|
||||
subject_key TEXT NOT NULL,
|
||||
purl TEXT,
|
||||
symbol_id TEXT,
|
||||
callgraph_id TEXT,
|
||||
|
||||
-- Scoring factors
|
||||
popularity_score FLOAT DEFAULT 0,
|
||||
deployment_count INT DEFAULT 0,
|
||||
exploit_potential_score FLOAT DEFAULT 0,
|
||||
uncertainty_score FLOAT DEFAULT 0,
|
||||
centrality_score FLOAT DEFAULT 0,
|
||||
degree_centrality INT DEFAULT 0,
|
||||
betweenness_centrality FLOAT DEFAULT 0,
|
||||
staleness_score FLOAT DEFAULT 0,
|
||||
days_since_last_analysis INT DEFAULT 0,
|
||||
|
||||
-- Composite score and band
|
||||
score FLOAT DEFAULT 0,
|
||||
band TEXT DEFAULT 'cold' CHECK (band IN ('hot', 'warm', 'cold')),
|
||||
|
||||
-- Metadata
|
||||
flags JSONB DEFAULT '{}',
|
||||
normalization_trace JSONB,
|
||||
rescan_attempts INT DEFAULT 0,
|
||||
last_rescan_result TEXT,
|
||||
|
||||
-- Timestamps
|
||||
created_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
updated_at TIMESTAMPTZ NOT NULL DEFAULT NOW(),
|
||||
last_analyzed_at TIMESTAMPTZ,
|
||||
next_scheduled_rescan TIMESTAMPTZ
|
||||
);
|
||||
|
||||
-- Indexes for band-based queries
|
||||
CREATE INDEX idx_unknowns_band ON signals.unknowns(band);
|
||||
CREATE INDEX idx_unknowns_score ON signals.unknowns(score DESC);
|
||||
CREATE INDEX idx_unknowns_next_rescan ON signals.unknowns(next_scheduled_rescan)
|
||||
WHERE next_scheduled_rescan IS NOT NULL;
|
||||
CREATE INDEX idx_unknowns_subject ON signals.unknowns(subject_key);
|
||||
```
|
||||
|
||||
## Metrics and Observability
|
||||
|
||||
The following metrics are exposed for monitoring:
|
||||
|
||||
| Metric | Type | Description |
|
||||
|--------|------|-------------|
|
||||
| `signals_unknowns_total` | Gauge | Total unknowns by band |
|
||||
| `signals_unknowns_rescans_total` | Counter | Rescans triggered by band |
|
||||
| `signals_unknowns_scoring_duration_seconds` | Histogram | Scoring computation time |
|
||||
| `signals_unknowns_band_transitions_total` | Counter | Band changes (e.g., WARM->HOT) |
|
||||
|
||||
## Related Documentation
|
||||
|
||||
- [Unknowns Registry](./unknowns-registry.md) - Data model and API for unknowns
|
||||
- [Reachability Analysis](./reachability.md) - Reachability scoring integration
|
||||
- [Callgraph Schema](./callgraph-formats.md) - Graph structure for centrality computation
|
||||
81
docs/modules/signals/guides/unknowns-registry.md
Normal file
81
docs/modules/signals/guides/unknowns-registry.md
Normal file
@@ -0,0 +1,81 @@
|
||||
# Unknowns Registry (Signals) — November 2026
|
||||
|
||||
This document defines the Unknowns Registry that turns unresolved identities or edges into first-class signals. It replaces the temporary notes from late 2026 advisories.
|
||||
|
||||
## 1. Purpose
|
||||
|
||||
When scanners or runtime probes cannot decisively map artifacts, symbols, or package identities, the gap is recorded as an **Unknown** instead of being dropped. Policy and scoring can then incorporate “unknowns pressure” to avoid silent false negatives.
|
||||
|
||||
## 2. Data model (v0)
|
||||
|
||||
```json
|
||||
{
|
||||
"unknown_id": "unk:sha256:<type+scope+evidence>",
|
||||
"observed_at": "2025-11-20T00:00:00Z",
|
||||
"provenance": { "source": "Scanner|Signals|SbomService|Vexer", "host": "runner-42", "scan_id": "scan:..." },
|
||||
"scope": { "artifact": { "type": "oci.image", "ref": "registry/app@sha256:..." }, "subpath": "/app/bin/libssl.so.3", "phase": "scan|runtime|build" },
|
||||
"unknown_type": "identity_gap|version_conflict|hash_mismatch|missing_edge|runtime_shadow|policy_undecidable",
|
||||
"evidence": { "raw": "dynsym missing for libssl.so.3", "signals": ["sym:memcpy", "import:SSL_free"] },
|
||||
"transitive": { "depth": 1, "parents": ["pkg:deb/openssl@3.0.2"], "children": [] },
|
||||
"confidence": { "p": 0.42, "method": "rule" },
|
||||
"exposure_hints": { "surface": ["startup"], "runtime_hits": 0 },
|
||||
"status": "open|triaged|suppressed|resolved",
|
||||
"labels": ["reachability:possible", "sbom:incomplete"]
|
||||
}
|
||||
```
|
||||
|
||||
## 3. API (idempotent)
|
||||
|
||||
- `POST /unknowns/ingest` — upsert by `unknown_id`; repeat payloads are no-ops.
|
||||
- `GET /unknowns?artifact=...&status=open` — list unknowns for a target.
|
||||
- `POST /unknowns/{id}/triage` — update `status`/`labels`, attach rationale.
|
||||
- `GET /unknowns/metrics` — density by artifact / unknown_type / depth.
|
||||
|
||||
All endpoints are additive; no hard deletes. Payloads must include tenant bindings and CAS URIs when evidence is stored externally.
|
||||
|
||||
## 4. Producers
|
||||
|
||||
- **Scanner**: unresolved symbol → package mapping (stripped binaries), missing build-id, ambiguous purl; log with `unknown_type=identity_gap` or `missing_edge`.
|
||||
- **Signals**: runtime hits that cannot map to a graph node or purl; unresolved call edges.
|
||||
- **SbomService**: conflicting versions for same path; hash mismatch between SBOM and observed file.
|
||||
- **Vexer/Policy**: advisory without trustable provenance (`unknown_type=policy_undecidable`).
|
||||
|
||||
## 5. Consumers & scoring
|
||||
|
||||
- Signals scoring adds `unknowns_pressure = f(density(depth<=1), runtime_shadow, policy_undecidable)` and feeds it into reachability/risk scores.
|
||||
- Policy can block `not_affected` claims when `unknowns_pressure` exceeds thresholds.
|
||||
- UI/CLI show unknown chips with reason and depth; operators can triage or suppress.
|
||||
|
||||
### 5.1 Multi-Factor Ranking
|
||||
|
||||
Unknowns are ranked using a 5-factor scoring algorithm that computes a composite score from:
|
||||
- **Popularity (P)** - Deployment impact based on usage count
|
||||
- **Exploit Potential (E)** - CVE severity if known
|
||||
- **Uncertainty (U)** - Accumulated flag weights
|
||||
- **Centrality (C)** - Graph position importance (betweenness)
|
||||
- **Staleness (S)** - Evidence age since last analysis
|
||||
|
||||
Based on the composite score, unknowns are assigned to triage bands:
|
||||
- **HOT** (score >= 0.70): Immediate rescan, 15-minute scheduling
|
||||
- **WARM** (0.40 <= score < 0.70): Scheduled rescan within 12-72h
|
||||
- **COLD** (score < 0.40): Weekly batch processing
|
||||
|
||||
See [Unknowns Ranking Algorithm](./unknowns-ranking.md) for the complete formula reference.
|
||||
|
||||
## 6. Storage & CAS
|
||||
|
||||
- Primary store: append-only KV/graph in PostgreSQL (tables `unknowns`, `unknown_metrics`).
|
||||
- Evidence blobs: CAS under `cas://unknowns/{sha256}` for large payloads (runtime traces, partial SBOMs).
|
||||
- Include analyzer fingerprint + schema version in each record for replay.
|
||||
|
||||
## 7. Integration checkpoints
|
||||
|
||||
- Add writer hooks in Scanner/Signals once `richgraph-v1` and runtime ingestion surface unmapped items.
|
||||
- Extend reachability lattice docs to note `unknowns_pressure` input.
|
||||
- Add Grafana panel for unknown density per artifact/namespace.
|
||||
|
||||
## 8. Acceptance criteria
|
||||
|
||||
- APIs deployed with idempotent behavior and tenant guards.
|
||||
- At least two producer paths writing Unknowns (Scanner unresolved symbol; Signals runtime shadow).
|
||||
- Metrics endpoint shows density and trend; UI/CLI expose triage status.
|
||||
10000
docs/modules/signals/samples/reachability/callgraph-10k.ndjson
Normal file
10000
docs/modules/signals/samples/reachability/callgraph-10k.ndjson
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1 @@
|
||||
2eae348a6d122682eb24924e6340ca6213dfc896a3b3e7484e5e52967f65af1f callgraph-10k.ndjson
|
||||
50000
docs/modules/signals/samples/reachability/callgraph-50k.ndjson
Normal file
50000
docs/modules/signals/samples/reachability/callgraph-50k.ndjson
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1 @@
|
||||
f30708cf68f3ade5b06dcbd8b5056aae9ff93f61cfe21843cbd9d2bf13413774 callgraph-50k.ndjson
|
||||
1000
docs/modules/signals/samples/reachability/runtime-10k.ndjson
Normal file
1000
docs/modules/signals/samples/reachability/runtime-10k.ndjson
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1 @@
|
||||
39ca5e95f3b61afc657d104397106ed5d1ee1e7be2b31397b9c4ac24d1d2b903 runtime-10k.ndjson
|
||||
5000
docs/modules/signals/samples/reachability/runtime-50k.ndjson
Normal file
5000
docs/modules/signals/samples/reachability/runtime-50k.ndjson
Normal file
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1 @@
|
||||
5c0673d43fa3a1617de735e68d69f9a9b6db5634408c6a6e6bba764d36e3111b runtime-50k.ndjson
|
||||
Reference in New Issue
Block a user