feat: Implement console session management with tenant and profile handling
- Add ConsoleSessionStore for managing console session state including tenants, profile, and token information. - Create OperatorContextService to manage operator context for orchestrator actions. - Implement OperatorMetadataInterceptor to enrich HTTP requests with operator context metadata. - Develop ConsoleProfileComponent to display user profile and session details, including tenant information and access tokens. - Add corresponding HTML and SCSS for ConsoleProfileComponent to enhance UI presentation. - Write unit tests for ConsoleProfileComponent to ensure correct rendering and functionality.
This commit is contained in:
		
							
								
								
									
										337
									
								
								docs/export-center/api.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										337
									
								
								docs/export-center/api.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,337 @@ | ||||
| # Export Center REST API | ||||
|  | ||||
| > **Audience:** Platform integrators, Console/CLI developers, and automation engineers orchestrating export runs.   | ||||
| > **Base route:** `/api/export/*` behind the StellaOps gateway; requires Authority-issued tokens with export scopes. | ||||
|  | ||||
| This reference describes the Export Center API introduced in Export Center Phase 1 (Epic 10) and extended in Phase 2. Use it alongside the [Export Center Architecture](architecture.md) and [Profiles](profiles.md) guides for service-level semantics. | ||||
|  | ||||
| > Status: Endpoint implementation lands with `EXPORT-SVC-35-006` (Sprint 35) and related follow-on tasks. As of the current build the WebService hosts only the template stub; use this contract for coordination and update once the API is wired. | ||||
|  | ||||
| ## 1. Authentication and headers | ||||
|  | ||||
| - **Authorization:** Bearer tokens in `Authorization: Bearer <token>` paired with DPoP proof. Required scopes per endpoint: | ||||
|   - `export:profile:manage` for profile CRUD. | ||||
|   - `export:run` to submit and cancel runs. | ||||
|   - `export:read` to list and inspect runs. | ||||
|   - `export:download` for bundle downloads and manifests. | ||||
| - **Tenant context:** Provide `X-Stella-Tenant` when the token carries multiple tenants; defaults to token tenant otherwise. | ||||
| - **Idempotency:** Mutating endpoints accept `Idempotency-Key` (UUID). Retrying with the same key returns the original result. | ||||
| - **Rate limits and quotas:** Responses include `X-Stella-Quota-Limit`, `X-Stella-Quota-Remaining`, and `X-Stella-Quota-Reset`. Exceeding quotas returns `429 Too Many Requests` with `ERR_EXPORT_QUOTA`. | ||||
| - **Content negotiation:** Requests and responses use `application/json; charset=utf-8` unless otherwise stated. Downloads stream binary content with profile-specific media types. | ||||
| - **SSE:** Event streams set `Content-Type: text/event-stream` and keep connections alive with comment heartbeats every 15 seconds. | ||||
|  | ||||
| ## 2. Error model | ||||
|  | ||||
| Errors follow standard HTTP codes with structured payloads: | ||||
|  | ||||
| ```json | ||||
| { | ||||
|   "code": "ERR_EXPORT_002", | ||||
|   "message": "Profile not found for tenant acme", | ||||
|   "details": [], | ||||
|   "traceId": "01J9N4Y4K2XY8C5V7T2S", | ||||
|   "timestamp": "2025-10-29T13:42:11Z" | ||||
| } | ||||
| ``` | ||||
|  | ||||
| | Code | Description | Typical HTTP status | Notes | | ||||
| |------|-------------|---------------------|-------| | ||||
| | `ERR_EXPORT_001` | Validation failure (selectors, configuration) | 400 | `details` enumerates offending fields. | | ||||
| | `ERR_EXPORT_002` | Profile missing or not accessible for tenant | 404 | Returned on run submission or profile fetch. | | ||||
| | `ERR_EXPORT_003` | Concurrency or quota exceeded | 429 | Includes `retryAfterSeconds` in `details`. | | ||||
| | `ERR_EXPORT_004` | Adapter failure (schema mismatch, upstream outage) | 502 | Worker logs contain adapter error reason. | | ||||
| | `ERR_EXPORT_005` | Signing or KMS error | 500 | Run marked failed with `errorCode=signing`. | | ||||
| | `ERR_EXPORT_006` | Distribution failure (HTTP, OCI, object storage) | 502 | `details` lists failing distribution driver. | | ||||
| | `ERR_EXPORT_007` | Run canceled or expired | 409 | Includes cancel author and timestamp. | | ||||
| | `ERR_EXPORT_BASE_MISSING` | Base manifest for delta exports not found | 400 | Specific to `mirror:delta`. | | ||||
| | `ERR_EXPORT_EMPTY` | No records matched selectors (when `allowEmpty=false`) | 422 | Useful for guard-railled automation. | | ||||
| | `ERR_EXPORT_QUOTA` | Daily quota exhausted | 429 | Always paired with quota headers. | | ||||
|  | ||||
| All responses include `traceId` for correlation with logs and metrics. | ||||
|  | ||||
| ## 3. Profiles endpoints | ||||
|  | ||||
| ### 3.1 List profiles | ||||
|  | ||||
| ``` | ||||
| GET /api/export/profiles?kind=json&variant=raw&page=1&pageSize=20 | ||||
| Scopes: export:read | ||||
| ``` | ||||
|  | ||||
| Returns tenant-scoped profiles. Response headers: `X-Total-Count`, `Link` for pagination. | ||||
|  | ||||
| **Response** | ||||
|  | ||||
| ```json | ||||
| { | ||||
|   "items": [ | ||||
|     { | ||||
|       "profileId": "prof-json-raw", | ||||
|       "name": "Daily JSON Raw", | ||||
|       "kind": "json", | ||||
|       "variant": "raw", | ||||
|       "distribution": ["http", "object"], | ||||
|       "retention": {"mode": "days", "value": 14}, | ||||
|       "createdAt": "2025-10-23T08:00:00Z", | ||||
|       "createdBy": "user:ops" | ||||
|     } | ||||
|   ], | ||||
|   "page": 1, | ||||
|   "pageSize": 20 | ||||
| } | ||||
| ``` | ||||
|  | ||||
| ### 3.2 Get a profile | ||||
|  | ||||
| ``` | ||||
| GET /api/export/profiles/{profileId} | ||||
| Scopes: export:read | ||||
| ``` | ||||
|  | ||||
| Returns full configuration, including `config` payload, distribution options, and metadata. | ||||
|  | ||||
| ### 3.3 Create a profile | ||||
|  | ||||
| ``` | ||||
| POST /api/export/profiles | ||||
| Scopes: export:profile:manage | ||||
| ``` | ||||
|  | ||||
| **Request** | ||||
|  | ||||
| ```json | ||||
| { | ||||
|   "profileId": "prof-airgap-mirror", | ||||
|   "name": "Airgap Mirror Weekly", | ||||
|   "kind": "mirror", | ||||
|   "variant": "full", | ||||
|   "include": ["advisories", "vex", "sboms", "policy"], | ||||
|   "distribution": ["http", "object"], | ||||
|   "encryption": { | ||||
|     "enabled": true, | ||||
|     "recipientKeys": ["age1tenantkey..."], | ||||
|     "strict": false | ||||
|   }, | ||||
|   "retention": {"mode": "days", "value": 30} | ||||
| } | ||||
| ``` | ||||
|  | ||||
| **Response 201** | ||||
|  | ||||
| ```json | ||||
| { | ||||
|   "profileId": "prof-airgap-mirror", | ||||
|   "version": 1, | ||||
|   "createdAt": "2025-10-29T12:05:22Z", | ||||
|   "createdBy": "user:ops", | ||||
|   "status": "active" | ||||
| } | ||||
| ``` | ||||
|  | ||||
| ### 3.4 Update profile metadata | ||||
|  | ||||
| ``` | ||||
| PATCH /api/export/profiles/{profileId} | ||||
| Scopes: export:profile:manage | ||||
| ``` | ||||
|  | ||||
| Allows renaming, toggling distribution switches, or updating retention. Structural configuration updates (kind/variant/include) create a new revision; the API returns `revisionCreated=true` and the new `profileId` (e.g., `prof-airgap-mirror@2`). | ||||
|  | ||||
| ### 3.5 Archive profile | ||||
|  | ||||
| ``` | ||||
| POST /api/export/profiles/{profileId}:archive | ||||
| Scopes: export:profile:manage | ||||
| ``` | ||||
|  | ||||
| Marks profile as inactive; existing runs remain accessible. Use `:restore` to reactivate. | ||||
|  | ||||
| ## 4. Run management | ||||
|  | ||||
| ### 4.1 Submit an export run | ||||
|  | ||||
| ``` | ||||
| POST /api/export/runs | ||||
| Scopes: export:run | ||||
| ``` | ||||
|  | ||||
| **Request** | ||||
|  | ||||
| ```json | ||||
| { | ||||
|   "profileId": "prof-json-raw", | ||||
|   "selectors": { | ||||
|     "tenants": ["acme"], | ||||
|     "timeWindow": { | ||||
|       "from": "2025-10-01T00:00:00Z", | ||||
|       "to": "2025-10-29T00:00:00Z" | ||||
|     }, | ||||
|     "products": ["registry.example.com/app:*"], | ||||
|     "sboms": ["sbom:S-1001", "sbom:S-2004"] | ||||
|   }, | ||||
|   "policySnapshotId": "policy-snap-42", | ||||
|   "options": { | ||||
|     "allowEmpty": false, | ||||
|     "priority": "standard" | ||||
|   } | ||||
| } | ||||
| ``` | ||||
|  | ||||
| **Response 202** | ||||
|  | ||||
| ```json | ||||
| { | ||||
|   "runId": "run-20251029-01", | ||||
|   "status": "pending", | ||||
|   "profileId": "prof-json-raw", | ||||
|   "createdAt": "2025-10-29T12:12:11Z", | ||||
|   "createdBy": "user:ops", | ||||
|   "selectors": { "...": "..." }, | ||||
|   "links": { | ||||
|     "self": "/api/export/runs/run-20251029-01", | ||||
|     "events": "/api/export/runs/run-20251029-01/events" | ||||
|   } | ||||
| } | ||||
| ``` | ||||
|  | ||||
| ### 4.2 List runs | ||||
|  | ||||
| ``` | ||||
| GET /api/export/runs?status=active&profileId=prof-json-raw&page=1&pageSize=10 | ||||
| Scopes: export:read | ||||
| ``` | ||||
|  | ||||
| Returns latest runs with pagination. Each item includes summary counts, duration, and last event. | ||||
|  | ||||
| ### 4.3 Get run status | ||||
|  | ||||
| ``` | ||||
| GET /api/export/runs/{runId} | ||||
| Scopes: export:read | ||||
| ``` | ||||
|  | ||||
| Response fields: | ||||
|  | ||||
| | Field | Description | | ||||
| |-------|-------------| | ||||
| | `status` | `pending`, `running`, `success`, `failed`, `canceled`. | | ||||
| | `progress` | Object with `adapters`, `bytesWritten`, `recordsProcessed`. | | ||||
| | `errorCode` | Populated when `status=failed` (`signing`, `distribution`, etc). | | ||||
| | `policySnapshotId` | Returned for policy-aware profiles. | | ||||
| | `distributions` | List of available distribution descriptors (type, location, sha256, expiresAt). | | ||||
|  | ||||
| ### 4.4 Cancel a run | ||||
|  | ||||
| ``` | ||||
| POST /api/export/runs/{runId}:cancel | ||||
| Scopes: export:run | ||||
| ``` | ||||
|  | ||||
| Body optional (`{"reason": "Aborted due to incident INC-123"}`). Returns 202 and pushes `run.canceled` event. | ||||
|  | ||||
| ## 5. Events and telemetry | ||||
|  | ||||
| ### 5.1 Server-sent events | ||||
|  | ||||
| ``` | ||||
| GET /api/export/runs/{runId}/events | ||||
| Scopes: export:read | ||||
| Accept: text/event-stream | ||||
| ``` | ||||
|  | ||||
| Event payload example: | ||||
|  | ||||
| ``` | ||||
| event: run.progress | ||||
| data: {"runId":"run-20251029-01","phase":"adapter","adapter":"json","records":1024,"bytes":7340032,"timestamp":"2025-10-29T12:13:15Z"} | ||||
| ``` | ||||
|  | ||||
| Event types: | ||||
|  | ||||
| | Event | Meaning | | ||||
| |-------|---------| | ||||
| | `run.accepted` | Planner accepted job and queued with Orchestrator. | | ||||
| | `run.progress` | Periodic updates with phase, adapter, counts. | | ||||
| | `run.distribution` | Distribution driver finished (includes descriptor). | | ||||
| | `run.signed` | Signing completed successfully. | | ||||
| | `run.succeeded` | Run marked `success`. | | ||||
| | `run.failed` | Run failed; payload includes `errorCode`. | | ||||
| | `run.canceled` | Run canceled; includes `canceledBy`. | | ||||
|  | ||||
| SSE heartbeats (`: ping`) keep long-lived connections alive and should be ignored by clients. | ||||
|  | ||||
| ### 5.2 Audit events | ||||
|  | ||||
| `GET /api/export/runs/{runId}/events?format=audit` returns the same event stream in newline-delimited JSON for offline ingestion. | ||||
|  | ||||
| ## 6. Download endpoints | ||||
|  | ||||
| ### 6.1 Bundle download | ||||
|  | ||||
| ``` | ||||
| GET /api/export/runs/{runId}/download | ||||
| Scopes: export:download | ||||
| ``` | ||||
|  | ||||
| Streams the primary bundle (tarball, zip, or profile-specific layout). Headers: | ||||
|  | ||||
| - `Content-Disposition: attachment; filename="export-run-20251029-01.tar.zst"` | ||||
| - `X-Export-Digest: sha256:...` | ||||
| - `X-Export-Size: 73482019` | ||||
| - `X-Export-Encryption: age` (when mirror encryption enabled) | ||||
|  | ||||
| Supports HTTP range requests for resume functionality. If no bundle exists yet, responds `409` with `ERR_EXPORT_007`. | ||||
|  | ||||
| ### 6.2 Manifest download | ||||
|  | ||||
| ``` | ||||
| GET /api/export/runs/{runId}/manifest | ||||
| Scopes: export:download | ||||
| ``` | ||||
|  | ||||
| Returns signed `export.json`. To fetch the detached signature, append `?signature=true`. | ||||
|  | ||||
| ### 6.3 Provenance download | ||||
|  | ||||
| ``` | ||||
| GET /api/export/runs/{runId}/provenance | ||||
| Scopes: export:download | ||||
| ``` | ||||
|  | ||||
| Returns signed `provenance.json`. Supports `?signature=true`. Provenance includes attestation subject digests, policy snapshot ids, adapter versions, and KMS key identifiers. | ||||
|  | ||||
| ### 6.4 Distribution descriptors | ||||
|  | ||||
| ``` | ||||
| GET /api/export/runs/{runId}/distributions | ||||
| Scopes: export:read | ||||
| ``` | ||||
|  | ||||
| Lists all registered distribution targets (HTTP, OCI, object storage). Each item includes `type`, `location`, `sha256`, `sizeBytes`, and `expiresAt`. | ||||
|  | ||||
| ## 7. Webhook hand-off | ||||
|  | ||||
| Exports can notify external systems once a run succeeds by registering an HTTP webhook: | ||||
|  | ||||
| ``` | ||||
| POST /api/export/webhooks | ||||
| Scopes: export:profile:manage | ||||
| ``` | ||||
|  | ||||
| Payload includes `targetUrl`, `events` (e.g., `run.succeeded`), and optional secret for HMAC signatures. Webhook deliveries sign payloads with `X-Stella-Signature` header (`sha256=...`). Retries follow exponential backoff with dead-letter capture in `export_events`. | ||||
|  | ||||
| ## 8. Observability | ||||
|  | ||||
| - **Metrics endpoint:** `/metrics` (service-local) exposes Prometheus metrics listed in [Architecture](architecture.md#observability). | ||||
| - **Tracing:** When `traceparent` header is provided, worker spans join the calling trace. | ||||
| - **Run lookup by trace:** Use `GET /api/export/runs?traceId={id}` when troubleshooting distributed traces. | ||||
|  | ||||
| ## 9. Related documentation | ||||
|  | ||||
| - [Export Center Overview](overview.md) | ||||
| - [Export Center Architecture](architecture.md) | ||||
| - [Export Center Profiles](profiles.md) | ||||
| - [Export Center CLI Guide](cli.md) *(companion document)* | ||||
| - [Aggregation-Only Contract reference](../ingestion/aggregation-only-contract.md) | ||||
|  | ||||
| > **Imposed rule:** Work of this type or tasks of this type on this component must also be applied everywhere else it should be applied. | ||||
							
								
								
									
										125
									
								
								docs/export-center/architecture.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										125
									
								
								docs/export-center/architecture.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,125 @@ | ||||
| # Export Center Architecture | ||||
|  | ||||
| The Export Center is the dedicated service layer that packages StellaOps evidence and policy overlays into reproducible bundles. It runs as a multi-surface API backed by asynchronous workers and format adapters, enforcing Aggregation-Only Contract (AOC) guardrails while providing deterministic manifests, signing, and distribution paths. | ||||
|  | ||||
| ## Runtime topology | ||||
| - **Export Center API (`StellaOps.ExportCenter.WebService`).** Receives profile CRUD, export run requests, status queries, and download streams through the unified Web API gateway. Enforces tenant scopes, RBAC, quotas, and concurrency guards. | ||||
| - **Export Center Worker (`StellaOps.ExportCenter.Worker`).** Dequeues export jobs from the Orchestrator, resolves selectors, invokes adapters, and writes manifests and bundle artefacts. Stateless; scales horizontally. | ||||
| - **Backing stores.** | ||||
|   - MongoDB collections: `export_profiles`, `export_runs`, `export_inputs`, `export_distributions`, `export_events`. | ||||
|   - Object storage bucket or filesystem for staging bundle payloads. | ||||
|   - Optional registry/object storage credentials injected via Authority-scoped secrets. | ||||
| - **Integration peers.** | ||||
|   - **Findings Ledger** for advisory, VEX, SBOM payload streaming. | ||||
|   - **Policy Engine** for deterministic policy snapshots and evaluated findings. | ||||
|   - **Orchestrator** for job scheduling, quotas, and telemetry fan-out. | ||||
|   - **Authority** for tenant-aware access tokens and KMS key references. | ||||
|   - **Console & CLI** as presentation surfaces consuming the API. | ||||
|  | ||||
| ## Job lifecycle | ||||
| 1. **Profile selection.** Operator or automation picks a profile (`json:raw`, `json:policy`, `trivy:db`, `trivy:java-db`, `mirror:full`, `mirror:delta`) and submits scope selectors (tenant, time window, products, SBOM subjects, ecosystems). See `docs/export-center/profiles.md` for profile definitions and configuration fields. | ||||
| 2. **Planner resolution.** API validates selectors, expands include/exclude lists, and writes a pending `export_run` with immutable parameters and deterministic ordering hints. | ||||
| 3. **Orchestrator dispatch.** `export_run` triggers a job lease via Orchestrator with quotas per tenant/profile and concurrency caps (default 4 active per tenant). | ||||
| 4. **Worker execution.** Worker streams data from Findings Ledger and Policy Engine using pagination cursors. Adapters write canonical payloads to staging storage, compute checksums, and emit streaming progress events (SSE). | ||||
| 5. **Manifest and provenance emission.** Worker writes `export.json` and `provenance.json`, signs them with configured KMS keys (cosign-compatible), and uploads signatures alongside content. | ||||
| 6. **Distribution registration.** Worker records available distribution methods (download URL, OCI reference, object storage path), raises completion/failure events, and exposes metrics/logs. | ||||
| 7. **Download & verification.** Clients download bundles or pull OCI artefacts, verify signatures, and consume provenance to trace source artefacts. | ||||
|  | ||||
| Cancellation requests mark runs as `aborted` and cause workers to stop iterating sources; partially written files are destroyed and the run is marked with an audit entry. | ||||
|  | ||||
| ## Core components | ||||
| ### API surface | ||||
| - Detailed request and response payloads are catalogued in `docs/export-center/api.md`. | ||||
| - **Profiles API.** | ||||
|   - `GET /api/export/profiles`: list tenant-scoped profiles. | ||||
|   - `POST /api/export/profiles`: create custom profiles (variants of JSON, Trivy, mirror) with validated configuration schema. | ||||
|   - `PATCH /api/export/profiles/{id}`: update metadata; config changes clone new revision to preserve determinism. | ||||
| - **Runs API.** | ||||
|   - `POST /api/export/runs`: submit export run for a profile with selectors and options (policy snapshot id, mirror base manifest). | ||||
|   - `GET /api/export/runs/{id}`: status, progress counters, provenance summary. | ||||
|   - `GET /api/export/runs/{id}/events`: server-sent events with state transitions, adapter milestones, signing status. | ||||
|   - `POST /api/export/runs/{id}/cancel`: cooperative cancellation with audit logging. | ||||
| - **Downloads API.** | ||||
|   - `GET /api/export/runs/{id}/download`: streaming download with range support and checksum trailers. | ||||
|   - `GET /api/export/runs/{id}/manifest`: signed `export.json`. | ||||
|   - `GET /api/export/runs/{id}/provenance`: signed `provenance.json`. | ||||
|  | ||||
| All endpoints require Authority-issued JWT + DPoP tokens with scopes `export:run`, `export:read`, and tenant claim alignment. Rate-limiting and quotas surface via `X-Stella-Quota-*` headers. | ||||
|  | ||||
| ### Worker pipeline | ||||
| - **Input resolvers.** Query Findings Ledger and Policy Engine using stable pagination (Mongo `_id` ascending, or resume tokens for change streams). Selector expressions compile into Mongo filter fragments and/or API query parameters. | ||||
| - **Adapter host.** Adapter plugin loader (restart-time only) resolves profile variant to adapter implementation. Adapters present a deterministic `RunAsync(context)` contract with streaming writers and telemetry instrumentation. | ||||
| - **Content writers.** | ||||
|   - JSON adapters emit `.jsonl.zst` files with canonical ordering (tenant, subject, document id). | ||||
|   - Trivy adapters materialise SQLite databases or tar archives matching Trivy DB expectations; schema version gates prevent unsupported outputs. | ||||
|   - Mirror adapters assemble deterministic filesystem trees (manifests, indexes, payload subtrees) and, when configured, OCI artefact layers. | ||||
| - **Manifest generator.** Aggregates counts, bytes, hash digests (SHA-256), profile metadata, and input references. Writes `export.json` and `provenance.json` using canonical JSON (sorted keys, RFC3339 UTC timestamps). | ||||
| - **Signing service.** Integrates with platform KMS via Authority (default cosign signer). Produces in-toto SLSA attestations when configured. Supports detached signatures and optional in-bundle signatures. | ||||
| - **Distribution drivers.** `dist-http` exposes staged files via download endpoint; `dist-oci` pushes artefacts to registries using ORAS with digest pinning; `dist-objstore` uploads to tenant-specific prefixes with immutability flags. | ||||
|  | ||||
| ## Data model snapshots | ||||
|  | ||||
| | Collection | Purpose | Key fields | Notes | | ||||
| |------------|---------|------------|-------| | ||||
| | `export_profiles` | Profile definitions (kind, variant, config). | `_id`, `tenant`, `name`, `kind`, `variant`, `config_json`, `created_by`, `created_at`. | Config includes adapter parameters (included record types, compression, encryption). | | ||||
| | `export_runs` | Run state machine and audit info. | `_id`, `profile_id`, `tenant`, `status`, `requested_by`, `selectors`, `policy_snapshot_id`, `started_at`, `completed_at`, `duration_ms`, `error_code`. | Immutable selectors; status transitions recorded in `export_events`. | | ||||
| | `export_inputs` | Resolved input ranges. | `run_id`, `source`, `cursor`, `count`, `hash`. | Enables resumable retries and audit. | | ||||
| | `export_distributions` | Distribution artefacts. | `run_id`, `type` (`http`, `oci`, `object`), `location`, `sha256`, `size_bytes`, `expires_at`. | `expires_at` used for retention policies and automatic pruning. | | ||||
| | `export_events` | Timeline of state transitions and metrics. | `run_id`, `event_type`, `message`, `at`, `metrics`. | Feeds SSE stream and audit trails. | | ||||
|  | ||||
| ## Adapter responsibilities | ||||
| - **JSON (`json:raw`, `json:policy`).** | ||||
|   - Ensures canonical casing, timezone normalization, and linkset preservation. | ||||
|   - Policy variant embeds policy snapshot metadata (`policy_version`, `inputs_hash`, `decision_trace` fingerprint) and emits evaluated findings as separate files. | ||||
|   - Enforces AOC guardrails: no derived modifications to raw evidence fields. | ||||
| - **Trivy (`trivy:db`, `trivy:java-db`).** | ||||
|   - Maps StellaOps advisory schema to Trivy DB format, handling namespace collisions and ecosystem-specific ranges. | ||||
|   - Validates compatibility against supported Trivy schema versions; run fails fast if mismatch. | ||||
|   - Emits optional manifest summarising package counts and severity distribution. | ||||
| - **Mirror (`mirror:full`, `mirror:delta`).** | ||||
|   - Builds self-contained filesystem layout (`/manifests`, `/data/raw`, `/data/policy`, `/indexes`). | ||||
|   - Delta variant compares against base manifest (`base_export_id`) to write only changed artefacts; records `removed` entries for cleanup. | ||||
|   - Supports optional encryption of `/data` subtree (age/AES-GCM) with key wrapping stored in `provenance.json`. | ||||
|  | ||||
| Adapters expose structured telemetry events (`adapter.start`, `adapter.chunk`, `adapter.complete`) with record counts and byte totals per chunk. Failures emit `adapter.error` with reason codes. | ||||
|  | ||||
| ## Signing and provenance | ||||
| - **Manifest schema.** `export.json` contains run metadata, profile descriptor, selector summary, counts, SHA-256 digests, compression hints, and distribution list. Deterministic field ordering and normalized timestamps. | ||||
| - **Provenance schema.** `provenance.json` captures in-toto subject listing (bundle digest, manifest digest), referenced inputs (findings ledger queries, policy snapshot ids, SBOM identifiers), tool version (`exporter_version`, adapter versions), and KMS key identifiers. | ||||
| - **Attestation.** Cosign SLSA Level 2 template by default; optional SLSA Level 3 when supply chain attestations are enabled. Detached signatures stored alongside manifests; CLI/Console encourage `cosign verify --key <tenant-key>` workflow. | ||||
| - **Audit trail.** Each run stores success/failure status, signature identifiers, and verification hints for downstream automation (CI pipelines, offline verification scripts). | ||||
|  | ||||
| ## Distribution flows | ||||
| - **HTTP download.** Console and CLI stream bundles via chunked transfer; supports range requests and resumable downloads. Response includes `X-Export-Digest`, `X-Export-Length`, and optional encryption metadata. | ||||
| - **OCI push.** Worker uses ORAS to publish bundles as OCI artefacts with annotations describing profile, tenant, manifest digest, and provenance reference. Supports multi-tenant registries with `repository-per-tenant` naming. | ||||
| - **Object storage.** Writes to tenant-prefixed paths (`s3://stella-exports/{tenant}/{run-id}/...`) with immutable retention policies. Retention scheduler purges expired runs based on profile configuration. | ||||
| - **Offline Kit seeding.** Mirror bundles optionally staged into Offline Kit assembly pipelines, inheriting the same manifests and signatures. | ||||
|  | ||||
| ## Observability | ||||
| - **Metrics.** Emits `exporter_run_duration_seconds`, `exporter_run_bytes_total{profile}`, `exporter_run_failures_total{error_code}`, `exporter_active_runs{tenant}`, `exporter_distribution_push_seconds{type}`. | ||||
| - **Logs.** Structured logs with fields `run_id`, `tenant`, `profile_kind`, `adapter`, `phase`, `correlation_id`, `error_code`. Phases include `plan`, `resolve`, `adapter`, `manifest`, `sign`, `distribute`. | ||||
| - **Traces.** Optional OpenTelemetry spans (`export.plan`, `export.fetch`, `export.write`, `export.sign`, `export.distribute`) for cross-service correlation. | ||||
| - **Dashboards & alerts.** DevOps pipeline seeds Grafana dashboards summarising throughput, size, failure ratios, and distribution latency. Alert thresholds: failure rate >5% per profile, median run duration >p95 baseline, signature verification failures >0. | ||||
|  | ||||
| ## Security posture | ||||
| - Tenant claim enforced at every query and distribution path; cross-tenant selectors rejected unless explicit cross-tenant mirror feature toggled with signed approval. | ||||
| - RBAC scopes: `export:profile:manage`, `export:run`, `export:read`, `export:download`. Console hides actions without scope; CLI returns `401/403`. | ||||
| - Encryption options configurable per profile; keys derived from Authority-managed KMS. Mirror encryption uses tenant-specific recipients; JSON/Trivy rely on transport security plus optional encryption at rest. | ||||
| - Restart-only plugin loading ensures adapters and distribution drivers are vetted at deployment time, reducing runtime injection risks. | ||||
| - Deterministic output ensures tamper detection via content hashes; provenance links to source runs and policy snapshots to maintain auditability. | ||||
|  | ||||
| ## Deployment considerations | ||||
| - Packaged as separate API and worker containers. Helm chart and compose overlays define horizontal scaling, worker concurrency, queue leases, and object storage credentials. | ||||
| - Requires Authority client credentials for KMS and optional registry credentials stored via sealed secrets. | ||||
| - Offline-first deployments disable OCI distribution by default and provide local object storage endpoints; HTTP downloads served via internal gateway. | ||||
| - Health endpoints: `/health/ready` validates Mongo connectivity, object storage access, adapter registry integrity, and KMS signer readiness. | ||||
|  | ||||
| ## Compliance checklist | ||||
| - [ ] Profiles and runs enforce tenant scoping; cross-tenant exports disabled unless approved. | ||||
| - [ ] Manifests and provenance files are generated with deterministic hashes and signed via configured KMS. | ||||
| - [ ] Adapters run with restart-time registration only; no runtime plugin loading. | ||||
| - [ ] Distribution drivers respect allowlist; OCI push disabled when offline mode is active. | ||||
| - [ ] Metrics, logs, and traces follow observability guidelines; dashboards and alerts configured. | ||||
| - [ ] Retention policies and pruning jobs configured for staged bundles. | ||||
|  | ||||
| > **Imposed rule:** Work of this type or tasks of this type on this component must also be applied everywhere else it should be applied. | ||||
							
								
								
									
										231
									
								
								docs/export-center/cli.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										231
									
								
								docs/export-center/cli.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,231 @@ | ||||
| # Stella CLI - Export Center Commands | ||||
|  | ||||
| > **Audience:** Operators, release engineers, and CI maintainers using the `stella` CLI to manage Export Center profiles and runs.   | ||||
| > **Supported from:** `stella` CLI >= 0.22.0 (Export Center Phase 1).   | ||||
| > **Prerequisites:** Authority token with the scopes noted per command (`export:profile:manage`, `export:run`, `export:read`, `export:download`). | ||||
|  | ||||
| Use this guide with the [Export Center API reference](api.md) and [Profiles](profiles.md) catalogue. The CLI wraps the same REST endpoints, preserving deterministic behaviour and guardrails. | ||||
|  | ||||
| > Status: CLI support is tracked under `CLI-EXPORT-35-001` and `CLI-EXPORT-36-001`. The current CLI build does not yet surface these commands; treat this guide as the target contract and adjust once implementations merge. | ||||
|  | ||||
| ## 1. Global options and configuration | ||||
|  | ||||
| | Flag | Default | Description | | ||||
| |------|---------|-------------| | ||||
| | `--server <url>` | `https://stella.local` | Gateway root. Matches `STELLA_SERVER`. | | ||||
| | `--tenant <id>` | Token tenant | Override tenant for multi-tenant tokens. | | ||||
| | `--profile <name>` | none | Loads saved defaults from `~/.stella/profiles/<name>.toml`. | | ||||
| | `--output <file>` | stdout | Redirect full JSON response. | | ||||
| | `--format <table|json|yaml>` | `table` on TTY | Controls table formatting for list commands. | | ||||
| | `--trace` | false | Emit request timing and correlation ids. | | ||||
|  | ||||
| Environment variables: `STELLA_TOKEN`, `STELLA_SERVER`, `STELLA_TENANT`, `STELLA_PROFILE`. | ||||
|  | ||||
| Exit codes align with API error codes (see section 6). | ||||
|  | ||||
| ## 2. Profile management commands | ||||
|  | ||||
| ### 2.1 `stella export profile list` | ||||
|  | ||||
| List profiles for the current tenant. | ||||
|  | ||||
| ``` | ||||
| stella export profile list --kind json --variant raw --format table | ||||
| ``` | ||||
|  | ||||
| Outputs columns `PROFILE`, `KIND`, `VARIANT`, `DISTRIBUTION`, `RETENTION`. Use `--format json` for automation. | ||||
|  | ||||
| ### 2.2 `stella export profile show` | ||||
|  | ||||
| ``` | ||||
| stella export profile show prof-json-raw --output profile.json | ||||
| ``` | ||||
|  | ||||
| Fetches full configuration and writes it to file. | ||||
|  | ||||
| ### 2.3 `stella export profile create` | ||||
|  | ||||
| ``` | ||||
| stella export profile create --file profiles/prof-json-raw.json | ||||
| ``` | ||||
|  | ||||
| JSON schema matches `POST /api/export/profiles`. CLI validates against built-in schema before submission. Requires `export:profile:manage`. | ||||
|  | ||||
| ### 2.4 `stella export profile update` | ||||
|  | ||||
| ``` | ||||
| stella export profile update prof-json-raw \ | ||||
|   --retention "days:21" \ | ||||
|   --distribution http,object | ||||
| ``` | ||||
|  | ||||
| Supports toggling retention, adding/removing distribution targets, and renaming. Structural changes (kind, variant, include set) require editing the JSON and using `--replace-file` to create a new revision. | ||||
|  | ||||
| ### 2.5 `stella export profile archive` | ||||
|  | ||||
| ``` | ||||
| stella export profile archive prof-json-raw --reason "Superseded by Phase 2 profile" | ||||
| ``` | ||||
|  | ||||
| Marks the profile inactive. Use `stella export profile restore` to re-activate. | ||||
|  | ||||
| ## 3. Run lifecycle commands | ||||
|  | ||||
| ### 3.1 `stella export run submit` | ||||
|  | ||||
| ``` | ||||
| stella export run submit prof-json-raw \ | ||||
|   --selector tenant=acme \ | ||||
|   --selector product=registry.example.com/app:* \ | ||||
|   --selector time=2025-10-01T00:00:00Z,2025-10-29T00:00:00Z \ | ||||
|   --policy-snapshot policy-snap-42 \ | ||||
|   --allow-empty=false | ||||
| ``` | ||||
|  | ||||
| Selectors accept `key=value` pairs; use `time=<from>,<to>` for windows. The command prints the `runId` and initial status. | ||||
|  | ||||
| ### 3.2 `stella export run ls` | ||||
|  | ||||
| ``` | ||||
| stella export run ls --profile prof-json-raw --status active --tail 5 | ||||
| ``` | ||||
|  | ||||
| Shows recent runs with columns `RUN`, `PROFILE`, `STATUS`, `PROGRESS`, `UPDATED`. | ||||
|  | ||||
| ### 3.3 `stella export run show` | ||||
|  | ||||
| ``` | ||||
| stella export run show run-20251029-01 --format json | ||||
| ``` | ||||
|  | ||||
| Outputs full metadata, progress counters, distribution descriptors, and links. | ||||
|  | ||||
| ### 3.4 `stella export run watch` | ||||
|  | ||||
| ``` | ||||
| stella export run watch run-20251029-01 --follow | ||||
| ``` | ||||
|  | ||||
| Streams server-sent events and renders a live progress bar. `--json` prints raw events for scripting. | ||||
|  | ||||
| ### 3.5 `stella export run cancel` | ||||
|  | ||||
| ``` | ||||
| stella export run cancel run-20251029-01 --reason "Replacing with refined selectors" | ||||
| ``` | ||||
|  | ||||
| Gracefully cancels the run; exit code `0` indicates cancellation request accepted. | ||||
|  | ||||
| ## 4. Download and verification commands | ||||
|  | ||||
| ### 4.1 `stella export download` | ||||
|  | ||||
| ``` | ||||
| stella export download run-20251029-01 \ | ||||
|   --output out/exports/run-20251029-01.tar.zst \ | ||||
|   --resume | ||||
| ``` | ||||
|  | ||||
| Downloads the primary bundle. `--resume` enables HTTP range requests; the CLI checkpoints progress to `.part` files. | ||||
|  | ||||
| ### 4.2 `stella export manifest` | ||||
|  | ||||
| ``` | ||||
| stella export manifest run-20251029-01 --output manifests/export.json | ||||
| ``` | ||||
|  | ||||
| Fetches the signed manifest. Use `--signature manifests/export.json.sig` to save the detached signature. | ||||
|  | ||||
| ### 4.3 `stella export provenance` | ||||
|  | ||||
| ``` | ||||
| stella export provenance run-20251029-01 --output manifests/provenance.json | ||||
| ``` | ||||
|  | ||||
| Retrieves the signed provenance file. `--signature` behaves like the manifest command. | ||||
|  | ||||
| ### 4.4 `stella export verify` | ||||
|  | ||||
| ``` | ||||
| stella export verify run-20251029-01 \ | ||||
|   --manifest manifests/export.json \ | ||||
|   --provenance manifests/provenance.json \ | ||||
|   --key keys/acme-export.pub | ||||
| ``` | ||||
|  | ||||
| Wrapper around `cosign verify`. Returns exit `0` when signatures and digests validate. Exit `20` when verification fails. | ||||
|  | ||||
| ## 5. CI recipe (GitHub Actions example) | ||||
|  | ||||
| ```yaml | ||||
| name: Export Center Bundle | ||||
| on: | ||||
|   workflow_dispatch: | ||||
| jobs: | ||||
|   export: | ||||
|     runs-on: ubuntu-latest | ||||
|     steps: | ||||
|       - uses: actions/checkout@v4 | ||||
|       - name: Install Stella CLI | ||||
|         run: curl -sSfL https://downloads.stellaops.org/cli/install.sh | sh | ||||
|       - name: Submit export run | ||||
|         env: | ||||
|           STELLA_TOKEN: ${{ secrets.STELLA_TOKEN }} | ||||
|         run: | | ||||
|           run_id=$(stella export run submit prof-json-raw \ | ||||
|             --selector tenant=acme \ | ||||
|             --selector product=registry.example.com/app:* \ | ||||
|             --allow-empty=false \ | ||||
|             --format json | jq -r '.runId') | ||||
|           echo "RUN_ID=$run_id" >> $GITHUB_ENV | ||||
|       - name: Wait for completion | ||||
|         env: | ||||
|           STELLA_TOKEN: ${{ secrets.STELLA_TOKEN }} | ||||
|         run: | | ||||
|           stella export run watch "$RUN_ID" --json \ | ||||
|             | tee artifacts/run.log \ | ||||
|             | jq -e 'select(.event == "run.succeeded")' > /dev/null | ||||
|       - name: Download bundle | ||||
|         env: | ||||
|           STELLA_TOKEN: ${{ secrets.STELLA_TOKEN }} | ||||
|         run: | | ||||
|           stella export download "$RUN_ID" --output artifacts/export.tar.zst --resume | ||||
|           stella export manifest "$RUN_ID" --output artifacts/export.json --signature artifacts/export.json.sig | ||||
|           stella export provenance "$RUN_ID" --output artifacts/provenance.json --signature artifacts/provenance.json.sig | ||||
|       - name: Verify signatures | ||||
|         run: | | ||||
|           stella export verify "$RUN_ID" \ | ||||
|             --manifest artifacts/export.json \ | ||||
|             --provenance artifacts/provenance.json \ | ||||
|             --key keys/acme-export.pub | ||||
| ``` | ||||
|  | ||||
| ## 6. Exit codes | ||||
|  | ||||
| | Code | Meaning | | ||||
| |------|---------| | ||||
| | `0` | Command succeeded. | | ||||
| | `10` | Validation error (`ERR_EXPORT_001`). | | ||||
| | `11` | Profile missing or inaccessible (`ERR_EXPORT_002`). | | ||||
| | `12` | Quota or concurrency exceeded (`ERR_EXPORT_003` or `ERR_EXPORT_QUOTA`). | | ||||
| | `13` | Run failed due to adapter/signing/distribution error. | | ||||
| | `20` | Verification failure (`stella export verify`). | | ||||
| | `21` | Download incomplete after retries (network errors). | | ||||
| | `30` | CLI configuration error (missing token, invalid profile file). | | ||||
|  | ||||
| Exit codes above 100 are reserved for future profile-specific tooling. | ||||
|  | ||||
| ## 7. Offline usage notes | ||||
|  | ||||
| - Use profiles that enable `object` distribution with local object storage endpoints. CLI reads `STELLA_EXPORT_OBJECT_ENDPOINT` when provided (falls back to gateway). | ||||
| - Mirror bundles work offline by skipping OCI distribution. CLI adds `--offline` to bypass OCI checks. | ||||
| - `stella export verify` works fully offline when provided with tenant public keys (packaged in Offline Kit). | ||||
|  | ||||
| ## 8. Related documentation | ||||
|  | ||||
| - [Export Center Profiles](profiles.md) | ||||
| - [Export Center API reference](api.md) | ||||
| - [Export Center Architecture](architecture.md) | ||||
| - [Aggregation-Only Contract reference](../ingestion/aggregation-only-contract.md) | ||||
|  | ||||
| > **Imposed rule:** Work of this type or tasks of this type on this component must also be applied everywhere else it should be applied. | ||||
							
								
								
									
										202
									
								
								docs/export-center/mirror-bundles.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										202
									
								
								docs/export-center/mirror-bundles.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,202 @@ | ||||
| # Export Center Mirror Bundles | ||||
|  | ||||
| Mirror bundles package StellaOps evidence, policy overlays, and indexes for air-gapped or bandwidth-constrained environments. They are produced by the `mirror:full` and `mirror:delta` profiles described in Epic 10 (Export Center) and implemented across Sprints 35-37 (`EXPORT-SVC-35-004`, `EXPORT-SVC-37-001`, `EXPORT-SVC-37-002`). This guide details bundle layouts, delta mechanics, encryption workflow, import procedures, and operational best practices. | ||||
|  | ||||
| > Export Center workers are being wired while this document is written. Treat the content as the target contract for adapter development and update specifics as the implementation lands. | ||||
|  | ||||
| ## 1. Bundle overview | ||||
|  | ||||
| | Profile | Contents | Typical use cases | Distribution | | ||||
| |---------|----------|-------------------|--------------| | ||||
| | `mirror:full` | Complete snapshot of raw evidence, normalized records, indexes, policy snapshots, provenance, signatures. | Initial seeding of an air-gapped mirror, disaster recovery drills. | Download bundle, optional OCI artifact. | | ||||
| | `mirror:delta` | Changes since a specified base export: added/updated/removed advisories, VEX statements, SBOMs, indexes, manifests. | Incremental updates, bandwidth reduction, nightly refreshes. | Download bundle, optional OCI artifact. | | ||||
|  | ||||
| Both profiles respect AOC boundaries: raw ingestion data remains untouched, and policy outputs live under their own directory with explicit provenance. | ||||
|  | ||||
| ## 2. Filesystem layout | ||||
|  | ||||
| Directory structure inside the extracted bundle: | ||||
|  | ||||
| ``` | ||||
| mirror/ | ||||
|   manifest.yaml | ||||
|   export.json | ||||
|   provenance.json | ||||
|   README.md | ||||
|   indexes/ | ||||
|     advisories.index.json | ||||
|     vex.index.json | ||||
|     sbom.index.json | ||||
|     findings.index.json | ||||
|   data/ | ||||
|     raw/ | ||||
|       advisories/*.jsonl.zst | ||||
|       vex/*.jsonl.zst | ||||
|       sboms/<subject>/sbom.json | ||||
|     normalized/ | ||||
|       advisories/*.jsonl.zst | ||||
|       vex/*.jsonl.zst | ||||
|     policy/ | ||||
|       snapshot.json | ||||
|       evaluations.jsonl.zst | ||||
|     consensus/ | ||||
|       vex_consensus.jsonl.zst | ||||
|   signatures/ | ||||
|     export.sig | ||||
|     manifest.sig | ||||
| ``` | ||||
|  | ||||
| `manifest.yaml` summarises profile metadata, selectors, counts, sizes, and SHA-256 digests. `export.json` and `provenance.json` mirror the JSON profile manifests and are signed using the configured KMS key. | ||||
|  | ||||
| Example `manifest.yaml`: | ||||
|  | ||||
| ```yaml | ||||
| profile: mirror:full | ||||
| runId: run-20251029-01 | ||||
| tenant: acme | ||||
| selectors: | ||||
|   products: | ||||
|     - registry.example.com/app:* | ||||
|   timeWindow: | ||||
|     from: 2025-10-01T00:00:00Z | ||||
|     to: 2025-10-29T00:00:00Z | ||||
| counts: | ||||
|   advisories: 15234 | ||||
|   vex: 3045 | ||||
|   sboms: 872 | ||||
|   policyEvaluations: 19876 | ||||
| artifacts: | ||||
|   - path: data/raw/advisories/a0.jsonl.zst | ||||
|     sha256: 9f4b... | ||||
|     bytes: 7340021 | ||||
| encryption: | ||||
|   mode: age | ||||
|   strict: false | ||||
|   recipients: | ||||
|     - age1tenantkey... | ||||
| ``` | ||||
|  | ||||
| ## 3. Delta mechanics | ||||
|  | ||||
| Delta bundles reference a previous full or delta run via `baseExportId` and `baseManifestDigest`. They contain: | ||||
|  | ||||
| ``` | ||||
| delta/ | ||||
|   changed/ | ||||
|     data/raw/advisories/*.jsonl.zst | ||||
|     ... | ||||
|   removed/ | ||||
|     advisories.jsonl        # list of advisory IDs removed | ||||
|     vex.jsonl | ||||
|     sboms.jsonl | ||||
|   manifest.diff.json        # summary of counts, hashes, base export metadata | ||||
| ``` | ||||
|  | ||||
| - **Base lookup:** The worker verifies that the base export is reachable (download path or OCI reference). If missing, the run fails with `ERR_EXPORT_BASE_MISSING`. | ||||
| - **Change detection:** Uses deterministic hashing of normalized records to compute additions/updates. Indexes are regenerated only for affected subjects. | ||||
| - **Application order:** Consumers apply deltas sequentially. A `resetBaseline=true` flag instructs them to drop cached state and apply the bundle as a full refresh. | ||||
|  | ||||
| Example `manifest.diff.json` (delta): | ||||
|  | ||||
| ```json | ||||
| { | ||||
|   "baseExportId": "run-20251025-01", | ||||
|   "baseManifestDigest": "sha256:aa11...", | ||||
|   "resetBaseline": false, | ||||
|   "added": { | ||||
|     "advisories": 43, | ||||
|     "vex": 12, | ||||
|     "sboms": 5 | ||||
|   }, | ||||
|   "changed": { | ||||
|     "advisories": 18, | ||||
|     "vex": 7 | ||||
|   }, | ||||
|   "removed": { | ||||
|     "advisories": 2, | ||||
|     "vex": 0, | ||||
|     "sboms": 0 | ||||
|   } | ||||
| } | ||||
| ``` | ||||
|  | ||||
| ## 4. Encryption workflow | ||||
|  | ||||
| Mirror bundles support optional encryption of the `data/` subtree: | ||||
|  | ||||
| - **Algorithm:** Age (X25519) or AES-GCM (256-bit) based on profile configuration. | ||||
| - **Key wrapping:** Keys fetched from Authority-managed KMS through Export Center. Wrapped data keys stored in `provenance.json` under `encryption.recipients[]`. | ||||
| - **Metadata:** `manifest.yaml` records `encryption.mode`, `recipients`, and `encryptedPaths`. | ||||
| - **Strict mode:** `strict=true` encrypts everything except `manifest.yaml` and `export.json`. Default (`false`) leaves manifests unencrypted to simplify discovery. | ||||
| - **Verification:** CLI (`stella export verify`) and Offline Kit scripts perform signature checks prior to decryption. | ||||
|  | ||||
| Operators must distribute recipient keys out of band. Export Center does not transmit private keys. | ||||
|  | ||||
| ## 5. Import workflow | ||||
|  | ||||
| ### 5.1 Offline Kit | ||||
|  | ||||
| Offline Kit bundles reference the latest full mirror export plus the last `N` deltas. Administrators run: | ||||
|  | ||||
| ``` | ||||
| ./offline-kit/bin/mirror import /path/to/mirror-20251029-full.tar.zst | ||||
| ./offline-kit/bin/mirror import /path/to/mirror-20251030-delta.tar.zst | ||||
| ``` | ||||
|  | ||||
| The tool verifies signatures, applies deltas, and updates the mirror index served by the local gateway. | ||||
|  | ||||
| ### 5.2 Custom automation | ||||
|  | ||||
| 1. Download bundle (`stella export download`) and verify signatures (`stella export verify`). | ||||
| 2. Extract archive into a staging directory. | ||||
| 3. For encrypted bundles, decrypt using the provided age/AES key. | ||||
| 4. Sync `mirror/data` onto the target mirror store (object storage, NFS, etc.). | ||||
| 5. Republish indexes or reload services that depend on the mirror. | ||||
|  | ||||
| Delta consumers must track `appliedExportIds` to ensure ordering. | ||||
|  | ||||
| Sequence diagram of download/import: | ||||
|  | ||||
| ```mermaid | ||||
| sequenceDiagram | ||||
|   participant CLI as stella CLI | ||||
|   participant Mirror as Mirror Store | ||||
|   participant Verify as Verification Tool | ||||
|   CLI->>CLI: stella export download run-20251029-01 | ||||
|   CLI->>Verify: stella export verify run-20251029-01 | ||||
|   CLI->>Mirror: mirror import mirror-20251029-full.tar.zst | ||||
|   CLI->>Mirror: mirror import mirror-20251030-delta.tar.zst | ||||
|   Mirror-->>CLI: import complete (run-20251030-02) | ||||
| ``` | ||||
|  | ||||
| ## 6. Operational guidance | ||||
|  | ||||
| - **Retention:** Keep at least one full bundle plus the deltas required for disaster recovery. Configure `ExportCenter:Retention:Mirror` to prune older bundles automatically. | ||||
| - **Storage footprint:** Full bundles can exceed tens of gigabytes. Plan object storage or NAS capacity accordingly and enable compression (`compression.codec=zstd`). | ||||
| - **Scheduling:** For high-churn environments, run daily full exports and hourly deltas. Record cadence in `manifest.yaml` (`schedule.cron`). | ||||
| - **Incident recovery:** To rebuild a mirror: | ||||
|   1. Apply the most recent full bundle. | ||||
|   2. Apply deltas in order of `createdAt`. | ||||
|   3. Re-run integrity checks (`mirror verify <path>`). | ||||
| - **Audit logging:** Export Center logs `mirror.bundle.created`, `mirror.delta.applied`, and `mirror.encryption.enabled` events. Consume them in the central observability pipeline. | ||||
|  | ||||
| ## 7. Troubleshooting | ||||
|  | ||||
| | Symptom | Meaning | Action | | ||||
| |---------|---------|--------| | ||||
| | `ERR_EXPORT_BASE_MISSING` | Base export not available | Republish base bundle or rebuild as full export. | | ||||
| | Delta applies but mirror misses entries | Deltas applied out of order | Rebuild from last full bundle and reapply in sequence. | | ||||
| | Decryption fails | Recipient key mismatch or corrupted bundle | Confirm key distribution and re-download bundle. | | ||||
| | Verification errors | Signature mismatch | Do not import; regenerate bundle and investigate signing pipeline. | | ||||
| | Manifest hash mismatch | Files changed after extraction | Re-extract bundle and re-run verification; check storage tampering. | | ||||
|  | ||||
| ## 8. References | ||||
|  | ||||
| - [Export Center Overview](overview.md) | ||||
| - [Export Center Architecture](architecture.md) | ||||
| - [Export Center API reference](api.md) | ||||
| - [Export Center CLI Guide](cli.md) | ||||
| - [Concelier mirror runbook](../ops/concelier-mirror-operations.md) | ||||
| - [Aggregation-Only Contract reference](../ingestion/aggregation-only-contract.md) | ||||
|  | ||||
| > **Imposed rule:** Work of this type or tasks of this type on this component must also be applied everywhere else it should be applied. | ||||
							
								
								
									
										63
									
								
								docs/export-center/overview.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										63
									
								
								docs/export-center/overview.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,63 @@ | ||||
| # Export Center Overview | ||||
|  | ||||
| The Export Center packages StellaOps evidence and policy outputs into portable, verifiable bundles. It provides one workflow for operators to deliver advisories, SBOMs, VEX statements, and policy decisions into downstream systems or air-gapped environments without rewriting data or violating the Aggregation-Only Contract (AOC). | ||||
|  | ||||
| ## What the Export Center delivers | ||||
| - **Unified export service.** A dedicated `exporter` service coordinates profiles, runs, signing, and distribution targets with deterministic manifests. | ||||
| - **Profile catalogue.** Out of the box variants include `json:raw`, `json:policy`, `trivy:db`, `trivy:java-db`, `mirror:full`, and `mirror:delta`, each aligned with AOC rules and downstream compatibility requirements. | ||||
| - **Surface parity.** Operators can create, monitor, and download exports through the Web API gateway, Console workflows, and the CLI (`stella export ...`). All surfaces enforce tenant scope and RBAC consistently. | ||||
| - **Automation hooks.** One-off, cron, and event triggers are orchestrated via the Scheduler/Orchestrator integration. Export telemetry (durations, bundle size, verification outcomes) feeds structured logs, metrics, and optional OpenTelemetry traces. | ||||
|  | ||||
| ### Profile variants at a glance | ||||
|  | ||||
| | Profile | Contents | Primary scenarios | Distribution defaults | | ||||
| |---------|----------|-------------------|-----------------------| | ||||
| | `json:raw` | Canonical advisories, VEX, SBOM JSONL with hashes | Downstream analytics, evidence escrow | HTTP download, object storage | | ||||
| | `json:policy` | `json:raw` plus policy snapshot, evaluated findings | Policy attestation, audit packages | HTTP download, object storage | | ||||
| | `trivy:db` / `trivy:java-db` | Trivy-compatible vulnerability databases | Feed external scanners and CI | OCI artifact push, download | | ||||
| | `mirror:full` | Complete evidence, indexes, policy, provenance | Air-gap mirror, disaster recovery | Filesystem bundle, OCI artifact | | ||||
| | `mirror:delta` | Changes relative to prior manifest | Incremental updates to mirrors | Filesystem bundle, OCI artifact | | ||||
|  | ||||
| ## How it works end-to-end | ||||
| 1. **Profile & scope resolution.** A profile defines export type, content filters, and bundle settings. Scope selectors target tenants, artifacts, time windows, ecosystems, or SBOM subjects. | ||||
| 2. **Ledger collection.** Workers stream canonical data from Findings Ledger, VEX Lens, Conseiller feeds, and SBOM service. Policy exports pin a deterministic policy snapshot from Policy Engine. | ||||
| 3. **Adapter execution.** JSON adapters produce normalized `.jsonl.zst` outputs, Trivy adapters translate to the Trivy DB schema, and mirror adapters build filesystem or OCI bundle layouts. | ||||
| 4. **Manifesting & provenance.** Every run emits `export.json` (profile, filters, counts, checksums) and `provenance.json` (source artifacts, policy snapshot ids, signature references). | ||||
| 5. **Signing & distribution.** Bundles are signed via configured KMS (cosign-compatible) and distributed through HTTP streaming, OCI registry pushes, or object storage staging. | ||||
|  | ||||
| Refer to `docs/export-center/architecture.md` (Sprint 35 task) for component diagrams and adapter internals once published. | ||||
|  | ||||
| ## Security and compliance guardrails | ||||
| - **AOC alignment.** Exports bundle raw evidence and optional policy evaluations without mutating source content. Policy overlays remain attributed to Policy Engine and are clearly partitioned. | ||||
| - **Tenant isolation.** All queries, manifests, and bundle paths carry tenant identifiers. Cross-tenant exports require explicit signed approval and ship with provenance trails. | ||||
| - **Signing and encryption.** Manifests and payloads are signed using the platform KMS. Mirror profiles support optional in-bundle encryption (age/AES-GCM) with key wrapping. | ||||
| - **Determinism.** Identical inputs yield identical bundles. Timestamps serialize in UTC ISO-8601; manifests include content hashes for audit replay. | ||||
|  | ||||
| See `docs/security/policy-governance.md` and `docs/ingestion/aggregation-only-contract.md` for broader guardrail context. | ||||
|  | ||||
| ## Operating it offline | ||||
| - **Offline Kit integration.** Air-gapped deployments receive pre-built export profiles and object storage layout templates through the Offline Kit bundles. | ||||
| - **Mirror bundles.** `mirror:full` packages raw evidence, normalized indexes, policy snapshots, and provenance in a portable filesystem layout suitable for disconnected environments. `mirror:delta` tracks changes relative to a prior export manifest. | ||||
| - **No unsanctioned egress.** The exporter respects the platform allowlist. External calls (e.g., OCI pushes) require explicit configuration and are disabled by default for offline installs. | ||||
|  | ||||
| Consult `docs/24_OFFLINE_KIT.md` for Offline Kit delivery and `docs/ops/concelier-mirror-operations.md` for mirror ingestion procedures. | ||||
|  | ||||
| ## Getting started | ||||
| 1. **Choose a profile.** Map requirements to the profile table above. Policy-aware exports need a published policy snapshot. | ||||
| 2. **Define selectors.** Decide on tenants, products, SBOM subjects, or time windows to include. Default selectors export the entire tenant scope. | ||||
| 3. **Run via preferred surface.** | ||||
|    - **Console:** Navigate to the Export Center view, create a run, monitor progress, and download artifacts. | ||||
|    - **CLI:** Use `stella export run --profile <name> --selector <filters>` to submit a job, then `stella export download`. | ||||
|    - **API:** POST to `/api/export/runs` with profile id and scope payload; stream results from `/api/export/runs/{id}/download`. | ||||
| 4. **Verify bundles.** Use the attached provenance manifest and cosign signature to validate contents before distributing downstream. | ||||
|  | ||||
| Refer to `docs/export-center/cli.md` for detailed command syntax and automation examples. | ||||
|  | ||||
| ## Observability & troubleshooting | ||||
| - Structured logs emit lifecycle events (`fetch`, `adapter`, `sign`, `publish`) with correlation IDs for parallel job tracing. | ||||
| - Metrics `exporter_run_duration_seconds`, `exporter_bundle_bytes_total`, and `exporter_run_failures_total` feed Grafana dashboards defined in the deployment runbooks. | ||||
| - Verification failures or schema mismatches bubble up through failure events and appear in Console/CLI with actionable error messages. Inspect the run's audit log and `provenance.json` for root cause. | ||||
|  | ||||
| See `docs/observability/policy.md` and `docs/ops/deployment-upgrade-runbook.md` for telemetry and operations guidance. | ||||
|  | ||||
| > **Imposed rule:** Work of this type or tasks of this type on this component must also be applied everywhere else it should be applied. | ||||
							
								
								
									
										139
									
								
								docs/export-center/profiles.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										139
									
								
								docs/export-center/profiles.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,139 @@ | ||||
| # Export Center Profiles | ||||
|  | ||||
| Export Center profiles define what data is collected, how it is encoded, and which distribution paths are enabled for a run. Profiles are tenant-scoped and deterministic: identical selectors and source data produce identical bundles. This guide summarises built-in profiles, configuration fields, schema conventions, and compatibility notes. | ||||
|  | ||||
| ## Profile catalogue | ||||
|  | ||||
| | Profile | Kind / Variant | Output artefacts | Primary use cases | | ||||
| |---------|----------------|------------------|-------------------| | ||||
| | `json:raw` | `json` / `raw` | Canonical JSONL archives of advisories, VEX, SBOMs | Evidence escrow, analytics pipelines | | ||||
| | `json:policy` | `json` / `policy` | `json:raw` artefacts plus policy snapshot + evaluated findings | Audit, compliance attestations | | ||||
| | `trivy:db` | `trivy` / `db` | Trivy-compatible vulnerability database | Feeding external scanners / CI | | ||||
| | `trivy:java-db` | `trivy` / `java-db` | Java ecosystem supplement for Trivy | Supply Java CVE data to Trivy | | ||||
| | `mirror:full` | `mirror` / `full` | Complete mirror bundle (raw, policy, indexes, provenance) | Air-gap deployments, disaster recovery | | ||||
| | `mirror:delta` | `mirror` / `delta` | Incremental changes relative to a prior manifest | Efficient mirror updates | | ||||
|  | ||||
| Profiles can be cloned and customised; configuration is immutable per revision to keep runs reproducible. | ||||
|  | ||||
| ## Common configuration fields | ||||
|  | ||||
| | Field | Description | Applies to | Notes | | ||||
| |-------|-------------|------------|-------| | ||||
| | `name` | Human-readable identifier displayed in Console/CLI | All | Unique per tenant. | | ||||
| | `kind` | Logical family (`json`, `trivy`, `mirror`) | All | Determines eligible adapters. | | ||||
| | `variant` | Specific export flavour (see table above) | All | Controls adapter behaviour. | | ||||
| | `include` | Record types to include (`advisories`, `vex`, `sboms`, `findings`) | JSON, mirror | Defaults depend on variant. | | ||||
| | `policySnapshotMode` | `required`, `optional`, or `none` | JSON policy, mirror | `required` forces a policy snapshot id when creating runs. | | ||||
| | `distribution` | Enabled distribution drivers (`http`, `oci`, `object`) | All | Offline installs typically disable `oci`. | | ||||
| | `compression` | Compression settings (`zstd`, level) | JSON, mirror | Trivy adapters manage compression internally. | | ||||
| | `encryption` | Mirror encryption options (`enabled`, `recipientKeys`, `strict`) | Mirror | When enabled, only `/data` subtree is encrypted; manifests remain plaintext. | | ||||
| | `retention` | Retention policy (days or `never`) | All | Drives pruning jobs for staged bundles. | | ||||
|  | ||||
| Selectors (time windows, tenants, products, SBOM subjects, ecosystems) are supplied per run, not stored in the profile. | ||||
|  | ||||
| ## JSON profiles | ||||
|  | ||||
| ### `json:raw` | ||||
| - **Content:** Exports raw advisories, VEX statements, and SBOMs as newline-delimited JSON (`.jsonl.zst`). | ||||
| - **Schema:** Follows canonical StellaOps schema with casing and timestamps normalised. Each record includes `tenant`, `source`, `linkset`, and `content` fields. | ||||
| - **Options:** | ||||
|   - `include` defaults to `["advisories", "vex", "sboms"]`. | ||||
|   - `compression` defaults to `zstd` level 9. | ||||
|   - `maxRecordsPerFile` (optional) splits outputs for large datasets. | ||||
| - **Compatibility:** Intended for analytics platforms, data escrow, or feeding downstream normalisation pipelines. | ||||
| - **Sample manifest excerpt:** | ||||
|  | ||||
| ```json | ||||
| { | ||||
|   "profile": { "kind": "json", "variant": "raw" }, | ||||
|   "outputs": [ | ||||
|     { "type": "advisories.jsonl.zst", "sha256": "...", "count": 15234 }, | ||||
|     { "type": "vex.jsonl.zst", "sha256": "...", "count": 3045 }, | ||||
|     { "type": "sboms.jsonl.zst", "sha256": "...", "count": 872 } | ||||
|   ], | ||||
|   "selectors": { "tenant": "acme", "products": ["registry.example/app"] } | ||||
| } | ||||
| ``` | ||||
|  | ||||
| ### `json:policy` | ||||
| - **Content:** Everything from `json:raw` plus: | ||||
|   - `policy_snapshot.json` (policy metadata, version, hash). | ||||
|   - `findings.policy.jsonl.zst` (evaluated findings with decision, rationale, rule id, inputs hash). | ||||
| - **Determinism:** Requires a policy snapshot id; runs fail if snapshot is missing or non-deterministic mode is active. | ||||
| - **Use cases:** Compliance exports, auditor packages, policy attestation archives. | ||||
| - **Guardrails:** AOC boundaries preserved: policy outputs are clearly partitioned from raw evidence. | ||||
|  | ||||
| ## Trivy profiles | ||||
|  | ||||
| ### `trivy:db` | ||||
| - Detailed adapter behaviour is documented in `docs/export-center/trivy-adapter.md`. | ||||
| - **Content:** Produces a Trivy DB-compatible bundle (SQLite database or tarball as required by Trivy version). | ||||
| - **Mapping rules:** | ||||
|   - Advisory namespaces mapped to Trivy vendor IDs (e.g., `ubuntu`, `debian`, `npm`). | ||||
|   - Version ranges translated into Trivy's semantic version syntax. | ||||
|   - Severity mapped to Trivy standard (`UNKNOWN`, `LOW`, `MEDIUM`, `HIGH`, `CRITICAL`). | ||||
| - **Validation:** Adapter enforces supported Trivy schema versions; configuration includes `targetSchemaVersion`. | ||||
| - **Distribution:** Typically pushed to OCI or object storage for downstream scanners; Console download remains available. | ||||
|  | ||||
| ### `trivy:java-db` | ||||
| - Refer to `docs/export-center/trivy-adapter.md` for ecosystem-specific notes. | ||||
| - **Content:** Optional Java ecosystem supplement for Trivy (matching Trivy's separate Java DB). | ||||
| - **Dependencies:** Requires Java advisories in Findings Ledger; run fails with `ERR_EXPORT_EMPTY` if no Java data present and `allowEmpty=false`. | ||||
| - **Compatibility:** Intended for organisations using Trivy's Java plugin or hardened pipelines that split general and Java feeds. | ||||
|  | ||||
| ## Mirror profiles | ||||
|  | ||||
| ### `mirror:full` | ||||
| - Bundle structure and delta strategy are covered in `docs/export-center/mirror-bundles.md`. | ||||
| - **Content:** Complete export with: | ||||
|   - Raw advisories, VEX, SBOMs (`/data/raw`). | ||||
|   - Policy overlays (`/data/policy`), including evaluated findings and policy snapshots. | ||||
|   - Indexes for fast lookup (`/indexes/advisories.pb`, `/indexes/sboms.pb`). | ||||
|   - Manifests and provenance (`/manifests/export.json`, `/manifests/provenance.json`). | ||||
| - **Layout:** Deterministic directory structure with hashed filenames to reduce duplication. | ||||
| - **Encryption:** Optional `encryption` block enables age/AES-GCM encryption of `/data`. `strict=true` encrypts everything except `export.json`. | ||||
| - **Use cases:** Air-gap replication, disaster recovery drills, Offline Kit seeding. | ||||
|  | ||||
| ### `mirror:delta` | ||||
| - See `docs/export-center/mirror-bundles.md` for delta mechanics and application order. | ||||
| - **Content:** Includes only changes relative to a base manifest (specified by `baseExportId` when running). | ||||
|   - `changed`, `added`, `removed` lists in `manifests/delta.json`. | ||||
|   - Incremental indexes capturing only updated subjects. | ||||
| - **Constraints:** Requires the base manifest to exist in object storage or artifact registry accessible to the worker. Fails with `ERR_EXPORT_BASE_MISSING` otherwise. | ||||
| - **Workflow:** Ideal for frequent updates to mirrored environments with limited bandwidth. | ||||
|  | ||||
| ## Compatibility and guardrails | ||||
| - **Aggregation-Only Contract:** All profiles respect AOC boundaries: raw evidence is never mutated. Policy outputs are appended separately with clear provenance. | ||||
| - **Tenant scoping:** Profiles are tenant-specific. Cross-tenant exports require explicit administrative approval and signed justification. | ||||
| - **Retriable runs:** Re-running a profile with identical selectors yields matching manifests and hashes, facilitating verify-on-download workflows. | ||||
| - **Offline operation:** JSON and mirror profiles function in offline mode without additional configuration. Trivy profiles require pre-seeded schema metadata shipped via Offline Kit. | ||||
| - **Quota integration:** Profiles can define run quotas (per tenant per day). Quota exhaustion surfaces as `429 Too Many Requests` with `X-Stella-Quota-*` hints. | ||||
|  | ||||
| ## Example profile definition (CLI) | ||||
|  | ||||
| ```jsonc | ||||
| { | ||||
|   "name": "daily-json-raw", | ||||
|   "kind": "json", | ||||
|   "variant": "raw", | ||||
|   "include": ["advisories", "vex", "sboms"], | ||||
|   "distribution": ["http", "object"], | ||||
|   "compression": { "codec": "zstd", "level": 9 }, | ||||
|   "retention": { "mode": "days", "value": 14 } | ||||
| } | ||||
| ``` | ||||
|  | ||||
| Create via `stella export profile create --file profile.json` (CLI command documented separately). | ||||
|  | ||||
| ## Verification workflow | ||||
| - Download bundle via Console/CLI and extract `export.json` and `provenance.json`. | ||||
| - Run `cosign verify --key <tenant-key> export.json` (for detatched signatures use `--signature export.json.sig`). | ||||
| - Validate Trivy bundles with `trivy --cache-dir <temp> --debug --db-repository <oci-ref>` or local `trivy module db import`. | ||||
| - For mirror bundles, run internal `mirror verify` script (bundled in Offline Kit) to ensure directory layout and digests match manifest. | ||||
|  | ||||
| ## Extending profiles | ||||
| - Use API/CLI to clone an existing profile and adjust `include`, `distribution`, or retention policies. | ||||
| - Adapter plug-ins can introduce new variants (e.g., `json:raw-lite`, `mirror:policy-only`). Plug-ins must be registered at service restart and documented under `/docs/export-center/profiles.md`. | ||||
| - Any new profile must append the imposed rule line and follow determinism and guardrail requirements. | ||||
|  | ||||
| > **Imposed rule:** Work of this type or tasks of this type on this component must also be applied everywhere else it should be applied. | ||||
							
								
								
									
										150
									
								
								docs/export-center/provenance-and-signing.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										150
									
								
								docs/export-center/provenance-and-signing.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,150 @@ | ||||
| # Export Center Provenance & Signing | ||||
|  | ||||
| > **Imposed rule:** Work of this type or tasks of this type on this component must also be applied everywhere else it should be applied. | ||||
|  | ||||
| Export Center runs emit deterministic manifests, provenance records, and signatures so operators can prove bundle integrity end-to-end—whether the artefact is downloaded over HTTPS, pulled as an OCI object, or staged through the Offline Kit. This guide captures the canonical artefacts, signing pipeline, verification workflows, and failure handling expectations that backlogs `EXPORT-SVC-35-005` and `EXPORT-SVC-37-002` implement. | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## 1. Goals & scope | ||||
|  | ||||
| - **Authenticity.** Every export manifest and provenance document is signed using Authority-managed KMS keys (cosign-compatible) with optional SLSA Level 3 attestation. | ||||
| - **Traceability.** Provenance links each bundle to the inputs that produced it: tenant, findings ledger queries, policy snapshots, SBOM identifiers, adapter versions, and encryption recipients. | ||||
| - **Determinism.** Canonical JSON (sorted keys, RFC 3339 UTC timestamps, normalized numbers) guarantees byte-for-byte stability across reruns with identical input. | ||||
| - **Portability.** Signatures and attestations travel with filesystem bundles, OCI artefacts, and Offline Kit staging trees. Verification does not require online Authority access when the bundle includes the cosign public key. | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## 2. Artefact inventory | ||||
|  | ||||
| | File | Location | Description | Notes | | ||||
| |------|----------|-------------|-------| | ||||
| | `export.json` | `manifests/export.json` or HTTP `GET /api/export/runs/{id}/manifest` | Canonical manifest describing profile, selectors, counts, SHA-256 digests, compression hints, distribution targets. | Hash of this file is included in provenance `subjects[]`. | | ||||
| | `provenance.json` | `manifests/provenance.json` or `GET /api/export/runs/{id}/provenance` | In-toto provenance record listing subjects, materials, toolchain metadata, encryption recipients, and KMS key identifiers. | Mirrors SLSA Level 2 schema; optionally upgraded to Level 3 with builder attestations. | | ||||
| | `export.json.sig` / `export.json.dsse` | `signatures/export.json.sig` | Cosign signature (and optional DSSE envelope) for manifest. | File naming matches cosign defaults; offline verification scripts expect `.sig`. | | ||||
| | `provenance.json.sig` / `provenance.json.dsse` | `signatures/provenance.json.sig` | Cosign signature (and optional DSSE envelope) for provenance document. | `dsse` present when SLSA Level 3 is enabled. | | ||||
| | `bundle.attestation` | `signatures/bundle.attestation` (optional) | SLSA Level 2/3 attestation binding bundle tarball/OCI digest to the run. | Only produced when `export.attestation.enabled=true`. | | ||||
| | `manifest.yaml` | bundle root | Human-readable summary including digests, sizes, encryption metadata, and verification hints. | Unsigned but redundant; signatures cover the JSON manifests. | | ||||
|  | ||||
| All digests use lowercase hex SHA-256 (`sha256:<digest>`). When bundle encryption is enabled, `provenance.json` records wrapped data keys and recipient fingerprints under `encryption.recipients[]`. | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## 3. Signing pipeline | ||||
|  | ||||
| 1. **Canonicalisation.** Export worker serialises `export.json` and `provenance.json` using `NotifyCanonicalJsonSerializer` (identical canonical JSON helpers shared across services). Keys are sorted lexicographically, arrays ordered deterministically, timestamps normalised to UTC. | ||||
| 2. **Digest creation.** SHA-256 digests are computed and recorded: | ||||
|    - `manifest_hash` and `provenance_hash` stored in the run metadata (Mongo) and exported via `/api/export/runs/{id}`. | ||||
|    - Provenance `subjects[]` contains both manifest hash and bundle/archive hash. | ||||
| 3. **Key retrieval.** Worker obtains a short-lived signing token from Authority’s KMS client using tenant-scoped credentials (`export.sign` scope). Keys live in Authority or tenant-specific HSMs depending on deployment. | ||||
| 4. **Signature emission.** Cosign generates detached signatures (`*.sig`). If DSSE is enabled, cosign wraps payload bytes in a DSSE envelope (`*.dsse`). Attestations follow the SLSA Level 2 provenance template; Level 3 requires builder metadata (`EXPORT-SVC-37-002` optional feature flag). | ||||
| 5. **Storage & distribution.** Signatures and attestations are written alongside manifests in object storage, included in filesystem bundles, and attached as OCI artefact layers/annotations. | ||||
| 6. **Audit trail.** Run metadata captures signer identity (`signing_key_id`), cosign certificate serial, signature timestamps, and verification hints. Console/CLI surface these details for downstream automation. | ||||
|  | ||||
| > **Key management.** Secrets and key references are configured per tenant via `export.signing`, pointing to Authority clients or external HSM aliases. Offline deployments pre-load cosign public keys into the bundle (`signatures/pubkeys/{tenant}.pem`). | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## 4. Provenance schema highlights | ||||
|  | ||||
| `provenance.json` follows the SLSA provenance (`https://slsa.dev/provenance/v1`) structure with StellaOps-specific extensions. Key fields: | ||||
|  | ||||
| | Path | Description | | ||||
| |------|-------------| | ||||
| | `subject[]` | Array of `{name,digest}` pairs. Includes bundle tarball/OCI digest and `export.json` digest. | | ||||
| | `predicateType` | SLSA v1 (default). | | ||||
| | `predicate.builder` | `{id:"stellaops/export-center@<region>"}` identifies the worker instance/cluster. | | ||||
| | `predicate.buildType` | Profile identifier (`mirror:full`, `mirror:delta`, etc.). | | ||||
| | `predicate.invocation.parameters` | Profile selectors, retention flags, encryption mode, base export references. | | ||||
| | `predicate.materials[]` | Source artefacts with digests: findings ledger query snapshots, policy snapshot IDs + hashes, SBOM identifiers, adapter release digests. | | ||||
| | `predicate.metadata.buildFinishedOn` | RFC 3339 timestamp when signing completed. | | ||||
| | `predicate.metadata.reproducible` | Always `true`—workers guarantee determinism. | | ||||
| | `predicate.environment.encryption` | Records encryption recipients, wrapped keys, algorithm (`age` or `aes-gcm`). | | ||||
| | `predicate.environment.kms` | Signing key identifier (`authority://tenant/export-signing-key`) and certificate chain fingerprints. | | ||||
|  | ||||
| Sample (abridged): | ||||
|  | ||||
| ```json | ||||
| { | ||||
|   "subject": [ | ||||
|     { "name": "bundle.tar.zst", "digest": { "sha256": "c1fe..." } }, | ||||
|     { "name": "manifests/export.json", "digest": { "sha256": "ad42..." } } | ||||
|   ], | ||||
|   "predicate": { | ||||
|     "buildType": "mirror:delta", | ||||
|     "invocation": { | ||||
|       "parameters": { | ||||
|         "tenant": "tenant-01", | ||||
|         "baseExportId": "run-20251020-01", | ||||
|         "selectors": { "sources": ["concelier","vexer"], "profiles": ["mirror"] } | ||||
|       } | ||||
|     }, | ||||
|     "materials": [ | ||||
|       { "uri": "ledger://tenant-01/findings?cursor=rev-42", "digest": { "sha256": "0f9a..." } }, | ||||
|       { "uri": "policy://tenant-01/snapshots/rev-17", "digest": { "sha256": "8c3d..." } } | ||||
|     ], | ||||
|     "environment": { | ||||
|       "encryption": { | ||||
|         "mode": "age", | ||||
|         "recipients": [ | ||||
|           { "recipient": "age1qxyz...", "wrappedKey": "BASE64...", "keyId": "tenant-01/notify-age" } | ||||
|         ] | ||||
|       }, | ||||
|       "kms": { | ||||
|         "signingKeyId": "authority://tenant-01/export-signing", | ||||
|         "certificateChainSha256": "1f5e..." | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| } | ||||
| ``` | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## 5. Verification workflows | ||||
|  | ||||
| | Scenario | Steps | | ||||
| |----------|-------| | ||||
| | **CLI verification** | 1. `stella export manifest <runId> --output manifests/export.json --signature manifests/export.json.sig`<br>2. `stella export provenance <runId> --output manifests/provenance.json --signature manifests/provenance.json.sig`<br>3. `cosign verify-blob --key pubkeys/tenant.pem --signature manifests/export.json.sig manifests/export.json`<br>4. `cosign verify-blob --key pubkeys/tenant.pem --signature manifests/provenance.json.sig manifests/provenance.json` | | ||||
| | **Bundle verification (offline)** | 1. Extract bundle (or mount OCI artefact).<br>2. Validate manifest/provenance signatures using bundled public key.<br>3. Recompute SHA-256 for `data/` files and compare with entries in `export.json`.<br>4. If encrypted, decrypt with Age/AES-GCM recipient key, then re-run digest comparisons on decrypted content. | | ||||
| | **CI pipeline** | Use `stella export verify --manifest manifests/export.json --provenance manifests/provenance.json --signature manifests/export.json.sig --signature manifests/provenance.json.sig` (task `CLI-EXPORT-37-001`). Failure exits non-zero with reason codes (`ERR_EXPORT_SIG_INVALID`, `ERR_EXPORT_DIGEST_MISMATCH`). | | ||||
| | **Console download** | Console automatically verifies signatures before exposing the bundle; failure surfaces an actionable error referencing the export run ID and required remediation. | | ||||
|  | ||||
| Verification guidance (docs/cli/cli-reference.md §export) cross-links here; keep both docs in sync when CLI behaviour changes. | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## 6. Distribution considerations | ||||
|  | ||||
| - **HTTP headers.** `X-Export-Digest` includes bundle digest; `X-Export-Provenance` references `provenance.json` URL; `X-Export-Signature` references `.sig`. Clients use these hints to short-circuit re-downloads. | ||||
| - **OCI annotations.** `org.opencontainers.image.ref.name`, `io.stellaops.export.manifest-digest`, and `io.stellaops.export.provenance-ref` allow registry tooling to locate manifests/signatures quickly. | ||||
| - **Offline Kit staging.** Offline kit assembler copies `manifests/`, `signatures/`, and `pubkeys/` verbatim. Verification scripts (`offline-kits/bin/verify-export.sh`) wrap the cosign commands described above. | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## 7. Failure handling & observability | ||||
|  | ||||
| - Runs surface signature status via `/api/export/runs/{id}` (`signing.status`, `signing.lastError`). Common errors include `ERR_EXPORT_KMS_UNAVAILABLE`, `ERR_EXPORT_ATTESTATION_FAILED`, `ERR_EXPORT_CANONICALIZE`. | ||||
| - Metrics: `exporter_sign_duration_seconds`, `exporter_sign_failures_total{error_code}`, `exporter_provenance_verify_failures_total`. | ||||
| - Logs: `phase=sign`, `error_code`, `signing_key_id`, `cosign_certificate_sn`. | ||||
| - Alerts: DevOps dashboards (task `DEVOPS-EXPORT-37-001`) trigger on consecutive signing failures or verification failures >0. | ||||
|  | ||||
| When verification fails downstream, operators should: | ||||
| 1. Confirm signatures using the known-good key. | ||||
| 2. Inspect `provenance.json` materials; rerun the source queries to ensure matching digests. | ||||
| 3. Review run audit logs and retry export with `--resume` to regenerate manifests. | ||||
|  | ||||
| --- | ||||
|  | ||||
| ## 8. Compliance checklist | ||||
|  | ||||
| - [ ] Manifests and provenance documents generated with canonical JSON, deterministic digests, and signatures. | ||||
| - [ ] Cosign public keys published per tenant, rotated through Authority, and distributed to Offline Kit consumers. | ||||
| - [ ] SLSA attestations enabled where supply-chain requirements demand Level 3 evidence. | ||||
| - [ ] CLI/Console verification paths documented and tested (CI pipelines exercise `stella export verify`). | ||||
| - [ ] Encryption metadata (recipients, wrapped keys) recorded in provenance and validated during verification. | ||||
| - [ ] Run audit logs capture signature timestamps, signer identity, and failure reasons. | ||||
|  | ||||
| --- | ||||
|  | ||||
| > **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. | ||||
							
								
								
									
										246
									
								
								docs/export-center/trivy-adapter.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										246
									
								
								docs/export-center/trivy-adapter.md
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,246 @@ | ||||
| # Export Center Trivy Adapters | ||||
|  | ||||
| The Trivy adapters translate StellaOps normalized advisories into the format consumed by Aqua Security's Trivy scanner. They enable downstream tooling to reuse StellaOps' curated data without bespoke converters, while preserving Aggregation-Only Contract (AOC) boundaries. This guide documents bundle layouts, field mappings, compatibility guarantees, validation workflows, and configuration toggles introduced in Sprint 36 (`EXPORT-SVC-36-001`, `EXPORT-SVC-36-002`). | ||||
|  | ||||
| > The current Export Center build is wiring the API and workers. Treat this document as the canonical interface for adapter implementation and update any behavioural changes during task sign-off. | ||||
|  | ||||
| ## 1. Adapter overview | ||||
|  | ||||
| | Variant | Bundle | Default profile | Notes | | ||||
| |---------|--------|-----------------|-------| | ||||
| | `trivy:db` | `db.bundle` | `trivy:db` | Core vulnerability database compatible with Trivy CLI >= 0.50.0 (schema v2). | | ||||
| | `trivy:java-db` | `java-db.bundle` | Optional extension | Java ecosystem supplement (Maven, Gradle). Enabled when `ExportCenter:Profiles:Trivy:EnableJavaDb=true`. | | ||||
|  | ||||
| Both variants ship inside the export run under `/export/trivy/`. Each bundle is a gzip-compressed tarball containing: | ||||
|  | ||||
| ``` | ||||
| metadata.json | ||||
| trivy.db                # BoltDB file with vulnerability/provider tables | ||||
| packages/*.json         # Only when schema requires JSON overlays (language ecosystems) | ||||
| ``` | ||||
|  | ||||
| The adapters never mutate input evidence. They only reshape normalized advisories and copy the exact upstream references so consumers can trace provenance. | ||||
|  | ||||
| ## 2. Bundle layout | ||||
|  | ||||
| ``` | ||||
| trivy/ | ||||
|   db.bundle | ||||
|     +-- metadata.json | ||||
|     +-- trivy.db | ||||
|   java-db.bundle        # present when Java DB enabled | ||||
|     +-- metadata.json | ||||
|     +-- trivy-java.db | ||||
|     +-- ecosystem/... | ||||
| signatures/ | ||||
|   trivy-db.sig | ||||
|   trivy-java-db.sig | ||||
| ``` | ||||
|  | ||||
| `metadata.json` aligns with Trivy's expectations (`schemaVersion`, `buildInfo`, `updatedAt`, etc.). Export Center adds an `stella` block to capture profile id, run id, and policy snapshot hints. | ||||
|  | ||||
| Example `metadata.json` (trimmed): | ||||
|  | ||||
| ```json | ||||
| { | ||||
|   "schemaVersion": 2, | ||||
|   "buildInfo": { | ||||
|     "trivyVersion": "0.50.1", | ||||
|     "vulnerabilityDBVersion": "2025-10-28T00:00:00Z" | ||||
|   }, | ||||
|   "updatedAt": "2025-10-29T11:42:03Z", | ||||
|   "stella": { | ||||
|     "runId": "run-20251029-01", | ||||
|     "profileId": "prof-trivy-db", | ||||
|     "tenant": "acme", | ||||
|     "policySnapshotId": "policy-snap-42", | ||||
|     "schemaVersion": 2 | ||||
|   } | ||||
| } | ||||
| ``` | ||||
|  | ||||
| ## 3. Field mappings | ||||
|  | ||||
| ### 3.1 Namespace resolution | ||||
|  | ||||
| | Stella field | Trivy field | Notes | | ||||
| |--------------|-------------|-------| | ||||
| | `advisory.source.vendor` | `namespace` | Canonicalized to lowercase; e.g. `Ubuntu` -> `ubuntu`. | | ||||
| | `advisory.source.product` | `distribution` / `ecosystem` | Mapped via allowlist (`Ubuntu 22.04` -> `ubuntu:22.04`). | | ||||
| | `package.ecosystem` | `package.ecosystem` | OSS ecosystems (`npm`, `pip`, `nuget`, etc.). | | ||||
| | `package.nevra` / `package.evr` | `package.version` (OS) | RPM/DEB version semantics preserved. | | ||||
|  | ||||
| If a record lacks a supported namespace, the adapter drops it and logs `adapter.trivy.unsupported_namespace`. | ||||
|  | ||||
| ### 3.2 Vulnerability metadata | ||||
|  | ||||
| | Stella field | Trivy field | Transformation | | ||||
| |--------------|-------------|----------------| | ||||
| | `advisory.identifiers.cve[]` | `vulnerability.CVEIDs` | Array of strings. | | ||||
| | `advisory.identifiers.aliases[]` | `vulnerability.CWEIDs` / `References` | CVE -> `CVEIDs`, others appended to `References`. | | ||||
| | `advisory.summary` | `vulnerability.Title` | Stripped to 256 chars; rest moved to `Description`. | | ||||
| | `advisory.description` | `vulnerability.Description` | Markdown allowed, normalized to LF line endings. | | ||||
| | `advisory.severity.normalized` | `vulnerability.Severity` | Uses table below. | | ||||
| | `advisory.cvss[]` | `vulnerability.CVSS` | Stored as `{"vector": "...", "score": 7.8, "source": "NVD"}`. | | ||||
| | `advisory.published` | `vulnerability.PublishedDate` | ISO 8601 UTC. | | ||||
| | `advisory.modified` | `vulnerability.LastModifiedDate` | ISO 8601 UTC. | | ||||
| | `advisory.vendorStatement` | `vulnerability.VendorSeverity` / `VendorVectors` | Preserved in vendor block. | | ||||
|  | ||||
| Severity mapping: | ||||
|  | ||||
| | Stella severity | Trivy severity | | ||||
| |-----------------|----------------| | ||||
| | `critical` | `CRITICAL` | | ||||
| | `high` | `HIGH` | | ||||
| | `medium` | `MEDIUM` | | ||||
| | `low` | `LOW` | | ||||
| | `none` / `info` | `UNKNOWN` | | ||||
|  | ||||
| ### 3.3 Affected packages | ||||
|  | ||||
| | Stella field | Trivy field | Notes | | ||||
| |--------------|-------------|-------| | ||||
| | `package.name` | `package.name` | For OS distros uses source package when available. | | ||||
| | `package.purl` | `package.PURL` | Copied verbatim. | | ||||
| | `affects.vulnerableRange` | `package.vulnerableVersionRange` | SemVer or distro version range. | | ||||
| | `remediations.fixedVersion` | `package.fixedVersion` | Latest known fix. | | ||||
| | `remediations.urls[]` | `package.links` | Array; duplicates removed. | | ||||
| | `states.cpes[]` | `package.cpes` | For CPE-backed advisories. | | ||||
|  | ||||
| The adapter deduplicates entries by `(namespace, package.name, vulnerableRange)` to avoid duplicate records when multiple upstream segments agree. | ||||
|  | ||||
| Example mapping (Ubuntu advisory): | ||||
|  | ||||
| ```jsonc | ||||
| // Stella normalized input | ||||
| { | ||||
|   "source": {"vendor": "Ubuntu", "product": "22.04"}, | ||||
|   "identifiers": {"cve": ["CVE-2024-12345"]}, | ||||
|   "severity": {"normalized": "high"}, | ||||
|   "affects": [{ | ||||
|     "package": {"name": "openssl", "ecosystem": "ubuntu", "nevra": "1.1.1f-1ubuntu2.12"}, | ||||
|     "vulnerableRange": "< 1.1.1f-1ubuntu2.13", | ||||
|     "remediations": [{"fixedVersion": "1.1.1f-1ubuntu2.13"}] | ||||
|   }] | ||||
| } | ||||
|  | ||||
| // Trivy vulnerability entry | ||||
| { | ||||
|   "namespace": "ubuntu", | ||||
|   "package": { | ||||
|     "name": "openssl", | ||||
|     "version": "< 1.1.1f-1ubuntu2.13", | ||||
|     "fixedVersion": "1.1.1f-1ubuntu2.13" | ||||
|   }, | ||||
|   "vulnerability": { | ||||
|     "ID": "CVE-2024-12345", | ||||
|     "Severity": "HIGH" | ||||
|   } | ||||
| } | ||||
| ``` | ||||
|  | ||||
| ### 3.4 Java DB specifics | ||||
|  | ||||
| The Java supplement only includes ecosystems `maven`, `gradle`, `sbt`. Additional fields: | ||||
|  | ||||
| | Stella field | Trivy Java field | Notes | | ||||
| |--------------|------------------|-------| | ||||
| | `package.group` | `GroupID` | Derived from Maven coordinates. | | ||||
| | `package.artifact` | `ArtifactID` | Derived from Maven coordinates. | | ||||
| | `package.version` | `Version` | Compared with semver-lite rules. | | ||||
| | `affects.symbolicRanges[]` | `VulnerableVersions` | Strings like `[1.0.0,1.2.3)`. | | ||||
|  | ||||
| ## 4. Compatibility matrix | ||||
|  | ||||
| | Trivy version | Schema version | Supported by adapter | Notes | | ||||
| |---------------|----------------|----------------------|-------| | ||||
| | 0.46.x | 2 | Yes | Baseline compatibility target. | | ||||
| | 0.50.x | 2 | Yes | Default validation target in CI. | | ||||
| | 0.51.x+ | 3 | Pending | Adapter throws `ERR_EXPORT_UNSUPPORTED_SCHEMA` until implemented. | | ||||
|  | ||||
| Schema mismatches emit `adapter.trivy.unsupported_schema_version` and abort the run. Operators can pin the schema via `ExportCenter:Adapters:Trivy:SchemaVersion`. | ||||
|  | ||||
| ## 5. Validation workflow | ||||
|  | ||||
| 1. **Unit tests** (`StellaOps.ExportCenter.Tests`): | ||||
|    - Mapping tests for OS and ecosystem packages. | ||||
|    - Severity conversion and range handling property tests. | ||||
| 2. **Integration tests** (`EXPORT-SVC-36-001`): | ||||
|    - Generate bundle from fixture dataset. | ||||
|    - Run `trivy module db import <bundle>` (Trivy CLI) to ensure the bundle is accepted. | ||||
|    - For Java DB, run `trivy java-repo --db <bundle>` against sample repository. | ||||
| 3. **CI smoke (`DEVOPS-EXPORT-36-001`)**: | ||||
|    - Validate metadata fields using `jq`. | ||||
|    - Ensure signatures verify with `cosign`. | ||||
|    - Check runtime by invoking `trivy fs --cache-dir <temp> --skip-update --custom-db <bundle> fixtures/image`. | ||||
|  | ||||
| Failures set the run status to `failed` with `errorCode="adapter-trivy"` so Console/CLI expose the reason. | ||||
|  | ||||
| ## 6. Configuration knobs | ||||
|  | ||||
| ```yaml | ||||
| ExportCenter: | ||||
|   Adapters: | ||||
|     Trivy: | ||||
|       SchemaVersion: 2           # enforce schema version | ||||
|       IncludeJavaDb: true        # enable java-db.bundle | ||||
|       AllowEmpty: false          # fail when no records match | ||||
|       MaxCvssVectorsPerEntry: 5  # truncate to avoid oversized payloads | ||||
|   Distribution: | ||||
|     Oras: | ||||
|       TrivyRepository: "registry.example.com/stella/trivy-db" | ||||
|       PublishDelta: false | ||||
|     Download: | ||||
|       FilenameFormat: "trivy-db-{runId}.tar.gz" | ||||
|       IncludeMetadata: true | ||||
| ``` | ||||
|  | ||||
| - `AllowEmpty=false` converts empty datasets into `ERR_EXPORT_EMPTY`. | ||||
| - `MaxCvssVectorsPerEntry` prevents extremely large multi-vector advisories from bloating the DB. | ||||
| - `PublishDelta` works in tandem with the planner's delta logic; when true, only changed blobs are pushed. | ||||
| - `FilenameFormat` lets operators align downloads with existing mirror tooling. | ||||
| - `IncludeMetadata` toggles whether `metadata.json` is stored alongside the bundle in the staging directory for quick inspection. | ||||
|  | ||||
| ## 7. Distribution guidelines | ||||
|  | ||||
| - **Download profile**: `db.bundle` placed under `/export/trivy/` and signed. Recommended filename `trivy-db-<runId>.tar.gz`. | ||||
| - **OCI push**: ORAS artifact with annotations: | ||||
|   - `org.opencontainers.artifact.description=StellaOps Trivy DB` | ||||
|   - `io.stella.export.profile=trivy:db` | ||||
|   - `io.stella.export.run=<runId>` | ||||
|   - `io.stella.export.schemaVersion=2` | ||||
| - **Offline Kit**: When `offlineBundle.includeTrivyDb=true`, the exporter copies the latest full bundle plus the last `N` deltas (configurable) with manifests for quick import. | ||||
|  | ||||
| Consumers should always verify signatures using `trivy-db.sig` / `trivy-java-db.sig` before trusting the bundle. | ||||
|  | ||||
| Example verification flow: | ||||
|  | ||||
| ```bash | ||||
| cosign verify-blob \ | ||||
|   --key tenants/acme/export-center.pub \ | ||||
|   --signature signatures/trivy-db.sig \ | ||||
|   trivy/db.bundle | ||||
|  | ||||
| trivy module db import trivy/db.bundle --cache-dir /tmp/trivy-cache | ||||
| ``` | ||||
|  | ||||
| ## 8. Troubleshooting | ||||
|  | ||||
| | Symptom | Likely cause | Remedy | | ||||
| |---------|--------------|--------| | ||||
| | `ERR_EXPORT_UNSUPPORTED_SCHEMA` | Trivy CLI updated schema version. | Bump `SchemaVersion`, extend mapping tables, regenerate fixtures. | | ||||
| | `adapter.trivy.unsupported_namespace` | Advisory namespace not in allowlist. | Extend namespace mapping or exclude in selector. | | ||||
| | `trivy import` fails with "invalid bolt page" | Corrupted bundle or truncated upload. | Re-run export; verify storage backend and signatures. | | ||||
| | Missing Java advisories | `IncludeJavaDb=false` or no Java data in Findings Ledger. | Enable flag and confirm upstream connectors populate Java ecosystems. | | ||||
| | Severity downgraded to UNKNOWN | Source severity missing or unrecognized. | Ensure upstream connectors populate severity or supply CVSS scores. | | ||||
| | `ERR_EXPORT_EMPTY` returned unexpectedly | Selectors yielded zero records while `AllowEmpty=false`. | Review selectors; set `AllowEmpty=true` if empty exports are acceptable. | | ||||
|  | ||||
| ## 9. References | ||||
|  | ||||
| - [Export Center API reference](api.md) | ||||
| - [Export Center CLI Guide](cli.md) | ||||
| - [Export Center Architecture](architecture.md) | ||||
| - [Export Center Overview](overview.md) | ||||
| - [Aqua Security Trivy documentation](https://aquasecurity.github.io/trivy/dev/database/structure/) *(external reference for schema expectations)* | ||||
|  | ||||
| > **Imposed rule:** Work of this type or tasks of this type on this component must also be applied everywhere else it should be applied. | ||||
		Reference in New Issue
	
	Block a user