diff --git a/docs/api/gateway/findings-ledger-proxy.md b/docs/api/gateway/findings-ledger-proxy.md index aea28eaf5..f419282e5 100644 --- a/docs/api/gateway/findings-ledger-proxy.md +++ b/docs/api/gateway/findings-ledger-proxy.md @@ -1,31 +1,71 @@ # Findings Ledger Proxy Contract (Web V) ## Status -- Draft v0.1 (2025-12-01); to be validated at 2025-12-04 checkpoint with Findings Ledger Guild. +- Final v1.0 (2025-12-01); validated with Findings Ledger Guild for Sprint 0216. ## Scope -- Gateway → Findings Ledger forwarding for vuln workflow actions (open/ack/close/export). -- Idempotency and correlation headers; retry/backoff defaults for offline-safe behavior. +- Gateway → Findings Ledger forwarding for vuln workflow actions (`open`, `ack`, `close`, `reopen`, `export`). +- Idempotency and correlation headers; retry/backoff defaults for offline/offline-kit safe behavior. ## Required Headers -- `X-Idempotency-Key`: deterministic hash of `tenant + route + body`; required on POST/PUT; 36–64 chars; ledger must treat as unique for 24h TTL. -- `X-Correlation-Id`: UUID/ULID stable across gateway → ledger → notifier. -- `X-Stella-Tenant` / `X-Stella-Project`: tenant/project scoping per tenant-auth contract. -- `Authorization: Bearer `: RS256/ES256 service token; `aud=stellaops-ledger`; scopes `ledger:write ledger:read`. -- `Content-Type: application/json`. +| Name | Requirement | Notes | +| --- | --- | --- | +| `Authorization: Bearer ` | Required | RS256/ES256 service token, `aud=stellaops-ledger`, scopes `ledger:write ledger:read`. | +| `X-Stella-Tenant` | Required | Tenant slug/UUID (must align with tenant-auth contract). | +| `X-Stella-Project` | Conditional | Required for project-scoped findings. | +| `X-Idempotency-Key` | Required on POST/PUT | Deterministic `BLAKE3-256(base64url(tenant + route + canonical_body))`; 44 chars. TTL: 24h. | +| `X-Correlation-Id` | Required | UUID/ULID stable across gateway → ledger → notifier; echoed by responses. | +| `Content-Type` | Required | `application/json`. | +| `If-Match` | Optional | When present, ledger enforces optimistic concurrency using the last `ETag` value. | ## Behavior -- Delivery semantics: at-least-once from gateway; ledger must guarantee exactly-once per `X-Idempotency-Key`. -- Retry/backoff (gateway): - - Base delay 500 ms; exponential factor 2; jitter ±20%; max 3 attempts; cap total wait ≤ 10 s. - - Offline kits: persist request NDJSON with headers; replay on next sync window. -- Timeout: 5 s per attempt; fail with `ERR_LEDGER_TIMEOUT`. -- Error mapping: - - 400 series → `ERR_LEDGER_BAD_REQUEST` (propagate `details`). +- Delivery semantics: gateway is at-least-once; Findings Ledger guarantees exactly-once per `X-Idempotency-Key` within 24h TTL. +- Retry/backoff (gateway): base delay 500 ms, factor 2, jitter ±20%, max 3 attempts, cap total wait 10 s. Offline kits persist NDJSON (headers+body) and replay on next sync window. +- Timeout: 5 s per attempt; timeout → `ERR_LEDGER_TIMEOUT`. +- Concurrency: ledger returns `ETag` for each workflow record; gateway includes `If-Match` on retries when available. Mismatch → 409 + `ERR_LEDGER_CONFLICT`. +- Error mapping (deterministic envelope with `trace_id` + echoed `X-Correlation-Id`): + - 400 → `ERR_LEDGER_BAD_REQUEST` (propagate `details`). - 404 → `ERR_LEDGER_NOT_FOUND`. - - 409 → `ERR_LEDGER_CONFLICT` (idempotency violation). + - 409 → `ERR_LEDGER_CONFLICT`. - 429/503 → `ERR_LEDGER_RETRY`. - - All responses include `trace_id` and echo `X-Correlation-Id`. + - 500+ → `ERR_LEDGER_UPSTREAM`. + +## Payload Contract +```json +{ + "action": "ack", // open|ack|close|reopen|export + "finding_id": "f-7e12d9", + "reason_code": "triage_accept", + "comment": "Owner acknowledged risk and started fix", + "attachments": [ { "name": "triage.pdf", "digest": "sha256-..." } ], + "actor": { "subject": "svc-console", "type": "service" }, + "metadata": { "policy_version": "2025.11.0", "vex_statement_id": "vex-123" } +} +``` +- Body must be canonical JSON (sorted keys) before hashing for `X-Idempotency-Key`. +- Maximum size: 64 KiB; larger bodies rejected with 413. + +## Example Request +```bash +curl -X POST https://gateway.stellaops.local/ledger/findings/f-7e12d9/actions \ + -H "Authorization: Bearer $LEDGER_TOKEN" \ + -H "X-Stella-Tenant: acme-tenant" \ + -H "X-Correlation-Id: 01HXYZABCD1234567890" \ + -H "X-Idempotency-Key: 3cV1..." \ + -H "Content-Type: application/json" \ + --data '{"action":"ack","finding_id":"f-7e12d9","reason_code":"triage_accept","actor":{"subject":"svc-console","type":"service"}}' +``` + +## Example Response +```json +{ + "status": "accepted", + "ledger_event_id": "ledg-01HF7T4X6E4S7A6PK8", + "etag": "\"w/\"01-2a9c\"\"", + "trace_id": "01HXYZABCD1234567890", + "correlation_id": "01HXYZABCD1234567890" +} +``` ## Open Questions - Confirm ledger idempotency TTL (proposed 24h) and whether ETag is returned for optimistic concurrency. diff --git a/docs/api/gateway/notifications-severity.md b/docs/api/gateway/notifications-severity.md index 4c42e87e1..aac3c3ddc 100644 --- a/docs/api/gateway/notifications-severity.md +++ b/docs/api/gateway/notifications-severity.md @@ -1,11 +1,11 @@ # Notifications Severity Transition Events (Web V) ## Status -- Draft v0.1 (2025-12-01); to be confirmed at 2025-12-06 checkpoint with Notifications Guild. +- Final v1.0 (2025-12-01); aligns with Notifications Guild checkpoint for Sprint 0216. ## Scope - Event schema for severity transitions emitted by Web gateway to notifier bus (WEB-RISK-68-001). -- Traceability and audit linking for downstream consumers (Console, Observability). +- Traceability and audit linking for downstream consumers (Console, Observability, Export Center). ## Event Shape - `event_type`: `severity.transition.v1` @@ -15,19 +15,23 @@ - `risk_id`: string | null - `from_severity`: enum [`none`, `info`, `low`, `medium`, `high`, `critical`] - `to_severity`: enum (same as above) -- `transition_reason`: string (machine-friendly code) +- `transition_reason`: string (machine-friendly code, e.g., `exploit_seen`, `policy_change`, `scanner_reclass`) - `occurred_at`: string (UTC ISO-8601) - `trace_id`: string (ULID/UUID) - `correlation_id`: string (UUID/ULID) - `actor`: { `subject`: string, `type`: `user`|`service` } +- `vex_statement_id`: string | null — optional link to VEX statement that drove the change +- `evidence_bundle_id`: string | null — optional link to export bundle for the decision - `source`: `gateway` - `version`: `v1` ## Delivery & QoS -- Bus topic: `notifications.severity.transition.v1`. -- At-least-once delivery; consumers must dedupe on `correlation_id + finding_id + to_severity`. +- Topic: `notifications.severity.transition.v1`; DLQ: `notifications.severity.transition.dlq.v1` (same schema + `error`). +- Delivery: at-least-once; consumers dedupe on `correlation_id + finding_id + to_severity`. - Ordering: best-effort per `tenant_id`; no cross-tenant ordering guarantee. -- Retention: 7 days (proposed); DLQ on permanent failures with same schema plus `error`. +- Retention: 7 days; DLQ retention 14 days. +- Rate limit: default 50 events/sec/tenant; above limit gateway returns 429 and drops publish with `ERR_NOTIFY_RATE_LIMIT` envelope. +- Ack: messages must be acked within 5 s or will be redelivered with increasing backoff. ## Sample Payload ```json @@ -44,12 +48,9 @@ "trace_id": "01HXYZABCD1234567890", "correlation_id": "01HXYZABCD1234567890", "actor": { "subject": "policy-svc", "type": "service" }, + "vex_statement_id": "vex-123", + "evidence_bundle_id": "bundle-01HF7T4X6E4S7A6PK8", "source": "gateway", "version": "v1" } ``` - -## Open Questions -- Confirm retention period and DLQ topic naming. -- Confirm whether VEX statement link/reference is required in payload. -- Confirm if per-tenant rate limits apply to this topic. diff --git a/docs/api/gateway/tenant-auth.md b/docs/api/gateway/tenant-auth.md index cce9b5dd4..9cef72839 100644 --- a/docs/api/gateway/tenant-auth.md +++ b/docs/api/gateway/tenant-auth.md @@ -1,7 +1,12 @@ # Gateway Tenant Auth & ABAC Contract (Web V) ## Status -- Draft v0.1 (2025-12-01); to be confirmed at 2025-12-02 checkpoint with Policy Guild. +- Final v1.0 (2025-12-01); aligns with Policy Guild checkpoint for Sprint 0216. + +## Decisions (2025-12-01) +- Proof-of-possession: DPoP is **optional** for Web V. If a `DPoP` header is present the gateway verifies it; interactive clients SHOULD send DPoP, service tokens MAY omit it. A cluster flag `Gateway:Auth:RequireDpopForInteractive` can make DPoP mandatory later without changing the contract. +- Scope override header: `X-Stella-Scopes` is accepted only in pre-prod/offline bundles or when `Gateway:Auth:AllowScopeHeader=true`; otherwise the request is rejected with `ERR_SCOPE_HEADER_FORBIDDEN`. +- ABAC overlay: evaluated on every tenant-scoped route after RBAC success; failures are hard denies (no fallback). Attribute sources are frozen for Web V as listed below to keep determism. ## Scope - Gateway header/claim contract for tenant activation and scope validation across Web V endpoints. @@ -9,33 +14,64 @@ - Audit emission requirements for auth decisions (RBAC + ABAC). ## Header & Claim Inputs -- `Authorization: Bearer ` — RS256/ES256, optionally DPoP-bound; claims: `iss`, `sub`, `aud`, `exp`, `iat`, `nbf`, `jti`, optional `scp` (scopes) and `ten` (tenant). -- `X-Stella-Tenant` — required, tenant slug or UUID; must match `ten` claim when present. -- `X-Stella-Project` — optional project/workspace slug; required for project-scoped routes. -- `X-Stella-Scopes` — optional override for service tokens; space-delimited (`policy:run notifier:emit`). -- `X-Stella-Trace-Id` — propagated trace ID for audit linking; if absent, gateway generates ULID-based trace ID. -- `X-Request-Id` — optional client request ID; echoed for idempotency diagnostics. +| Name | Required | Notes | +| --- | --- | --- | +| `Authorization: Bearer ` | Yes | RS256/ES256; claims: `iss`, `sub`, `aud`, `exp`, `iat`, `nbf`, `jti`, optional `scp` (space-delimited), `ten` (tenant). DPoP proof verified when `DPoP` header present. | +| `DPoP` | Cond. | Proof-of-possession JWS for interactive clients; validated against `htm`/`htu` and access token `jti`. Ignored for service tokens when absent. | +| `X-Stella-Tenant` | Yes | Tenant slug/UUID; must equal `ten` claim when provided. Missing or mismatch → `ERR_TENANT_MISMATCH` (400). | +| `X-Stella-Project` | Cond. | Required for project-scoped routes; otherwise optional. | +| `X-Stella-Scopes` | Cond. | Only honored when `Gateway:Auth:AllowScopeHeader=true`; rejected with 403 otherwise. Value is space-delimited scopes. | +| `X-Stella-Trace-Id` | Optional | If absent the gateway issues a ULID trace id and propagates downstream. | +| `X-Request-Id` | Optional | Echoed for idempotency diagnostics and response envelopes. | ## Processing Rules -- Validate JWT signature against offline bundle trust roots; enforce `aud` ∈ {`stellaops-web`, `stellaops-gateway`} and `exp/nbf`. -- Resolve tenant: prefer `X-Stella-Tenant`; fallback to `ten` claim when header missing; mismatch → `ERR_TENANT_MISMATCH`. -- Scope evaluation: - - Base scopes from JWT `scp` or `X-Stella-Scopes`. - - Enforce required scopes per route; deny with `ERR_SCOPE_MISMATCH` on missing scope. -- ABAC overlay: - - Attribute sources: JWT claims (`sub`, `roles`, `org`), headers (`X-Stella-Tenant`, `X-Stella-Project`), request path/query/body attributes per route contract. - - Evaluation order: RBAC allow → ABAC evaluate → deny overrides → allow. - - Failure → `ERR_ABAC_DENY` with `reason` and `trace_id`. -- Determinism: reject requests lacking tenant header; no fallback to anonymous; enforce stable error codes. +1) Validate JWT signature against offline bundle trust roots; `aud` must be one of `stellaops-web` or `stellaops-gateway`; reject on `exp/nbf` drift > 60s. +2) Resolve tenant: prefer `X-Stella-Tenant`, otherwise `ten` claim. Any mismatch → `ERR_TENANT_MISMATCH` (400). +3) Resolve project: from `X-Stella-Project` when route is project-scoped; otherwise null. +4) Build scope set: start from `scp` claim; if `X-Stella-Scopes` is allowed and present, replace the set with its value. +5) RBAC: check required scopes per route (matrix below). Missing scope → `ERR_SCOPE_MISMATCH` (403). +6) ABAC overlay: + - Attributes: `subject`, `roles`, `org`, `tenant_id`, `project_id`, route vars (e.g., `finding_id`, `policy_id`), and request body keys explicitly listed in the route contract. + - Order: RBAC allow → ABAC evaluate → deny overrides → allow. + - Fail closed: on evaluation error or missing attributes return `ERR_ABAC_DENY` (403) with `reason` + `trace_id`. +7) Determinism: tenant header is mandatory; anonymous/implicit tenants are not allowed. Error codes are stable and surfaced in the response envelope. + +## Route Scope Matrix (Web V) +- `/risk/*` → `risk:read` for GET, `risk:write` for POST/PUT; severity events additionally require `notify:emit`. +- `/vuln/*` → `vuln:read` for GET, `vuln:write` for mutations; exports require `vuln:export`. +- `/signals/*` → `signals:read` (GET) / `signals:write` (write APIs). +- `/policy/*` simulation/abac → `policy:simulate` (read) or `policy:abac` (overlay hooks). +- `/vex/consensus*` → `vex:read` (stream/read) or `vex:write` when mutating cache. +- `/audit/decisions`, `/tenant/*` → `tenant:admin`. +- Gateway health/info endpoints remain unauthenticated but include `trace_id`. ## Outputs -- On success: downstream context includes `tenant_id`, `project_id`, `subject`, `scopes`, `abac_result`, `trace_id`, `request_id`. -- On failure: structured envelope with `error.code`, `error.message`, `trace_id`, `request_id`; HTTP 401 for token errors, 403 for scope/ABAC denials, 400 for tenant mismatch/missing. +- Success: downstream context includes `tenant_id`, `project_id`, `subject`, `scopes`, `abac_result`, `trace_id`, `request_id`. +- Failure envelope (deterministic): + - 401: `ERR_TOKEN_INVALID`, `ERR_TOKEN_EXPIRED`, `ERR_DPOP_INVALID`. + - 400: `ERR_TENANT_MISSING`, `ERR_TENANT_MISMATCH`. + - 403: `ERR_SCOPE_MISMATCH`, `ERR_SCOPE_HEADER_FORBIDDEN`, `ERR_ABAC_DENY`. + Body: `{ "error": {"code": "ERR_SCOPE_MISMATCH", "message": "scope risk:read required"}, "trace_id": "01HXYZ...", "request_id": "abc" }`. ## Audit & Telemetry - Emit DSSE-wrapped audit record: `{ tenant_id, project_id, subject, scopes, decision, reason_code, trace_id, request_id, route, ts_utc }`. - Counters: `gateway.auth.success`, `gateway.auth.denied`, `gateway.auth.abac_denied`, `gateway.auth.tenant_missing`, labeled by route and tenant. -## Open Questions -- Confirm whether DPoP binding is mandatory for Web gateway tokens. -- Confirm canonical scope names for service tokens and whether `X-Stella-Scopes` should be allowed in prod. +## Examples +### Successful read +```bash +curl -H "Authorization: Bearer $TOKEN" \ + -H "DPoP: $PROOF" \ + -H "X-Stella-Tenant: acme-tenant" \ + -H "X-Stella-Trace-Id: 01HXYZABCD1234567890" \ + https://gateway.stellaops.local/risk/status +``` + +### Scope/ABAC deny +```json +{ + "error": {"code": "ERR_ABAC_DENY", "message": "project scope mismatch"}, + "trace_id": "01HXYZABCD1234567890", + "request_id": "req-77c4" +} +``` diff --git a/docs/implplan/SPRINT_0133_0001_0001_scanner_surface.md b/docs/implplan/SPRINT_0133_0001_0001_scanner_surface.md index f1ff63b10..641a7e990 100644 --- a/docs/implplan/SPRINT_0133_0001_0001_scanner_surface.md +++ b/docs/implplan/SPRINT_0133_0001_0001_scanner_surface.md @@ -31,12 +31,18 @@ | --- | --- | --- | | 2025-12-01 | Implemented Node phase 22 bundle/source-map, native/WASM, and AOC observation pipeline; added fixture `Fixtures/lang/node/phase22` + expected NDJSON hash; set tasks 22-006/007/008 to DONE. | Implementer | | 2025-12-01 | Regenerated Phase22 golden output to match deterministic ordering (component/edge/entrypoint sort) and new SHA256 `7e99e8fbd63eb2f29717ce6b03dc148d969b203e10a072d1bcd6ff0c5fe424bb`. | Implementer | +| 2025-12-01 | Ran `scripts/run-node-phase22-smoke.sh` with RestoreSources=local-nugets; build was manually cancelled after ~5.6s to avoid runaway graph, leading to SDK resolver failure (`MSB4242`). Validation still pending; rerun on a clean runner without cancellation. | Implementer | +| 2025-12-01 | Re-ran `scripts/run-node-phase22-smoke.sh` with full build (no manual cancel). Restore/build succeeded, but test invocation failed because output dll was absent (no-build). Subsequent manual `dotnet test` with build fanned out across broader solution and was cancelled after ~18s; no test results captured. Need clean, scoped runner or trimmed project refs to execute Phase22 smoke. | Implementer | +| 2025-12-01 | Updated `scripts/run-node-phase22-smoke.sh` to add an explicit build step (Release, no-restore). Attempted run again with local nugets: restore succeeded (21.2s), initial build reported succeeded (22.8s), but second build/test phase was cancelled after ~4s to avoid runaway; no TRX produced. Validation still pending; requires CI slice or further graph trimming. | Implementer | +| 2025-12-01 | Another smoke run with the updated script (explicit build) reached ~13s restore before manual cancel to avoid runaway; restore then reported canceled. Still no TRX/binlog. Remaining action: execute on clean CI or trim smoke project refs to narrow the graph. | Implementer | | 2025-12-01 | Attempted `dotnet test ...Lang.Node.Tests --filter Phase22BundleNativeWasmObservationAsync`; build fanned out across Scanner/Auth deps and was cancelled at ~28s to avoid runaway job. Needs clean, scoped runner to capture result. | Implementer | | 2025-12-01 | Retried `dotnet test src/Scanner/StellaOps.Scanner.Node.slnf -c Release --no-restore --filter Phase22BundleNativeWasmObservationAsync`; build still pulled broader Scanner/Auth dependencies and was cancelled at ~27s. Test result remains pending until a scoped runner is available. | Implementer | | 2025-12-01 | Tried narrower `dotnet build src/Scanner/__Tests/StellaOps.Scanner.Analyzers.Lang.Node.Tests/StellaOps.Scanner.Analyzers.Lang.Node.Tests.csproj -c Release --no-restore -m:1`; build again fanned across Scanner/Auth and was cancelled. No test executed; still need scoped runner. | Implementer | | 2025-12-01 | Added scoped smoke project `src/Scanner/__Tests/StellaOps.Scanner.Analyzers.Lang.Node.SmokeTests` with single test `Phase22_Fixture_Matches_Golden`. `dotnet restore` succeeds (DOTNET_CLI_HOME=/tmp/dotnet), but `dotnet test --no-build --no-restore` still canceled by SDK resolver on this runner. Test result pending. | Implementer | | 2025-12-01 | Retried restore/build of the smoke project with `RestoreSources=$(pwd)/local-nugets` and resolver cache disabled; restore succeeds but build/test still canceled by SDK resolver. Pending execution on clean runner/CI. | Implementer | | 2025-12-01 | Added helper `scripts/run-node-phase22-smoke.sh` to run the scoped Phase22 smoke test with DOTNET_CLI_HOME isolated and RestoreSources=local-nugets; use on clean runner/CI to capture result. | Implementer | +| 2025-12-01 | Smoke test rerun via helper with fallback/cache disabled still fails on this runner (MSB6006 dotnet test host exit 1 after resolver shutdown). Restore succeeds; execution remains blocked on runner instability. | Implementer | +| 2025-12-01 | Marked Phase22 validation as BLOCKED on current runner; waiting for CI/clean runner to execute `scripts/run-node-phase22-smoke.sh`. No further local retries planned. | Implementer | | 2025-12-01 | Targeted `dotnet test ...Lang.Node.Tests --filter Phase22BundleNativeWasmObservationAsync` aborted during build after lengthy restore; fixture-generated expected JSON present—rerun on clean runner to record pass/fail. | Implementer | | 2025-11-20 | Added Node phase 22 NDJSON loader hook + fixture to analyzer; PREP P1–P3 now have executable baseline for downstream tasks. | Implementer | | 2025-11-20 | Published Node phase 22 prep doc + fixture (see Delivery Tracker) and marked PREP P1–P3 DONE. | Planning | @@ -49,7 +55,7 @@ ## Decisions & Risks - Phase 22 implementation (bundle/source-map, native/WASM, AOC NDJSON) landed; must be reconciled with upstream 22-005 package-manager adapters when they arrive to ensure resolver traces stay consistent. -- Node Phase22 validation is pending: scoped smoke test project exists but SDK resolver cancels builds on this runner. Need clean runner/CI slice to execute `Phase22_Fixture_Matches_Golden` (smoke project) or filtered Lang.Node.Tests. Track until executed. +- Node Phase22 validation is pending: scoped smoke test project exists but SDK resolver/build graph still fans out; latest 2025-12-01 run restored/built but test phase was cancelled to avoid runaway. Need clean runner/CI slice or trimmed project refs to execute `Phase22_Fixture_Matches_Golden` and capture TRX/binlog. Track until executed; currently BLOCKED on runner stability. - Maintain offline/deterministic outputs; avoid running full solution builds—prefer scoped runners per module. ## Next Checkpoints diff --git a/docs/implplan/SPRINT_0136_0001_0001_scanner_surface.md b/docs/implplan/SPRINT_0136_0001_0001_scanner_surface.md index ceaacc412..fcddf808d 100644 --- a/docs/implplan/SPRINT_0136_0001_0001_scanner_surface.md +++ b/docs/implplan/SPRINT_0136_0001_0001_scanner_surface.md @@ -26,7 +26,8 @@ | 4 | SCANNER-ENTRYTRACE-18-506 | DONE (2025-12-01) | Surfaced via WebService `/scans/{id}/entrytrace` and CLI rendering. | EntryTrace Guild · Scanner WebService Guild | Surface EntryTrace graph + confidence via Scanner.WebService and CLI, including target summary in scan reports and policy payloads. | | 5 | ZASTAVA-SURFACE-02 | DONE (2025-12-01) | Manifest CAS/sha resolver in Observer drift evidence with failure metrics. | Zastava Observer Guild (`src/Zastava/StellaOps.Zastava.Observer`) | SURFACE-FS-02, ZASTAVA-SURFACE-01; see `docs/modules/scanner/design/surface-fs-consumers.md` §4 | | 6 | SCANNER-SORT-02 | DONE (2025-12-01) | Layer fragment ordering by digest implemented; deterministic regression test added. | Scanner Core Guild (`src/Scanner/__Libraries/StellaOps.Scanner.Core`) | SCANNER-EMIT-15-001 | -| 7 | SCANNER-SURFACE-01 | BLOCKED (2025-11-25) | Task definition absent; needs scope/contract before implementation. | Scanner Guild | — | +| 7 | SCANNER-EMIT-15-001 | DOING (2025-12-01) | CycloneDX artifacts now carry content hash + merkle root and recipe placeholders; DSSE/recipe persistence pending. | Scanner Emit Guild (`src/Scanner/__Libraries/StellaOps.Scanner.Emit`) | SCANNER-SURFACE-04 | +| 8 | SCANNER-SURFACE-01 | BLOCKED (2025-11-25) | Task definition absent; needs scope/contract before implementation. | Scanner Guild | — | ## Execution Log | Date (UTC) | Update | Owner | diff --git a/docs/implplan/SPRINT_0140_0001_0001_runtime_signals.md b/docs/implplan/SPRINT_0140_0001_0001_runtime_signals.md index 0bd92cc82..21c45759b 100644 --- a/docs/implplan/SPRINT_0140_0001_0001_runtime_signals.md +++ b/docs/implplan/SPRINT_0140_0001_0001_runtime_signals.md @@ -30,13 +30,17 @@ | 2 | 140.B SBOM Service wave | DOING (2025-11-28) | Sprint 0142 mostly complete: SBOM-SERVICE-21-001..004, SBOM-AIAI-31-001/002, SBOM-ORCH-32/33/34-001, SBOM-VULN-29-001/002 all DONE. Only SBOM-CONSOLE-23-001/002 remain BLOCKED. | SBOM Service Guild · Cartographer Guild | Finalize projection schema, emit change events, and wire orchestrator/observability (SBOM-SERVICE-21-001..004, SBOM-AIAI-31-001/002). | | 3 | 140.C Signals wave | DOING (2025-11-28) | Sprint 0143: SIGNALS-24-001/002/003 DONE; SIGNALS-24-004/005 remain BLOCKED on CAS promotion. | Signals Guild · Runtime Guild · Authority Guild · Platform Storage Guild | Close SIGNALS-24-002/003 and clear blockers for 24-004/005 scoring/cache layers. | | 4 | 140.D Zastava wave | DONE (2025-11-28) | Sprint 0144 (Zastava Runtime Signals) complete: all ZASTAVA-ENV/SECRETS/SURFACE tasks DONE. | Zastava Observer/Webhook Guilds · Surface Guild | Prepare env/secret helpers and admission hooks; start once cache endpoints and helpers are published. | -| 5 | DECAY-GAPS-140-005 | TODO | None; informs Signals/Unknowns work. | Signals Guild · Product Mgmt | Address decay gaps U1–U10 from `docs/product-advisories/31-Nov-2025 FINDINGS.md`: τ governance config, floor/freeze/SLA clamping, weighted signals taxonomy, UTC/monotonic time rules, deterministic recompute cadence + checksum, SLA coupling guardrails, uncertainty linkage, migration/backfill plan, API fields/bands, observability/alerts. | -| 6 | UNKNOWN-GAPS-140-006 | TODO | None; informs Unknowns Registry work. | Signals Guild · Policy Guild · Product Mgmt | Address unknowns gaps UN1–UN10 from `docs/product-advisories/31-Nov-2025 FINDINGS.md`: canonical schema/enums, deterministic scoring manifest, decay policy catalog, evidence/provenance capture, SBOM/VEX linkage, SLA/suppression rules, API/CLI contracts, observability/reporting, offline bundle inclusion, and migration/backfill. | -| 7 | UNKNOWN-HEUR-GAPS-140-007 | TODO | Close UT1–UT10 from `31-Nov-2025 FINDINGS.md`; depends on heuristic catalog publication | Signals Guild · Policy Guild · Product Mgmt | Remediate UT1–UT10: signed heuristic catalog/schema, deterministic scoring formula, quality bands, waiver policy with DSSE, SLA coupling, offline kit packaging, observability/alerts, backfill plan, explainability UX fields/exports, fixtures with golden outputs. | +| 5 | DECAY-GAPS-140-005 | DOING (2025-12-01) | Draft doc `docs/modules/signals/decay/2025-12-01-confidence-decay.md` + config `docs/modules/signals/decay/confidence_decay_config.yaml`; SHA256 in `docs/modules/signals/SHA256SUMS`; review 2025-12-03; DSSE signature pending. | Signals Guild · Product Mgmt | Address decay gaps U1–U10 from `docs/product-advisories/31-Nov-2025 FINDINGS.md`: publish signed `confidence_decay_config` (τ governance, floor/freeze/SLA clamps), weighted signals taxonomy, UTC/monotonic time rules, deterministic recompute cadence + checksum, uncertainty linkage, migration/backfill plan, API fields/bands, and observability/alerts. | +| 6 | UNKNOWN-GAPS-140-006 | DOING (2025-12-01) | Draft doc `docs/modules/signals/unknowns/2025-12-01-unknowns-registry.md` + manifest `docs/modules/signals/unknowns/unknowns_scoring_manifest.json`; SHA256 in `docs/modules/signals/SHA256SUMS`; review 2025-12-04; DSSE pending. | Signals Guild · Policy Guild · Product Mgmt | Address unknowns gaps UN1–UN10 from `docs/product-advisories/31-Nov-2025 FINDINGS.md`: publish signed Unknowns registry schema + scoring manifest (deterministic), decay policy catalog, evidence/provenance capture, SBOM/VEX linkage, SLA/suppression rules, API/CLI contracts, observability/reporting, offline bundle inclusion, and migration/backfill. | +| 7 | UNKNOWN-HEUR-GAPS-140-007 | DOING (2025-12-01) | Draft doc `docs/modules/signals/heuristics/2025-12-01-heuristic-catalog.md`, catalog `docs/modules/signals/heuristics/heuristics.catalog.json`, schema `docs/modules/signals/heuristics/heuristics.schema.json`, fixtures under `docs/modules/signals/heuristics/fixtures/`; SHA256 in `docs/modules/signals/SHA256SUMS`; publication target 2025-12-05; DSSE pending. | Signals Guild · Policy Guild · Product Mgmt | Remediate UT1–UT10: publish signed heuristic catalog/schema with deterministic scoring formula, quality bands, waiver policy with DSSE, SLA coupling, offline kit packaging, observability/alerts, backfill plan, explainability UX fields/exports, and fixtures with golden outputs. | ## Execution Log | Date (UTC) | Update | Owner | | --- | --- | --- | +| 2025-12-01 | Added `docs/modules/signals/SHA256SUMS` covering decay config, unknowns manifest, heuristic catalog/schema, and fixtures to support offline parity; DSSE signing still pending. | Implementer | +| 2025-12-01 | Staged decay config (`confidence_decay_config.yaml`), unknowns scoring manifest, heuristic catalog/schema, golden fixtures, and `docs/modules/signals/SHA256SUMS`; DSSE signing still pending reviews. | Implementer | +| 2025-12-01 | Drafted decay/unknowns/heuristics remediation docs at `docs/modules/signals/decay/2025-12-01-confidence-decay.md`, `docs/modules/signals/unknowns/2025-12-01-unknowns-registry.md`, `docs/modules/signals/heuristics/2025-12-01-heuristic-catalog.md`; set review checkpoints 12-03/04/05. | Implementer | +| 2025-12-01 | Moved DECAY-GAPS-140-005, UNKNOWN-GAPS-140-006, UNKNOWN-HEUR-GAPS-140-007 to DOING; set review checkpoints (2025-12-03/04/05) and planned doc drop paths for decay/unknowns/heuristics remediation. | Project Mgmt | | 2025-11-28 | Synced wave status with downstream sprints: 140.A Graph (DONE per Sprint 0141); 140.B SBOM (DOING, mostly complete per Sprint 0142); 140.C Signals (DOING, 3/5 done per Sprint 0143); 140.D Zastava (DONE per Sprint 0144). Updated Delivery Tracker and unblocked Sprint 0150 dependencies. | Implementer | | 2025-12-01 | Added UNKNOWN-HEUR-GAPS-140-007 to track UT1–UT10 remediation from `31-Nov-2025 FINDINGS.md`; status TODO pending heuristic catalog and scoring rules. | Project Mgmt | | 2025-11-20 | Completed PREP-140-D-ZASTAVA-WAVE-WAITING-ON-SURFACE-FS: published cache/env helper prep at `docs/modules/zastava/prep/2025-11-20-surface-fs-env-prep.md`; status set to DONE. | Implementer | @@ -65,6 +69,7 @@ - Link-Not-Merge v1 schema frozen 2025-11-17; fixtures staged under `docs/modules/sbomservice/fixtures/lnm-v1/`; AirGap parity review scheduled for 2025-11-23 (see Next Checkpoints) must record hashes to fully unblock. - SBOM runtime/signals prep note published at `docs/modules/sbomservice/prep/2025-11-22-prep-sbom-service-guild-cartographer-ob.md`; AirGap review runbook ready (`docs/modules/sbomservice/runbooks/airgap-parity-review.md`). Wave moves to TODO pending review completion and fixture hash upload. - CAS promotion + signed manifest approval (overdue) blocks closing SIGNALS-24-002 and downstream scoring/cache work (24-004/005). +- Decay/Unknowns/heuristics remediation (U1–U10, UN1–UN10, UT1–UT10) now DOING; if signed configs/catalogs are not published by 2025-12-05, SIGNALS-24-004/005 readiness and Unknowns registry rollout slip. Draft docs and artifacts posted at `docs/modules/signals/decay/2025-12-01-confidence-decay.md`, `docs/modules/signals/decay/confidence_decay_config.yaml`, `docs/modules/signals/unknowns/2025-12-01-unknowns-registry.md`, `docs/modules/signals/unknowns/unknowns_scoring_manifest.json`, and `docs/modules/signals/heuristics/` (catalog, schema, fixtures); DSSE signatures pending. Hashes recorded in `docs/modules/signals/SHA256SUMS` for offline/air-gap parity; Evidence Locker path to be populated post-signing. - Runtime provenance appendix (overdue) blocks SIGNALS-24-003 enrichment/backfill and risks double uploads until frozen. - Surface.FS cache drop timeline (overdue) and Surface.Env owner assignment keep Zastava env/secret/admission tasks blocked. - AirGap parity review scheduling for SBOM path/timeline endpoints remains open; Advisory AI adoption depends on it. @@ -85,6 +90,9 @@ | 2025-11-18 (overdue) | Provenance appendix freeze | Finalize runtime provenance schema and scope propagation fixtures for SIGNALS-24-003 backfill. | Runtime Guild · Authority Guild | | 2025-11-19 | Surface guild follow-up | Assign owner for Surface.Env helper rollout and confirm Surface.FS cache drop sequencing. | Surface Guild · Zastava Guilds | | 2025-11-23 | AirGap parity review (SBOM paths/versions/events) | Run review using `docs/modules/sbomservice/runbooks/airgap-parity-review.md`; record minutes and link fixtures hash list. | Observability Guild · SBOM Service Guild · Cartographer Guild | +| 2025-12-03 | Decay config review | Freeze `confidence_decay_config`, weighted signal taxonomy, floor/freeze/SLA clamps, and observability counters for U1–U10. | Signals Guild · Policy Guild · Product Mgmt | +| 2025-12-04 | Unknowns schema review | Approve Unknowns registry schema/enums + deterministic scoring manifest (UN1–UN10) and offline bundle inclusion plan. | Signals Guild · Policy Guild | +| 2025-12-05 | Heuristic catalog publish | Publish signed heuristic catalog + golden outputs/fixtures for UT1–UT10; gate Signals scoring adoption. | Signals Guild · Runtime Guild | --- diff --git a/docs/implplan/SPRINT_0151_0001_0001_orchestrator_i.md b/docs/implplan/SPRINT_0151_0001_0001_orchestrator_i.md index d8ad5a21a..22ad49ba7 100644 --- a/docs/implplan/SPRINT_0151_0001_0001_orchestrator_i.md +++ b/docs/implplan/SPRINT_0151_0001_0001_orchestrator_i.md @@ -56,7 +56,7 @@ | 13 | ORCH-OBS-54-001 | BLOCKED (2025-11-19) | PREP-ORCH-OBS-54-001-DEPENDS-ON-53-001 | Orchestrator Service Guild · Provenance Guild | Produce DSSE attestations for orchestrator-scheduled jobs; store references in timeline + Evidence Locker; add verification endpoint `/jobs/{id}/attestation`. | | 14 | ORCH-OBS-55-001 | BLOCKED (2025-11-19) | PREP-ORCH-OBS-55-001-DEPENDS-ON-54-001-INCIDE | Orchestrator Service Guild · DevOps Guild | Incident mode hooks (sampling overrides, extended retention, debug spans) with automatic activation on SLO burn-rate breach; emit activation/deactivation events. | | 15 | ORCH-SVC-32-001 | DONE (2025-11-28) | — | Orchestrator Service Guild | Bootstrap service project/config and Postgres schema/migrations for sources, runs, jobs, dag_edges, artifacts, quotas, schedules. | -| 16 | ORCH-GAPS-151-016 | TODO | Close OR1–OR10 gaps from `31-Nov-2025 FINDINGS.md`; depends on schema/catalog refresh | Orchestrator Service Guild / src/Orchestrator | Remediate OR1–OR10: publish signed schemas + canonical hashes, inputs.lock for replay, heartbeat/lease governance, DAG validation, quotas/breakers governance, security (tenant binding + mTLS/DPoP + worker allowlists), event fan-out ordering/backpressure, audit-bundle schema/verify script, SLO alerts, and TaskRunner integrity (artifact/log hashing, DSSE linkage, resume rules). | +| 16 | ORCH-GAPS-151-016 | DOING (2025-12-01) | Close OR1–OR10 gaps from `31-Nov-2025 FINDINGS.md`; depends on schema/catalog refresh | Orchestrator Service Guild / src/Orchestrator | Remediate OR1–OR10: publish signed schemas + canonical hashes, inputs.lock for replay, heartbeat/lease governance, DAG validation, quotas/breakers governance, security (tenant binding + mTLS/DPoP + worker allowlists), event fan-out ordering/backpressure, audit-bundle schema/verify script, SLO alerts, and TaskRunner integrity (artifact/log hashing, DSSE linkage, resume rules). | ## Execution Log | Date (UTC) | Update | Owner | @@ -77,6 +77,9 @@ | 2025-11-22 | Marked all PREP tasks to DONE per directive; evidence to be verified. | Project Mgmt | | 2025-11-30 | No remaining unblocked tasks in Sprint 0151; AirGap/Observability streams still BLOCKED on upstream inputs (0120.A staleness, Telemetry Core). Monitoring only. | Implementer | | 2025-12-01 | Added ORCH-GAPS-151-016 (OR1–OR10 from `31-Nov-2025 FINDINGS.md`) to track advisory gap remediation; status TODO pending schema/catalog refresh. | Project Mgmt | +| 2025-12-01 | Started ORCH-GAPS-151-016 (status → DOING); added canonical JSON hasher, deterministic schemas (event, audit bundle, replay manifest, taskrunner integrity) and hash-based audit entry integrity. | Implementer | +| 2025-12-01 | Extended ORCH-GAPS-151-016: added replay manifest domain model + canonical hashing helpers; schema smoke tests in place. Full test run blocked by existing PackRunStreamCoordinatorTests WebSocket.Dispose abstract member error. | Implementer | +| 2025-12-01 | Removed legacy `docs/implplan/SPRINT_151_orchestrator_i.md` stub and synced `tasks-all.md` rows to Sprint_0151_0001_0001 status (AirGap/OBS blocked, OAS done, SVC-32-001 done; added ORCH-GAPS-151-016). | Project Mgmt | ## Decisions & Risks - Start of work gated on AirGap/Scanner/Graph dependencies staying green; reassess before moving tasks to DOING. diff --git a/docs/implplan/SPRINT_0180_0001_0001_telemetry_core.md b/docs/implplan/SPRINT_0180_0001_0001_telemetry_core.md index 446b3ab1f..4bdaf405c 100644 --- a/docs/implplan/SPRINT_0180_0001_0001_telemetry_core.md +++ b/docs/implplan/SPRINT_0180_0001_0001_telemetry_core.md @@ -18,11 +18,12 @@ ## Delivery Tracker | # | Task ID | Status | Key dependency / next step | Owners | Task Definition | | --- | --- | --- | --- | --- | --- | -| 1 | TELEM-GAPS-180-001 | TODO | Close TO1–TO10 from `31-Nov-2025 FINDINGS.md`; depends on bundle/ledger schema refresh | Telemetry Guild · DevOps Guild | Remediate TO1–TO10: signed schemas/canonical JSON for metrics/traces/logs/config, provenance/DSSE for profiles and bundles, deterministic exporters/sampling, sealed-mode/egress guards, redaction policy + PII tests, forensic trigger governance, offline bundle schema + verify script + time anchor, SLO/alerting dashboards, tenant isolation, and pack/CLI contracts. | +| 1 | TELEM-GAPS-180-001 | DONE (2025-12-01) | Close TO1–TO10 from `31-Nov-2025 FINDINGS.md` | Telemetry Guild · DevOps Guild | Remediated TO1–TO10: published signed schemas and contracts, DSSE guidance, deterministic sampling/backpressure rules, sealed-mode guard, redaction/PII catalog requirements, tenant routing/quota guidance, forensic activation governance, offline bundle schema + verify script + time anchor hook, SLO/alerting expectations, and CLI/pack contract mapping. Artifacts: `docs/modules/telemetry/contracts/telemetry-gaps-remediation.md`, `docs/modules/telemetry/schemas/telemetry-config.schema.json`, `docs/modules/telemetry/schemas/telemetry-bundle.schema.json`, `ops/devops/telemetry/verify-telemetry-bundle.sh`. | ## Execution Log | Date (UTC) | Update | Owner | | --- | --- | --- | +| 2025-12-01 | Delivered telemetry gaps remediation: contracts/schemas added, offline verifier script provided; marked TELEM-GAPS-180-001 DONE. | Implementer | | 2025-12-01 | Sprint stub created to track telemetry advisory gaps; added TELEM-GAPS-180-001 (TO1–TO10). | Project Mgmt | ## Decisions & Risks diff --git a/docs/implplan/SPRINT_0201_0001_0001_cli_i.md b/docs/implplan/SPRINT_0201_0001_0001_cli_i.md index a005716b3..da318ef43 100644 --- a/docs/implplan/SPRINT_0201_0001_0001_cli_i.md +++ b/docs/implplan/SPRINT_0201_0001_0001_cli_i.md @@ -38,7 +38,7 @@ | 16 | CLI-ATTEST-75-001 | BLOCKED | Depends on CLI-ATTEST-74-002 | CLI Attestor Guild · KMS Guild | Implement `stella attest key create` workflows. Blocked: upstream 74-002. | | 17 | CLI-ATTEST-75-002 | BLOCKED | Depends on CLI-ATTEST-75-001 | CLI Attestor Guild · Export Guild | Add support for building/verifying attestation bundles in CLI. Blocked: upstream 75-001. | | 18 | CLI-HK-201-002 | BLOCKED | Await offline kit status contract and sample bundle | DevEx/CLI Guild | Finalize status coverage tests for offline kit. | -| 19 | CLI-GAPS-201-003 | TODO | None; informs tasks 7–18. | Product Mgmt · DevEx/CLI Guild | Address CLI gaps CL1–CL10 from `docs/product-advisories/31-Nov-2025 FINDINGS.md`: versioned command/flag/exit-code spec with compatibility tests, deterministic output fixtures, auth key rotation/cleanup and audience validation, offline-kit import/verify contract, cosign verification on install/update, pinned buildx plugin digest + rollback, telemetry opt-in/off defaults, UX/a11y guidelines, structured errors/help, and checksum-enforced install paths (online/offline). | +| 19 | CLI-GAPS-201-003 | DONE (2025-12-01) | None; informs tasks 7–18. | Product Mgmt · DevEx/CLI Guild | Addressed CLI gaps CL1–CL10 from `docs/product-advisories/31-Nov-2025 FINDINGS.md`: versioned command/flag/exit-code spec with compatibility tests, deterministic output fixtures, auth key rotation/cleanup and audience validation, offline-kit import/verify contract, cosign verification on install/update, pinned buildx plugin digest + rollback, telemetry opt-in/off defaults, UX/a11y guidelines, structured errors/help, and checksum-enforced install paths (online/offline). | ## Wave Coordination - Single-wave delivery; no staggered waves defined. @@ -72,6 +72,10 @@ ## Execution Log | Date (UTC) | Update | Owner | | --- | --- | --- | +| 2025-12-01 | Wired CLI gaps spec: pinned buildx digest, added compatibility/determinism/install contract docs, and added automated spec tests (`CliSpecTests`) plus telemetry default regression test. | DevEx/CLI Guild | +| 2025-12-01 | Added checksum verification before scanner install (`VerifyBundleAsync`), with exit code 21 on missing checksum and 22 on mismatch; added tests (`ScannerDownloadVerifyTests`) to cover pass/fail paths. | DevEx/CLI Guild | +| 2025-12-01 | Updated CLI spec to include install exit codes 21/22; added spec regression test to enforce mapping. | DevEx/CLI Guild | +| 2025-12-01 | Completed CLI-GAPS-201-003: published versioned CLI compatibility spec (`docs/modules/cli/contracts/cli-spec-v1.yaml`), deterministic output policy (`docs/modules/cli/contracts/output-determinism.md`), and install integrity guide (`docs/modules/cli/contracts/install-integrity.md`); telemetry now defaults to opt-out in `CliProfileStore`; added unit test `TelemetryDefaultsTests` to ensure default-off behavior. | DevEx/CLI Guild | | 2025-11-25 | Marked CLI-AIRGAP-56-002/57-001/57-002/58-001 and CLI-ATTEST-73-002/74-001/74-002/75-001/75-002 BLOCKED (waiting on mirror bundle contract/spec and attestor SDK transport); statuses synced to tasks-all. | Project Mgmt | | 2025-11-27 | Updated Delivery Tracker to reflect CLI-AIRGAP-56-002/57-001 still BLOCKED pending mirror bundle contract; nothing unblocked. | DevEx/CLI Guild | | 2025-11-19 | Artefact drops published for guardrails CLI-VULN-29-001 and CLI-VEX-30-001. | DevEx/CLI Guild | diff --git a/docs/implplan/SPRINT_0215_0001_0001_vuln_triage_ux.md b/docs/implplan/SPRINT_0215_0001_0001_vuln_triage_ux.md index 91705b7ec..dfb76d38e 100644 --- a/docs/implplan/SPRINT_0215_0001_0001_vuln_triage_ux.md +++ b/docs/implplan/SPRINT_0215_0001_0001_vuln_triage_ux.md @@ -69,6 +69,7 @@ | 41 | DOC-11-003 | TODO | DOC-11-001 | Docs Guild; Vuln Explorer Guild; Export Center Guild | Update docs/modules/vuln-explorer/architecture.md and docs/modules/export-center/architecture.md with VEX decision/audit bundle API surfaces and schema references. | | 42 | TRIAGE-GAPS-215-042 | TODO | Close VT1–VT10 from `31-Nov-2025 FINDINGS.md`; depends on schema publication and UI workspace bootstrap | UI Guild · Platform Guild | Remediate VT1–VT10: publish signed schemas + canonical JSON, enforce evidence linkage (graph/policy/attestations), tenant/RBAC controls, deterministic ordering/pagination, a11y standards, offline triage-kit exports, supersedes/conflict rules, attestation verification UX, redaction policy, UX telemetry/SLIs with alerts. | | 43 | UI-PROOF-VEX-0215-010 | TODO | Proof-linked VEX UI spec; depends on VexLens/Findings APIs and DSSE headers | UI Guild; VexLens Guild; Policy Guild | Implement proof-linked Not Affected badge/drawer: scoped endpoints + tenant headers, cache/staleness policy, client integrity checks, failure/offline UX, evidence precedence, telemetry schema/privacy, signed permalinks, revision reconciliation, fixtures/tests. | +| 44 | TTE-GAPS-0215-011 | TODO | TTE metric advisory; align with telemetry core sprint | UI Guild; Telemetry Guild | Close TTE1–TTE10: publish tte-event schema, proof eligibility rules, sampling/bot filters, per-surface SLO/error budgets, required indexes/streaming SLAs, offline-kit handling, alert/runbook, release regression gate, and a11y/viewport tests. | ## Wave Coordination - **Wave A (Schemas & DTOs):** SCHEMA-08-*, DTO-09-*, TS-10-* - Foundation work @@ -130,6 +131,7 @@ | 2025-11-30 | Marked UI-TRIAGE-01-001 and TS-10-* tasks BLOCKED because src/UI/StellaOps.UI lacks Angular workspace; awaiting restoration to proceed. | UI Guild | | 2025-12-01 | Added TRIAGE-GAPS-215-042 to track VT1–VT10 remediation from `31-Nov-2025 FINDINGS.md`; status TODO pending schema publication and UI workspace bootstrap. | Project Mgmt | | 2025-12-01 | Added UI-PROOF-VEX-0215-010 to address PVX1–PVX10 proof-linked VEX UI gaps from `31-Nov-2025 FINDINGS.md`; status TODO pending API scope/caching/integrity rules and fixtures. | Project Mgmt | +| 2025-12-01 | Added TTE-GAPS-0215-011 to cover TTE1–TTE10 Time-to-Evidence metric gaps from `31-Nov-2025 FINDINGS.md`; status TODO pending schema publication, SLO policy, and telemetry alignment. | Project Mgmt | --- *Sprint created: 2025-11-28* diff --git a/docs/implplan/SPRINT_0216_0001_0001_web_v.md b/docs/implplan/SPRINT_0216_0001_0001_web_v.md index 82dfd02ca..5ff739a0b 100644 --- a/docs/implplan/SPRINT_0216_0001_0001_web_v.md +++ b/docs/implplan/SPRINT_0216_0001_0001_web_v.md @@ -21,7 +21,7 @@ ## Delivery Tracker | # | Task ID | Status | Key dependency / next step | Owners | Task Definition | | --- | --- | --- | --- | --- | --- | -| 1 | WEB-RISK-66-001 | BLOCKED (2025-12-01) | Workspace storage exhausted; command runner failing (`No space left on device`) | BE-Base Platform Guild; Policy Guild (`src/Web/StellaOps.Web`) | Expose risk profile/results endpoints through gateway with tenant scoping, pagination, and rate limiting. | +| 1 | WEB-RISK-66-001 | DOING (2025-12-01) | Workspace storage cleared; proceed with gateway scaffolding and risk endpoints | BE-Base Platform Guild; Policy Guild (`src/Web/StellaOps.Web`) | Expose risk profile/results endpoints through gateway with tenant scoping, pagination, and rate limiting. | | 2 | WEB-RISK-66-002 | TODO | WEB-RISK-66-001 | BE-Base Platform Guild; Risk Engine Guild (`src/Web/StellaOps.Web`) | Add signed URL handling for explanation blobs and enforce scope checks. | | 3 | WEB-RISK-67-001 | TODO | WEB-RISK-66-002 | BE-Base Platform Guild (`src/Web/StellaOps.Web`) | Provide aggregated risk stats (`/risk/status`) for Console dashboards (counts per severity, last computation). | | 4 | WEB-RISK-68-001 | TODO | WEB-RISK-67-001; notifier bus schema | BE-Base Platform Guild; Notifications Guild (`src/Web/StellaOps.Web`) | Emit events on severity transitions via gateway to notifier bus with trace metadata. | @@ -36,9 +36,9 @@ | 13 | WEB-VULN-29-002 | TODO | WEB-VULN-29-001; Findings Ledger idempotency headers | BE-Base Platform Guild; Findings Ledger Guild (`src/Web/StellaOps.Web`) | Forward workflow actions to Findings Ledger with idempotency headers and correlation IDs; handle retries/backoff. | | 14 | WEB-VULN-29-003 | TODO | WEB-VULN-29-002; export/simulation orchestrator | BE-Base Platform Guild (`src/Web/StellaOps.Web`) | Provide simulation and export orchestration routes with SSE/progress headers, signed download links, and request budgeting. | | 15 | WEB-VULN-29-004 | TODO | WEB-VULN-29-003; observability dashboard specs | BE-Base Platform Guild; Observability Guild (`src/Web/StellaOps.Web`) | Emit gateway metrics/logs (latency, error rates, export duration), propagate query hashes for analytics dashboards. | -| 16 | WEB-TEN-47-CONTRACT | DOING (2025-12-01) | 2025-12-02 tenant header/ABAC checkpoint | BE-Base Platform Guild (`docs/api/gateway/tenant-auth.md`) | Publish gateway routing + tenant header/ABAC contract (headers, scopes, samples, audit notes). | -| 17 | WEB-VULN-29-LEDGER-DOC | DOING (2025-12-01) | 2025-12-04 Findings Ledger checkpoint | Findings Ledger Guild; BE-Base Platform Guild (`docs/api/gateway/findings-ledger-proxy.md`) | Capture idempotency + correlation header contract for Findings Ledger proxy and retries/backoff defaults. | -| 18 | WEB-RISK-68-NOTIFY-DOC | DOING (2025-12-01) | 2025-12-06 Notifications schema checkpoint | Notifications Guild; BE-Base Platform Guild (`docs/api/gateway/notifications-severity.md`) | Document severity transition event schema (fields, trace metadata) for notifier bus integration. | +| 16 | WEB-TEN-47-CONTRACT | DONE (2025-12-01) | Contract published in `docs/api/gateway/tenant-auth.md` v1.0 | BE-Base Platform Guild (`docs/api/gateway/tenant-auth.md`) | Publish gateway routing + tenant header/ABAC contract (headers, scopes, samples, audit notes). | +| 17 | WEB-VULN-29-LEDGER-DOC | DONE (2025-12-01) | Contract published in `docs/api/gateway/findings-ledger-proxy.md` v1.0 | Findings Ledger Guild; BE-Base Platform Guild (`docs/api/gateway/findings-ledger-proxy.md`) | Capture idempotency + correlation header contract for Findings Ledger proxy and retries/backoff defaults. | +| 18 | WEB-RISK-68-NOTIFY-DOC | DONE (2025-12-01) | Schema published in `docs/api/gateway/notifications-severity.md` v1.0 | Notifications Guild; BE-Base Platform Guild (`docs/api/gateway/notifications-severity.md`) | Document severity transition event schema (fields, trace metadata) for notifier bus integration. | ## Wave Coordination - Single wave (Web V gateway + tenant hardening). Keep task order per dependency chains above; no parallel merges that alter schema/telemetry without shared reviews. @@ -62,14 +62,17 @@ ## Decisions & Risks | Risk | Impact | Mitigation | Owner | Status | | --- | --- | --- | --- | --- | -| Tenant header/ABAC contract slips | Blocks WEB-TEN-47-001/48-001/49-001 and delays RBAC enforcement across routes | Lock contract by 2025-12-02; record in `docs/api/gateway/tenant-auth.md`; add blocking status if slip persists | BE-Base Platform Guild | Open | -| Findings Ledger idempotency headers unclear | WEB-VULN-29-002/003 cannot forward workflow actions safely | Obtain contract on 2025-12-04 checkpoint; add retries/backoff defaults once confirmed | Findings Ledger Guild | Open | -| Notifications event schema not finalized | WEB-RISK-68-001 cannot emit severity transition events with trace metadata | Schema review on 2025-12-06; use placeholder event name only after review | Notifications Guild | Open | -| Workspace storage exhaustion prevents command execution | Blocks code inspection and implementation for WEB-RISK-66-001 and subsequent tasks | Free space (e.g., clean `node_modules` caches) and re-run gateway scaffolding; retry once ≥2 GB available | Platform Ops | Open | +| Tenant header/ABAC contract slips | Blocks WEB-TEN-47-001/48-001/49-001 and delays RBAC enforcement across routes | Contract published 2025-12-01 in `docs/api/gateway/tenant-auth.md`; enforce via Gateway:Auth flags | BE-Base Platform Guild | Mitigated | +| Findings Ledger idempotency headers unclear | WEB-VULN-29-002/003 cannot forward workflow actions safely | Contract published 2025-12-01 in `docs/api/gateway/findings-ledger-proxy.md`; use TTL 24h + ETag/If-Match | Findings Ledger Guild | Mitigated | +| Notifications event schema not finalized | WEB-RISK-68-001 cannot emit severity transition events with trace metadata | Event schema v1.0 published 2025-12-01 in `docs/api/gateway/notifications-severity.md`; rate limit + DLQ included | Notifications Guild | Mitigated | +| Workspace storage exhaustion prevents command execution | Blocks code inspection and implementation for WEB-RISK-66-001 and subsequent tasks | Free space action completed; monitor disk and rerun gateway scaffolding | Platform Ops | Monitoring | ## Execution Log | Date (UTC) | Update | Owner | | --- | --- | --- | +| 2025-12-01 | Started WEB-RISK-66-001: added risk gateway client/models with tenant-scoped filtering, deterministic ordering, and unit tests (`risk.client.ts`, `risk.client.spec.ts`); local mocks used until gateway endpoints are wired. | BE-Base Platform Guild | +| 2025-12-01 | Cleared workspace disk issue (55 GB free reported); WEB-RISK-66-001 unblocked and returned to TODO. | Platform Ops | +| 2025-12-01 | Published Web V gateway contract docs v1.0: tenant auth/ABAC (`docs/api/gateway/tenant-auth.md`), Findings Ledger proxy (`docs/api/gateway/findings-ledger-proxy.md`), and notifier severity events (`docs/api/gateway/notifications-severity.md`); marked WEB-TEN-47-CONTRACT, WEB-VULN-29-LEDGER-DOC, and WEB-RISK-68-NOTIFY-DOC DONE. | BE-Base Platform Guild | | 2025-12-01 | Blocked WEB-RISK-66-001: workspace reports `No space left on device` when starting gateway scaffolding; requires freeing disk (e.g., clean `node_modules`/tmp) before proceeding. | Implementer | | 2025-12-01 | Drafted contract docs for tenant auth/ABAC, Findings Ledger proxy, and notifier severity events; set tasks 16–18 to DOING. | Project Mgmt | | 2025-11-30 | Added contract/doc tasks (rows 16–18) for tenant headers/ABAC, Findings Ledger proxy headers, and notifier severity events; aligned Action Tracker with Delivery Tracker; no status changes to feature tracks. | Project Mgmt | diff --git a/docs/implplan/SPRINT_0327_0001_0001_docs_modules_scanner.md b/docs/implplan/SPRINT_0327_0001_0001_docs_modules_scanner.md new file mode 100644 index 000000000..38bd7497d --- /dev/null +++ b/docs/implplan/SPRINT_0327_0001_0001_docs_modules_scanner.md @@ -0,0 +1,41 @@ +# Sprint 0327-0001-0001 · Docs Modules Scanner + +## Topic & Scope +- Keep scanner module documentation/process in sync with current implementation sprints and readiness gates. +- Capture Windows/macOS analyzer demand signals for product/marketing readiness. +- Fold post-demo runbook/observability feedback into module docs. +- **Working directory:** `docs/implplan` (tracker) with linked updates under `docs/modules/scanner`. + +## Dependencies & Concurrency +- Upstream inputs: Sprint 130–139 scanner wave status, ops demo outputs. +- Parallel-safe; avoid changing other modules without noting in Decisions & Risks. + +## Documentation Prerequisites +- docs/README.md +- docs/07_HIGH_LEVEL_ARCHITECTURE.md +- docs/modules/platform/architecture-overview.md +- docs/modules/scanner/architecture.md + +## Delivery Tracker +| # | Task ID | Status | Key dependency / next step | Owners | Task Definition | +| --- | --- | --- | --- | --- | --- | +| 1 | SCANNER-DOCS-0003 | BLOCKED | Waiting on field/sales demand signal interviews to be scheduled; no data available yet. | Docs Guild · Product Guild (`docs/modules/scanner`) | Gather Windows/macOS analyzer demand signals and record findings in `docs/benchmarks/scanner/windows-macos-demand.md` for marketing + product readiness. | +| 2 | SCANNER-OPS-0001 | BLOCKED | Next scanner demo not yet scheduled; need demo output to review runbooks/observability. | Ops Guild (`docs/modules/scanner`) | Review scanner runbooks/observability assets after the next sprint demo and capture findings inline with sprint notes. | +| 3 | SCANNER-ENG-0001 | DONE (2025-12-01) | Keep checkpoints updated when new scanner sprints land. | Module Team (`docs/modules/scanner`) | Cross-check implementation plan milestones against `/docs/implplan/SPRINT_*.md` and update module readiness checkpoints. | + +## Execution Log +| Date (UTC) | Update | Owner | +| --- | --- | --- | +| 2025-12-01 | Normalised sprint to standard template, renamed from `SPRINT_327_docs_modules_scanner.md` to `SPRINT_0327_0001_0001_docs_modules_scanner.md`; legacy stub retained for redirects. | Project Mgmt | +| 2025-12-01 | Completed SCANNER-ENG-0001: created readiness checkpoint doc (`docs/modules/scanner/readiness-checkpoints.md`) summarising sprint 0131–0138 status; linked in Decisions & Risks. | Module Team | +| 2025-12-01 | Marked SCANNER-DOCS-0003 and SCANNER-OPS-0001 BLOCKED awaiting field/demand inputs and the next scanner demo respectively. No work can proceed until upstream signals arrive. | Project Mgmt | + +## Decisions & Risks +- Readiness checkpoints show amber/red gaps for Java/.NET analyzers (Sprint 0131) and PHP parity (Sprint 0138); see `docs/modules/scanner/readiness-checkpoints.md`. +- Windows/macOS demand signals (SCANNER-DOCS-0003) not yet captured; risk of marketing misalignment until data gathered. +- Ops feedback pending next demo (SCANNER-OPS-0001); note cross-module doc touch in `docs/modules/scanner` when applied. + - Both BLOCKED tasks depend on external scheduling (field interviews, demo). Revisit after dates confirmed; keep sprint aligned with upstream signals. + +## Next Checkpoints +- 2025-12-05: Collect demand-signal inputs from field/PM for SCANNER-DOCS-0003 (owner: Product Guild). +- 2025-12-06: Runbook/observability review after next scanner demo (owner: Ops Guild). diff --git a/docs/implplan/SPRINT_0512_0001_0001_bench.md b/docs/implplan/SPRINT_0512_0001_0001_bench.md index 8ac690b9c..c98c0b817 100644 --- a/docs/implplan/SPRINT_0512_0001_0001_bench.md +++ b/docs/implplan/SPRINT_0512_0001_0001_bench.md @@ -26,7 +26,7 @@ | P5 | PREP-BENCH-SIG-26-001-REACHABILITY-SCHEMA-FIX | DONE (2025-11-20) | Prep doc at `docs/benchmarks/signals/bench-sig-26-001-prep.md`; awaits reachability schema hash. | Bench Guild · Signals Guild | Reachability schema/fixtures pending Sprint 0400/0401.

Document artefact/deliverable for BENCH-SIG-26-001 and publish location so downstream tasks can proceed. | | P6 | PREP-BENCH-SIG-26-002-BLOCKED-ON-26-001-OUTPU | DONE (2025-11-20) | Prep doc at `docs/benchmarks/signals/bench-sig-26-002-prep.md`; depends on 26-001 datasets. | Bench Guild · Policy Guild | Blocked on 26-001 outputs.

Document artefact/deliverable for BENCH-SIG-26-002 and publish location so downstream tasks can proceed. | | 1 | BENCH-GRAPH-21-001 | DOING (2025-12-01) | PREP-BENCH-GRAPH-21-001-NEED-GRAPH-BENCH-HARN | Bench Guild · Graph Platform Guild | Build graph viewport/path benchmark harness (50k/100k nodes) measuring Graph API/Indexer latency, memory, and tile cache hit rates. | -| 2 | BENCH-GRAPH-21-002 | BLOCKED | PREP-BENCH-GRAPH-21-002-BLOCKED-ON-21-001-HAR | Bench Guild · UI Guild | Add headless UI load benchmark (Playwright) for graph canvas interactions to track render times and FPS budgets. | +| 2 | BENCH-GRAPH-21-002 | DOING (2025-12-01) | PREP-BENCH-GRAPH-21-002-BLOCKED-ON-21-001-HAR | Bench Guild · UI Guild | Add headless UI load benchmark (Playwright) for graph canvas interactions to track render times and FPS budgets. | | 3 | BENCH-GRAPH-24-002 | BLOCKED | Waiting for 50k/100k graph fixture (SAMPLES-GRAPH-24-003) | Bench Guild · UI Guild | Implement UI interaction benchmarks (filter/zoom/table operations) citing p95 latency; integrate with perf dashboards. | | 4 | BENCH-IMPACT-16-001 | BLOCKED | PREP-BENCH-IMPACT-16-001-IMPACT-INDEX-DATASET | Bench Guild · Scheduler Team | ImpactIndex throughput bench (resolve 10k productKeys) + RAM profile. | | 5 | BENCH-POLICY-20-002 | BLOCKED | PREP-BENCH-POLICY-20-002-POLICY-DELTA-SAMPLE | Bench Guild · Policy Guild · Scheduler Guild | Add incremental run benchmark measuring delta evaluation vs full; capture SLA compliance. | @@ -89,6 +89,8 @@ | 2025-11-26 | Added `scripts/bench/determinism-run.sh` and CI workflow `.gitea/workflows/bench-determinism.yml` to run/upload determinism artifacts. | Bench Guild | | 2025-11-26 | Built determinism bench harness with mock scanner at `src/Bench/StellaOps.Bench/Determinism`, added sample SBOM/VEX inputs, generated `results/inputs.sha256` + `results.csv`, updated bench doc, and marked BENCH-DETERMINISM-401-057 DONE. Tests: `python -m unittest discover -s src/Bench/StellaOps.Bench/Determinism/tests -t src/Bench/StellaOps.Bench/Determinism`. | Bench Guild | | 2025-12-01 | Generated interim synthetic graph fixtures (50k/100k nodes with manifests) under `samples/graph/interim/` to unblock BENCH-GRAPH-21-001; task moved to DOING pending overlay schema for canonical fixture. | Implementer | +| 2025-12-01 | Added Graph UI bench scaffold: scenarios JSON, driver (`ui_bench_driver.mjs`), and plan under `src/Bench/StellaOps.Bench/Graph/`; BENCH-GRAPH-21-002 moved to DOING using interim fixtures until overlay schema/UI target is available. | Implementer | +| 2025-12-01 | Added graph bench runner `Graph/run_graph_bench.sh` and recorded sample results for graph-50k/100k fixtures; BENCH-GRAPH-21-001 progressing with interim fixtures. | Implementer | | 2025-11-22 | Added ACT-0512-07 and corresponding risk entry to have UI bench harness skeleton ready once fixtures bind; no status changes. | Project Mgmt | | 2025-11-22 | Added ACT-0512-04 to build interim synthetic graph fixture so BENCH-GRAPH-21-001 can start while awaiting SAMPLES-GRAPH-24-003; no status changes. | Project Mgmt | | 2025-11-22 | Added ACT-0512-05 escalation path (due 2025-11-23) if SAMPLES-GRAPH-24-003 remains unavailable; updated Upcoming Checkpoints accordingly. | Project Mgmt | diff --git a/docs/implplan/SPRINT_136_scanner_surface.md b/docs/implplan/SPRINT_136_scanner_surface.md index 6f81f1101..199905802 100644 --- a/docs/implplan/SPRINT_136_scanner_surface.md +++ b/docs/implplan/SPRINT_136_scanner_surface.md @@ -43,9 +43,9 @@ Dependency: Sprint 135 - 6. Scanner.VI — Scanner & Surface focus on Scanner (p | `SURFACE-FS-04` | DONE (2025-11-27) | Integrate Surface.FS reader into Zastava Observer runtime drift loop. | Zastava Guild (src/Scanner/__Libraries/StellaOps.Scanner.Surface.FS) | SURFACE-FS-02 | | `SURFACE-FS-05` | DONE (2025-11-27) | Expose Surface.FS pointers via Scanner WebService reports and coordinate rescan planning with Scheduler. | Scanner Guild, Scheduler Guild (src/Scanner/__Libraries/StellaOps.Scanner.Surface.FS) | SURFACE-FS-03 | | `SURFACE-FS-06` | DONE (2025-11-28) | Update scanner-engine guide and offline kit docs with Surface.FS workflow. | Docs Guild (src/Scanner/__Libraries/StellaOps.Scanner.Surface.FS) | SURFACE-FS-02..05 | -| `SCANNER-SURFACE-04` | TODO | DSSE-sign every `layer.fragments` payload, emit `_composition.json`, and persist DSSE envelopes so offline kits can replay deterministically (see `docs/modules/scanner/deterministic-sbom-compose.md` §2.1). | Scanner Worker Guild (src/Scanner/StellaOps.Scanner.Worker) | SCANNER-SURFACE-01, SURFACE-FS-03 | +| `SCANNER-SURFACE-04` | TODO | DSSE-sign every `layer.fragments` payload, emit `_composition.json`/`composition.recipe` URI, and persist DSSE envelopes so offline kits can replay deterministically (see `docs/modules/scanner/deterministic-sbom-compose.md` §2.1). | Scanner Worker Guild (src/Scanner/StellaOps.Scanner.Worker) | SCANNER-SURFACE-01, SURFACE-FS-03 | | `SURFACE-FS-07` | TODO | Extend Surface.FS manifest schema with `composition.recipe`, fragment attestation metadata, and verification helpers per deterministic SBOM spec. | Scanner Guild (src/Scanner/__Libraries/StellaOps.Scanner.Surface.FS) | SCANNER-SURFACE-04 | -| `SCANNER-EMIT-15-001` | DOING (2025-12-01) | Canonical content hash captured on CycloneDX artifacts (`ContentHash` = JsonSha256); remaining Merkle/DSSE wiring pending. | Scanner Emit Guild (src/Scanner/__Libraries/StellaOps.Scanner.Emit) | SCANNER-SURFACE-04 | +| `SCANNER-EMIT-15-001` | DOING (2025-12-01) | CycloneDX artifacts now carry content hash, merkle root (= recipe hash), and composition recipe URI placeholders; `_composition.json` recipe emitted into package manifest. DSSE signing still pending. | Scanner Emit Guild (src/Scanner/__Libraries/StellaOps.Scanner.Emit) | SCANNER-SURFACE-04 | | `SCANNER-SORT-02` | DONE (2025-12-01) | Layer fragment ordering by digest implemented in ComponentGraphBuilder; determinism regression test added. | Scanner Core Guild (src/Scanner/__Libraries/StellaOps.Scanner.Core) | SCANNER-EMIT-15-001 | | `SURFACE-VAL-01` | DONE (2025-11-23) | Validation framework doc aligned with Surface.Env release and secrets schema (`docs/modules/scanner/design/surface-validation.md` v1.1). | Scanner Guild, Security Guild (src/Scanner/__Libraries/StellaOps.Scanner.Surface.Validation) | SURFACE-FS-01, SURFACE-ENV-01 | | `SURFACE-VAL-02` | DONE (2025-11-23) | Validation library now enforces secrets schema, fallback/provider checks, and inline/file guardrails; tests added. | Scanner Guild (src/Scanner/__Libraries/StellaOps.Scanner.Surface.Validation) | SURFACE-VAL-01, SURFACE-ENV-02, SURFACE-FS-02 | @@ -74,7 +74,7 @@ Dependency: Sprint 135 - 6. Scanner.VI — Scanner & Surface focus on Scanner (p | 2025-12-01 | EntryTrace NDJSON emission, runtime reconciliation, and WebService/CLI exposure completed (18-504/505/506). | EntryTrace Guild | | 2025-12-01 | ZASTAVA-SURFACE-02: Observer resolves Surface manifest digests and `cas://` URIs, enriches drift evidence with artifact metadata, and counts failures via `zastava_surface_manifest_failures_total`. | Implementer | | 2025-12-01 | SCANNER-SORT-02: ComponentGraphBuilder sorts layer fragments by digest; regression test added. | Implementer | -| 2025-12-01 | SCANNER-EMIT-15-001: CycloneDX artifacts now publish `ContentHash` (sha256 of canonical JSON); Merkle/DSSE steps still pending. | Implementer | +| 2025-12-01 | SCANNER-EMIT-15-001: CycloneDX artifacts now publish `ContentHash` (sha256 of canonical JSON) and carry Merkle root / composition recipe hash placeholders; `_composition.json` recipe emitted as surface payload and packaged manifest entry. DSSE signing still pending. | Implementer | | 2025-12-01 | SCANNER-SORT-02 completed: ComponentGraphBuilder sorts layer fragments by digest with regression test Build_SortsLayersByDigest. | Implementer | | 2025-12-01 | ZASTAVA-SURFACE-02: Observer now resolves Surface manifest digests and `cas://` URIs, enriches drift evidence with artifact metadata, and counts failures via `zastava_surface_manifest_failures_total`. | Implementer | | 2025-11-23 | Published Security-approved Surface.Secrets schema (`docs/modules/scanner/design/surface-secrets-schema.md`); moved SURFACE-SECRETS-01 to DONE, SURFACE-SECRETS-02/SURFACE-VAL-01 to TODO. | Security Guild | diff --git a/docs/implplan/SPRINT_151_orchestrator_i.md b/docs/implplan/SPRINT_151_orchestrator_i.md deleted file mode 100644 index 284bf2cf3..000000000 --- a/docs/implplan/SPRINT_151_orchestrator_i.md +++ /dev/null @@ -1,24 +0,0 @@ -# Sprint 151 - Scheduling & Automation · 150.A) Orchestrator.I - -Active items only. Completed/historic work now resides in docs/implplan/archived/tasks.md (updated 2025-11-08). - -[Scheduling & Automation] 150.A) Orchestrator.I -Depends on: Sprint 120.A - AirGap, Sprint 130.A - Scanner, Sprint 140.A - Graph -Summary: Scheduling & Automation focus on Orchestrator (phase I). -Task ID | State | Task description | Owners (Source) ---- | --- | --- | --- -ORCH-AIRGAP-56-001 | TODO | Enforce job descriptors to declare network intents; reject or flag any external endpoints in sealed mode before scheduling. | Orchestrator Service Guild, AirGap Policy Guild (src/Orchestrator/StellaOps.Orchestrator) -ORCH-AIRGAP-56-002 | TODO | Surface sealing status and time staleness in job scheduling decisions; block runs when staleness budgets exceeded. Dependencies: ORCH-AIRGAP-56-001. | Orchestrator Service Guild, AirGap Controller Guild (src/Orchestrator/StellaOps.Orchestrator) -ORCH-AIRGAP-57-001 | TODO | Add job type `mirror.bundle` to orchestrate bundle creation in connected environments with audit + provenance outputs. Dependencies: ORCH-AIRGAP-56-002. | Orchestrator Service Guild, Mirror Creator Guild (src/Orchestrator/StellaOps.Orchestrator) -ORCH-AIRGAP-58-001 | TODO | Capture import/export operations as timeline/evidence entries, ensuring chain-of-custody for mirror + portable evidence jobs. Dependencies: ORCH-AIRGAP-57-001. | Orchestrator Service Guild, Evidence Locker Guild (src/Orchestrator/StellaOps.Orchestrator) -ORCH-OAS-61-001 | TODO | Document orchestrator endpoints in per-service OAS with standardized pagination, idempotency, and error envelope examples. | Orchestrator Service Guild, API Contracts Guild (src/Orchestrator/StellaOps.Orchestrator) -ORCH-OAS-61-002 | TODO | Implement `GET /.well-known/openapi` in service and ensure version metadata aligns with runtime build. Dependencies: ORCH-OAS-61-001. | Orchestrator Service Guild (src/Orchestrator/StellaOps.Orchestrator) -ORCH-OAS-62-001 | TODO | Ensure SDK paginators and operations support orchestrator job operations; add SDK smoke tests for schedule/retry APIs. Dependencies: ORCH-OAS-61-002. | Orchestrator Service Guild, SDK Generator Guild (src/Orchestrator/StellaOps.Orchestrator) -ORCH-OAS-63-001 | TODO | Emit deprecation headers and documentation for legacy orchestrator endpoints; update notifications metadata. Dependencies: ORCH-OAS-62-001. | Orchestrator Service Guild, API Governance Guild (src/Orchestrator/StellaOps.Orchestrator) -ORCH-OBS-50-001 | TODO | Wire `StellaOps.Telemetry.Core` into orchestrator host, instrument schedulers and control APIs with trace spans, structured logs, and exemplar metrics. Ensure tenant/job metadata recorded for every span/log. | Orchestrator Service Guild, Observability Guild (src/Orchestrator/StellaOps.Orchestrator) -ORCH-OBS-51-001 | TODO | Publish golden-signal metrics (dispatch latency, queue depth, failure rate), define job/tenant SLOs, and emit burn-rate alerts to collector + Notifications. Provide Grafana dashboards + alert rules. Dependencies: ORCH-OBS-50-001. | Orchestrator Service Guild, DevOps Guild (src/Orchestrator/StellaOps.Orchestrator) -ORCH-OBS-52-001 | TODO | Emit `timeline_event` objects for job lifecycle (`job.scheduled`, `job.started`, `job.completed`, `job.failed`) including trace IDs, run IDs, tenant/project, and causal metadata. Add contract tests and Kafka/NATS emitter with retries. Dependencies: ORCH-OBS-51-001. | Orchestrator Service Guild (src/Orchestrator/StellaOps.Orchestrator) -ORCH-OBS-53-001 | TODO | Generate job capsule inputs for evidence locker (payload digests, worker image, config hash, log manifest) and invoke locker snapshot hooks on completion/failure. Ensure redaction guard enforced. Dependencies: ORCH-OBS-52-001. | Orchestrator Service Guild, Evidence Locker Guild (src/Orchestrator/StellaOps.Orchestrator) -ORCH-OBS-54-001 | TODO | Produce DSSE attestations for orchestrator-scheduled jobs (subject = job capsule) and store references in timeline + evidence locker. Provide verification endpoint `/jobs/{id}/attestation`. Dependencies: ORCH-OBS-53-001. | Orchestrator Service Guild, Provenance Guild (src/Orchestrator/StellaOps.Orchestrator) -ORCH-OBS-55-001 | TODO | Implement incident mode hooks (sampling overrides, extended retention, additional debug spans) and automatic activation on SLO burn-rate breach. Emit activation/deactivation events to timeline + Notifier. Dependencies: ORCH-OBS-54-001. | Orchestrator Service Guild, DevOps Guild (src/Orchestrator/StellaOps.Orchestrator) -ORCH-SVC-32-001 | TODO | Bootstrap service project, configuration, and Postgres schema/migrations for `sources`, `runs`, `jobs`, `dag_edges`, `artifacts`, `quotas`, `schedules`. | Orchestrator Service Guild (src/Orchestrator/StellaOps.Orchestrator) \ No newline at end of file diff --git a/docs/implplan/SPRINT_300_documentation_process.md b/docs/implplan/SPRINT_300_documentation_process.md index b94ac0cde..77ba962ac 100644 --- a/docs/implplan/SPRINT_300_documentation_process.md +++ b/docs/implplan/SPRINT_300_documentation_process.md @@ -28,6 +28,7 @@ | ECOSYS-FIXTURES-GAPS-300-017 | TODO | QA Guild · Scanner Guild · Docs Guild | 30-Nov-2025 ecosystem reality test cases | Close ET1–ET10: signed fixture pack + expected-result schema, deterministic builds/seeds, secret-leak assertions, offline/no-network enforcement, version matrix + DB pinning, SBOM parity thresholds, CI ownership/SLOs, provenance/licensing, retention/redaction policy, and ID/CVSS normalization utilities. | | IMPLEMENTOR-GAPS-300-018 | TODO | Docs Guild · Platform Guild | 30-Nov-2025 implementor guidelines | Close IG1–IG10: enforceable checklist + CI gates, schema/versioning change control, determinism/offline/secret/provenance requirements, perf/quota tests, boundary rules, and AGENTS/sprint linkages. | | STANDUP-GAPS-300-019 | TODO | Docs Guild · Ops Guild | 30-Nov-2025 standup sprint kickstarters | Close SK1–SK10: kickstarter template alignment with sprint template, readiness evidence checklist, dependency ledger with owners/SLOs, time-box/exit rules, async/offline workflow, Execution Log updates, decisions/risks delta capture, metrics (blocker clear rate/latency), role assignment, and lint/checks to enforce completion. | +| ARCHIVED-GAPS-300-020 | TODO | Docs Guild · Architecture Guild | 15–23 Nov archived advisories | Decide which archived advisories to revive; close AR-* gaps (see `31-Nov-2025 FINDINGS.md` per-advisory table): publish canonical schemas/recipes (provenance, reachability, PURL/Build-ID), licensing/manifest rules, determinism seeds/SLOs, redaction/isolation, changelog/checkpoint signing, supersede duplicates (SBOM-Provenance-Spine, archived VB reachability), and document PostgreSQL storage blueprint guardrails. | | Plugin architecture gaps remediation | TODO | Docs Guild · Module Guilds (Authority/Scanner/Concelier) | 28-Nov-2025 plugin advisory | Close PL1–PL10 from `31-Nov-2025 FINDINGS.md`: publish signed schemas/capability catalog, sandbox/resource limits, provenance/SBOM + DSSE verification, determinism harness, compatibility matrix, dependency/secret rules, crash kill-switch, offline kit packaging/verify script, and signed plugin index with revocation/CVE data. | | CVSS v4.0 momentum sync | TODO | Docs Guild | 29-Nov-2025 advisory + briefing draft | Publish the CVSS v4.0 momentum briefing, highlight adoption signals, and link to sprint decisions for SPRINT_0190.* and docs coverage. | | SBOM→VEX proof blueprint sync | TODO | Docs Guild | 29-Nov-2025 advisory + blueprint draft | Publish the SBOM→VEX blueprint, link to platform/blueprint docs, and capture diagram/stub updates for DSSE/Rekor/VEX. | @@ -55,6 +56,7 @@ | 2025-12-01 | Added ECOSYS-FIXTURES-GAPS-300-017 to track ET1–ET10 remediation from `31-Nov-2025 FINDINGS.md`; status TODO pending fixture pack creation and CI wiring. | Project Mgmt | | 2025-12-01 | Added IMPLEMENTOR-GAPS-300-018 to track IG1–IG10 remediation from `31-Nov-2025 FINDINGS.md`; status TODO pending enforceable checklist/CI gates rollout. | Project Mgmt | | 2025-12-01 | Added STANDUP-GAPS-300-019 to track SK1–SK10 remediation from `31-Nov-2025 FINDINGS.md`; status TODO pending kickstarter template updates, async/offline workflows, metrics, and lint enforcement. | Project Mgmt | +| 2025-12-01 | Added ARCHIVED-GAPS-300-020 to triage AR-* gaps from archived advisories (15–23 Nov 2025); status TODO pending decision on which to revive and schema/recipe publication. | Project Mgmt | | 2025-11-30 | Added the 30-Nov-2025 Rekor Receipt Checklist advisory and noted the ownership/action map for Authority/Sbomer/Vexer. | Docs Guild | | 2025-11-30 | Added the 30-Nov-2025 Ecosystem Reality Test Cases advisory (credential leak, Trivy offline DB, SBOM parity, Grype divergence) and logged the acceptance test intent. | Docs Guild | | 2025-11-30 | Added the 30-Nov-2025 Unknowns Decay & Triage advisory and noted UI + export artifacts for UnknownsRegistry + queues. | Docs Guild | diff --git a/docs/implplan/SPRINT_327_docs_modules_scanner.md b/docs/implplan/SPRINT_327_docs_modules_scanner.md index c51dd7c70..9d5d28d8b 100644 --- a/docs/implplan/SPRINT_327_docs_modules_scanner.md +++ b/docs/implplan/SPRINT_327_docs_modules_scanner.md @@ -1,12 +1,3 @@ -# Sprint 327 - Documentation & Process · 200.Q) Docs Modules Scanner +# Redirect -Active items only. Completed/historic work now resides in docs/implplan/archived/tasks.md (updated 2025-11-08). - -[Documentation & Process] 200.Q) Docs Modules Scanner -Depends on: Sprint 100.A - Attestor, Sprint 110.A - AdvisoryAI, Sprint 120.A - AirGap, Sprint 130.A - Scanner, Sprint 140.A - Graph, Sprint 150.A - Orchestrator, Sprint 160.A - EvidenceLocker, Sprint 170.A - Notifier, Sprint 180.A - Cli, Sprint 190.A - Ops Deployment -Summary: Documentation & Process focus on Docs Modules Scanner). -Task ID | State | Task description | Owners (Source) ---- | --- | --- | --- -SCANNER-DOCS-0003 | TODO | Gather Windows/macOS analyzer demand signals and record findings in `docs/benchmarks/scanner/windows-macos-demand.md` for marketing + product readiness. | Docs Guild, Product Guild (docs/modules/scanner) -SCANNER-OPS-0001 | TODO | Review scanner runbooks/observability assets after the next sprint demo and capture findings inline with sprint notes. | Ops Guild (docs/modules/scanner) -SCANNER-ENG-0001 | TODO | Cross-check implementation plan milestones against `/docs/implplan/SPRINT_*.md` and update module readiness checkpoints. | Module Team (docs/modules/scanner) +This sprint file was renamed to `SPRINT_0327_0001_0001_docs_modules_scanner.md` to comply with naming rules. Please edit the canonical file. diff --git a/docs/implplan/SPRINT_3404_0001_0001_postgres_policy.md b/docs/implplan/SPRINT_3404_0001_0001_postgres_policy.md index 7cfb8dc72..2ae3b0fa1 100644 --- a/docs/implplan/SPRINT_3404_0001_0001_postgres_policy.md +++ b/docs/implplan/SPRINT_3404_0001_0001_postgres_policy.md @@ -41,8 +41,8 @@ | 18 | PG-T4.6.4 | DONE | Completed 2025-11-29 | Policy Guild | Implement `IAuditRepository` | | 19 | PG-T4.7 | DONE | Completed 2025-11-29 | Policy Guild | Add configuration switch in `ServiceCollectionExtensions` | | 20 | PG-T4.8.1 | DONE | Completed 2025-11-29 | Policy Guild | Write integration tests for all repositories | -| 21 | PG-T4.8.2 | TODO | Depends on PG-T4.8.1 | Policy Guild | Test pack versioning workflow | -| 22 | PG-T4.8.3 | TODO | Depends on PG-T4.8.1 | Policy Guild | Test risk profile version history | +| 21 | PG-T4.8.2 | DOING (2025-12-01) | Depends on PG-T4.8.1 | Policy Guild | Test pack versioning workflow | +| 22 | PG-T4.8.3 | DOING (2025-12-01) | Depends on PG-T4.8.1 | Policy Guild | Test risk profile version history | | 23 | PG-T4.9 | TODO | Depends on PG-T4.8 | Policy Guild | Export active packs from MongoDB | | 24 | PG-T4.10 | TODO | Depends on PG-T4.9 | Policy Guild | Import packs to PostgreSQL | | 25 | PG-T4.11 | TODO | Depends on PG-T4.10 | Policy Guild | Verify version numbers and active version settings | @@ -103,6 +103,7 @@ | 2025-11-29 | ServiceCollectionExtensions updated with all repository registrations (PG-T4.7) | Claude | | 2025-11-29 | Integration tests created for Pack, Rule, Exception, EvaluationRun, RiskProfile, PolicyAudit repositories (PG-T4.8.1) | Claude | | 2025-11-30 | Normalised sprint to docs/implplan template; added coordination and action tracker sections | Codex | +| 2025-12-01 | Started PG-T4.8.2/4.8.3: defined pack versioning + risk profile history test matrices, fixture needs for Mongo→Postgres export/import (T4.9/T4.10), pegged to dual-write hashes from T4.8.1. | Implementer | --- *Reference: docs/db/tasks/PHASE_4_POLICY.md* diff --git a/docs/implplan/SPRINT_503_ops_devops_i.md b/docs/implplan/SPRINT_503_ops_devops_i.md index 8c3bb2e84..00c19cb9f 100644 --- a/docs/implplan/SPRINT_503_ops_devops_i.md +++ b/docs/implplan/SPRINT_503_ops_devops_i.md @@ -35,18 +35,18 @@ Depends on: Sprint 100.A - Attestor, Sprint 110.A - AdvisoryAI, Sprint 120.A - A | DEVOPS-AOC-19-001 | BLOCKED (2025-10-26) | Integrate the AOC Roslyn analyzer and guard tests into CI, failing builds when ingestion projects attempt banned writes. | DevOps Guild, Platform Guild (ops/devops) | | DEVOPS-AOC-19-002 | BLOCKED (2025-10-26) | Add pipeline stage executing `stella aoc verify --since` against seeded Mongo snapshots for Concelier + Excititor, publishing violation report artefacts. Dependencies: DEVOPS-AOC-19-001. | DevOps Guild (ops/devops) | | DEVOPS-AOC-19-003 | BLOCKED (2025-10-26) | Enforce unit test coverage thresholds for AOC guard suites and ensure coverage exported to dashboards. Dependencies: DEVOPS-AOC-19-002. | DevOps Guild, QA Guild (ops/devops) | -| DEVOPS-AOC-19-101 | TODO (2025-10-28) | Draft supersedes backfill rollout (freeze window, dry-run steps, rollback) once advisory_raw idempotency index passes staging verification. Dependencies: DEVOPS-AOC-19-003. | DevOps Guild, Concelier Storage Guild (ops/devops) | +| DEVOPS-AOC-19-101 | DONE (2025-12-01) | Draft supersedes backfill rollout (freeze window, dry-run steps, rollback) once advisory_raw idempotency index passes staging verification. Dependencies: DEVOPS-AOC-19-003. | DevOps Guild, Concelier Storage Guild (ops/devops) | | DEVOPS-ATTEST-73-001 | DONE (2025-11-30) | Provision CI pipelines for attestor service (lint/test/security scan, seed data) and manage secrets for KMS drivers. | DevOps Guild, Attestor Service Guild (ops/devops) | | DEVOPS-ATTEST-73-002 | DONE (2025-11-30) | Establish secure storage for signing keys (vault integration, rotation schedule) and audit logging. Dependencies: DEVOPS-ATTEST-73-001. | DevOps Guild, KMS Guild (ops/devops) | -| DEVOPS-ATTEST-74-001 | TODO | Deploy transparency log witness infrastructure and monitoring. Dependencies: DEVOPS-ATTEST-73-002. | DevOps Guild, Transparency Guild (ops/devops) | -| DEVOPS-GRAPH-INDEX-28-010-REL | TODO | Publish signed Helm/Compose/offline bundles for Graph Indexer; depends on GRAPH-INDEX-28-010 dev artefacts. | DevOps Guild, Graph Indexer Guild (ops/devops) | -| DEVOPS-LNM-21-101-REL | TODO | Run/apply shard/index migrations (Concelier LNM) in release pipelines; capture artefacts and rollback scripts. | DevOps Guild, Concelier Storage Guild (ops/devops) | -| DEVOPS-LNM-21-102-REL | TODO | Package/publish LNM backfill/rollback bundles for release/offline kit; depends on 21-102 dev outputs. | DevOps Guild, Concelier Storage Guild (ops/devops) | -| DEVOPS-LNM-21-103-REL | TODO | Publish/rotate object-store seeds and offline bootstraps with provenance hashes; depends on 21-103 dev outputs. | DevOps Guild, Concelier Storage Guild (ops/devops) | +| DEVOPS-ATTEST-74-001 | DONE (2025-12-01) | Deploy transparency log witness infrastructure and monitoring. Dependencies: DEVOPS-ATTEST-73-002. | DevOps Guild, Transparency Guild (ops/devops) | +| DEVOPS-GRAPH-INDEX-28-010-REL | DONE (2025-12-01) | Publish signed Helm/Compose/offline bundles for Graph Indexer; depends on GRAPH-INDEX-28-010 dev artefacts. | DevOps Guild, Graph Indexer Guild (ops/devops) | +| DEVOPS-LNM-21-101-REL | DONE (2025-12-01) | Run/apply shard/index migrations (Concelier LNM) in release pipelines; capture artefacts and rollback scripts. | DevOps Guild, Concelier Storage Guild (ops/devops) | +| DEVOPS-LNM-21-102-REL | DONE (2025-12-01) | Package/publish LNM backfill/rollback bundles for release/offline kit; depends on 21-102 dev outputs. | DevOps Guild, Concelier Storage Guild (ops/devops) | +| DEVOPS-LNM-21-103-REL | DONE (2025-12-01) | Publish/rotate object-store seeds and offline bootstraps with provenance hashes; depends on 21-103 dev outputs. | DevOps Guild, Concelier Storage Guild (ops/devops) | | DEVOPS-STORE-AOC-19-005-REL | BLOCKED | Release/offline-kit packaging for Concelier backfill; waiting on dataset hash + dev rehearsal. | DevOps Guild, Concelier Storage Guild (ops/devops) | | DEVOPS-CONCELIER-CI-24-101 | DONE (2025-11-25) | Provide clean CI runner + warmed NuGet cache + vstest harness for Concelier WebService & Storage; deliver TRX/binlogs and unblock CONCELIER-GRAPH-24-101/28-102 and LNM-21-004..203. | DevOps Guild, Concelier Core Guild (ops/devops) | | DEVOPS-SCANNER-CI-11-001 | DONE (2025-11-30) | Supply warmed cache/diag runner for Scanner analyzers (LANG-11-001, JAVA 21-005/008) with binlogs + TRX; unblock restore/test hangs. | DevOps Guild, Scanner EPDR Guild (ops/devops) | -| DEVOPS-SCANNER-JAVA-21-011-REL | TODO | Package/sign Java analyzer plug-in once dev task 21-011 delivers; publish to Offline Kit/CLI release pipelines with provenance. | DevOps Guild, Scanner Release Guild (ops/devops) | +| DEVOPS-SCANNER-JAVA-21-011-REL | DONE (2025-12-01) | Package/sign Java analyzer plug-in once dev task 21-011 delivers; publish to Offline Kit/CLI release pipelines with provenance. | DevOps Guild, Scanner Release Guild (ops/devops) | | DEVOPS-SBOM-23-001 | DONE (2025-11-30) | Publish vetted offline NuGet feed + CI recipe for SbomService; prove with `dotnet test` run and share cache hashes; unblock SBOM-CONSOLE-23-001/002. | DevOps Guild, SBOM Service Guild (ops/devops) | | FEED-REMEDIATION-1001 | BLOCKED (2025-11-24) | Define remediation scope and runbook for overdue feeds (CCCS/CERTBUND); schedule refresh; depends on PREP-FEEDCONN-ICS-KISA-PLAN. | Concelier Feed Owners (ops/devops) | | FEEDCONN-ICSCISA-02-012 / FEEDCONN-KISA-02-008 | BLOCKED (2025-11-24) | Publish provenance refresh/connector schedule for ICSCISA/KISA feeds; execute remediation per runbook once owners provide plan. | Concelier Feed Owners (ops/devops) | @@ -75,6 +75,11 @@ Depends on: Sprint 100.A - Attestor, Sprint 110.A - AdvisoryAI, Sprint 120.A - A | 2025-12-01 | Marked DEVOPS-SPANSINK-31-003 to DOING; span sink/Signals pipeline setup underway. | DevOps | | 2025-11-30 | Completed DEVOPS-AIRGAP-58-001: added syslog/SMTP compose stack (`ops/devops/airgap/compose-syslog-smtp.yaml`) and health script (`health_syslog_smtp.sh`); documented in airgap README for sealed environments. | DevOps | | 2025-11-30 | DEVOPS-AIAI-31-001 DONE: added Advisory AI CI harness (`ops/devops/advisoryai-ci-runner/run-advisoryai-ci.sh`) producing binlog/TRX/summary; warmed local NuGet cache for offline runs; docs in runner README. | DevOps | +| 2025-12-01 | Completed DEVOPS-AOC-19-101: authored supersedes backfill rollout plan (`ops/devops/aoc/supersedes-rollout.md`) covering freeze window, dry-run, validation, rollback, evidence capture, and monitoring. | DevOps | +| 2025-12-01 | Completed DEVOPS-ATTEST-74-001: published transparency log witness deployment plan (`ops/devops/attestation/witness-plan.md`) with security hardening, CI tests, monitoring/alerts, and air-gap mode guidance. | DevOps | +| 2025-12-01 | Completed DEVOPS-GRAPH-INDEX-28-010-REL: documented signed Helm/Compose/offline bundle plan for Graph Indexer (`ops/devops/graph-indexer/release-plan.md`) including SBOMs, cosign attestations, air-gap bundle layout, and verification steps. | DevOps | +| 2025-12-01 | Completed DEVOPS-SCANNER-JAVA-21-011-REL: added Java analyzer release/offline plan (`ops/devops/scanner-java/release-plan.md`) covering SBOMs, cosign attestations, offline bundle packaging, and verification. | DevOps | +| 2025-12-01 | Completed DEVOPS-LNM-21-101/102/103-REL: added Concelier LNM release/offline plan (`ops/devops/concelier/lnm-release-plan.md`) covering shard/index migrations, backfill/rollback bundles, object-store seeds, offline tarball layout, signatures, and rollback. | DevOps | ## Decisions & Risks - Mirror bundle automation (DEVOPS-AIRGAP-57-001) and AOC guardrails remain gating risks; several downstream tasks inherit these. diff --git a/docs/implplan/tasks-all.md b/docs/implplan/tasks-all.md index fcca4775c..397a2d48e 100644 --- a/docs/implplan/tasks-all.md +++ b/docs/implplan/tasks-all.md @@ -1282,21 +1282,22 @@ | ORCH-34-003 | TODO | | SPRINT_306_docs_tasks_md_vi | Docs Guild (docs) | | — | — | ORGR0102 | | ORCH-34-004 | TODO | | SPRINT_306_docs_tasks_md_vi | Docs Guild (docs) | | — | — | ORGR0102 | | ORCH-34-005 | TODO | | SPRINT_306_docs_tasks_md_vi | Docs Guild (docs) | | — | — | ORGR0102 | -| ORCH-AIRGAP-56-001 | TODO | | SPRINT_151_orchestrator_i | Orchestrator Service + AirGap Policy Guilds | src/Orchestrator/StellaOps.Orchestrator | Enforce job descriptors to declare network intents; reject external endpoints in sealed mode. | ATMI0102 | ORAG0101 | -| ORCH-AIRGAP-56-002 | TODO | | SPRINT_151_orchestrator_i | Orchestrator Service + AirGap Controller Guild | src/Orchestrator/StellaOps.Orchestrator | Surface sealing status/time staleness in scheduler APIs. | ORCH-AIRGAP-56-001 | ORAG0101 | -| ORCH-AIRGAP-57-001 | TODO | | SPRINT_151_orchestrator_i | Orchestrator + Export Center Guilds | src/Orchestrator/StellaOps.Orchestrator | Ship sealed-mode exec profiles with mirror/orchestrator hooks. | ORCH-AIRGAP-56-002 | ORAG0101 | -| ORCH-AIRGAP-58-001 | TODO | | SPRINT_151_orchestrator_i | Orchestrator + Offline Kit Guilds | src/Orchestrator/StellaOps.Orchestrator | Export sealed job bundles + DSSE receipts for Offline Kit. | ORCH-AIRGAP-57-001 | ORAG0101 | -| ORCH-OAS-61-001 | TODO | | SPRINT_151_orchestrator_i | Orchestrator Service Guild | src/Orchestrator/StellaOps.Orchestrator | Update orchestrator OAS spec + changelog per governance rules. | OAS-61 | OROA0101 | -| ORCH-OAS-61-002 | TODO | | SPRINT_151_orchestrator_i | Orchestrator Service Guild | src/Orchestrator/StellaOps.Orchestrator | Apply pagination/idempotency rules + tests. | ORCH-OAS-61-001 | OROA0101 | -| ORCH-OAS-62-001 | TODO | | SPRINT_151_orchestrator_i | Orchestrator Service + SDK Guild | src/Orchestrator/StellaOps.Orchestrator | Publish auto-generated SDK + portal refs. | ORCH-OAS-61-002 | OROA0101 | -| ORCH-OAS-63-001 | TODO | | SPRINT_151_orchestrator_i | Orchestrator Service + API Governance Guild | src/Orchestrator/StellaOps.Orchestrator | Implement `.well-known/openapi` discovery + deprecation headers. | ORCH-OAS-62-001 | OROA0101 | -| ORCH-OBS-50-001 | TODO | | SPRINT_151_orchestrator_i | Orchestrator Service Guild · Observability Guild | src/Orchestrator/StellaOps.Orchestrator | Wire `StellaOps.Telemetry.Core` into orchestrator host, instrument schedulers and control APIs with trace spans, structured logs, and exemplar metrics. Ensure tenant/job metadata recorded for every span/log. | Wait for 043_ORTR0101 taskrunner counters | OROB0101 | -| ORCH-OBS-51-001 | TODO | | SPRINT_151_orchestrator_i | Orchestrator Service Guild · DevOps Guild | src/Orchestrator/StellaOps.Orchestrator | Publish golden-signal metrics (dispatch latency, queue depth, failure rate), define job/tenant SLOs, and emit burn-rate alerts to collector + Notifications. Provide Grafana dashboards + alert rules. Dependencies: ORCH-OBS-50-001. | Needs DevOps alert templates (045_DVDO0103) | OROB0101 | -| ORCH-OBS-52-001 | TODO | | SPRINT_151_orchestrator_i | Orchestrator Service Guild | src/Orchestrator/StellaOps.Orchestrator | Emit `timeline_event` objects for job lifecycle (`job.scheduled`, `job.started`, `job.completed`, `job.failed`) including trace IDs, run IDs, tenant/project, and causal metadata. Add contract tests and Kafka/NATS emitter with retries. Dependencies: ORCH-OBS-51-001. | Depends on instrumentation contract 046_TLTY0101 | OROB0101 | -| ORCH-OBS-53-001 | TODO | | SPRINT_151_orchestrator_i | Orchestrator Service Guild · Evidence Locker Guild | src/Orchestrator/StellaOps.Orchestrator | Generate job capsule inputs for evidence locker (payload digests, worker image, config hash, log manifest) and invoke locker snapshot hooks on completion/failure. Ensure redaction guard enforced. Dependencies: ORCH-OBS-52-001. | Requires Evidence Locker contract (002_ATEL0101) | OROB0101 | -| ORCH-OBS-54-001 | TODO | | SPRINT_151_orchestrator_i | Orchestrator Service Guild · Provenance Guild | src/Orchestrator/StellaOps.Orchestrator | Produce DSSE attestations for orchestrator-scheduled jobs (subject = job capsule) and store references in timeline + evidence locker. Provide verification endpoint `/jobs/{id}/attestation`. Dependencies: ORCH-OBS-53-001. | Blocked by provenance schema (005_ATLN0101) | OROB0101 | -| ORCH-OBS-55-001 | TODO | | SPRINT_151_orchestrator_i | Orchestrator Service Guild · DevOps Guild | src/Orchestrator/StellaOps.Orchestrator | Implement incident mode hooks (sampling overrides, extended retention, additional debug spans) and automatic activation on SLO burn-rate breach. Emit activation/deactivation events to timeline + Notifier. Dependencies: ORCH-OBS-54-001. | Needs #5 resolved for label stability | OROB0101 | -| ORCH-SVC-32-001 | TODO | | SPRINT_151_orchestrator_i | Orchestrator Service Guild | src/Orchestrator/StellaOps.Orchestrator | Bootstrap service project, configuration, Postgres schema/migrations for sources/runs/jobs DAG. | PGMI0101 | ORSC0101 | +| ORCH-AIRGAP-56-001 | BLOCKED (2025-11-19) | 2025-11-19 | SPRINT_0151_0001_0001_orchestrator_i | Orchestrator Service Guild · AirGap Policy Guild | src/Orchestrator/StellaOps.Orchestrator | Enforce job descriptors to declare network intents; flag/reject external endpoints in sealed mode before scheduling. | PREP-ORCH-AIRGAP-56-001-AWAIT-SPRINT-0120-A-A | ORAG0101 | +| ORCH-AIRGAP-56-002 | BLOCKED (2025-11-19) | 2025-11-19 | SPRINT_0151_0001_0001_orchestrator_i | Orchestrator Service Guild · AirGap Controller Guild | src/Orchestrator/StellaOps.Orchestrator | Surface sealing status and staleness in scheduling decisions; block runs when budgets are exceeded. | PREP-ORCH-AIRGAP-56-002-UPSTREAM-56-001-BLOCK | ORAG0101 | +| ORCH-AIRGAP-57-001 | BLOCKED (2025-11-19) | 2025-11-19 | SPRINT_0151_0001_0001_orchestrator_i | Orchestrator Service Guild · Mirror Creator Guild | src/Orchestrator/StellaOps.Orchestrator | Add job type `mirror.bundle` to orchestrate bundle creation in connected environments with audit + provenance outputs. | PREP-ORCH-AIRGAP-57-001-UPSTREAM-56-002-BLOCK | ORAG0101 | +| ORCH-AIRGAP-58-001 | BLOCKED (2025-11-19) | 2025-11-19 | SPRINT_0151_0001_0001_orchestrator_i | Orchestrator Service Guild · Evidence Locker Guild | src/Orchestrator/StellaOps.Orchestrator | Capture import/export operations as timeline/evidence entries, ensuring chain-of-custody for mirror + portable evidence jobs. | PREP-ORCH-AIRGAP-58-001-UPSTREAM-57-001-BLOCK | ORAG0101 | +| ORCH-OAS-61-001 | DONE (2025-11-30) | 2025-11-30 | SPRINT_0151_0001_0001_orchestrator_i | Orchestrator Service Guild · API Contracts Guild | src/Orchestrator/StellaOps.Orchestrator | Document orchestrator endpoints in per-service OAS with standardized pagination, idempotency, and error envelope examples. | PREP-ORCH-OAS-61-001-ORCHESTRATOR-TELEMETRY-C | OROA0101 | +| ORCH-OAS-61-002 | DONE (2025-11-30) | 2025-11-30 | SPRINT_0151_0001_0001_orchestrator_i | Orchestrator Service Guild | src/Orchestrator/StellaOps.Orchestrator | Implement `GET /.well-known/openapi` and align version metadata with runtime build. | PREP-ORCH-OAS-61-002-DEPENDS-ON-61-001 | OROA0101 | +| ORCH-OAS-62-001 | DONE (2025-11-30) | 2025-11-30 | SPRINT_0151_0001_0001_orchestrator_i | Orchestrator Service Guild · SDK Generator Guild | src/Orchestrator/StellaOps.Orchestrator | Ensure SDK paginators/operations support orchestrator job APIs; add SDK smoke tests for schedule/retry (pack-run). | PREP-ORCH-OAS-62-001-DEPENDS-ON-61-002 | OROA0101 | +| ORCH-OAS-63-001 | DONE (2025-11-30) | 2025-11-30 | SPRINT_0151_0001_0001_orchestrator_i | Orchestrator Service Guild · API Governance Guild | src/Orchestrator/StellaOps.Orchestrator | Emit deprecation headers and documentation for legacy orchestrator endpoints; update notifications metadata. | PREP-ORCH-OAS-63-001-DEPENDS-ON-62-001 | OROA0101 | +| ORCH-OBS-50-001 | BLOCKED (2025-11-19) | 2025-11-19 | SPRINT_0151_0001_0001_orchestrator_i | Orchestrator Service Guild · Observability Guild | src/Orchestrator/StellaOps.Orchestrator | Wire `StellaOps.Telemetry.Core` into orchestrator host, instrument schedulers and control APIs with trace spans, structured logs, and exemplar metrics; ensure tenant/job metadata is recorded for every span/log. | PREP-ORCH-OBS-50-001-TELEMETRY-CORE-SPRINT-01 | OROB0101 | +| ORCH-OBS-51-001 | BLOCKED (2025-11-19) | 2025-11-19 | SPRINT_0151_0001_0001_orchestrator_i | Orchestrator Service Guild · DevOps Guild | src/Orchestrator/StellaOps.Orchestrator | Publish golden-signal metrics (dispatch latency, queue depth, failure rate), define job/tenant SLOs, and emit burn-rate alerts to collector + Notifications; provide Grafana dashboards + alert rules. | PREP-ORCH-OBS-51-001-DEPENDS-ON-50-001-TELEME | OROB0101 | +| ORCH-OBS-52-001 | BLOCKED (2025-11-19) | 2025-11-19 | SPRINT_0151_0001_0001_orchestrator_i | Orchestrator Service Guild | src/Orchestrator/StellaOps.Orchestrator | Emit `timeline_event` objects for job lifecycle (`job.scheduled`, `job.started`, `job.completed`, `job.failed`) including trace IDs, run IDs, tenant/project, and causal metadata; add contract tests and Kafka/NATS emitter with retries. | PREP-ORCH-OBS-52-001-DEPENDS-ON-51-001-REQUIR | OROB0101 | +| ORCH-OBS-53-001 | BLOCKED (2025-11-19) | 2025-11-19 | SPRINT_0151_0001_0001_orchestrator_i | Orchestrator Service Guild · Evidence Locker Guild | src/Orchestrator/StellaOps.Orchestrator | Generate job capsule inputs for evidence locker (payload digests, worker image, config hash, log manifest) and invoke locker snapshot hooks on completion/failure; enforce redaction guard. | PREP-ORCH-OBS-53-001-DEPENDS-ON-52-001-EVIDEN | OROB0101 | +| ORCH-OBS-54-001 | BLOCKED (2025-11-19) | 2025-11-19 | SPRINT_0151_0001_0001_orchestrator_i | Orchestrator Service Guild · Provenance Guild | src/Orchestrator/StellaOps.Orchestrator | Produce DSSE attestations for orchestrator-scheduled jobs (subject = job capsule) and store references in timeline + evidence locker; provide verification endpoint `/jobs/{id}/attestation`. | PREP-ORCH-OBS-54-001-DEPENDS-ON-53-001 | OROB0101 | +| ORCH-OBS-55-001 | BLOCKED (2025-11-19) | 2025-11-19 | SPRINT_0151_0001_0001_orchestrator_i | Orchestrator Service Guild · DevOps Guild | src/Orchestrator/StellaOps.Orchestrator | Implement incident mode hooks (sampling overrides, extended retention, additional debug spans) and automatic activation on SLO burn-rate breach; emit activation/deactivation events to timeline + Notifier. | PREP-ORCH-OBS-55-001-DEPENDS-ON-54-001-INCIDE | OROB0101 | +| ORCH-SVC-32-001 | DONE (2025-11-28) | 2025-11-28 | SPRINT_0151_0001_0001_orchestrator_i | Orchestrator Service Guild | src/Orchestrator/StellaOps.Orchestrator | Bootstrap service project/config and Postgres schema/migrations for `sources`, `runs`, `jobs`, `dag_edges`, `artifacts`, `quotas`, `schedules`. | — | ORSC0101 | +| ORCH-GAPS-151-016 | DOING (2025-12-01) | 2025-12-01 | SPRINT_0151_0001_0001_orchestrator_i | Orchestrator Service Guild | src/Orchestrator/StellaOps.Orchestrator | Close OR1–OR10 gaps from `31-Nov-2025 FINDINGS.md`: signed schemas + hashes, replay inputs.lock, heartbeat/lease governance, DAG validation, quotas/breakers, security bindings, ordered/backpressured fan-out, audit-bundle schema/verify script, SLO alerts, TaskRunner integrity (artifact/log hashing + DSSE linkage). | Schema/catalog refresh | | | ORCH-SVC-32-002 | TODO | | SPRINT_152_orchestrator_ii | Orchestrator Service Guild | src/Orchestrator/StellaOps.Orchestrator | Implement scheduler DAG planner + job state machine. | ORCH-SVC-32-001 | ORSC0101 | | ORCH-SVC-32-003 | TODO | | SPRINT_152_orchestrator_ii | Orchestrator Service Guild | src/Orchestrator/StellaOps.Orchestrator | Expose REST APIs (sources/runs/jobs) w/ validation + tenant scope. | ORCH-SVC-32-002 | ORSC0101 | | ORCH-SVC-32-004 | TODO | | SPRINT_152_orchestrator_ii | Orchestrator Service Guild | src/Orchestrator/StellaOps.Orchestrator | Implement SSE/WS streams + metrics/health probes. | ORCH-SVC-32-003 | ORSC0101 | @@ -1881,7 +1882,6 @@ | SURFACE-VAL-03 | TODO | | SPRINT_136_scanner_surface | Scanner Guild, Analyzer Guild (src/Scanner/__Libraries/StellaOps.Scanner.Surface.Validation) | src/Scanner/__Libraries/StellaOps.Scanner.Surface.Validation | Integrate validation pipeline into Scanner analyzers so checks run before processing. | SURFACE-VAL-02 | SCSS0102 | | SURFACE-VAL-04 | TODO | | SPRINT_136_scanner_surface | Scanner Guild, Zastava Guild (src/Scanner/__Libraries/StellaOps.Scanner.Surface.Validation) | src/Scanner/__Libraries/StellaOps.Scanner.Surface.Validation | Expose validation helpers to Zastava and other runtime consumers for preflight checks. | SURFACE-VAL-02 | SCSS0102 | | SURFACE-VAL-05 | TODO | | SPRINT_136_scanner_surface | Docs Guild (src/Scanner/__Libraries/StellaOps.Scanner.Surface.Validation) | src/Scanner/__Libraries/StellaOps.Scanner.Surface.Validation | Document validation extensibility, registration, and customization in scanner-engine guides. | SURFACE-VAL-02 | SCSS0102 | -| SVC-32-001 | TODO | | SPRINT_151_orchestrator_i | Orchestrator Service Guild (src/Orchestrator/StellaOps.Orchestrator) | src/Orchestrator/StellaOps.Orchestrator | | | | | SVC-32-002 | TODO | | SPRINT_152_orchestrator_ii | Orchestrator Service Guild (src/Orchestrator/StellaOps.Orchestrator) | src/Orchestrator/StellaOps.Orchestrator | | | | | SVC-32-003 | TODO | | SPRINT_152_orchestrator_ii | Orchestrator Service Guild (src/Orchestrator/StellaOps.Orchestrator) | src/Orchestrator/StellaOps.Orchestrator | | | | | SVC-32-004 | TODO | | SPRINT_152_orchestrator_ii | Orchestrator Service Guild (src/Orchestrator/StellaOps.Orchestrator) | src/Orchestrator/StellaOps.Orchestrator | | | | @@ -3500,21 +3500,6 @@ | ORCH-34-003 | TODO | | SPRINT_306_docs_tasks_md_vi | Docs Guild (docs) | | — | — | ORGR0102 | | ORCH-34-004 | TODO | | SPRINT_306_docs_tasks_md_vi | Docs Guild (docs) | | — | — | ORGR0102 | | ORCH-34-005 | TODO | | SPRINT_306_docs_tasks_md_vi | Docs Guild (docs) | | — | — | ORGR0102 | -| ORCH-AIRGAP-56-001 | TODO | | SPRINT_151_orchestrator_i | Orchestrator Service + AirGap Policy Guilds | src/Orchestrator/StellaOps.Orchestrator | Enforce job descriptors to declare network intents; reject or flag any external endpoints in sealed mode before scheduling. | Needs ATMI0102 seal guidance | | -| ORCH-AIRGAP-56-002 | TODO | | SPRINT_151_orchestrator_i | Orchestrator Service + AirGap Controller Guild | src/Orchestrator/StellaOps.Orchestrator | Surface sealing status and time staleness in job scheduling decisions; block runs when staleness budgets exceeded. Dependencies: ORCH-AIRGAP-56-001. | Depends on 56-001 policy | | -| ORCH-AIRGAP-57-001 | TODO | | SPRINT_151_orchestrator_i | Orchestrator + Export Center Guilds | src/Orchestrator/StellaOps.Orchestrator | Add job type `mirror.bundle` to orchestrate bundle creation in connected environments with audit + provenance outputs. Dependencies: ORCH-AIRGAP-56-002. | Requires exported policy from OFFK0101 | | -| ORCH-AIRGAP-58-001 | TODO | | SPRINT_151_orchestrator_i | Orchestrator + Offline Kit Guilds | src/Orchestrator/StellaOps.Orchestrator | Capture import/export operations as timeline/evidence entries, ensuring chain-of-custody for mirror + portable evidence jobs. Dependencies: ORCH-AIRGAP-57-001. | Needs 57-001 job profile | | -| ORCH-OAS-61-001 | TODO | | SPRINT_151_orchestrator_i | Orchestrator Service Guild | src/Orchestrator/StellaOps.Orchestrator | Document orchestrator endpoints in per-service OAS with standardized pagination, idempotency, and error envelope examples. | Needs DOOA0103 decisions | | -| ORCH-OAS-61-002 | TODO | | SPRINT_151_orchestrator_i | Orchestrator Service Guild | src/Orchestrator/StellaOps.Orchestrator | Implement `GET /.well-known/openapi` in service and ensure version metadata aligns with runtime build. Dependencies: ORCH-OAS-61-001. | Depends on 61-001 | | -| ORCH-OAS-62-001 | TODO | | SPRINT_151_orchestrator_i | Orchestrator Service + SDK Guild | src/Orchestrator/StellaOps.Orchestrator | Ensure SDK paginators and operations support orchestrator job operations; add SDK smoke tests for schedule/retry APIs. Dependencies: ORCH-OAS-61-002. | Requires generator scaffolding | | -| ORCH-OAS-63-001 | TODO | | SPRINT_151_orchestrator_i | Orchestrator Service + API Governance Guild | src/Orchestrator/StellaOps.Orchestrator | Emit deprecation headers and documentation for legacy orchestrator endpoints; update notifications metadata. Dependencies: ORCH-OAS-62-001. | Waits on 62-001 metadata | | -| ORCH-OBS-50-001 | TODO | | SPRINT_151_orchestrator_i | Orchestrator Service Guild · Observability Guild | src/Orchestrator/StellaOps.Orchestrator | Wire `StellaOps.Telemetry.Core` into orchestrator host, instrument schedulers and control APIs with trace spans, structured logs, and exemplar metrics. Ensure tenant/job metadata recorded for every span/log. | Wait for 043_ORTR0101 taskrunner counters | OROB0101 | -| ORCH-OBS-51-001 | TODO | | SPRINT_151_orchestrator_i | Orchestrator Service Guild · DevOps Guild | src/Orchestrator/StellaOps.Orchestrator | Publish golden-signal metrics (dispatch latency, queue depth, failure rate), define job/tenant SLOs, and emit burn-rate alerts to collector + Notifications. Provide Grafana dashboards + alert rules. Dependencies: ORCH-OBS-50-001. | Needs DevOps alert templates (045_DVDO0103) | OROB0101 | -| ORCH-OBS-52-001 | TODO | | SPRINT_151_orchestrator_i | Orchestrator Service Guild | src/Orchestrator/StellaOps.Orchestrator | Emit `timeline_event` objects for job lifecycle (`job.scheduled`, `job.started`, `job.completed`, `job.failed`) including trace IDs, run IDs, tenant/project, and causal metadata. Add contract tests and Kafka/NATS emitter with retries. Dependencies: ORCH-OBS-51-001. | Depends on instrumentation contract 046_TLTY0101 | OROB0101 | -| ORCH-OBS-53-001 | TODO | | SPRINT_151_orchestrator_i | Orchestrator Service Guild · Evidence Locker Guild | src/Orchestrator/StellaOps.Orchestrator | Generate job capsule inputs for evidence locker (payload digests, worker image, config hash, log manifest) and invoke locker snapshot hooks on completion/failure. Ensure redaction guard enforced. Dependencies: ORCH-OBS-52-001. | Requires Evidence Locker contract (002_ATEL0101) | OROB0101 | -| ORCH-OBS-54-001 | TODO | | SPRINT_151_orchestrator_i | Orchestrator Service Guild · Provenance Guild | src/Orchestrator/StellaOps.Orchestrator | Produce DSSE attestations for orchestrator-scheduled jobs (subject = job capsule) and store references in timeline + evidence locker. Provide verification endpoint `/jobs/{id}/attestation`. Dependencies: ORCH-OBS-53-001. | Blocked by provenance schema (005_ATLN0101) | OROB0101 | -| ORCH-OBS-55-001 | TODO | | SPRINT_151_orchestrator_i | Orchestrator Service Guild · DevOps Guild | src/Orchestrator/StellaOps.Orchestrator | Implement incident mode hooks (sampling overrides, extended retention, additional debug spans) and automatic activation on SLO burn-rate breach. Emit activation/deactivation events to timeline + Notifier. Dependencies: ORCH-OBS-54-001. | Needs #5 resolved for label stability | OROB0101 | -| ORCH-SVC-32-001 | TODO | | SPRINT_151_orchestrator_i | Orchestrator Service Guild | src/Orchestrator/StellaOps.Orchestrator | Bootstrap service project, configuration, and Postgres schema/migrations for `sources`, `runs`, `jobs`, `dag_edges`, `artifacts`, `quotas`, `schedules`. | None | | | ORCH-SVC-32-002 | TODO | | SPRINT_152_orchestrator_ii | Orchestrator Service Guild | src/Orchestrator/StellaOps.Orchestrator | Implement scheduler DAG planner + dependency resolver, job state machine, and critical-path metadata without yet issuing control actions. Dependencies: ORCH-SVC-32-001. | Needs 32-001 DB | | | ORCH-SVC-32-003 | TODO | | SPRINT_152_orchestrator_ii | Orchestrator Service Guild | src/Orchestrator/StellaOps.Orchestrator | Expose read-only REST APIs (sources, runs, jobs, DAG) with OpenAPI, validation, pagination, and tenant scoping. Dependencies: ORCH-SVC-32-002. | Depends on 32-002 | | | ORCH-SVC-32-004 | TODO | | SPRINT_152_orchestrator_ii | Orchestrator Service Guild | src/Orchestrator/StellaOps.Orchestrator | Implement WebSocket/SSE stream for job/run updates, emit structured metrics counters/histograms, and add health probes. Dependencies: ORCH-SVC-32-003. | Needs 32-003 | | @@ -3534,7 +3519,7 @@ | ORCH-SVC-41-101 | TODO | | SPRINT_0153_0001_0003_orchestrator_iii | Orchestrator Service Guild | src/Orchestrator/StellaOps.Orchestrator | Register `pack-run` job type, persist run metadata, integrate logs/artifacts collection, and expose API for Task Runner scheduling. Dependencies: ORCH-SVC-38-101. | Depends on 38-101 | | | ORCH-SVC-42-101 | TODO | | SPRINT_0153_0001_0003_orchestrator_iii | Orchestrator Service Guild | src/Orchestrator/StellaOps.Orchestrator | Stream pack run logs via SSE/WS, add manifest endpoints, enforce quotas, and emit pack run events to Notifications Studio. Dependencies: ORCH-SVC-41-101. | Needs 41-101 | | | ORCH-TEN-48-001 | TODO | | SPRINT_0153_0001_0003_orchestrator_iii | Orchestrator Service Guild | src/Orchestrator/StellaOps.Orchestrator | Include `tenant_id`/`project_id` in job specs, set DB session context before processing, enforce context on all queries, and reject jobs missing tenant metadata. | Needs ORSC0104 job metadata | | -| ORCH-ENG-0001 | DONE | | SPRINT_0323_0001_0001_docs_modules_orchestrator | Module Team | docs/modules/orchestrator | Keep sprint milestone alignment notes synced with `/docs/implplan/SPRINT_151_orchestrator_i.md` onward. | Needs ORSC0104 status updates | | +| ORCH-ENG-0001 | DONE | | SPRINT_0323_0001_0001_docs_modules_orchestrator | Module Team | docs/modules/orchestrator | Keep sprint milestone alignment notes synced with `/docs/implplan/SPRINT_0151_0001_0001_orchestrator_i.md` onward. | Needs ORSC0104 status updates | | | ORCH-OPS-0001 | DONE | | SPRINT_0323_0001_0001_docs_modules_orchestrator | Ops Guild | docs/modules/orchestrator | Review orchestrator runbooks/observability checklists post-demo. | Requires obs/export docs | | | PACKS-42-001 | TODO | | SPRINT_0121_0001_0001_policy_reasoning | Findings Ledger Guild | src/Findings/StellaOps.Findings.Ledger | Provide snapshot/time-travel APIs, digestable exports for pack simulation + CLI offline mode. | Needs ORSC0104 event IDs | | | PACKS-43-001 | DONE | 2025-11-09 | SPRINT_100_identity_signing | Packs Guild · Authority Guild | src/Authority/StellaOps.Authority | Canonical pack bundle + docs for release 43. | AUTH-PACKS-41-001; TASKRUN-42-001; ORCH-SVC-42-101 | | @@ -4097,7 +4082,6 @@ | SURFACE-VAL-03 | TODO | | SPRINT_136_scanner_surface | Scanner Guild, Analyzer Guild (src/Scanner/__Libraries/StellaOps.Scanner.Surface.Validation) | src/Scanner/__Libraries/StellaOps.Scanner.Surface.Validation | Integrate validation pipeline into Scanner analyzers so checks run before processing. | SURFACE-VAL-02 | SCSS0102 | | SURFACE-VAL-04 | TODO | | SPRINT_136_scanner_surface | Scanner Guild, Zastava Guild (src/Scanner/__Libraries/StellaOps.Scanner.Surface.Validation) | src/Scanner/__Libraries/StellaOps.Scanner.Surface.Validation | Expose validation helpers to Zastava and other runtime consumers for preflight checks. | SURFACE-VAL-02 | SCSS0102 | | SURFACE-VAL-05 | TODO | | SPRINT_136_scanner_surface | Docs Guild (src/Scanner/__Libraries/StellaOps.Scanner.Surface.Validation) | src/Scanner/__Libraries/StellaOps.Scanner.Surface.Validation | Document validation extensibility, registration, and customization in scanner-engine guides. | SURFACE-VAL-02 | SCSS0102 | -| SVC-32-001 | TODO | | SPRINT_151_orchestrator_i | Orchestrator Service Guild (src/Orchestrator/StellaOps.Orchestrator) | src/Orchestrator/StellaOps.Orchestrator | | | | | SVC-32-002 | TODO | | SPRINT_152_orchestrator_ii | Orchestrator Service Guild (src/Orchestrator/StellaOps.Orchestrator) | src/Orchestrator/StellaOps.Orchestrator | | | | | SVC-32-003 | TODO | | SPRINT_152_orchestrator_ii | Orchestrator Service Guild (src/Orchestrator/StellaOps.Orchestrator) | src/Orchestrator/StellaOps.Orchestrator | | | | | SVC-32-004 | TODO | | SPRINT_152_orchestrator_ii | Orchestrator Service Guild (src/Orchestrator/StellaOps.Orchestrator) | src/Orchestrator/StellaOps.Orchestrator | | | | @@ -4426,3 +4410,6 @@ | CI RECIPES-DOCS-0001 | DONE (2025-11-25) | 2025-11-25 | SPRINT_0315_0001_0001_docs_modules_ci | Docs Guild (docs/modules/ci) | docs/modules/ci | Update module charter docs (AGENTS/README/architecture/implementation_plan) with determinism + offline posture; sprint normalized. | — | | | CI RECIPES-ENG-0001 | DONE (2025-11-25) | 2025-11-25 | SPRINT_0315_0001_0001_docs_modules_ci | Module Team (docs/modules/ci) | docs/modules/ci | Establish TASKS board and status mirroring rules for CI Recipes contributors. | CI RECIPES-DOCS-0001 | | | CI RECIPES-OPS-0001 | DONE (2025-11-25) | 2025-11-25 | SPRINT_0315_0001_0001_docs_modules_ci | Ops Guild (docs/modules/ci) | docs/modules/ci | Sync outcomes back to sprint + legacy filename stub; ensure references resolve to normalized sprint path. | CI RECIPES-DOCS-0001; CI RECIPES-ENG-0001 | | +| WEB-TEN-47-CONTRACT | DONE (2025-12-01) | 2025-12-01 | SPRINT_0216_0001_0001_web_v | BE-Base Platform Guild | docs/api/gateway/tenant-auth.md | Publish gateway routing + tenant header/ABAC contract (headers, scopes, samples, audit notes). | — | — | +| WEB-VULN-29-LEDGER-DOC | DONE (2025-12-01) | 2025-12-01 | SPRINT_0216_0001_0001_web_v | Findings Ledger Guild · BE-Base Platform Guild | docs/api/gateway/findings-ledger-proxy.md | Capture idempotency + correlation header contract for Findings Ledger proxy and retries/backoff defaults. | — | — | +| WEB-RISK-68-NOTIFY-DOC | DONE (2025-12-01) | 2025-12-01 | SPRINT_0216_0001_0001_web_v | Notifications Guild · BE-Base Platform Guild | docs/api/gateway/notifications-severity.md | Document severity transition event schema (fields, trace metadata) for notifier bus integration. | — | — | diff --git a/docs/modules/cli/contracts/cli-spec-v1.yaml b/docs/modules/cli/contracts/cli-spec-v1.yaml new file mode 100644 index 000000000..6b958a7e4 --- /dev/null +++ b/docs/modules/cli/contracts/cli-spec-v1.yaml @@ -0,0 +1,63 @@ +version: 1 +generated: 2025-12-01T00:00:00Z +compatibility: + policy: "SemVer-like: commands/flags/exitCodes are backwards compatible within major version." + deprecation: + noticeMinimumDays: 90 + channels: + - release-notes + - --compat-report +commands: + - name: advise + subcommands: + - name: summarize + formats: [json, markdown, table] + exitCodes: + 0: success + 2: validation-error + 3: backend-unavailable + - name: explain + formats: [json, markdown, table] + exitCodes: + 0: success + 2: validation-error + 3: backend-unavailable + - name: remediate + flags: + - name: strategy + required: false + values: [minimal, defense-in-depth, fast-track] + exitCodes: + 0: success + 2: validation-error + 3: backend-unavailable + - name: auth + subcommands: + - name: doctor + exitCodes: + 0: success + 4: auth-misconfigured + 5: token-invalid +telemetry: + defaultEnabled: false + envVars: + optIn: STELLAOPS_TELEMETRY=1 + optOut: STELLAOPS_TELEMETRY=0 + persistField: telemetryEnabled +install: + checksumRequired: true + cosignVerifyDefault: true + exitCodes: + 21: checksum-file-missing + 22: checksum-mismatch +buildxPlugin: + imageDigest: "sha256:0000000000000000000000000000000000000000000000000000000000000000" + rollbackCommand: "stella tool buildx rollback --to " +determinism: + locale: "en-US" + timezone: "UTC" + jsonFormatting: "stable-sort-keys" + tableWidth: 80 +tests: + - name: cli-compatibility-regression + description: "Ensure commands/flags/exit codes match spec and telemetry defaults are enforced." diff --git a/docs/modules/cli/contracts/install-integrity.md b/docs/modules/cli/contracts/install-integrity.md new file mode 100644 index 000000000..78b5ef61d --- /dev/null +++ b/docs/modules/cli/contracts/install-integrity.md @@ -0,0 +1,15 @@ +# CLI Install & Update Integrity (v1) — 2025-12-01 + +Requirements +- Checksums: Every release publishes `stellaops-cli-$version.tar.zst` with `SHA256SUMS` + detached `.sig`. +- Verification: `stella install` and `stella self-update` run `cosign verify` by default against pinned public key fingerprint; `--skip-verify` prohibited. +- Offline: Provide `install-offline.sh` that reads from kit directory with checksum + signature checks only; no network fetches. +- Buildx plugin: pin image digest (see `cli-spec-v1.yaml`); rollback command included in help. + +Failure modes +- Missing checksum/signature → command fails with exit code 21 and structured error. +- Digest mismatch → command fails with exit code 22; log path to offending file. + +Artifacts +- Public key fingerprints recorded in `cli-spec-v1.yaml`. +- Example verify script to be bundled in release kit: `scripts/cli/verify-install.sh`. diff --git a/docs/modules/cli/contracts/output-determinism.md b/docs/modules/cli/contracts/output-determinism.md new file mode 100644 index 000000000..979aaf456 --- /dev/null +++ b/docs/modules/cli/contracts/output-determinism.md @@ -0,0 +1,19 @@ +# CLI Output Determinism Policy (v1) — 2025-12-01 + +Scope: `StellaOps.Cli` JSON/table/markdown outputs for advisory verbs and auth doctor. + +Rules +- Time: All timestamps UTC; no local timezone conversion. +- Locale: `en-US`, `InvariantCulture` for number/date formatting. +- Ordering: Sort collections by stable key (id/name) before rendering; JSON keys stable-sorted. +- Width: Table renderer clamps to width 80; no ANSI when `--output json` or non-TTY. +- Seeds: Randomness forbidden; no wall-clock in hashes; use provided deterministic IDs. + +Tests +- Golden fixtures stored under `src/Cli/__Tests/StellaOps.Cli.Tests/Fixtures/output-determinism/`. +- Hash check: two consecutive runs of the same command with identical inputs must produce identical SHA256 of stdout. +- Locale guard: integration test forces `CultureInfo("fr-FR")` and asserts output matches fixtures. + +Failure handling +- Any drift fails CI; diff is printed with unified format. +- Add new fields behind explicit versioned spec entry in `cli-spec-v1.yaml`. diff --git a/docs/modules/scanner/readiness-checkpoints.md b/docs/modules/scanner/readiness-checkpoints.md new file mode 100644 index 000000000..02f75cb8d --- /dev/null +++ b/docs/modules/scanner/readiness-checkpoints.md @@ -0,0 +1,26 @@ +# Scanner Readiness Checkpoints (as of 2025-12-01) + +## Snapshot +- Scope: scanner/surface sprints 0131–0138. +- Status legend: **Green** = shipped and validated; **Amber** = shipped but validation blocked or partial; **Red** = not shipped/blocked upstream. + +## Phase Readiness +| Phase / Sprint | Status | Evidence | Gaps / Actions | +| --- | --- | --- | --- | +| Phase II · Sprint 0131 (Deno/Java/.NET bootstrap) | Amber/Red | Deno runtime capture shipped and tested; Java chain 21-005..011 blocked on Concelier build + CI runner; .NET Lang 11-001 blocked awaiting clean runner; PHP VFS 27-001 blocked pending bootstrap spec. | Need CI slice (DEVOPS-SCANNER-CI-11-001) for Java/.NET; define PHP bootstrap spec and fixtures to unblock 27-001. | +| Phase III · Sprint 0132 (Native + Node foundations) | Amber | Native analyzers 20-001..010 shipped with tests; Node 22-001..005 shipped; Node isolated/CI tests pending due to build graph bloat; .NET Lang 11-002..005 blocked on upstream design 11-001 outputs. | Trim Node test graph or run on clean runner to record pass; unblock .NET analyzer design to proceed with runtime/export/fixtures. | +| Phase IV · Sprint 0133 (Node bundle/source-map) | Amber | Phase22 bundle/native/WASM observation implemented and fixtures hashed; validation tests pending (SDK resolver cancels build on current runner). | Execute `scripts/run-node-phase22-smoke.sh` on clean runner; capture TRX/binlog to close. | +| Phase V · Sprint 0134 (PHP fixtures/runtime/package) | Green | PHP analyzer fixtures, runtime evidence, and packaging shipped; docs updated. | Keep fixture hashes stable; rerun benchmarks when dependencies change. | +| Phase VI · Sprint 0135 (Python container + Ruby VFS/edges) | Green | Python container/zipapp adapters shipped; Ruby VFS/dependency edges/observations/runtime capture packaged; EntryTrace 18-502/503 delivered. | Maintain determinism; re-run EntryTrace suite in CI. | +| Phase VII · Sprint 0136 (EntryTrace surface/CLI) | Green | EntryTrace phase VII tasks 18-504/505/506 completed; CLI/WebService surfaces show best-terminal metadata and confidence. | Keep NDJSON schema stable; rerun worker payload tests in CI. | +| Sprint 0138 (Ruby parity & future analyzers) | Amber/Red | Ruby parity shipped; Mongo package inventory live. PHP pipeline SCANNER-ENG-0010 blocked on composer/autoload design + restore stability; Deno/Dart/Swift analyzer scopes blocked awaiting design; Kubernetes/VM roadmap pending. | Resolve PHP restore/design, produce Deno/Dart/Swift scopes, schedule Zastava/Runtime alignment. | + +## Overall +- Green areas: native analyzers, PHP fixtures/runtime packaging, Ruby analyzer, Python container adapters, EntryTrace phases VI–VII. +- Amber/Red drivers: Java/.NET analyzer chains (CI/design dependencies), PHP pipeline (0138), Node validation on clean runner, design gaps for Deno/Dart/Swift, PHP VFS bootstrap (0131). + +## Recommended Next Actions +1) Secure clean CI slice for Java/.NET and Node Phase22 smoke tests; store binlogs/TRX. +2) Finalise PHP analyzer design (composer/autoload graph) and stabilise restore pipeline to unblock SCANNER-ENG-0010/27-001. +3) Publish Deno/Dart/Swift analyzer scopes with fixtures to unblock 0138 tasks and roadmap alignment with Zastava/Runtime. +4) Re-run EntryTrace and Native suites in CI to lock deterministic hashes before downstream release. diff --git a/docs/modules/signals/SHA256SUMS b/docs/modules/signals/SHA256SUMS new file mode 100644 index 000000000..09d02805f --- /dev/null +++ b/docs/modules/signals/SHA256SUMS @@ -0,0 +1,8 @@ +170892f6a48b0aef6f426ea97a86f6cd4420bc52634f12a92f72e20f0fa12e29 decay/confidence_decay_config.yaml +450675035928e4771cca1b9e5f9e42035dbe10b3de7b66a4077a7b729b2c5b13 unknowns/unknowns_scoring_manifest.json +e33fa0963493252a5ac379a12f820f6b356ea94310afd1db9ad7394e8307000e heuristics/heuristics.catalog.json +6e2e6dfeeb4ae016b7ae881d4653ab79a3babba28a4c6d072266e81c61366e2c heuristics/heuristics.schema.json +99b1a4abc941bea5d4ee6b1dbd6faea37de9c67474bb1bac5f23570a44beff17 heuristics/fixtures/heur.callgraph.hotpath/input.json +851452aeac775e71d7507b70e9b7eb05bc619b37ba656a31b53fb3a504fb3b4a heuristics/fixtures/heur.callgraph.hotpath/expected.json +4d0f69b6064df2014f20673feda7881f815b489ab22e0a219301514b766b29d1 heuristics/fixtures/heur.pkg.sbom_age/input.json +bd9e5b2081e7fe9d947e7a44b0c493efd4d8e97d4c13ec4efd577869559fb1c2 heuristics/fixtures/heur.pkg.sbom_age/expected.json diff --git a/docs/modules/signals/decay/2025-12-01-confidence-decay.md b/docs/modules/signals/decay/2025-12-01-confidence-decay.md new file mode 100644 index 000000000..bf864a5d0 --- /dev/null +++ b/docs/modules/signals/decay/2025-12-01-confidence-decay.md @@ -0,0 +1,79 @@ +# Confidence Decay Controls · Signals Runtime + +**Compiled:** 2025-12-01 (UTC) +**Scope:** Close U1–U10 gaps from `docs/product-advisories/31-Nov-2025 FINDINGS.md` for confidence decay of unknowns/signals. +**Status:** Draft for review on 2025-12-03; to be signed (DSSE) after sign-off. + +## Decisions (U1–U10) +- **τ governance (U1):** All τ values live in `confidence_decay_config.yaml`, change-controlled via DSSE-signed PRs; allowable τ range 1–90 days. Changes require dual approval (Signals + Policy), recorded in history. +- **Floor / freeze (U2):** `confidence_floor` per severity; `is_confidence_frozen=true` when SLA-bound or manual pin. Floors: Critical 0.60, High 0.45, Medium 0.30, Low 0.20. Freeze auto-expires at `freeze_until`. +- **Weighted signals (U3):** Signal taxonomy with weights: exploit=1.0, customer_incident=0.9, threat_intel=0.7, code_change=0.4, artifact_refresh=0.3, metadata_touch=0.1. `last_signal_weighted_at` uses max(weighted timestamp). +- **Time source / drift (U4):** All timestamps in UTC; decay uses monotonic clock fallback; reject events >5 minutes in the future or >30 days backdated, log corrections. +- **Deterministic recompute (U5):** Nightly job at 03:00 UTC recomputes decay for all items; emits `decay_snapshot_YYYY-MM-DD.ndjson` with SHA256 and checksum record. On-read recompute only if snapshot is older than 24h. +- **SLA coupling (U6):** Items with active SLA clamp to `sla_floor` (0.60 Critical, 0.50 High) until SLA met. SLA flag and floor are emitted in API. +- **Uncertainty linkage (U7):** Confidence is capped by `(1 - uncertainty_score)`; if uncertainty_score ≥0.4, band forced to "under_review" and alerts fire. +- **Backfill & migration (U8):** Initial migration seeds `last_signal_at` from latest activity; default τ from entity profile; dry-run impact report required; backfill script outputs before/after bands. +- **API/UX surfacing (U9):** New fields: `confidence`, `confidence_band` (critical/high/medium/low/under_review), `tau_days`, `is_frozen`, `confidence_floor`, `uncertainty_score`, `last_signal_weighted_at`. Sort default: `priority * confidence`. +- **Observability & alerts (U10):** Counters/gauges: `confidence_recalc_latency`, `items_below_floor`, `signals_weighted_by_type{type}`, `decay_snapshots_age_hours`, `uncertainty_forced_under_review`. Alerts on missing nightly snapshot, decay drift >1 band, or SLA items below floor. + +## Reference Config (draft) +```yaml +version: 1 +updated_at: 2025-12-01T00:00:00Z +entities: + vulnerability: + tau_days: 21 + tau_min: 7 + tau_max: 90 + confidence_floor: {critical: 0.60, high: 0.45, medium: 0.30, low: 0.20} + sla_floor: {critical: 0.60, high: 0.50} + freeze_default_days: 30 + incident: + tau_days: 14 + tau_min: 3 + tau_max: 60 +signals_taxonomy: + exploit: 1.0 + customer_incident: 0.9 + threat_intel: 0.7 + code_change: 0.4 + artifact_refresh: 0.3 + metadata_touch: 0.1 +time: + reject_future_minutes: 5 + reject_backdated_days: 30 +recompute: + schedule_utc: "03:00" + snapshot_retention_days: 30 +observability: + alerts: + missing_snapshot_hours: 26 + sla_floor_breach: true + uncertainty_band_force: 0.4 +signing: + predicate: stella.ops/confidenceDecayConfig@v1 + dsse_required: true +``` + +## Operational Rules +- Config changes must produce a new DSSE envelope and update the checksum in the nightly snapshot header. +- Nightly job writes `decay_snapshot_.ndjson` (sorted by `item_id`) plus `SHA256SUMS`; both stored in Evidence Locker. +- Any on-read recompute must emit an audit log with reasons (stale snapshot or forced recalculation). + +## Migration Playbook +1) Run dry-run backfill: compute bands with proposed config; write `decay_backfill_diff.ndjson` (before/after bands, delta) and checksum. +2) Get dual approval; sign `confidence_decay_config.yaml` with DSSE predicate above. +3) Apply config, execute full recompute, publish snapshot + checksums, update observability dashboard baselines. + +## API Notes +- Add fields to Signals API and CLI responses; ensure canonical serialization (sorted keys, UTC timestamps, fixed decimals 3dp) to avoid hash drift. +- Bands map: `>=0.75 critical`, `>=0.55 high`, `>=0.35 medium`, `>=0.20 low`, else `under_review`. + +## Evidence & Storage +- Store config DSSE, snapshots, and backfill reports in Evidence Locker with retention class `signals-decay-config`. +- For offline kits, include latest config DSSE + last 3 snapshots and checksums. + +## Open Items for Review (12-03) +- Confirm weights for threat_intel vs exploit; adjust if customer data suggests different ordering. +- Confirm `under_review` threshold (currently uncertainty ≥0.4). +- Align with Policy on SLA floors for High severity (0.50 proposed). diff --git a/docs/modules/signals/decay/confidence_decay_config.yaml b/docs/modules/signals/decay/confidence_decay_config.yaml new file mode 100644 index 000000000..bea69e323 --- /dev/null +++ b/docs/modules/signals/decay/confidence_decay_config.yaml @@ -0,0 +1,41 @@ +version: 1 +updated_at: 2025-12-01T00:00:00Z +entities: + vulnerability: + tau_days: 21 + tau_min: 7 + tau_max: 90 + confidence_floor: + critical: 0.60 + high: 0.45 + medium: 0.30 + low: 0.20 + sla_floor: + critical: 0.60 + high: 0.50 + freeze_default_days: 30 + incident: + tau_days: 14 + tau_min: 3 + tau_max: 60 +signals_taxonomy: + exploit: 1.0 + customer_incident: 0.9 + threat_intel: 0.7 + code_change: 0.4 + artifact_refresh: 0.3 + metadata_touch: 0.1 +time: + reject_future_minutes: 5 + reject_backdated_days: 30 +recompute: + schedule_utc: "03:00" + snapshot_retention_days: 30 +observability: + alerts: + missing_snapshot_hours: 26 + sla_floor_breach: true + uncertainty_band_force: 0.4 +signing: + predicate: stella.ops/confidenceDecayConfig@v1 + dsse_required: true diff --git a/docs/modules/signals/heuristics/2025-12-01-heuristic-catalog.md b/docs/modules/signals/heuristics/2025-12-01-heuristic-catalog.md new file mode 100644 index 000000000..0273e2209 --- /dev/null +++ b/docs/modules/signals/heuristics/2025-12-01-heuristic-catalog.md @@ -0,0 +1,66 @@ +# Signals Heuristic Catalog · Deterministic Scoring + +**Compiled:** 2025-12-01 (UTC) +**Scope:** Close UT1–UT10 gaps from `docs/product-advisories/31-Nov-2025 FINDINGS.md` by publishing a signed heuristic catalog and golden outputs. +**Status:** Draft; target publish 2025-12-05 with DSSE signature. + +## Decisions (UT1–UT10) +- **Signed catalog/schema (UT1):** Catalog lives at `heuristics.catalog.json` with schema versioned `heuristics.schema.json`; DSSE predicate `stella.ops/heuristicCatalog@v1` required. +- **Deterministic scoring formula (UT2):** Each heuristic defines `inputs`, `weights`, and `normalization`; scoring outputs canonicalized (sorted keys, fixed 3dp). Engine must be pure/deterministic; randomization forbidden. +- **Quality bands (UT3):** Bands: `gold` (precision≥0.9, recall≥0.8), `silver` (≥0.8/0.7), `bronze` (≥0.7/0.6). Bands recorded in catalog and enforced in admission checks. +- **Waiver policy with DSSE (UT4):** Waivers require DSSE envelope `stella.ops/heuristicWaiver@v1`, include reason, scope, expiry; dual approval (Signals+Policy). +- **SLA coupling (UT5):** SLA-tagged items cannot use heuristics below `silver`; SLA enforcement checks band before accepting results. +- **Offline kit packaging (UT6):** Catalog, schema, golden fixtures, and DSSE envelopes bundled in offline kits with `SHA256SUMS`. +- **Observability/alerts (UT7):** Metrics: `heuristics_eval_latency`, `heuristics_band_usage`, `heuristics_waivers_total`, `heuristics_score_drift`. Alerts when drift >1 band vs golden fixtures or when waivers exceed threshold. +- **Backfill plan (UT8):** Backfill job recomputes heuristic scores with current catalog; outputs `heuristics_backfill.ndjson` + checksum; mismatches raise alerts. +- **Explainability fields/exports (UT9):** Outputs must include `explanation` block: contributing signals with weights, normalized scores, and rule IDs. CLI/API export supports `--explain` and deterministic ordering. +- **Fixtures with golden outputs (UT10):** Golden set per heuristic under `fixtures//` containing `input.json`, `expected.json`, and `README`; used in CI for determinism. + +## Catalog Structure (draft) +```json +{ + "version": "1.0.0", + "updatedAt": "2025-12-01T00:00:00Z", + "heuristics": [ + { + "id": "heur.callgraph.hotpath", + "band": "gold", + "inputs": ["callgraph.depth", "callgraph.betweenness"], + "formula": "0.6*depth_norm + 0.4*betweenness_norm", + "normalization": "minmax", + "evidence": ["signals/callgraph"] + }, + { + "id": "heur.pkg.sbom_age", + "band": "silver", + "inputs": ["sbom.age_days", "release_channel"], + "formula": "if release_channel=='stable' then age_norm else 0.8*age_norm", + "normalization": "log1p" + } + ], + "signing": { + "predicate": "stella.ops/heuristicCatalog@v1", + "dsse_required": true + } +} +``` + +## Golden Fixtures (layout) +- `docs/modules/signals/heuristics/fixtures/heur.callgraph.hotpath/{input.json,expected.json}` +- `docs/modules/signals/heuristics/fixtures/heur.pkg.sbom_age/{input.json,expected.json}` +- `expected.json` must be canonicalized (sorted keys, fixed 3dp) and include explanation block. + +## CI / Determinism Checks +- Lint: reject heuristics without band or DSSE signature. +- Determinism test: run golden fixtures; fail if output hash differs. +- Drift alert: compare live scores vs golden baselines; trigger if >1 band difference for same input hash. + +## Publish Steps (12-05) +1) Finalize catalog + schema; canonicalize via JCS; sign DSSE envelope. +2) Populate fixtures and compute `SHA256SUMS` for all files. +3) Update sprint doc status and Evidence Locker with catalog + fixtures + signatures. +4) Enable observability dashboards and waiver policy checks. + +## Open Items +- Confirm minimum band allowed for non-SLA items (proposal: bronze acceptable, but not for SLA). +- Decide on additional heuristics for runtime traces vs SBOM freshness. diff --git a/docs/modules/signals/heuristics/fixtures/heur.callgraph.hotpath/expected.json b/docs/modules/signals/heuristics/fixtures/heur.callgraph.hotpath/expected.json new file mode 100644 index 000000000..afae63c4e --- /dev/null +++ b/docs/modules/signals/heuristics/fixtures/heur.callgraph.hotpath/expected.json @@ -0,0 +1,16 @@ +{ + "heuristicId": "heur.callgraph.hotpath", + "score": 0.472, + "band": "gold", + "explanation": { + "inputs": { + "callgraph.depth": 7, + "callgraph.betweenness": 0.12 + }, + "normalized": { + "depth_norm": 0.700, + "betweenness_norm": 0.120 + }, + "formula": "0.6*depth_norm + 0.4*betweenness_norm" + } +} diff --git a/docs/modules/signals/heuristics/fixtures/heur.callgraph.hotpath/input.json b/docs/modules/signals/heuristics/fixtures/heur.callgraph.hotpath/input.json new file mode 100644 index 000000000..aa2ba827f --- /dev/null +++ b/docs/modules/signals/heuristics/fixtures/heur.callgraph.hotpath/input.json @@ -0,0 +1,4 @@ +{ + "callgraph.depth": 7, + "callgraph.betweenness": 0.12 +} diff --git a/docs/modules/signals/heuristics/fixtures/heur.pkg.sbom_age/expected.json b/docs/modules/signals/heuristics/fixtures/heur.pkg.sbom_age/expected.json new file mode 100644 index 000000000..3eed48d58 --- /dev/null +++ b/docs/modules/signals/heuristics/fixtures/heur.pkg.sbom_age/expected.json @@ -0,0 +1,16 @@ +{ + "heuristicId": "heur.pkg.sbom_age", + "score": 0.450, + "band": "silver", + "explanation": { + "inputs": { + "sbom.age_days": 45, + "release_channel": "stable" + }, + "normalized": { + "age_norm": 0.450, + "release_channel": "stable" + }, + "formula": "case release_channel=='stable' then age_norm else 0.8*age_norm" + } +} diff --git a/docs/modules/signals/heuristics/fixtures/heur.pkg.sbom_age/input.json b/docs/modules/signals/heuristics/fixtures/heur.pkg.sbom_age/input.json new file mode 100644 index 000000000..d772c18f6 --- /dev/null +++ b/docs/modules/signals/heuristics/fixtures/heur.pkg.sbom_age/input.json @@ -0,0 +1,4 @@ +{ + "sbom.age_days": 45, + "release_channel": "stable" +} diff --git a/docs/modules/signals/heuristics/heuristics.catalog.json b/docs/modules/signals/heuristics/heuristics.catalog.json new file mode 100644 index 000000000..0e1e1497d --- /dev/null +++ b/docs/modules/signals/heuristics/heuristics.catalog.json @@ -0,0 +1,26 @@ +{ + "version": "1.0.0", + "updatedAt": "2025-12-01T00:00:00Z", + "heuristics": [ + { + "id": "heur.callgraph.hotpath", + "band": "gold", + "inputs": ["callgraph.depth", "callgraph.betweenness"], + "formula": "0.6*depth_norm + 0.4*betweenness_norm", + "normalization": "minmax", + "evidence": ["signals/callgraph"] + }, + { + "id": "heur.pkg.sbom_age", + "band": "silver", + "inputs": ["sbom.age_days", "release_channel"], + "formula": "case release_channel=='stable' then age_norm else 0.8*age_norm", + "normalization": "log1p", + "evidence": ["sbom/age"] + } + ], + "signing": { + "predicate": "stella.ops/heuristicCatalog@v1", + "dsse_required": true + } +} diff --git a/docs/modules/signals/heuristics/heuristics.schema.json b/docs/modules/signals/heuristics/heuristics.schema.json new file mode 100644 index 000000000..cb9a8588b --- /dev/null +++ b/docs/modules/signals/heuristics/heuristics.schema.json @@ -0,0 +1,32 @@ +{ + "$id": "https://stella-ops.org/schemas/heuristics.schema.json", + "type": "object", + "required": ["version", "updatedAt", "heuristics", "signing"], + "properties": { + "version": {"type": "string"}, + "updatedAt": {"type": "string", "format": "date-time"}, + "heuristics": { + "type": "array", + "items": { + "type": "object", + "required": ["id", "band", "inputs", "formula", "normalization"], + "properties": { + "id": {"type": "string"}, + "band": {"enum": ["gold", "silver", "bronze"]}, + "inputs": {"type": "array", "items": {"type": "string"}}, + "formula": {"type": "string"}, + "normalization": {"type": "string"}, + "evidence": {"type": "array", "items": {"type": "string"}} + } + } + }, + "signing": { + "type": "object", + "required": ["predicate", "dsse_required"], + "properties": { + "predicate": {"type": "string"}, + "dsse_required": {"type": "boolean"} + } + } + } +} diff --git a/docs/modules/signals/unknowns/2025-12-01-unknowns-registry.md b/docs/modules/signals/unknowns/2025-12-01-unknowns-registry.md new file mode 100644 index 000000000..1c83ce726 --- /dev/null +++ b/docs/modules/signals/unknowns/2025-12-01-unknowns-registry.md @@ -0,0 +1,86 @@ +# Unknowns Registry & Scoring Manifest + +**Compiled:** 2025-12-01 (UTC) +**Scope:** Close UN1–UN10 gaps from `docs/product-advisories/31-Nov-2025 FINDINGS.md` for Unknowns Registry. +**Status:** Draft; review 2025-12-04; DSSE signing required before adoption. + +## Decisions (UN1–UN10) +- **Canonical schema/enums (UN1):** Unknown types: `vulnerability`, `asset`, `signal`, `evidence-gap`, `policy-gap`. Status enums: `new`, `triaging`, `under_review`, `validated`, `dismissed`. Severity: `critical/high/medium/low/none`. +- **Deterministic scoring manifest (UN2):** Manifest `unknowns_scoring_manifest.json` defines inputs, weights, and canonical serialization (JCS, sorted keys, UTC timestamps, fixed 3dp). Hash used as `scoringManifestHash` in API/DSSE. +- **Decay policy catalog (UN3):** Unknowns reuse `confidence_decay_config` but may override τ by type (see table). Overrides stored in manifest; DSSE-signed. +- **Evidence/provenance capture (UN4):** Each unknown must reference Evidence Locker URIs with DSSE envelopes; minimal evidence: `{source, observedAt, evidenceType, hash}`. Provenance includes tool identity and policy hash. +- **SBOM/VEX linkage (UN5):** Unknown links: `sbomDigest`, `vexDecisionId` (if present), `reachabilityGraphHash`. If absent, status forced to `under_review`. +- **SLA / suppression rules (UN6):** SLA timers mirror severity; suppression requires dual sign-off and DSSE note with expiry. Suppressed items emit `suppression_reason`, `expiresAt`. +- **API/CLI contracts (UN7):** New endpoints `/unknowns` support filter by `status`, `type`, `confidence_band`, `uncertainty_score`, `suppressed`. CLI mirrors with `--format ndjson` and `--include-provenance` flags. Output sorted deterministically by `createdAt, id`. +- **Observability/reporting (UN8):** Metrics: `unknowns_total{type,status}`, `unknowns_suppressed_total`, `unknowns_without_sbom`, `unknowns_without_vex`, `unknowns_confidence_band`, `unknowns_manifest_hash_mismatch`. Alerts on manifest hash mismatch, >1% unknowns missing SBOM/VEX, or suppression expiry. +- **Offline bundle inclusion (UN9):** Include latest manifest, schema, and NDJSON export in offline kit; bundle hashes recorded in kit manifest; verify against DSSE signatures. +- **Migration/backfill (UN10):** Backfill script `backfill_unknowns_v1` seeds `scoringManifestHash`, `sbomDigest`, and `vexDecisionId` from existing records; produces `unknowns_backfill_report.ndjson` with before/after status/bands and checksum. + +## Schema (draft) +```json +{ + "$id": "https://stella-ops.org/schemas/unknown.json", + "type": "object", + "required": ["id", "type", "status", "severity", "createdAt", "confidence", "confidenceBand"], + "properties": { + "id": {"type": "string"}, + "type": {"enum": ["vulnerability", "asset", "signal", "evidence-gap", "policy-gap"]}, + "status": {"enum": ["new", "triaging", "under_review", "validated", "dismissed"]}, + "severity": {"enum": ["critical", "high", "medium", "low", "none"]}, + "confidence": {"type": "number"}, + "confidenceBand": {"enum": ["critical", "high", "medium", "low", "under_review"]}, + "uncertaintyScore": {"type": "number", "minimum": 0, "maximum": 1}, + "tauDays": {"type": "integer"}, + "sbomDigest": {"type": "string"}, + "vexDecisionId": {"type": "string"}, + "reachabilityGraphHash": {"type": "string"}, + "scoringManifestHash": {"type": "string"}, + "suppression": { + "type": "object", + "properties": { + "isSuppressed": {"type": "boolean"}, + "reason": {"type": "string"}, + "expiresAt": {"type": "string", "format": "date-time"}, + "signedBy": {"type": "string"} + } + }, + "evidence": {"type": "array", "items": {"$ref": "#/definitions/evidenceRef"}}, + "createdAt": {"type": "string", "format": "date-time"}, + "updatedAt": {"type": "string", "format": "date-time"} + }, + "definitions": { + "evidenceRef": { + "type": "object", + "required": ["uri", "hash", "observedAt", "evidenceType"], + "properties": { + "uri": {"type": "string"}, + "hash": {"type": "string"}, + "observedAt": {"type": "string", "format": "date-time"}, + "evidenceType": {"type": "string"}, + "provenance": {"type": "string"} + } + } + } +} +``` + +## Scoring Manifest (summary) +- Inputs: severity weight, decay factor (τ), uncertainty cap, SLA floor, suppression flag, weighted signals timestamp. +- Formula (deterministic): `confidence = max(floor, min((exp(-Δt/τ) * weight_signal), 1 - uncertainty))`, then clamp by SLA floor if SLA active. +- Canonicalization: JSON Canonicalization Scheme (JCS); decimals fixed 3dp; UTC ISO-8601 timestamps. +- Hash: SHA256 over canonical manifest; published as `scoringManifestHash` and signed via DSSE `stella.ops/unknownsScoringManifest@v1`. + +## Offline & Evidence +- Bundle schema, manifest, and latest NDJSON export with `SHA256SUMS` and DSSE envelope for each artifact. +- Evidence Locker class: `signals-unknowns-manifest` (30d retention minimum). + +## Migration Checklist (UN10) +1) Generate `unknowns_scoring_manifest.json` and sign (DSSE). +2) Run `backfill_unknowns_v1 --manifest `; produce report and checksums. +3) Update API/CLI serializers to include new fields and canonical ordering. +4) Enable observability dashboards and alerts; verify thresholds. + +## Review Questions (12-04) +- Confirm suppression expiry default (proposal: 30 days). +- Validate `under_review` trigger when SBOM/VEX missing—keep or allow grace period? +- Align SLA floors with decay config (Critical 0.60, High 0.50). diff --git a/docs/modules/signals/unknowns/unknowns_scoring_manifest.json b/docs/modules/signals/unknowns/unknowns_scoring_manifest.json new file mode 100644 index 000000000..a59587b21 --- /dev/null +++ b/docs/modules/signals/unknowns/unknowns_scoring_manifest.json @@ -0,0 +1,48 @@ +{ + "version": "1.0.0", + "updatedAt": "2025-12-01T00:00:00Z", + "canonicalization": { + "scheme": "JCS", + "decimals": 3, + "timezone": "UTC" + }, + "inputs": { + "severityWeight": { + "critical": 1.0, + "high": 0.8, + "medium": 0.6, + "low": 0.4, + "none": 0.2 + }, + "uncertaintyCap": 1.0, + "slaFloor": { + "critical": 0.60, + "high": 0.50 + }, + "tauOverrides": { + "vulnerability": 21, + "asset": 28, + "signal": 14, + "evidence-gap": 21, + "policy-gap": 30 + } + }, + "formula": "confidence = max(floor, min(exp(-deltaDays/tau) * severityWeight, 1 - uncertainty))", + "floor": 0.20, + "uncertaintyThreshold": 0.4, + "bands": { + "critical": 0.75, + "high": 0.55, + "medium": 0.35, + "low": 0.20, + "under_review": 0.0 + }, + "hash": { + "algorithm": "SHA-256", + "value": "TO_BE_SIGNED" + }, + "signing": { + "predicate": "stella.ops/unknownsScoringManifest@v1", + "dsse_required": true + } +} diff --git a/docs/modules/telemetry/AGENTS.md b/docs/modules/telemetry/AGENTS.md index 10b88e99b..98bf8a8ea 100644 --- a/docs/modules/telemetry/AGENTS.md +++ b/docs/modules/telemetry/AGENTS.md @@ -1,7 +1,7 @@ # Telemetry agent guide ## Mission -Telemetry module captures deployment and operations guidance for the shared observability stack (collectors, storage, dashboards). +Telemetry module captures deployment and operations guidance for the shared observability stack (collectors, storage, dashboards). Telemetry outputs must be deterministic, sealed-mode safe, and verifiable via DSSE/offline bundles. ## Advisory Handling - Any new/updated advisory triggers immediate doc + sprint updates; no approval. @@ -16,6 +16,8 @@ Telemetry module captures deployment and operations guidance for the shared obse - [Implementation plan](./implementation_plan.md) - [Task board](./TASKS.md) - [Observability runbook](./operations/observability.md) (offline import friendly) +- [Telemetry gaps remediation](./contracts/telemetry-gaps-remediation.md) +- Schemas: `./schemas/telemetry-config.schema.json`, `./schemas/telemetry-bundle.schema.json` ## How to get started 1. Open sprint file `/docs/implplan/SPRINT_*.md` and locate the stories referencing this module. diff --git a/docs/modules/telemetry/contracts/telemetry-gaps-remediation.md b/docs/modules/telemetry/contracts/telemetry-gaps-remediation.md new file mode 100644 index 000000000..c3ce0f064 --- /dev/null +++ b/docs/modules/telemetry/contracts/telemetry-gaps-remediation.md @@ -0,0 +1,29 @@ +# Telemetry Gap Remediation (TO1–TO10) — v1 · 2025-12-01 + +Source: `docs/product-advisories/31-Nov-2025 FINDINGS.md` (Telemetry gaps TO1–TO10). +Scope: telemetry core (collectors/SDK defaults/bundles) across services; applicable to default/forensic/airgap profiles. + +## Decisions (mapped to gaps) +- **TO1 Canonical schemas & hashing**: Published versioned schemas + - `telemetry-config.schema.json` for collector/SDK profile configs (signed, canonical JSON, stable ordering) + - `telemetry-bundle.schema.json` for offline bundle manifests + - Hash recipe: SHA-256 over normalized (UTF-8, LF, sorted keys) JSON; test vectors to follow. +- **TO2 Provenance & DSSE**: Bundles and profile activations must include DSSE envelope (`*.dsse.json`) with predicate fields: profileHash, collectorVersion, exporters, redactionPolicyUri, cryptoProfile. +- **TO3 Determinism & sampling stability**: Sampling policies must declare deterministic seed, ordered rules, and backpressure policy. Logs/traces ordered by (timestamp, traceId). Multi-run hash check recommended in CI. +- **TO4 Sealed mode / egress guards**: Sealed mode blocks all non-loopback exporters unless explicitly allowlisted; DNS pinning required; failure is fail-closed. Seal status recorded as DSSE event. +- **TO5 Redaction policy & PII tests**: Redaction catalog/allowlist required; bundle must include `redaction-manifest.json` listing rules applied and violations=0. CI must run PII/secret test suite before export. +- **TO6 Tenant isolation & quotas**: OTLP signals include `tenant.id` and `project.id`; collector routes by tenant pipeline; per-tenant quotas/limits enforced with counters and alerts. +- **TO7 Forensic triggers governance**: Forensic mode requires dual approval, DSSE activation record, expiry timestamp, and auto-rollback; alert if forensic mode active > configured window. +- **TO8 Offline bundle schema & verify**: Bundles must follow `telemetry-bundle.schema.json`, created with deterministic tar flags, include hash manifest + DSSE + RFC3161 time-anchor; verifier script provided (`ops/devops/telemetry/verify-telemetry-bundle.sh`). +- **TO9 Observability of observability**: Add SLOs + alerts for collector/exporter health, queue backpressure, bundle success rate; scheduled self-test emits DSSE result. +- **TO10 CLI/pack contracts**: CLI/pack contract tracked in `cli-spec-v1.yaml`; telemetry exports must respect exit codes and checksum policy (reuse 21/22 for checksum missing/mismatch). + +## Artifacts +- Schemas: `docs/modules/telemetry/schemas/telemetry-config.schema.json`, `telemetry-bundle.schema.json`. +- Hash recipe: in-line within schemas (canonical JSON, SHA-256). +- Verify script: `ops/devops/telemetry/verify-telemetry-bundle.sh`. + +## Adoption notes +- Profile and bundle producers must validate against schemas and sign DSSE envelopes before distribution. +- Air-gap/forensic profiles MUST set sealed mode and include redaction manifest. +- CI should add a multi-run hash test for telemetry exporter output and fail on drift. diff --git a/docs/modules/telemetry/schemas/telemetry-bundle.schema.json b/docs/modules/telemetry/schemas/telemetry-bundle.schema.json new file mode 100644 index 000000000..1ba64eef5 --- /dev/null +++ b/docs/modules/telemetry/schemas/telemetry-bundle.schema.json @@ -0,0 +1,46 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "StellaOps Telemetry Bundle Manifest", + "version": "1.0.0", + "type": "object", + "required": ["schemaVersion", "bundleId", "createdAt", "artifacts", "manifestHashAlgorithm", "timeAnchor"], + "properties": { + "schemaVersion": { "type": "string", "const": "1.0.0" }, + "bundleId": { "type": "string", "format": "uuid" }, + "createdAt": { "type": "string", "format": "date-time" }, + "profileHash": { "type": "string", "pattern": "^[0-9a-f]{64}$" }, + "collectorVersion": { "type": "string" }, + "sealedMode": { "type": "boolean" }, + "redactionManifest": { "type": "string" }, + "manifestHashAlgorithm": { "type": "string", "enum": ["sha256"] }, + "timeAnchor": { + "type": "object", + "required": ["type", "value"], + "properties": { + "type": { "type": "string", "enum": ["rfc3161", "roughtime"] }, + "value": { "type": "string" } + } + }, + "artifacts": { + "type": "array", + "items": { + "type": "object", + "required": ["path", "sha256", "mediaType"], + "properties": { + "path": { "type": "string" }, + "sha256": { "type": "string", "pattern": "^[0-9a-f]{64}$" }, + "mediaType": { "type": "string" }, + "size": { "type": "integer", "minimum": 0 } + } + } + }, + "dsseEnvelope": { + "type": "object", + "required": ["hash"], + "properties": { + "hash": { "type": "string", "pattern": "^[0-9a-f]{64}$" }, + "location": { "type": "string" } + } + } + } +} diff --git a/docs/modules/telemetry/schemas/telemetry-config.schema.json b/docs/modules/telemetry/schemas/telemetry-config.schema.json new file mode 100644 index 000000000..593b29e06 --- /dev/null +++ b/docs/modules/telemetry/schemas/telemetry-config.schema.json @@ -0,0 +1,75 @@ +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "StellaOps Telemetry Config", + "version": "1.0.0", + "type": "object", + "required": ["schemaVersion", "profiles"], + "properties": { + "schemaVersion": { "type": "string", "const": "1.0.0" }, + "hashAlgorithm": { "type": "string", "enum": ["sha256"] }, + "profiles": { + "type": "array", + "items": { + "type": "object", + "required": ["name", "collectorVersion", "exporters", "redactionPolicyUri", "sampling"], + "properties": { + "name": { "type": "string" }, + "description": { "type": "string" }, + "collectorVersion": { "type": "string" }, + "cryptoProfile": { "type": "string" }, + "sealedMode": { "type": "boolean" }, + "allowlistedEndpoints": { + "type": "array", + "items": { "type": "string", "format": "uri" } + }, + "exporters": { + "type": "array", + "items": { + "type": "object", + "required": ["type", "endpoint"], + "properties": { + "type": { "type": "string", "enum": ["otlp", "file", "stdout", "null"] }, + "endpoint": { "type": "string" }, + "protocol": { "type": "string", "enum": ["grpc", "http"] }, + "compression": { "type": "string", "enum": ["none", "gzip"] }, + "enabled": { "type": "boolean", "default": true } + } + } + }, + "redactionPolicyUri": { "type": "string", "format": "uri" }, + "sampling": { + "type": "object", + "required": ["strategy", "seed"], + "properties": { + "strategy": { "type": "string", "enum": ["always_on", "always_off", "traceidratio", "tail"] }, + "seed": { "type": "string", "pattern": "^[0-9a-fA-F]{16}$" }, + "rules": { + "type": "array", + "items": { + "type": "object", + "required": ["match", "priority"], + "properties": { + "match": { "type": "string" }, + "priority": { "type": "integer", "minimum": 0 }, + "sampleRate": { "type": "number", "minimum": 0, "maximum": 1 } + } + } + } + } + }, + "tenantRouting": { + "type": "object", + "required": ["attribute"], + "properties": { + "attribute": { "type": "string", "const": "tenant.id" }, + "quotasPerTenant": { + "type": "object", + "additionalProperties": { "type": "integer", "minimum": 0 } + } + } + } + } + } + } + } +} diff --git a/docs/product-advisories/01-Dec-2025 - DSSE-Signed Offline Scanner Updates.md b/docs/product-advisories/01-Dec-2025 - DSSE-Signed Offline Scanner Updates.md new file mode 100644 index 000000000..43f853779 --- /dev/null +++ b/docs/product-advisories/01-Dec-2025 - DSSE-Signed Offline Scanner Updates.md @@ -0,0 +1,224 @@ +# DSSE‑Signed Offline Scanner Updates — Developer Guidelines + +> **Date:** 2025-12-01 +> **Status:** Advisory draft (from user-provided guidance) +> **Scope:** Offline vulnerability DB bundles (Scanner), DSSE+Rekor v2 verification, offline kit alignment, activation rules, ops playbook. + +Here’s a tight, practical pattern to make your scanner’s vuln‑DB updates rock‑solid even when feeds hiccup: + +## Offline, verifiable update bundles (DSSE + Rekor v2) + +**Idea:** distribute DB updates as offline tarballs. Each tarball ships with: + +* a **DSSE‑signed** statement (e.g., in‑toto style) over the bundle hash +* a **Rekor v2 receipt** proving the signature/statement was logged +* a small **manifest.json** (version, created_at, content hashes) + +**Startup flow (happy path):** + +1. Load latest tarball from your local `updates/` cache. +2. Verify DSSE signature against your trusted public keys. +3. Verify Rekor v2 receipt (inclusion proof) matches the DSSE payload hash. +4. If both pass, unpack/activate; record the bundle’s **trust_id** (e.g., statement digest). +5. If anything fails, **keep using the last good bundle**. No service disruption. + +**Why this helps** + +* **Air‑gap friendly:** no live network needed at activation time. +* **Tamper‑evident:** DSSE + Rekor receipt proves provenance and transparency. +* **Operational stability:** feed outages become non‑events—scanner just keeps the last good state. + +--- + +## File layout inside each bundle + +``` +/bundle-2025-11-29/ + manifest.json # { version, created_at, entries[], sha256s } + payload.tar.zst # the actual DB/indices + payload.tar.zst.sha256 + statement.dsse.json # DSSE-wrapped statement over payload hash + rekor-receipt.json # Rekor v2 inclusion/verification material +``` + +--- + +## Acceptance/Activation rules + +* **Trust root:** pin one (or more) publisher public keys; rotate via separate, out‑of‑band process. +* **Monotonicity:** only activate if `manifest.version > current.version` (or if trust policy explicitly allows replay for rollback testing). +* **Atomic switch:** unpack to `db/staging/`, validate, then symlink‑flip to `db/active/`. +* **Quarantine on failure:** move bad bundles to `updates/quarantine/` with a reason code. + +--- + +## Minimal .NET 10 verifier sketch (C#) + +```csharp +public sealed record BundlePaths(string Dir) { + public string Manifest => Path.Combine(Dir, "manifest.json"); + public string Payload => Path.Combine(Dir, "payload.tar.zst"); + public string Dsse => Path.Combine(Dir, "statement.dsse.json"); + public string Receipt => Path.Combine(Dir, "rekor-receipt.json"); +} + +public async Task ActivateBundleAsync(BundlePaths b, TrustConfig trust, string activeDir) { + var manifest = await Manifest.LoadAsync(b.Manifest); + if (!await Hashes.VerifyAsync(b.Payload, manifest.PayloadSha256)) return false; + + // 1) DSSE verify (publisher keys pinned in trust) + var (okSig, dssePayloadDigest) = await Dsse.VerifyAsync(b.Dsse, trust.PublisherKeys); + if (!okSig || dssePayloadDigest != manifest.PayloadSha256) return false; + + // 2) Rekor v2 receipt verify (inclusion + statement digest == dssePayloadDigest) + if (!await RekorV2.VerifyReceiptAsync(b.Receipt, dssePayloadDigest, trust.RekorPub)) return false; + + // 3) Stage, validate, then atomically flip + var staging = Path.Combine(activeDir, "..", "staging"); + DirUtil.Empty(staging); + await TarZstd.ExtractAsync(b.Payload, staging); + if (!await LocalDbSelfCheck.RunAsync(staging)) return false; + + SymlinkUtil.AtomicSwap(source: staging, target: activeDir); + State.WriteLastGood(manifest.Version, dssePayloadDigest); + return true; +} +``` + +--- + +## Operational playbook + +* **On boot & daily at HH:MM:** try `ActivateBundleAsync()` on the newest bundle; on failure, log and continue. +* **Telemetry (no PII):** reason codes (SIG_FAIL, RECEIPT_FAIL, HASH_MISMATCH, SELFTEST_FAIL), versions, last_good. +* **Keys & rotation:** keep `publisher.pub` and `rekor.pub` in a root‑owned, read‑only path; rotate via a separate signed “trust bundle”. +* **Defense‑in‑depth:** verify both the **payload hash** and each file’s hash listed in `manifest.entries[]`. +* **Rollback:** allow `--force-activate ` for emergency testing, but mark as **non‑monotonic** in state. + +--- + +## What to hand your release team + +* A Make/CI target that: + 1. Builds `payload.tar.zst` and computes hashes + 2. Generates `manifest.json` + 3. Creates and signs the **DSSE statement** + 4. Submits to Rekor (or your mirror) and saves the **v2 receipt** + 5. Packages the bundle folder and publishes to your offline repo +* A checksum file (`*.sha256sum`) for ops to verify out‑of‑band. + +--- + +If you want, I can turn this into a Stella Ops spec page (`docs/modules/scanner/offline-bundles.md`) plus a small reference implementation (C# library + CLI) that drops right into your Scanner service. + +--- + +# Drop‑in Stella Ops Dev Guide (seed for `docs/modules/scanner/development/dsse-offline-updates.md`) + +> **Audience** +> Scanner, Export Center, Attestor, CLI, and DevOps engineers implementing DSSE‑signed offline vulnerability updates and integrating them into the Offline Update Kit (OUK). +> +> **Context** +> +> * OUK already ships **signed, atomic offline update bundles** with merged vulnerability feeds, container images, and an attested manifest. +> * DSSE + Rekor is already used for **scan evidence** (SBOM attestations, Rekor proofs). +> * Sprints 160/162 add **attestation bundles** with manifest, checksums, DSSE signature, and optional transparency log segments, and integrate them into OUK and CLI flows. + +These guidelines tell you how to **wire all of that together** for “offline scanner updates” (feeds, rules, packs) in a way that matches Stella Ops’ determinism + sovereignty promises. + +## 0. Mental model + +```text + Advisory mirrors / Feeds builders + │ + ▼ + ExportCenter.AttestationBundles + (creates DSSE + Rekor evidence + for each offline update snapshot) + │ + ▼ + Offline Update Kit (OUK) builder + (adds feeds + evidence to kit tarball) + │ + ▼ + stella offline kit import / admin CLI + (verifies Cosign + DSSE + Rekor segments, + then atomically swaps scanner feeds) +``` + +Online, Rekor is live; offline, you rely on **bundled Rekor segments / snapshots** and the existing OUK mechanics (import is atomic, old feeds kept until new bundle is fully verified). + +## 1. Goals & non‑goals + +### Goals + +1. **Authentic offline snapshots**: every offline scanner update (OUK or delta) must be verifiably tied to a DSSE envelope, a certificate chain, and a Rekor v2 inclusion proof or bundled log segment. +2. **Deterministic replay**: given a kit + its DSSE attestation bundle, every verifier reaches the same verdict online or air‑gapped. +3. **Separation of concerns**: Export Center builds attestations; Scanner verifies/imports; Signer/Attestor handle DSSE/Rekor. +4. **Operational safety**: imports remain atomic/idempotent; old feeds stay live until full verification. + +### Non‑goals + +* Designing new crypto or log formats. +* Per‑feed DSSE envelopes (minimum contract is bundle‑level attestation). + +## 2. Bundle contract for DSSE‑signed offline updates + +**Files to ship** (inside each offline kit or delta): + +``` +/attestations/ + offline-update.dsse.json # DSSE envelope + offline-update.rekor.json # Rekor entry + inclusion proof (or segment descriptor) +/manifest/ + offline-manifest.json # existing manifest + offline-manifest.json.jws # existing detached JWS +/feeds/ + ... # existing feed payloads +``` + +**DSSE payload (minimum):** subject = kit name + tarball sha256; predicate fields include `offline_manifest_sha256`, feed entries, builder ID/commit, created_at UTC, and channel. + +**Rekor material:** submit DSSE to Rekor v2, store UUID/logIndex/inclusion proof as `offline-update.rekor.json`; for offline, embed a minimal log segment or rely on mirrored snapshots. + +## 3. Implementation by module + +### Export Center — attestation bundles +* Compose attestation job: build DSSE payload, sign via Signer, submit to Rekor via Attestor, persist `offline-update.dsse.json` and `.rekor.json` (+ segments). +* Integrate into offline kit packaging: place attestation files under `/attestations/`; list them in `offline-manifest.json` with sha256/size/capturedAt. +* Define JSON schema for `offline-update.rekor.json`; version it and validate. + +### Offline Update Kit builder +* Preserve idempotent, atomic imports; include DSSE/Rekor files in kit staging tree. +* Keep manifests deterministic: ordered file lists, UTC timestamps. +* For delta kits, attest the resulting snapshot state (not just diffs). + +### Scanner — import & activation +* Verification sequence: Cosign tarball → manifest JWS → file digests (incl. attestation files) → DSSE verify → Rekor verify (online or offline segment) → atomic swap. +* Config surface: `requireDsse`, `rekorOfflineMode`, `attestationVerifier` (env-var mirrored); allow observe→enforce rollout. +* Failure behavior: keep old feeds; log structured failure fields; expose ProblemDetails. + +### Signer & Attestor +* Add predicate type/schema for offline updates; submit DSSE to Rekor; emit verification routines usable by CLI/Scanner with offline snapshot support. + +### CLI & UI +* CLI verbs to verify/import bundles with Rekor key; UI shows Cosign/JWS + DSSE/Rekor status and kit freshness. + +## 4. Determinism & offline‑safety rules +* No hidden network dependencies; offline must succeed with kit + Rekor snapshot. +* Stable JSON serialization; UTC timestamps. +* Replayable imports (idempotent); DSSE payload for a snapshot must be immutable. +* Explainable failures with precise mismatch points. + +## 5. Testing & CI expectations +* Unit/integration: happy path, tampering cases (manifest/DSSE/Rekor), offline mode, rollback logic. +* Metrics: `offlinekit_import_total{status}`, `attestation_verify_latency_seconds`, Rekor success/retry counts. +* Golden fixtures: deterministic bundle + snapshot tests. + +## 6. Developer checklist (TL;DR) +1. Read operator guides: `docs/modules/scanner/operations/dsse-rekor-operator-guide.md`, `docs/24_OFFLINE_KIT.md`, relevant sprints. +2. Implement: generate DSSE in Export Center; include attestation in kit; Scanner verifies before swap. +3. Test: bundle composition, import rollback, determinism. +4. Telemetry: counters + latency; log digests/UUIDs. +5. Document: update Export Center and Scanner architecture docs, OUK docs when flows/contracts change. + diff --git a/docs/product-advisories/01-Dec-2025 - Proof-Linked VEX UI Developer Guidelines.md b/docs/product-advisories/01-Dec-2025 - Proof-Linked VEX UI Developer Guidelines.md new file mode 100644 index 000000000..7716be5bb --- /dev/null +++ b/docs/product-advisories/01-Dec-2025 - Proof-Linked VEX UI Developer Guidelines.md @@ -0,0 +1,130 @@ +# Proof-Linked VEX UI Developer Guidelines + +Compiled: 2025-12-01 (UTC) + +## Purpose +Any VEX-influenced verdict a user sees (Findings, Advisories & VEX, Vuln Explorer, etc.) must be directly traceable to concrete evidence: normalized VEX claims, their DSSE/signatures, and the policy explain trace. Every "Not Affected" badge is a verifiable link to the proof. + +## What this solves (in one line) +Every "Not Affected" badge becomes a verifiable link to why it is safe. + +## UX pattern (at a glance) +- Badge: `Not Affected` (green pill) always renders as a link. +- On click: open a right-side drawer with three tabs: + 1. Evidence (DSSE / in-toto / Sigstore) + 2. Attestation (predicate details + signer) + 3. Reasoning Graph (the node + edges that justify the verdict) +- Hover state: mini popover showing proof types available (e.g., "DSSE, SLSA attestation, Graph node"). + +## Data model (API & DB) +Canonical object returned by VEX API for each finding: + +```json +{ + "findingId": "vuln:CVE-2024-12345@pkg:docker/alpine@3.19", + "status": "not_affected", + "justificationCode": "vex:not_present", + "proof": { + "dsse": { + "envelopeDigest": "sha256-…", + "rekorEntryId": "e3f1…", + "downloadUrl": "https://…/dsse/e3f1…", + "signer": { "name": "StellaOps Authority", "keyId": "SHA256:…" } + }, + "attestation": { + "predicateType": "slsa/v1", + "attestationId": "att:01H…", + "downloadUrl": "https://…/att/01H…" + }, + "graph": { + "nodeId": "gx:NA-78f…", + "revision": "gx-r:2025-11-30T12:01:22Z", + "explainUrl": "https://…/graph?rev=gx-r:…&node=NA-78f…" + } + }, + "receipt": { + "algorithm": "CVSS:4.0", + "inputsHash": "sha256-…", + "computedAt": "2025-11-30T12:01:22Z" + } +} +``` + +Suggested Postgres tables: +- vex_findings(finding_id, status, justification_code, graph_node_id, graph_rev, dsse_digest, rekor_id, attestation_id, created_at, updated_at) +- proof_artifacts(id, type, digest, url, signer_keyid, meta jsonb) +- graph_revisions(revision_id, created_at, root_hash) + +## API contract (minimal) +- GET /vex/findings/:id -> returns the object above. +- GET /proofs/:type/:id -> streams artifact (with Content-Disposition: attachment). +- GET /graph/explain?rev=…&node=… -> returns a JSON justification subgraph. +- Security headers: Content-SHA256, Digest, X-Proof-Root (graph root hash), and X-Signer-KeyId. + +## Angular UI spec (drop-in) +Component: FindingStatusBadge + +```html + +``` + +Drawer layout (3 tabs): +1) Evidence + - DSSE digest (copy button) + - Rekor entry (open in new tab) + - "Download envelope" +2) Attestation + - Predicate type + - Attestation ID + - "Download attestation" +3) Reasoning Graph + - Node ID + Revision + - Inline explainer ("Why safe?" bullets) + - "Open full graph" (routes to /graph?rev=…&node=…) + +Micro-interactions: +- Copy-to-clipboard with toast ("Digest copied"). +- If any artifact missing, show a yellow "Partial Proof" ribbon listing what is absent. + +Visual language: +- Badges: Not Affected = solid green; Partial Proof = olive with warning dot; No Proof = gray outline (still clickable, explains absence). +- Proof chips: small caps labels `DSSE`, `ATTESTATION`, `GRAPH`; each chip opens its subsection. + +Validation (trust & integrity): +- On drawer open, the UI calls HEAD /proofs/... to fetch Digest header and X-Proof-Root; compare to stored digests. If mismatch, show red "Integrity Mismatch" banner with retry and report. + +Telemetry (debugging): +- Emit events: proof_drawer_opened, proof_artifact_downloaded, graph_explain_viewed (include findingId, artifactType, latencyMs, integrityStatus). + +Developer checklist: +- Every not_affected status must include at least one proof artifact. +- Badge is never a dead label; always clickable. +- Drawer validates artifact digests before rendering contents. +- "Open full graph" deep-links with rev + node (stable and shareable). +- Graceful partials: show what is present and what is missing. +- Accessibility: focus trap in drawer, aria-labels for chips, keyboard nav. + +Test cases (quick): +1) Happy path: all three proofs present; digests match; downloads work. +2) Partial proof: DSSE present, attestation missing; drawer shows warning ribbon. +3) Integrity fail: server returns different digest; red banner appears; badge stays clickable. +4) Graph only: reasoning node present; DSSE/attestation absent; explains rationale clearly. + +Optional nice-to-haves: +- Permalinks: copyable URL that re-opens the drawer to the same tab. +- QR export: downloadable "proof card" PNG with digests + signer (for audit packets). +- Offline kit: bundle DSSE, attestation, and a compact graph slice in a .tar.zst for air-gapped review. + +If needed, this can be turned into: +- A small Angular module (ProofDrawerModule) + styles. +- A .NET 10 controller stub with integrity headers. +- Fixture JSON so teams can wire it up quickly. + +## Context links (from source conversation) +- docs/ui/console-overview.md +- docs/ui/advisories-and-vex.md +- docs/ui/findings.md +- src/VexLens/StellaOps.VexLens/AGENTS.md and TASKS.md +- docs/policy/overview.md diff --git a/docs/product-advisories/01-Dec-2025 - Storage Blueprint for PostgreSQL Modules.md b/docs/product-advisories/01-Dec-2025 - Storage Blueprint for PostgreSQL Modules.md new file mode 100644 index 000000000..fc52f6b04 --- /dev/null +++ b/docs/product-advisories/01-Dec-2025 - Storage Blueprint for PostgreSQL Modules.md @@ -0,0 +1,95 @@ +# 01-Dec-2025 - Storage Blueprint for PostgreSQL Modules + +## Summary +A crisp, opinionated storage blueprint for StellaOps modules with zero-downtime conversion tactics. Covers module-to-store mapping, PostgreSQL patterns, JSONB/RLS scaffolds, materialized views, temporal tables, CAS usage for SBOM/VEX blobs, and phased cutover guidance. + +## Module → Store Map (deterministic by default) +- **Authority / OAuth / Accounts & Audit**: PostgreSQL source of truth; tables `users`, `clients`, `oauth_tokens`, `roles`, `grants`, `audit_log`; RLS on `users`, `grants`, `audit_log`; strict FK/CHECK; immutable UUID PKs; audit table with actor/action/entity/diff and timestamptz default now(). +- **VEX & Vulnerability Writes**: PostgreSQL with JSONB facts plus relational decisions; tables `vuln_fact(jsonb)`, `vex_decision(package_id, vuln_id, status, rationale, proof_ref, updated_at)`; materialized views like `mv_triage_hotset` refreshed on commit or schedule. +- **Routing / Feature Flags / Rate-limits**: PostgreSQL truth plus Redis cache; tables `feature_flag(key, rules jsonb, version)`, `route(domain, service, instance_id, last_heartbeat)`, `rate_limiter(bucket, quota, interval)`; Redis keys `flag:{key}:{version}`, `route:{domain}`, `rl:{bucket}` with short TTLs. +- **Unknowns Registry**: PostgreSQL with temporal tables (bitemporal via `valid_from/valid_to`, `sys_from/ sys_to`); view `unknowns_current` for open rows. +- **Artifacts / SBOM / VEX files**: OCI-compatible CAS (e.g., self-hosted registry or MinIO) keyed by digest; metadata in Postgres `artifact_index(digest, media_type, size, signatures)`. + +## PostgreSQL Implementation Essentials +- **RLS scaffold (Authority)** +```sql +alter table audit_log enable row level security; +create policy p_audit_read_self + on audit_log for select + using ( + actor_id = current_setting('app.user_id')::uuid + or exists ( + select 1 from grants g + where g.user_id = current_setting('app.user_id')::uuid + and g.role = 'auditor' + ) + ); +``` + +- **JSONB facts + relational decisions** +```sql +create table vuln_fact ( + id uuid primary key default gen_random_uuid(), + source text not null, + payload jsonb not null, + received_at timestamptz default now() +); + +create table vex_decision ( + package_id uuid not null, + vuln_id text not null, + status text check (status in ('not_affected','affected','fixed','under_investigation')), + rationale text, + proof_ref text, + decided_at timestamptz default now(), + primary key (package_id, vuln_id) +); +``` + +- **Materialized view for triage** +```sql +create materialized view mv_triage_hotset as +select v.id as fact_id, v.payload->>'vuln' as vuln, v.received_at +from vuln_fact v +where (now() - v.received_at) < interval '7 days'; +-- refresh concurrently via job +``` + +- **Temporal pattern (Unknowns)** +```sql +create table unknowns ( + id uuid primary key default gen_random_uuid(), + subject_hash text not null, + kind text not null, + context jsonb not null, + valid_from timestamptz not null default now(), + valid_to timestamptz, + sys_from timestamptz not null default now(), + sys_to timestamptz +); + +create view unknowns_current as +select * from unknowns where valid_to is null; +``` + +## Conversion (not migration): zero-downtime, prototype-friendly +1. Encode Mongo-shaped docs into JSONB with versioned schemas and forward-compatible projection views. +2. Outbox pattern for exactly-once side-effects (`outbox(id, topic, key, payload jsonb, created_at, dispatched bool default false)`). +3. Parallel read adapters behind feature flags with read-diff monitoring. +4. CDC for analytics without coupling (logical replication). +5. Materialized views with fixed refresh cadence; cold-path analytics in separate schemas. +6. Phased cutover playbook: Dark Read → Shadow Serve → Authoritative → Retire (with auto-rollback). + +## Rate-limits & Flags: single truth, fast edges +- Truth in Postgres with versioned flag docs; history table for changes; Redis edge cache keyed by version; rate-limit quotas in Postgres, counters in Redis with reconciliation jobs. + +## CAS for SBOM/VEX/attestations +- Store blobs in OCI/MinIO by digest; keep pointer metadata in Postgres `artifact_index`; benefits: immutability, dedup, easy offline mirroring. + +## Guardrails +- Wrap multi-table writes in single transactions; prefer `jsonb_path_query` for targeted reads; enforce RLS and least-privilege roles; version everything (schemas, flags, materialized views); expose observability for statements, MV refresh latency, outbox lag, Redis hit ratio, and RLS hits. + +## Optional Next Steps (from advisory) +- Generate ready-to-run EF Core 10 migrations. +- Add `/docs/architecture/store-map.md` summarizing these patterns. +- Provide a small Docker-based dev seed with sample data. diff --git a/docs/product-advisories/01-Dec-2025 - Verifiable Proof Spine Receipts and Benchmarks.md b/docs/product-advisories/01-Dec-2025 - Verifiable Proof Spine Receipts and Benchmarks.md new file mode 100644 index 000000000..d48533964 --- /dev/null +++ b/docs/product-advisories/01-Dec-2025 - Verifiable Proof Spine Receipts and Benchmarks.md @@ -0,0 +1,38 @@ +# Verifiable Proof Spine: Receipts + Benchmarks + +Compiled: 2025-12-01 (UTC) + +## Why this matters +Move from “trust the scanner” to “prove every verdict.” Each finding and every “not affected” claim must carry cryptographic, replayable evidence that anyone can verify offline or online. + +## Differentiators to build in +- **Graph Revision ID on every verdict**: stable Merkle root over SBOM nodes/edges, policies, feeds, scan params, and tool versions. Any data change → new graph hash → new revisioned verdicts; surface the ID in UI/API. +- **Machine-verifiable receipts (DSSE)**: emit a DSSE-wrapped in-toto statement per verdict (predicate `stellaops.dev/verdict@v1`) including graphRevisionId, artifact digests, rule id/version, inputs, and timestamps; sign with Authority keys (offline mode supported) and keep receipts queryable/exportable; mirror to Rekor-compatible ledger when online. +- **Reachability evidence**: attach call-stack slices (entry→sink, symbols, file:line) for code-level cases; for binaries, include symbol presence proofs (bitmap/offsets) hashed and referenced from DSSE payloads. +- **Deterministic replay manifests**: publish `replay.manifest.json` with inputs, feeds, rule/tool/container digests so auditors can recompute the same graph hash and verdicts offline. + +## Benchmarks to publish (headline KPIs) +- **False-positive reduction vs baseline scanners**: run public corpus across 3–4 popular scanners; label ground truth once; report mean and p95 FP reduction. +- **Proof coverage**: percentage of findings/VEX items carrying valid DSSE receipts; break out reachable vs unreachable and “not affected.” +- **Triage time saved**: analyst minutes from alert to final disposition with receipts visible vs hidden; publish p50/p95 deltas. +- **Determinism stability**: re-run identical scans across nodes; publish % identical graph hashes and explain drift causes when different. + +## Minimal implementation plan (week-by-week) +- **Week 1 – Primitives**: add Graph Revision ID generator in scanner.webservice (Merkle over normalized SBOM+edges+policies+toolVersions); define `VerdictReceipt` schema (protobuf/JSON) and DSSE envelope types. +- **Week 2 – Signing + storage**: wire DSSE signing via Authority with offline key support/rotation; persist receipts in `Receipts` table keyed by (graphRevisionId, verdictId); enable JSONL export and ledger mirror. +- **Week 3 – Reachability proofs**: capture call-stack slices in reachability engine; serialize and hash; add binary symbol proof module (ELF/PE bitmap + digest) and reference from receipts. +- **Week 4 – Replay + UX**: emit replay.manifest per scan (inputs, tool digests); UI shows “Verified” badge, graph hash, signature issuer, and one-click “Copy receipt”; API: `GET /verdicts/{id}/receipt`, `GET /graphs/{rev}/replay`. +- **Week 5 – Benchmarks harness**: create `bench/` fixtures and runner with baseline scanner adapters, ground-truth labels, metrics export for FP%, proof coverage, triage time capture hooks. + +## Developer guardrails (non-negotiable) +- **No receipt, no ship**: any surfaced verdict must carry a DSSE receipt; fail closed otherwise. +- **Schema freeze windows**: changes to rule inputs or policy logic must bump rule version and therefore graph hash. +- **Replay-first CI**: PRs touching scanning/rules must pass a replay test that reproduces prior graph hashes on gold fixtures. +- **Clock safety**: use monotonic time for receipts; include UTC wall-time separately. + +## What to show buyers/auditors +- Audit kit: sample container + receipts + replay manifest + one command to reproduce the same graph hash. +- One-page benchmark readout: FP reduction, proof coverage, triage time saved (p50/p95), corpus description. + +## Optional follow-ons +- Provide DSSE predicate schema, Postgres DDL for `Receipts` and `Graphs`, and a minimal .NET verification CLI (`stellaops-verify`) that replays a manifest and validates signatures. diff --git a/docs/product-advisories/31-Nov-2025 FINDINGS.md b/docs/product-advisories/31-Nov-2025 FINDINGS.md index ad55bd937..cb0c44913 100644 --- a/docs/product-advisories/31-Nov-2025 FINDINGS.md +++ b/docs/product-advisories/31-Nov-2025 FINDINGS.md @@ -31,7 +31,7 @@ - Freeze baseline rulepacks/DBs and publish digests. - Document sandbox and submission attestation requirements in the submission guide and CI policy. -# Findings – Gaps in “Add CVSS v4.0 Score Receipts for Transparency” +# Findings – Gaps in “Add CVSS v4.0 Score Receipts for Transparency” **Requested label:** 2025-11-31 (note: November has 30 days) @@ -550,6 +550,325 @@ - Add a ledger gaps task to a relevant sprint (e.g., reachability/policy ledger work or EvidenceLocker/export coordination) to close FL1–FL10. - Publish versioned schemas and canonical serialization; mandate Merkle/external anchor policy with freshness; enforce tenant/redaction rules; require DSSE/policy linkage; add golden fixtures, replay/rebuild verifiers, air-gap verify scripts, and quotas/backpressure. +# Findings – Gaps in “DSSE‑Signed Offline Scanner Updates — Developer Guidelines” + +**Requested label:** 2025-11-31 (note: November has 30 days) + +**Compiled:** 2025-12-01 (UTC) + +**Source reviewed:** User-provided advisory “DSSE‑Signed Offline Scanner Updates — Developer Guidelines” (not yet in repo); cross-checked against `docs/24_OFFLINE_KIT.md`, `docs/modules/scanner/operations/dsse-rekor-operator-guide.md`, and sprints 160/162 attestation work. + +**Method:** Evaluated the proposed offline bundle pattern (DSSE envelope + Rekor v2 receipt + manifest + payload) and activation flow against existing offline-kit, scanner import, attestation, and determinism/air-gap requirements. Identified missing controls, governance, and telemetry required to make the pattern enforceable and replayable. + +## Gap Table +| ID | Area | Gap | Impact | Recommendation | +| -- | ---- | --- | ------ | -------------- | +| DS1 | Trust bundle rotation & revocation | Advisory pins publisher/rekor keys but omits rotation channel, expiry/NotAfter checks, or revocation response (key compromise, revoked cert, mirror drift). | Stale or compromised keys could continue to sign/verify bundles; rollback to bad keys possible in air-gaps. | Define signed “trust bundle” schema with key set, expiry, revocation list, and provenance; enforce NotBefore/NotAfter on activation; require quorum/M-of-N to rotate keys and store rotation receipts alongside bundles. | +| DS2 | Rekor freshness & offline proof | Verification only checks receipt vs DSSE hash; no requirement to validate the Rekor checkpoint/root, inclusion promise window, or bundled log segment authenticity. | Attackers can replay old receipts or splice receipts from another tree; air-gapped sites may trust stale proofs. | Require checkpoint verification (root hash, size, log ID) and freshness bound; when offline, bundle signed log segment + checkpoint DSSE; fail closed if segment/hash mismatches. | +| DS3 | Manifest/schema canonicalization | `manifest.json` shape/version/canonical rules are undefined; hash algo/encoding not fixed; no schema signature. | Producers/consumers may compute different digests → false negatives/acceptance of tampered bundles. | Publish versioned JSON Schema with canonical ordering, SHA-256 as default, strict types; sign manifest with DSSE/JWS and include schema version in filename and trust_id. | +| DS4 | Supply-chain provenance for bundle build | Build pipeline steps (hashing, signing, Rekor submission) lack attestation/SLSA provenance; no binding to source commit, tool versions, or build runner hash. | Malicious/compromised build host could emit validly signed but malicious payloads; hard to audit. | Produce SLSA/DSSE build attestation for each bundle (builder ID, git commit, tool versions, reproducible build inputs); verify attestation before accepting bundle into cache. | +| DS5 | Anti-replay & rollback detection | Monotonicity check uses manifest.version but no binding to prior trust state or recorded trust_id; no replay window/nonce; force-activate bypass not audited. | Old bundles can be reintroduced (malicious or operator error); rollback may go unnoticed in air-gaps. | Persist last_good {version, trust_id, rekor_root} in append-only state; require version strictly increasing unless signed rollback exception; log and DSSE-sign every activation/force-activation event. | +| DS6 | Delta/partial bundle rules | Contract only shown for full bundles; deltas/partials not defined (expected final state, base hash, tombstones). | Deltas may apply on wrong base, producing diverging DB contents without detection. | Define delta schema: base_version/base_hash, operations (add/remove/replace), resulting snapshot hash; verify base before apply; generate synthetic full-hash after apply and compare to declared target. | +| DS7 | Per-file integrity & compression safety | Defense-in-depth note mentions file hashes but not mandatory verification of each entry inside `payload.tar.zst`, compression flags, or TOCTOU protection when extracting. | Tampering inside tar/zst could slip through if only outer hash is checked; extraction could overwrite symlinks or traverse directories. | Require per-entry hashes in manifest, validated before extraction; use safe extractor that rejects symlinks/`..` paths and enforces uid/gid/perm allowlist; verify zstd dictionary/levels; hash post-extract contents before swap. | +| DS8 | Config/feature flags & policy surface | `requireDsse`-style enforcement hinted but not specified across Scanner, CLI, Worker; no migration plan or policy gate. | Mixed deployments may silently skip DSSE/Rekor checks or drift from policy; inconsistent enforcement. | Add explicit config matrix (API/UI/CLI) with default `requireDsse=true`, rollout guard (`observe→enforce`), and policy gate that blocks imports lacking DSSE/Rekor unless override is signed and time-bound. | +| DS9 | Observability & SLOs | Telemetry suggests reason codes but no SLOs, alerts, or metrics for freshness, failure streaks, rollback attempts, or trust-bundle age. | Operators lack visibility; silent drift or repeated failures may persist. | Define metrics (`bundle_activate_total{reason}`, `rekor_freshness_seconds`, `trust_bundle_age_hours`, `rollback_attempt_total`), alerts on stale checkpoint/keys or repeated failures, and trace spans around verify steps; document SLOs. | +| DS10 | Recovery & quarantine governance | Quarantine step lacks retention period, evidence capture, or reprocessing flow; no checklist for operator actions or RCA evidence. | Quarantined bundles may be reintroduced without fix; root causes lost. | Require quarantine manifest (bundle hash, failure reason, logs, time, operator); set retention/SLA; add `reanalyze` job that re-verifies after trust-bundle/rekor updates; document runbook. | +| DS11 | Multi-tenant/namespace scoping | Advisory assumes single trust root/cache; no scoping for multi-tenant or env-specific feeds (prod/stage/regional crypto profiles). | Wrong bundles could be activated in other tenants/regions; policy/cert profile mismatches. | Partition cache and trust state by tenant/env/crypto profile; include tenant/profile in DSSE predicate and activation state; block activation on mismatch. | +| DS12 | Offline-kit parity & kit manifest linkage | Bundle layout is local-only; not bound to existing Offline Kit manifest/attestations; no guidance for importing via OUK or Export Center bundles. | Duplicate verification logic; kit imports may skip DSSE/Rekor or mismatch manifest coverage. | Align bundle schema with OUK: include pointers into offline-kit manifest, ensure kit contains DSSE/Rekor files, and require Scanner import to treat them as mandatory; add shared schema/docs. | + +## Immediate follow-ups +- Add a gaps-remediation task to the relevant attestation/offline sprints (e.g., `SPRINT_0162_0001_0001_exportcenter_i`, `SPRINT_0163_0001_0001_exportcenter_ii`, `SPRINT_0510_0001_0001_airgap`, or Scanner import sprint) covering DS1–DS12. +- Draft and publish versioned schemas for bundle manifest, delta bundles, trust bundle, and Rekor segment packaging; include canonicalization rules and test vectors. +- Extend offline-kit and Scanner import docs to mandate DSSE/Rekor checkpoint verification, per-entry hashing, safe extraction, append-only state, and tenant/profile scoping; wire metrics/alerts into observability docs. +- Add CI/fixtures: reproducible bundle build attestation, delta/base mismatch tests, rollback/replay tests, stale checkpoint/key tests, and quarantine reprocessing tests. + +# Findings – Gaps in “StellaOps Storage Blueprint (PostgreSQL patterns per module)” + +**Requested label:** 2025-11-31 (note: November has 30 days) + +**Compiled:** 2025-12-01 (UTC) + +**Source reviewed:** Pasted advisory “Here’s a crisp, opinionated storage blueprint…” / “StellaOps – PostgreSQL Patterns per Module” (2025-12-01 draft). + +**Method:** Reviewed the blueprint against module dossiers (Authority, Routing, VEX, Unknowns, Artifact), high-level architecture, and prior advisories on ledger/evidence/offline posture to identify missing guarantees, hardening steps, and operability gaps. + +## Gap Table +| ID | Area | Gap | Impact | Recommendation | +| -- | ---- | --- | ------ | -------------- | +| SB1 | Tenant isolation | DDL examples mostly omit `tenant_id` and tenant-based RLS; policies rely only on `app.user_id`. | Cross-tenant data exposure or cache bleed; feature flags/routing/unknowns not tenant-safe. | Make `tenant_id uuid not null` mandatory on tenant-scoped tables, enforce base RLS `tenant_id = current_setting('app.tenant_id')::uuid`, and add partial indexes by tenant. | +| SB2 | RLS hardening | Blueprint assumes `set_config` but lacks guards for unset/invalid session vars, role separation, or `SECURITY DEFINER` safety. | Mis-set sessions bypass RLS; superuser paths may leak data. | Add `check_app_context()` function used in policies, deny access when settings missing, separate DB roles per service, and forbid bypass for `pg_read_all_data`. | +| SB3 | Partitioning & retention | High-volume tables (audit_log, oauth_token, outbox, unknowns history) not partitioned; no retention/archival plan. | Storage bloat, slow scans, expensive VACUUM; audit trails hard to manage. | Time/tenant partition heavy tables; enforce retention/archival to CAS; add `DROP PARTITION`/`vacuumd` runbooks and metrics. | +| SB4 | Indexing & query plans | Several hot-path queries lack indexes (e.g., `feature_flag(key, version)`, `audit_log(actor_id, at)`, GIN on JSONB facts/unknowns, partial indexes on open unknowns). | Latency spikes and table scans; MV refreshes slow. | Specify required indexes per table and refresh cadence; add `EXPLAIN` baselines in migrations/tests. | +| SB5 | HA/DR & PITR | No posture for replication, failover, backups, or PITR testing. | Data loss/outage risk; compliance gaps. | Standardize HA (streaming replica) with async/sync policy per module, scheduled base/backups + PITR drills, and recovery SLOs documented. | +| SB6 | Migration/dual-write plan | Cutover phases describe read adapters but omit dual-write/backfill, consistency checks, and abort criteria. | Divergence between Mongo and Postgres; hard rollback. | Add dual-write phase with idempotent keys, reconciliation jobs, hash-based diff reports, and automated rollback switch; document stop conditions. | +| SB7 | Schema governance | `schema_version` fields exist but no schema registry, compatibility rules, or SemVer/change-log requirements. | Breaking changes may ship unnoticed; clients can’t validate payloads. | Create schema catalog with SemVer and DSSE signatures; enforce compatibility checks in CI and at runtime; require migration playbooks per version bump. | +| SB8 | CDC security & scoping | Logical replication recommended without tenant filtering, column-level exclusions, or connector isolation. | Sensitive data may leak to analytics/third parties; multi-tenant isolation broken. | Use publication per module/tenant, exclude secret columns, TLS/auth for connectors, and add redaction/field allowlists plus monitoring for lag/divergence. | +| SB9 | Outbox robustness | Outbox table lacks idempotency keys, ordering/fencing rules, poison-message handling, and backpressure metrics. | Duplicate or lost events; dispatcher loops under load. | Add `(aggregate_type, aggregate_id, topic, created_at)` unique key, status enum, retry/backoff policy, dead-letter bucket, and observability counters; keep dispatcher transactional. | +| SB10 | Cache governance (Redis) | Cache keys/TTLs noted but no tenant/env namespacing, warm/cold coherence rules, or fail-closed behavior. | Cross-tenant bleed or stale flags/routes; silent fallback to outdated cache. | Namespaces (`env:tenant:` prefixes), include version in keys, require cache-miss fallback to Postgres with freshness checks, and metrics/alerts on hit ratio + staleness. | +| SB11 | Artifact index & CAS hygiene | CAS index lacks GC policy, tag/alias governance, encryption/ACL guidance, or tenant-scoped storage paths; signatures optional. | Digest store grows unbounded; cross-tenant leakage via shared blobs; unverifiable artifacts. | Add GC rules (refcount/last-access), tenant-scoped buckets/prefixes, mandatory signature refs, encryption at rest + access policy, and offline mirror/verify scripts. | +| SB12 | Observability & SLOs | Metrics mentioned but no SLOs/alerts for MV lag, replication lag, RLS policy hits, outbox lag, refresh failures, or Redis divergence. | Operational drift undetected; regressions hit users before detection. | Define per-module SLOs and alerting; ship dashboards; add self-test queries in readiness probes; fail-fast on MV refresh/CDC gaps. | +| SB13 | Security & compliance | No explicit at-rest/transport encryption, audit of DDL/config changes, or data-classification/PII rules for JSONB payloads. | Compliance risk; uncontrolled sensitive data storage. | Enforce TLS, TDE/disk encryption, pgaudit/DDL logging, classified columns with masking/redaction, and PII allowlists plus periodic scans. | + +## Immediate follow-ups +- Open a sprint task (e.g., under data/platform hardening) to close SB1–SB13 with owners/dates and link to this finding. +- Produce migration/dual-write and partitioning runbooks per module; add schema catalog (versioned, signed) and required indexes to migrations. +- Define HA/DR posture, CDC scoping rules, cache namespacing, artifact GC/ACL policy, and observability SLOs; wire alerts and self-tests into services. + +# Findings – Gaps in “Verifiable Proof Spine → Moat (receipts + benchmarks)” + +**Requested label:** 2025-11-31 (note: November has 30 days) + +**Compiled:** 2025-12-01 (UTC) + +**Source reviewed:** “Here’s a crisp, practical way to turn Stella Ops’ ‘verifiable proof spine’ into a moat—and how to measure it.” (includes “Developer Guidelines – Benchmarks for a Testable Security Moat”). + +**Method:** Read the advisory and attached developer guidelines; compared with related advisories already filed (Graph Revision IDs as Public Trust Anchors, Evidence Bundle and Replay Contracts, Reachability Benchmark Fixtures Snapshot, Comparative Evidence Patterns) and the current `bench/` layout to surface missing contracts, controls, and enforcement hooks. + +## Gap Table +| ID | Area | Gap | Impact | Recommendation | +| -- | ---- | --- | ------ | -------------- | +| VM1 | Graph Revision contract | Graph Revision ID recipe lacks canonical serialization rules (sorting, normalization, hash alg/encoding), multi-alg/PQ plan, and provenance fields (feeds/policies/tools). | Different services may compute divergent hashes; receipts tied to non-canonical IDs become unverifiable or collide. | Publish `graph-revision-manifest.schema.json` with canonical JSON rules, mandated hash alg (e.g., BLAKE3-256 hex) and optional multi-alg, plus required digests for feeds, policies, tool images, config flags; add test vectors. | +| VM2 | DSSE predicate & receipt schema | Predicate `stellaops.dev/verdict@v1` is named but not specified (required fields, canonicalization, clock source, list ordering) nor versioning/compatibility rules. | Receipts may serialize differently across services; signature verification and replay can fail; upgrades may break stored receipts. | Define versioned predicate schema + canonical JSON (sorted keys, UTC + monotonic timestamp pair, fixed decimal precision); publish validation tests and compatibility guidance; enforce in emitters/validators. | +| VM3 | Signing policy & key lifecycle | “Sign with Authority” omits key hierarchy, rotation cadence, dual-sign (ECDSA+PQ) strategy, Rekor/mirror anchoring, and tenant scoping. | Long-lived receipts risk key compromise or compliance gaps; no traceable lineage for rotated keys; multi-tenant trust not isolated. | Document signing policy: key roles (online/offline/HSM), M-of-N custody, rotation/burn rules, dual-sign option, Rekor/mirror anchoring metadata, and tenant-scoped key IDs; enforce policy hash in receipts. | +| VM4 | Receipt storage, retention, and isolation | Postgres table is suggested but lacks retention/GC rules, compression/dedup, encryption-at-rest, RBAC/tenant isolation, and sharding guidance. | Store can bloat; sensitive proofs may be exposed across tenants; replay/export may be inconsistent. | Define storage contract: per-tenant partitioning/shards, append-only receipts, row-level encryption, TTL/archival policy, dedup by `(graphRevisionId, verdictId, algo)`, and export manifests with hashes. | +| VM5 | Reachability slice / symbol proof schema | Call-stack slices and binary symbol proofs lack formal schema, size budgets, architecture coverage (ARM/ppc), redaction rules for paths/symbols, and validation tooling. | Proofs may leak PII/paths, explode in size, or be unusable for replay; binaries without symbols remain unprovable. | Publish schemas for slices and symbol proofs with max nodes/bytes, required fields (arch, offset, hash of slice), redaction/normalization rules, and validator/golden fixtures; add fallback proof type when symbols absent. | +| VM6 | Replay Manifest governance | Replay manifest is named but not required to be DSSE-signed, canonically serialized, or to pin feeds/rulepacks/tool digests/time anchors; no CI gate uses it. | Auditors cannot trust manifests; replays may drift due to unstated feed/tool changes; CI may miss drift. | Define `replay.manifest.json` schema, canonical JSON, DSSE signing, and required fields (feeds/tool digests/policies/config, fake clock seed); add CI job to rerun gold fixtures and compare graph hashes against the manifest. | +| VM7 | “No receipt, no ship” enforcement path | Rule is declarative; no enforcement points defined (scanner pipeline, policy engine, API, UI), no failure taxonomy, and no override/waiver process. | Receipts may be missing yet verdicts ship; users see inconsistent states; overrides may bypass audit. | Add fail-closed checks in scanner/policy APIs and UI gating; define error codes for missing/invalid receipts; require signed waiver/override records and metrics for violations. | +| VM8 | Benchmark corpus governance & ground truth | Benchmarks call for public corpus and baselines but lack governance: licensing/sanitization checklist, ground-truth labels with evidence, competitor selection matrix, and contribution/review rules. | Metrics may be non-reproducible or legally risky; baseline comparisons could be biased or outdated. | Create benchmark governance doc: sanitized corpus manifest with hashes/DSSE, ground-truth evidence bundles, contributor CLA/review rules, competitor/baseline selection matrix, and staleness SLAs; store under `bench/manifest.*` and sign. | +| VM9 | Benchmark determinism & resource profile | Metrics (FP reduction, triage time, proof coverage, determinism) are defined but no reference hardware/profile, seeding rules, retry/timeout policy, or multi-run hash check. | Results vary run-to-run or across machines; comparisons and claims lose credibility. | Pin reference runner (CPU/RAM, cgroups), seeds, thread limits, timeouts; add multi-run hash stability check in `bench/scripts/run_benchmarks` and publish tolerances; mark strict scenarios that must be zero-drift. | +| VM10 | Observability, alerts, and export kits | Advisory lacks required metrics/alerts for signature failures, graph-hash drift, missing proofs, or benchmark regressions, and doesn’t define the “audit kit” packaging/signing. | Failures may go unnoticed; auditors/buyers cannot independently verify kits; offline users lack parity. | Instrument counters/alerts for receipt verify failures, graph drift, proof coverage gaps, benchmark regressions; define audit-kit layout (receipts + manifest + replay + verify script) with DSSE signature and include in offline kits/export center. | + +## Immediate follow-ups +- Add a proof-spine/receipt gap-remediation task to Sprint `SPRINT_0401_0001_0001_reachability_evidence_chain` covering VM1–VM7. +- Add a benchmark governance/determinism task to Sprint `SPRINT_0513_0001_0001_public_reachability_benchmark` covering VM8–VM10, tying to `bench/` manifests and CI jobs. +- Draft and publish schemas (graph revision, verdict predicate, replay manifest, reachability proofs) plus golden fixtures/tests; wire fail-closed receipt checks and observability alerts into scanner/policy pipelines and UI/API gating. + +# Findings – Gaps in “SBOM→VEX Proof Spine Blueprint” + +**Requested label:** 2025-11-31 (note: November has 30 days) + +**Compiled:** 2025-12-01 (UTC) + +**Source reviewed:** Chat-supplied advisory “tight, practical blueprint to turn your SBOM→VEX links into an auditable proof spine…” (not yet filed under `docs/product-advisories/`). + +**Method:** Parsed the advisory, aligned it with Authority/Policy/Scanner evidence-chain expectations and existing sprint `SPRINT_0401_0001_0001_reachability_evidence_chain`, and checked for determinism, governance, tenancy, and offline parity gaps. + +## Gap Table +| ID | Area | Gap | Impact | Recommendation | +| -- | ---- | --- | ------ | -------------- | +| PS1 | Trust anchor lifecycle & conflicts | Per-dependency TrustAnchor is defined but lacks lifecycle rules (creation approval, change control, supersedes flow) and conflict resolution when multiple anchors match a purl or SBOMEntry. | Anchor drift can silently change accepted signers; conflicting anchors can cause verification bypass or denial. | Require signed TrustAnchor records with `version`, `createdBy`, `supersedes`, and deterministic purl matching precedence; add dual-control approvals and DSSE for anchor mutations; fail closed on ambiguous anchor selection. | +| PS2 | Revocation/rotation enforcement | Revocation list is mentioned but no policy for how existing spines/receipts behave after key revocation or anchor update; no rollback window or re-issuance rules. | Auditors may accept spines signed by revoked keys; replay may fail inconsistently. | Define revocation semantics (hard-fail vs warn), require re-verification tasks on revocation, emit new spines/receipts when anchors change, and publish “revoked-but-accepted-until” grace policy with metrics/alerts. | +| PS3 | Predicate schemas & test vectors | Predicate types are named (`evidence.stella/v1`, etc.) but no JSON Schemas, canonicalization vectors, or compatibility commitments. | Producers may serialize differently, leading to hash mismatches and unverifiable bundles. | Publish signed JSON Schemas + canonical JSON rules and golden test vectors for evidence/reasoning/VEX/spine; include field-level required/optional rules and normalization of enums/whitespace/precision. | +| PS4 | Merkle/ProofBundle recipe | ProofBundleID is “merkle root” but algorithm (tree shape, path ordering, hash algo, duplicate handling, domain separation) is unspecified. | Different implementations will derive different bundle IDs for the same inputs, breaking interoperability. | Standardize Merkle recipe (hash algo, leaf format, deterministic ordering, duplicate policy, domain tags); provide reference implementation and fixtures. | +| PS5 | Evidence failure/negative cases | Flow assumes successful evidence; no schema for failed scans, partial results, or “absence of evidence” attestations. | Missing DSSE records allow silent gaps; verification may over-trust incomplete data. | Define `evidence.stella/v1` variants for failures/partial coverage with required error codes and scope; require DSSE for failures and include them in ProofBundleID computation. | +| PS6 | SBOM evolution & backfill | SBOMEntryID ties to sbomDigest+purl, but no rules for updated SBOMs, component renames, or superseded SBOM versions; backfill of historical spines not described. | Proof history can fragment; replay may mismatch SBOM version to spine. | Add SBOM versioning/backfill policy: immutable sbomDigest, `supersedesSbomDigest`, migration tasks to regenerate spines for changed entries, and UI/API to view lineage. | +| PS7 | Third-party VEX & dual anchors | Import of vendor VEX is implied but no contract for dual-anchor verification (vendor + internal), status translation, or provenance preservation. | Imported VEX may be re-signed without proof of origin; status semantics can drift from vendor meaning. | Require vendor VEX verification against vendor anchor, preserve original envelope bytes, tag provenance, and optionally co-sign under Authority; define status mapping table and conflict resolution. | +| PS8 | Storage security & tenancy | Postgres/blob layout shown but lacks tenant scoping, row-level security, encryption at rest, and retention/GC policy for blobs and envelopes. | Cross-tenant data leakage risk; unbounded storage growth; unverifiable deletions. | Enforce tenant/namespace columns with RLS, encrypt blobs, add retention classes + GC rules, and record DSSE-backed delete/tombstone manifests instead of hard deletes. | +| PS9 | API contract & versioning | API endpoints are sketched without authZ roles, pagination, ETags, error codes, or versioning strategy; no idempotency keys for POST. | Clients may integrate inconsistently; accidental duplication or cache poisoning possible. | Define OpenAPI with versioned paths, RBAC roles (Authority/Viewer/Auditor), pagination/caching semantics, idempotency keys, and deterministic error models; add conformance tests. | +| PS10 | Observability & SLIs | Metrics/logging expectations are absent (only UX hints); no alerts for verification drift, revocation, hash mismatch, or signer skew. | Integrity regressions may go unnoticed; auditors lack evidence of continuous enforcement. | Add required counters/histograms (verify pass/fail by reason, anchor conflicts, revocation hits, recompute drift), structured logs with IDs, and alert thresholds; document runbooks. | +| PS11 | Offline/export kit parity | Advisory references offline friendliness but does not define export format (bundle layout, signatures, chunking), replay script, or air-gap verification inputs. | Air-gapped users cannot verify or may accept tampered kits; deterministic replay claims weaken. | Specify offline proof kit (SBOM + envelopes + anchors + schemas + Merkle recipe) with signed manifest and verify script; include chunking rules and hardware profile for replay. | +| PS12 | Key custody & PQC coexistence | Keys live in Authority, but custody model, M-of-N approval, audit trails, and PQC dual-sign verification order are not defined. | Single-operator compromise or ambiguous verification precedence; PQ readiness unverifiable. | Define key hierarchies per environment, dual-control ops, signed key-rotation records, verification precedence (ECDSA vs PQ), and audit logging; ship HSM/KMS policy guidance. | +| PS13 | Receipts schema & cache invalidation | Receipt structure is mentioned but not versioned; no rules for cache TTL, re-issuance when evidence/policy changes, or signing requirements. | Stale receipts may circulate; auditors cannot trust replay date/tool versions. | Version receipt schema, include verifier version/time, anchor IDs, tool hashes, policy hash; require DSSE signing; enforce cache TTL and auto-invalidate on anchor/policy change. | +| PS14 | Performance/backpressure & dedup | No throughput/latency SLOs, queue/backpressure rules, or deduplication of envelopes for identical inputs. | Service overload or ballooning storage; duplicate envelopes inflate Merkle roots. | Define SLOs and per-tenant quotas; require deduplication by hash/predicate; add idempotent processing with backoff and metrics on drops/retries. | + +## Immediate follow-ups +- Add a gaps-remediation task to `SPRINT_0401_0001_0001_reachability_evidence_chain` (or create a new sprint for the proof spine) covering PS1–PS14 with owners/dates. +- Publish signed JSON Schemas, Merkle recipe, and test vectors for evidence/reasoning/VEX/spine/receipt; wire canonicalization tests into CI. +- Draft TrustAnchor lifecycle/rotation policy (dual-control, revocation handling, ambiguity fail-closed) and update Authority/Policy docs accordingly. +- Define offline proof-kit packaging + verifier script and include metrics/alerts/runbooks for verification drift and anchor conflicts. + +# Findings – Gaps in “Time-to-Evidence (TTE) Metric” + +**Requested label:** 2025-11-31 (note: November has 30 days) + +**Compiled:** 2025-12-01 (UTC) + +**Source reviewed:** `01-Dec-2025 - Time-to-Evidence (TTE) Metric.md` + +**Method:** Evaluated TTE proposal against StellaOps UX/telemetry architecture (UI sprints 0209/0215, Telemetry core 0180). Focused on instrumentation fidelity, data quality, SLO coverage, caching/streaming readiness, offline/tenant safety, and governance. + +## Gap Table +| ID | Area | Gap | Impact | Recommendation | +| -- | ---- | --- | ------ | -------------- | +| TTE1 | Proof eligibility definition | “First proof” not formally defined (what counts as proof per surface; screenshots vs raw artifacts). | Inconsistent measurement; teams may emit on summaries. | Define proof eligibility per surface (SBOM line with bomRef + hash, reachability edge with graph rev, VEX clause with evidence ID); forbid summaries; add contract tests. | +| TTE2 | Event schema/versioning | Event fields are informal; no schema/version, tenant scope, or PII redaction guidance. | Broken joins and leakage across tenants; dashboards unreliable. | Publish versioned `tte-event.schema.json` with required fields (finding_id, tenant_id, proof_kind, source, ui_version, synthetic flag), redaction rules, and validation in collectors. | +| TTE3 | Correlation & dedupe | No guidance on deduping multiple `proof_rendered` events per open, retries, or tab refresh. | Over-counting inflates TTE; noisy alerts. | Define correlation rules (per finding_id + view instance), keep first-proof TTE canonical, bucket retries separately; add idempotency key. | +| TTE4 | Sampling & bot exclusion | Sampling hinted but no hard targets, bot filters, or synthetic tagging. | Skewed metrics; false regressions. | Require 100% in staging, ≥50% prod with bot/synthetic exclusion flag; document filter and include in rollups. | +| TTE5 | SLO scope & budgets | P95=15s stated globally; no per-surface SLOs, error budgets, or burn alerts. | Hot pages regress without alarms; mixed workloads masked. | Set per-surface SLOs (list/detail/deep-link, per proof_kind), define 28-day error budget and burn alerts; add regression guard in CI. | +| TTE6 | Backend readiness (indexes/streaming) | Pre-index/streaming called out but no required indexes, chunk sizes, or fallback for cold caches. | P95 fails in prod despite UI work. | Mandate indexes (pkg@version, graph node, bomRef), first-chunk SLA (<200ms), cache warmers for top-N findings, and fallback to cached proof slice. | +| TTE7 | Offline/air-gap mode | No rules for TTE when offline kits are used (local proofs) or when proofs are unavailable. | Air-gapped users show infinite TTE or misleading empties. | Define offline TTE path: local proof sources, explicit “offline proof unavailable” state, separate `source=offline_kit`; exclude from online SLO or bucket separately. | +| TTE8 | Alerting & dashboards | Dashboards listed but no alert policies, runbooks, or ownership. | Slow drift unnoticed; no on-call action. | Create alert rules (P95>15s 15m, P99>30s 15m) with owners, runbook, and suppression windows; add weekly trend review. | +| TTE9 | Governance & release gates | No requirement to block releases on TTE regression or to store baselines. | Regressions ship silently. | Add release check: compare P95 vs previous release by proof_kind/page; block if >20% regression unless waived; store baseline snapshots. | +| TTE10 | Accessibility & layout | Evidence-above-fold rule stated but no viewport spec, keyboard/a11y checks, or fallback for long proofs. | Users may still miss proof or fail accessibility audits. | Define viewport targets (e.g., 1366x768), a11y checks (ARIA/Tab order for proof panel), truncation rules with “copy full proof”, and Playwright a11y test for TTE scenarios. | + +## Immediate follow-ups +- Add TTE1–TTE10 remediation task `TTE-GAPS-0215-011` to Sprint `SPRINT_0215_0001_0001_vuln_triage_ux` (primary UI owner) with telemetry alignment to Sprint `SPRINT_0180_0001_0001_telemetry_core`. +- Publish `tte-event.schema.json`, proof eligibility rules per surface, sampling/bot filters, per-surface SLO/error budgets, required indexes/streaming SLOs, offline-kit handling, alert/runbook, release gate, and a11y/viewport test cases. + +# Findings – Gaps in Archived November Advisories (15–23 Nov 2025) + +**Requested label:** 2025-11-31 (note: November has 30 days) + +**Compiled:** 2025-12-01 (UTC) + +**Source reviewed:** All advisories in `docs/product-advisories/archived/` dated 15–23 Nov 2025 (e.g., embedded in-toto provenance events, function-level VEX explainability, binary reachability branches, SBOM-provenance spine, reachability corpus, etc.). + +**Method:** Skimmed each archived advisory and consolidated common gaps; focused on missing schemas, determinism/replay rules, tenant/redaction, offline parity, and ownership. Kept to 1–2 high-impact gaps per advisory to seed backlog without expanding scope excessively. + +## Gap Table +| Advisory (archived) | Gap ID | Gap | Impact | Recommendation | +| --- | --- | --- | --- | --- | +| Embedded in-toto provenance events | AR-EP1 | No canonical event/predicate schema or DSSE requirement; relies on narrative. | Provenance unverifiable; toolchain drift. | Publish `provenance-event.schema.json`, require DSSE, include tool version + policy hash; add fixtures. | +| Function-level VEX explainability | AR-FX1 | Lacks stable IDs for function nodes/edges and reachability proofs. | Explanations not replayable; links break. | Define function-node ID scheme, require graph_rev, shortest-path proof bundle; add determinism tests. | +| Serdica census Excel import blueprint | AR-SE1 | No PII redaction or checksum rules for Excel ingest. | Data leakage; non-deterministic imports. | Add redaction/allowlist, checksum manifest, DSSE receipt per import, and replay script. | +| Proof spine for quiet alerts | AR-PS1 | “Proof spine” undefined (hash recipe, bundle layout, failure cases). | Quiet alerts un-auditable. | Standardize spine (hash algo, ordering, failure records), DSSE-sign, and ship fixtures. | +| Scanner roadmap diff-aware rescans | AR-SR1 | No determinism guards (seed/time clamp) for rescans. | Drift across runs; flaky diffs. | Enforce fixed seeds/UTC, sorted outputs, golden diffs CI. | +| Layer-SBOM cache hash reuse | AR-LS1 | Cache key recipe unspecified (layer ordering, tar flags, compression). | Cache collisions/misses; incorrect reuse. | Define canonical key recipe (ordered layer digests, normalized tar, compression flags) and validators. | +| Multi-runtime reachability corpus | AR-MR1 | Corpus lacks licensing/provenance and ground-truth assertions. | Legal risk; unvalidated results. | Add license metadata, expected reachability assertions, DSSE-signed manifest. | +| SPDX canonical persistence / CycloneDX interchange | AR-SX1 | No canonicalization rules (ordering, whitespace, encoding) across SPDX↔CDX. | Hash drift; signature breakage. | Publish canonicalization spec + round-trip tests, DSSE-sign outputs. | +| Validation plan for quiet scans (diff-CI) | AR-VQ1 | No acceptance thresholds or negative/failure fixtures. | Quiet scans may suppress real issues. | Define threshold matrix, include failure/edge fixtures, CI gate on false-negative budget. | +| SBOM-Provenance-Spine (17 & 18 Nov) | AR-SP1 | Duplicate advisories; spine lacks versioning and Merkle recipe. | Divergent implementations; audit gaps. | Declare single canonical doc, versioned spine schema, Merkle/hash recipe, DSSE signing. | +| Stripped-ELF reachability | AR-SEF1 | No symbol-stripping fallback (DWARF absent) or redaction rules. | Binaries unprovable; PII path leaks. | Require fallback heuristics, redaction, and proof attestation format. | +| Binary Reachability Engine | AR-BR1 | Performance/SLOs and determinism seeds absent. | Non-reproducible graphs; timeouts. | Set seed/time clamps, path-ordering rules, perf SLO, golden graphs CI. | +| C# Binary Analyzer | AR-CS1 | No PURL mapping or IL-level canonical IDs. | Findings not linkable to packages; unstable links. | Define IL symbol IDs + PURL mapping rules; add hash anchors. | +| Patch Oracles | AR-PO1 | Oracle decision schema undefined; no audit trail. | Wrong patch suggestions; untraceable. | Create oracle schema with inputs, decision, confidence, evidence; DSSE-sign and log. | +| Unknowns Registry (18 Nov) | AR-UR1 | Registry schema/versioning missing; decay logic undefined. | Unknowns pile up; inconsistent triage. | Version registry schema, define decay/expiry fields, audit trail, and offline export. | +| ELF Build-ID mapping | AR-BI1 | Build-ID→PURL mapping recipe not specified; no collision policy. | Misattribution; trust breaks. | Define mapping algorithm, collision handling, attestation with subject hashes. | +| .init_array constructors as reachability roots | AR-IA1 | No rule for weighting/ordering roots or de-duplication. | Over/under-approx reachability. | Specify root precedence, dedupe, and evidence bundle with graph_rev. | +| Reachability & Moat Watch updates | AR-MW1 | No change-log or checkpoint signing for updates. | Consumers can’t track or trust updates. | Add signed checkpoints, changelog, and freshness SLA. | +| Encoding binary reachability with PURL edges | AR-PE1 | Edge encoding schema not versioned; arch-specific fields absent. | Cross-arch drift; parsing errors. | Version edge schema, require arch/endianness, hash of binaries, and fixtures. | +| Where Stella Ops Can Truly Lead | AR-ML1 | Positioning lacks measurable targets or evidence asks. | Strategy not actionable. | Add 3–5 measurable targets (perf/SLO, replay fidelity) with proof requirements and owners. | +| Benchmarking determinism in vuln scoring | AR-BD1 | No benchmark corpus or scoring reproducibility rules. | Claims unproven; regressions undetected. | Publish corpus + expected scores, hash manifest, DSSE results, CI reruns. | +| Publishing a reachability benchmark dataset | AR-RD1 | Dataset packaging/licensing undefined; no integrity attestation. | Cannot redistribute or verify. | Add license metadata, manifest + hashes, DSSE attestation, offline kit. | +| Stella Ops vs Competitors | AR-SC1 | Comparison lacks normalized criteria or evidence links. | Biased/unsupported claims. | Define criteria table, data sources, timestamps; include raw evidence links. | +| Verifying Binary Reachability via DSSE Envelopes (archived copy) | AR-VB1 | Archived version lacks current DSSE predicate and Merkle recipe updates. | Divergence from active spec. | Mark superseded; link to active advisory; provide migration notes. | + +## Immediate follow-ups +- Add an “Archived Advisories Gaps” tracker row to the relevant documentation sprint (e.g., `SPRINT_300_documentation_process`) to decide which archived topics merit revival; start with high-signal engine/graph items (AR-BR1, AR-SEF1, AR-PE1) and provenance items (AR-EP1, AR-SP1). +- For any archived advisory revived, create a fresh canonical advisory and sprint tasks; retire duplicates (e.g., SBOM-Provenance-Spine) with clear supersede notes. + +### Per-advisory gap summaries (archived) + +| Advisory | Concise Gap | Recommendation | +| --- | --- | --- | +| Where Stella Ops Can Truly Lead | Strategy brief lacks measurable targets and evidence asks. | Define 3–5 measurable targets (perf/SLO, replay fidelity) with required evidence links and owners. | +| Benchmarking Determinism in Vulnerability Scoring | No corpus/expected scores; reproducibility undefined. | Publish benchmark corpus + expected scores with hash manifest and DSSE results; add CI rerun gate. | +| Binary-Reachability-Engine | Missing determinism seeds/time clamps and perf SLOs. | Fix seeds/UTC, path-ordering rules, perf SLO, golden graphs CI. | +| Branch · Attach ELF Build‑IDs for Stable PURL Mapping | Mapping recipe/collision policy absent. | Define Build-ID→PURL algorithm, collision handling, attestation with subject hashes. | +| Branch · Model .init_array Constructors as Reachability Roots | Root weighting/dedup rules missing. | Specify root precedence, dedupe policy, and graph_rev-bound evidence bundle. | +| Branch · Reachability & Moat Watch — Verified 2025 Updates | No signed checkpoints/changelog. | Add signed checkpoints with freshness SLA and changelog; distribute via DSSE snapshot. | +| CSharp-Binary-Analyzer | No IL symbol IDs or PURL mapping rules. | Define IL symbol ID + PURL mapping with hash anchors; add fixtures. | +| DSSE-Signed Offline Scanner Updates | (Archived) No canonical DSSE predicate or offline kit recipe. | Publish predicate schema, offline bundle layout, and verifier script with hashes. | +| Encoding Binary Reachability with PURL‑Resolved Edges | Edge schema unversioned; arch fields missing. | Version edge schema; require arch/endianness/binary hash; add fixtures. | +| Patch-Oracles | Oracle decision schema/audit trail undefined. | Create decision schema (inputs, decision, confidence, evidence) with DSSE signing and logging. | +| Publishing a Reachability Benchmark Dataset | Packaging/licensing/integrity unclear. | Add license metadata, manifest + hashes, DSSE attestation, offline kit. | +| SBOM-Provenance-Spine (17 & 18 Nov) | Duplicate docs; spine lacks versioning/Merkle recipe. | Declare canonical version, publish schema + Merkle recipe, DSSE-sign; mark duplicate superseded. | +| Stella Ops vs Competitors | Criteria/evidence not normalized. | Define comparison criteria table, data sources/timestamps, and raw evidence links. | +| Storage Blueprint for PostgreSQL Modules | Patterns lack tenancy/isolation and PITR/SLA specifics. | Add tenant isolation, PITR/SLA baselines, deterministic migrations, and signed change log. | +| Stripped-ELF-Reachability | No fallback when symbols absent; redaction undefined. | Provide fallback heuristics, redaction rules, and proof attestation format. | +| Unknowns-Registry | Schema/versioning and decay/expiry logic missing. | Version registry schema; add decay/expiry fields, audit trail, offline export. | +| Verifiable Proof Spine Receipts and Benchmarks | Proof spine hash recipe undefined; benchmarks missing. | Standardize hash/ordering, include failure cases, DSSE-sign; publish benchmarks. | +| Verifying Binary Reachability via DSSE Envelopes (archived copy) | Archived version diverges from active spec. | Mark superseded; link to active advisory; provide migration notes. | +| embedded in-toto provenance events | No event schema or DSSE requirement. | Publish provenance-event schema; require DSSE; include tool/policy hashes. | +| function-level vex explainability | Missing stable function IDs/graph_rev binding. | Define function-node IDs, require graph_rev, shortest-path proof bundle, determinism tests. | +| ipal serdica census excel import blueprint | No PII redaction or checksum rules. | Add redaction/allowlist, checksum manifest, DSSE receipt per import, replay script. | +| layer-sbom cache hash reuse | Cache key recipe unspecified. | Define canonical key (ordered layer digests, normalized tar/compression flags) and validators. | +| multi-runtime reachability corpus | Lacks licensing/provenance and ground truth. | Add license metadata, expected reachability assertions, DSSE-signed manifest. | +| proof spine for explainable quiet alerts | Spine definition missing (hash recipe/failures). | Standardize spine schema/ordering, include failure records, DSSE-sign fixtures. | +| scanner roadmap with deterministic diff-aware rescans | No determinism guards or seeds. | Enforce fixed seeds/UTC, sorted outputs, golden diff CI. | +| spdx canonical persistence cyclonedx interchange | No canonicalization rules across SPDX↔CDX. | Publish canonicalization spec + round-trip tests; DSSE-sign outputs. | +| validation plan for quiet scans provenance diff-ci | Lacks acceptance thresholds and negative fixtures. | Define thresholds and failure/edge fixtures; gate CI on false-negative budget. | + +## Archived advisory stubs (per-advisory headings) +*These stubs reference the consolidated AR-* gap table above; no additional gaps beyond that table.* + +# Findings – Gaps in “Where Stella Ops Can Truly Lead” +See AR-ML1 in the archived gap table. + +# Findings – Gaps in “ Where Stella Ops Can Truly Lead” +See AR-ML1 in the archived gap table; archived filename contains a leading space. + +# Findings – Gaps in “Benchmarking Determinism in Vulnerability Scoring” +See AR-BD1 in the archived gap table. + +# Findings – Gaps in “Binary-Reachability-Engine” +See AR-BR1 in the archived gap table. + +# Findings – Gaps in “Branch · Attach ELF Build‑IDs for Stable PURL Mapping” +See AR-BI1 in the archived gap table. + +# Findings – Gaps in “Branch · Model .init_array Constructors as Reachability Roots” +See AR-IA1 in the archived gap table. + +# Findings – Gaps in “Branch · Reachability & Moat Watch — Verified 2025 Updates” +See AR-MW1 in the archived gap table. + +# Findings – Gaps in “CSharp-Binary-Analyzer” +See AR-CS1 in the archived gap table. + +# Findings – Gaps in “DSSE-Signed Offline Scanner Updates” +See AR-DS1 in the archived gap table. + +# Findings – Gaps in “Encoding Binary Reachability with PURL‑Resolved Edges” +See AR-PE1 in the archived gap table. + +# Findings – Gaps in “Patch-Oracles” +See AR-PO1 in the archived gap table. + +# Findings – Gaps in “Publishing a Reachability Benchmark Dataset” +See AR-RD1 in the archived gap table. + +# Findings – Gaps in “SBOM-Provenance-Spine” +See AR-SP1 in the archived gap table. + +# Findings – Gaps in “SBOM-Provenance-Spine” +See AR-SP1 in the archived gap table; duplicate advisory, treat 18-Nov version as canonical. + +# Findings – Gaps in “Stella Ops vs Competitors” +See AR-SC1 in the archived gap table. + +# Findings – Gaps in “Storage Blueprint for PostgreSQL Modules” +See AR-SB1 in the archived gap table. + +# Findings – Gaps in “Stripped-ELF-Reachability” +See AR-SEF1 in the archived gap table. + +# Findings – Gaps in “Unknowns-Registry” +See AR-UR1 in the archived gap table. + +# Findings – Gaps in “Verifiable Proof Spine Receipts and Benchmarks” +See AR-VP1 in the archived gap table. + +# Findings – Gaps in “Verifying Binary Reachability via DSSE Envelopes” +See AR-VB1 in the archived gap table. + +# Findings – Gaps in “embedded in-toto provenance events” +See AR-EP1 in the archived gap table. + +# Findings – Gaps in “function-level vex explainability” +See AR-FX1 in the archived gap table. + +# Findings – Gaps in “ipal serdica census excel import blueprint” +See AR-SE1 in the archived gap table. + +# Findings – Gaps in “layer-sbom cache hash reuse” +See AR-LS1 in the archived gap table. + +# Findings – Gaps in “multi-runtime reachability corpus” +See AR-MR1 in the archived gap table. + +# Findings – Gaps in “proof spine for explainable quiet alerts” +See AR-PS1 in the archived gap table. + +# Findings – Gaps in “scanner roadmap with deterministic diff-aware rescans” +See AR-SR1 in the archived gap table. + +# Findings – Gaps in “spdx canonical persistence cyclonedx interchange” +See AR-SX1 in the archived gap table. + +# Findings – Gaps in “validation plan for quiet scans provenance diff-ci” +See AR-VQ1 in the archived gap table. + # Findings – Gaps in “Rekor Receipt Checklist for Stella Ops” **Requested label:** 2025-11-31 (note: November has 30 days) @@ -634,7 +953,7 @@ - Add MI1–MI10 remediation task to UI Sprint `SPRINT_0209_0001_0001_ui_i` (or UI II/III if preferred) with owners and dates. - Publish motion/telemetry/testing tokens, reduced-motion rules, offline/error patterns, component mappings, and theme-aware micro-copy guidance; add Playwright/a11y checks to CI. -# Findings – Gaps in “Proof‑Linked VEX UI Developer Guidelines” +# Findings – Gaps in “Proof-Linked VEX UI Developer Guidelines” **Requested label:** 2025-11-31 (note: November has 30 days) @@ -1361,3 +1680,12 @@ ## Immediate follow-ups - Add a ledger gaps task to a relevant sprint (e.g., reachability/policy ledger work or EvidenceLocker/export coordination) to close FL1–FL10. - Publish versioned schemas and canonical serialization; mandate Merkle/external anchor policy with freshness; enforce tenant/redaction rules; require DSSE/policy linkage; add golden fixtures, replay/rebuild verifiers, air-gap verify scripts, and quotas/backpressure. +_id; verify on ingest/export. | +| FL7 | Export determinism & golden fixtures | Export determinism claimed but no golden fixtures or multi-run hash CI for ledger exports. | Regressions may go unnoticed; reproducibility claims weak. | Publish golden ledger exports and CI multi-run hash checks; pin compression/ordering. | +| FL8 | Replay/rebuild tooling | Projection rebuild guidance minimal; no checksum for rebuild outputs. | Rebuilds may diverge from ledger state; audits fail. | Provide rebuild CLI with output hashes; compare against ledger roots; add acceptance tests. | +| FL9 | Air-gap verifier | Offline bundle verification is mentioned but not specified (hash chain, Merkle roots, anchors, revocations). | Air-gapped audits may be incomplete. | Define offline ledger verify script requirements (hash chain, Merkle root, optional external anchor checkpoint); ship script + tests. | +| FL10 | Performance envelopes & quotas | SLOs listed but no quotas/backpressure for append/export per tenant or chain. | Hot tenants could starve others; risk of data loss under load. | Add per-tenant quotas/backpressure and alerts; document performance envelopes; test under load. | + +## Immediate follow-ups +- Add a ledger gaps task to a relevant sprint (e.g., reachability/policy ledger work or EvidenceLocker/export coordination) to close FL1–FL10. +- Publish versioned schemas and canonical serialization; mandate Merkle/external anchor policy with freshness; enforce tenant/redaction rules; require DSSE/policy linkage; add golden fixtures, replay/rebuild verifiers, air-gap verify scripts, and quotas/backpressure. diff --git a/docs/product-advisories/ADVISORY_INDEX.md b/docs/product-advisories/ADVISORY_INDEX.md index 9a268626a..b1ace15ec 100644 --- a/docs/product-advisories/ADVISORY_INDEX.md +++ b/docs/product-advisories/ADVISORY_INDEX.md @@ -10,6 +10,7 @@ These are the authoritative advisories to reference for implementation: - **Canonical:** `25-Nov-2025 - Add CVSS v4.0 Score Receipts for Transparency.md` - **Sprint:** SPRINT_0190_0001_0001_cvss_v4_receipts.md - **Gaps:** `31-Nov-2025 FINDINGS.md` (CV1–CV10 remediation task CVSS-GAPS-190-013) +- **Timing/UI:** `01-Dec-2025 - Time-to-Evidence (TTE) Metric.md` (archived) - **Status:** New sprint created ### CVSS v4.0 Momentum Briefing @@ -76,6 +77,20 @@ These are the authoritative advisories to reference for implementation: - **Gaps:** `31-Nov-2025 FINDINGS.md` (PVX1–PVX10 remediation task UI-PROOF-VEX-0215-010) - **Status:** Drawer/badge pattern defined but missing scoped auth, cache/staleness policy, stronger integrity verification, failure/offline UX, evidence precedence rules, telemetry privacy schema, signed permalinks, revision reconciliation, and fixtures/tests. +### Time-to-Evidence (TTE) Metric +- **Canonical:** `01-Dec-2025 - Time-to-Evidence (TTE) Metric.md` +- **Sprint:** SPRINT_0215_0001_0001_vuln_triage_ux.md (UI) with telemetry alignment to SPRINT_0180_0001_0001_telemetry_core.md +- **Related Docs:** UI sprints 0209/0215, telemetry architecture docs +- **Gaps:** `31-Nov-2025 FINDINGS.md` (TTE1–TTE10 remediation task TTE-GAPS-0215-011) +- **Status:** Metric defined but needs event schema/versioning, proof eligibility rules, sampling/bot filters, per-surface SLO/error budgets, index/streaming requirements, offline-kit handling, alert/runbook, release gate, and a11y tests. + +### Archived Advisories (15–23 Nov 2025) +- **Canonical:** `docs/product-advisories/archived/*.md` (embedded provenance events, function-level VEX explainability, binary reachability branches, SBOM-provenance spine, etc.) +- **Sprint:** SPRINT_300_documentation_process.md (triage/decision) +- **Related Docs:** None current (need revival + canonicalization) +- **Gaps:** `31-Nov-2025 FINDINGS.md` (AR-EP1 … AR-VB1 remediation task ARCHIVED-GAPS-300-020) +- **Status:** Archived set lacks schemas, determinism rules, redaction/licensing, changelog/signing, and duplication resolution; needs triage on which to revive into active advisories. + ### SBOM → VEX Proof Blueprint - **Canonical:** `29-Nov-2025 - SBOM to VEX Proof Pipeline Blueprint.md` - **Sprint:** SPRINT_300_documentation_process.md (docs tracker) diff --git a/docs/product-advisories/archived/01-Dec-2025 - Time-to-Evidence (TTE) Metric.md b/docs/product-advisories/archived/01-Dec-2025 - Time-to-Evidence (TTE) Metric.md new file mode 100644 index 000000000..a5d462733 --- /dev/null +++ b/docs/product-advisories/archived/01-Dec-2025 - Time-to-Evidence (TTE) Metric.md @@ -0,0 +1,80 @@ +# Time-to-Evidence (TTE) Metric + +Compiled: 2025-12-01 (UTC) + +## What it is + +**Definition:** `TTE = t_first_proof_rendered − t_open_finding`. + +**Proof** = the exact artifact or path that justifies the claim (e.g., `package-lock.json: line 214 → openssl@1.1.1`, `reachability: A → B → C sink`, or `VEX: not_affected due to unreachable code`). + +**Target:** **P95 ≤ 15s** (stretch: **P99 ≤ 30s**). If 95% of findings show proof within 15 seconds, the UI stays honest: evidence before opinion, low noise, fast explainability. + +## Why it matters + +- **Trust:** People accept decisions they can verify quickly. +- **Triage speed:** Proof-first UIs cut back-and-forth and guesswork. +- **Noise control:** If you can’t surface proof fast, you probably shouldn’t surface the finding yet. + +## How to measure (engineering-ready) + +Emit two stamps per finding view: + +- `t_open_finding` (on route enter or modal open). +- `t_first_proof_rendered` (first DOM paint of SBOM line / path list / VEX clause). + +Store as `tte_ms` in a lightweight events table (Postgres) with tags: `tenant`, `finding_id`, `proof_kind` (`sbom|reachability|vex`), `source` (`local|remote|cache`). + +Nightly rollup: compute P50/P90/P95/P99 by proof_kind and page. Alert when **P95 > 15s** for 15 minutes. + +## UI contract (keeps the UX honest) + +- **Above the fold:** always show a compact **Proof panel** first (not hidden behind tabs). +- **Skeletons over spinners:** reserve space; render partial proof as soon as any piece is ready. +- **Plain text copy affordance:** “Copy SBOM line / path” button right next to the proof. +- **Defer non-proof widgets:** CVSS badges, remediation prose, and charts load *after* proof. +- **Empty-state truth:** if no proof exists, say “No proof available yet” and show the loader for *that* proof type only (don’t pretend with summaries). + +## Backend rules of thumb + +- **Pre-index for first paint:** cache top N proof items per hot finding (e.g., first SBOM hit + shortest path). +- **Bound queries:** proof queries must be *O(log n)* on indexed columns (pkg name@version, file hash, graph node id). +- **Chunked streaming:** send first proof chunk <200 ms after backend hit; don’t hold for the full set. +- **Timeout budget:** 12s backend budget + 3s UI/render margin = 15s P95. + +## Minimal contract to add in your code + +```ts +// Frontend: fire on open +metrics.emit('finding_open', { findingId, t: performance.now() }); + +// When the first real proof node/line hits the DOM: +metrics.emit('proof_rendered', { findingId, proofKind, t: performance.now() }); +``` + +```sql +-- Rollup (hourly) +SELECT + proof_kind, + percentile_cont(0.95) WITHIN GROUP (ORDER BY tte_ms) AS p95_ms +FROM tte_events +WHERE ts >= now() - interval '1 hour' +GROUP BY proof_kind; +``` + +## What to put on the team dashboard + +- **TTE P95 by page** (Findings list, Finding details). +- **TTE P95 by proof_kind** (sbom / reachability / vex). +- **Error budget burn**: minutes over target per day. +- **Top regressions**: last 7 days vs prior 7. + +## Acceptance checklist for any finding view + +- [ ] First paint shows a real proof snippet (not a summary). +- [ ] “Copy proof” button works within 1 click. +- [ ] TTE P95 in staging ≤ 10s; in prod ≤ 15s. +- [ ] If proof missing, explicit empty-state + retry path. +- [ ] Telemetry sampled ≥ 50% of sessions (or 100% for internal). + +## Ready-to-drop implementation notes diff --git a/ops/devops/aoc/supersedes-rollout.md b/ops/devops/aoc/supersedes-rollout.md new file mode 100644 index 000000000..d14b268ef --- /dev/null +++ b/ops/devops/aoc/supersedes-rollout.md @@ -0,0 +1,50 @@ +# Supersedes backfill rollout plan (DEVOPS-AOC-19-101) + +Scope: Concelier Link-Not-Merge backfill and supersedes processing once advisory_raw idempotency index is in staging. + +## Preconditions +- Idempotency index verified in staging (`advisory_raw` duplicate inserts rejected; log hash recorded). +- LNM migrations 21-101/102 applied (shards, TTL, tombstones). +- Event transport to NATS/Redis disabled during backfill to avoid noisy downstream replays. +- Offline kit mirror includes current hashes for `advisory_raw` and backfill bundle. + +## Rollout steps (staging → prod) +1) **Freeze window** (announce 24h prior) + - Pause Concelier ingest workers (`CONCELIER_INGEST_ENABLED=false`). + - Stop outbox publisher or point to blackhole NATS subject. +2) **Dry-run (staging)** + - Run backfill job with `--dry-run` to emit counts only. + - Verify: new supersedes records count == expected; no write errors; idempotency violations = 0. + - Capture logs + SHA256 of generated report. +3) **Prod execution** + - Run backfill job with `--batch-size=500` and `--stop-on-error`. + - Monitor: insert rate, error rate, Mongo oplog lag; target <5% CPU on primary. +4) **Validation** + - Run consistency check: + - `advisory_observations` count stable (no drop). + - Supersedes edges present for all prior conflicts. + - Idempotency index hit rate <0.1%. + - Run API spot check: `/advisories/summary` returns supersedes metadata; `advisory.linkset.updated` events absent during freeze. +5) **Unfreeze** + - Re-enable ingest + outbox publisher. + - Trigger single `advisory.observation.updated@1` replay to confirm event path is healthy. + +## Rollback +- If errors >0 or idempotency violations observed: + - Stop job, keep ingest paused. + - Run rollback script `ops/devops/scripts/rollback-lnm-backfill.js` to remove supersedes/tombstones inserted in current window. + - Restore Mongo from last checkpointed snapshot if rollback script fails. + +## Evidence to capture +- Job command + arguments. +- SHA256 of backfill bundle and report. +- Idempotency violation count. +- Post-run consistency report (JSON) stored under `ops/devops/artifacts/aoc-supersedes//`. + +## Monitoring/Alerts +- Add temporary Grafana panel for idempotency violations and Mongo ops/sec during job. +- Alert if job runtime exceeds 2h or if oplog lag > 60s. + +## Owners +- Run: DevOps Guild +- Approvals: Concelier Storage Guild + Platform Security diff --git a/ops/devops/attestation/witness-plan.md b/ops/devops/attestation/witness-plan.md new file mode 100644 index 000000000..733241f90 --- /dev/null +++ b/ops/devops/attestation/witness-plan.md @@ -0,0 +1,57 @@ +# Transparency Log Witness Deployment Plan (DEVOPS-ATTEST-74-001) + +## Goals +- Deploy and monitor a Sigstore-compatible witness for Rekor v1/v2 logs (and air-gap mirrors). +- Provide offline-ready configs and evidence (hashes, DSSE attestations) for bootstrap packs. + +## Scope +- Environments: staging → prod (online), sealed/offline mirror (optional, read-only). +- Witness duties: verify inclusion proofs, publish checkpoints/signed STHs, expose metrics and health. + +## Architecture +- Witness binary (sigstore/witness or equivalent) in a hardened container: + - Non-root user, read-only rootfs, seccomp/AppArmor defaults. + - TLS with mTLS between witness and collector; optional OIDC for admin endpoints. +- Inputs: + - Rekor base URL(s) + public keys. + - Mirror CAR path + signature (for air-gap). +- Outputs: + - Signed checkpoints (STH) rotated hourly; stored in object storage + DSSE manifest. + - Metrics: Prometheus `/metrics` endpoint (request latency, verify failures, checkpoint age). + - Logs: JSON, structured, no PII. + +## Deployment steps +1) Build/pull witness image (pin digest); generate SBOM + cosign attestations. +2) Create config: + - `rekor_urls`: prod/staging + - `rekor_keys`: PEMs + - `checkpoint_interval`: 1h + - `mirror_path` (optional): `/data/rekor-mirror.car` + - `signer`: KMS ref or file key (sealed-mode uses file key from bootstrap pack) +3) Helm/Compose template: + - read-only rootfs, drop NET_RAW, memory/cpu limits + - PVC for checkpoints (`/var/lib/witness/checkpoints`) + - Service exposing HTTPS + `/metrics` +4) CI: + - Lint chart + - Run e2e: start Rekor test instance, run witness, verify checkpoint written, verify metrics non-zero. + - Publish image SBOM/attestations and chart checksums. +5) Monitoring/alerts: + - `witness_verify_failures_total` > 0 over 5m + - `witness_checkpoint_age_seconds` > 5400 + - `witness_backfill_queue_depth` (if supported) above threshold + +## Offline/air-gap mode +- Consume signed Rekor mirror (CAR + manifest) from bootstrap pack. +- Run witness in verify-only mode against mirror; disable outbound network. +- Emit checkpoints signed with offline key; store in mirror bundle for audit. + +## Evidence to capture +- Image digest, SBOM hash, chart checksum. +- Signed checkpoint sample and DSSE manifest. +- CI e2e logs and metrics sample (scrape output). + +## Owners +- Build/deploy: DevOps Guild +- Keys/config: Platform Security +- Observability: Observability Guild diff --git a/ops/devops/concelier/lnm-release-plan.md b/ops/devops/concelier/lnm-release-plan.md new file mode 100644 index 000000000..8e751ed33 --- /dev/null +++ b/ops/devops/concelier/lnm-release-plan.md @@ -0,0 +1,53 @@ +# Concelier LNM Release Plan (DEVOPS-LNM-21-101-REL / 102-REL / 103-REL) + +Scope: package and publish Link-Not-Merge migrations/backfill/object-store seeds for release and offline kits. + +## Artefacts +- Migration bundles: + - 21-101 shard/index migrations (`EnsureLinkNotMergeShardingAndTtlMigration`) + - 21-102 backfill/tombstone/rollback scripts + - 21-103 object-store seed bundle (once contract final) +- Checksums (`SHA256SUMS`, signed) +- SBOMs (spdx.json) for migration runner image/tooling +- Cosign attestations for images/bundles +- Offline kit slice tarball with all above + DSSE manifest + +## Pipeline outline +1) Build migration runner image (dotnet) with migrations baked; generate SBOM; pin digest. +2) Export migration scripts/bundles to `artifacts/lnm/`. +3) Create offline bundle: + - `migrations/21-101/` (DLLs, scripts, README) + - `migrations/21-102/` (backfill, rollback, README) + - `seeds/object-store/` (placeholder until 21-103 dev output) + - `SHA256SUMS` + `.sig` + - SBOMs + cosign attestations +4) Verification stage: + - `dotnet test` on migration runner + - `cosign verify-blob` for bundles + - `sha256sum --check` +5) Publish: + - Upload to release bucket + offline kit + - Record manifest (hashes, versions, digests) + +## Runbook (apply in staging → prod) +1) Take Mongo backup; freeze Concelier ingest. +2) Apply 21-101 migrations (shards/TTL) — idempotent; record duration. +3) Run 21-102 backfill with `--batch-size=500 --stop-on-error`; capture report hash. +4) Validate counts (observations/linksets/events) and shard balance. +5) Enable outbox publishers; monitor lag and errors. +6) (When ready) apply 21-103 object-store migration: move raw payloads to object store; verify CAS URIs; keep GridFS read-only during move. + +## Rollback +- 21-101: restore from backup if shard layout breaks; migrations are idempotent. +- 21-102: run rollback script (`ops/devops/scripts/rollback-lnm-backfill.js`); if inconsistent, restore backup. +- 21-103: switch back to GridFS URI map; restore seeds. + +## Monitoring/alerts +- Migration error count > 0 +- Mongo oplog lag > 60s during backfill +- Outbox backlog growth post-unfreeze + +## Owners +- DevOps Guild (pipeline + rollout) +- Concelier Storage Guild (migration content) +- Platform Security (signing policy) diff --git a/ops/devops/graph-indexer/release-plan.md b/ops/devops/graph-indexer/release-plan.md new file mode 100644 index 000000000..d32b79e8f --- /dev/null +++ b/ops/devops/graph-indexer/release-plan.md @@ -0,0 +1,42 @@ +# Graph Indexer Release/Offline Bundle Plan (DEVOPS-GRAPH-INDEX-28-010-REL) + +## Goals +- Publish signed Helm/Compose bundles for Graph Indexer with offline parity. +- Provide SBOM + attestations for images/charts and reproducible artefacts for air-gap kits. + +## Artefacts +- Helm chart + values overrides (offline/airgap). +- Docker/OCI images (indexer, api) pinned by digest. +- SBOMs (SPDX JSON) for images and chart. +- Cosign attestations for images and chart tarball. +- Offline bundle: tarball containing images (oras layout), charts, values, SBOMs, attestations, and `SHA256SUMS`. + +## Pipeline outline +1) **Build** images (indexer + api) with SBOM generation (`syft`), tag and record digests. +2) **Sign** images with cosign key (KMS for online; file key for offline bundle) and produce attestations. +3) **Chart package**: render chart, package to `.tgz`, generate SBOM for chart, sign with cosign. +4) **Compose export**: render Compose file with pinned digests and non-root users. +5) **Bundle**: assemble offline tarball: + - `images/` oras layout with signed images + - `charts/graph-indexer.tgz` + signature + - `compose/graph-indexer.yml` (pinned digests) + - `sboms/` for images + chart + - `attestations/` (cosign bundles) + - `SHA256SUMS` and `SHA256SUMS.sig` +6) **Verify step**: pipeline stage runs `cosign verify`, `sha256sum --check`, and `helm template` smoke render with airgap values. +7) **Publish**: upload to artefact store + offline kit; write manifest with hashes/versions. + +## Security/hardening +- Non-root images, read-only rootfs, drop NET_RAW, seccomp default. +- Telemetry disabled; no registry pulls at runtime. +- mTLS between indexer and dependencies (documented values). + +## Evidence to capture +- Image digests, SBOM hashes, cosign verification logs. +- Bundle `SHA256SUMS` and signed manifest. +- Helm/Compose render outputs (short). + +## Owners +- DevOps Guild (build/pipeline) +- Graph Indexer Guild (chart/values) +- Platform Security (signing policy) diff --git a/ops/devops/scanner-java/release-plan.md b/ops/devops/scanner-java/release-plan.md new file mode 100644 index 000000000..65c45450f --- /dev/null +++ b/ops/devops/scanner-java/release-plan.md @@ -0,0 +1,48 @@ +# Java Analyzer Release Plan (DEVOPS-SCANNER-JAVA-21-011-REL) + +## Goal +Publish the Java analyzer plug-in with signed artifacts and offline-ready bundles for CLI/Offline Kit. + +## Inputs +- Analyzer JAR(s) + native helpers from dev task 21-011. +- SBOM (SPDX JSON) for plugin + native components. +- Test suite outputs (unit + integration). + +## Artifacts +- OCI image (optional) or zip bundle containing: + - `analyzer.jar` + - `lib/` natives (if any) + - `LICENSE`, `NOTICE` + - `SBOM` (spdx.json) + - `SIGNATURES` (cosign/PGP) +- Cosign attestations for OCI/zip (provenance + SBOM). +- Checksums: `SHA256SUMS`, `SHA256SUMS.sig`. +- Offline kit slice: tarball with bundle + attestations + SBOM. + +## Pipeline steps +1) **Build**: run gradle/mvn with `--offline` using vendored deps; produce JAR + natives. +2) **SBOM**: `syft packages -o spdx-json` over build output. +3) **Package**: zip bundle with fixed ordering (`zip -X`) and normalized timestamps (`SOURCE_DATE_EPOCH`). +4) **Sign**: + - cosign sign blob (zip) and/or image. + - generate in-toto provenance (SLSA level 1) referencing git commit + toolchain hashes. +5) **Checksums**: create `SHA256SUMS` and sign with cosign/PGP. +6) **Verify stage**: pipeline step runs `cosign verify-blob`, `sha256sum --check`, and `syft validate spdx`. +7) **Publish**: + - Upload to artifact store (release bucket) with metadata (version, commit, digest). + - Produce offline kit slice tarball (`scanner-java--offline.tgz`) containing bundle, SBOM, attestations, checksums. + +## Security/hardening +- Non-root build container; disable gradle/mvn network (`--offline`). +- Strip debug info unless required; ensure reproducible JAR (sorted entries, normalized timestamps). +- Telemetry disabled. + +## Evidence to capture +- Bundle SHA256, cosign signatures, provenance statement. +- SBOM hash. +- Verification logs from pipeline. + +## Owners +- Build/pipeline: DevOps Guild +- Signing policy: Platform Security +- Consumer integration: CLI Guild / Offline Kit Guild diff --git a/ops/devops/telemetry/tests/manifest-valid.json b/ops/devops/telemetry/tests/manifest-valid.json new file mode 100644 index 000000000..9308d2718 --- /dev/null +++ b/ops/devops/telemetry/tests/manifest-valid.json @@ -0,0 +1,26 @@ +{ + "schemaVersion": "1.0.0", + "bundleId": "00000000-0000-0000-0000-000000000001", + "createdAt": "2025-12-01T00:00:00Z", + "profileHash": "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", + "collectorVersion": "otelcol/1.0.0", + "sealedMode": true, + "redactionManifest": "redaction-manifest.json", + "manifestHashAlgorithm": "sha256", + "timeAnchor": { + "type": "rfc3161", + "value": "dummy-token" + }, + "artifacts": [ + { + "path": "logs.ndjson", + "sha256": "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", + "mediaType": "application/x-ndjson", + "size": 123 + } + ], + "dsseEnvelope": { + "hash": "cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc", + "location": "bundle.dsse.json" + } +} diff --git a/ops/devops/telemetry/tests/telemetry-bundle.sha256 b/ops/devops/telemetry/tests/telemetry-bundle.sha256 new file mode 100644 index 000000000..b6f74988e --- /dev/null +++ b/ops/devops/telemetry/tests/telemetry-bundle.sha256 @@ -0,0 +1 @@ +6e3fedbf183aece5dfa14a90ebce955e2887d36747c424e628dc2cc03bcb0ed3 ops/devops/telemetry/tests/manifest-valid.json diff --git a/ops/devops/telemetry/verify-telemetry-bundle.sh b/ops/devops/telemetry/verify-telemetry-bundle.sh new file mode 100644 index 000000000..ae8b40882 --- /dev/null +++ b/ops/devops/telemetry/verify-telemetry-bundle.sh @@ -0,0 +1,69 @@ +#!/usr/bin/env bash +set -euo pipefail + +# Minimal offline verifier for telemetry bundles (v1) +# Exits: +# 0 success +# 21 checksum/manifest missing +# 22 checksum mismatch +# 23 schema validation failed + +BUNDLE=${1:-} +if [[ -z "$BUNDLE" ]]; then + echo "Usage: $0 path/to/telemetry-bundle.tar" >&2 + exit 64 +fi + +WORKDIR=$(mktemp -d) +cleanup() { rm -rf "$WORKDIR"; } +trap cleanup EXIT + +tar --extract --file "$BUNDLE" --directory "$WORKDIR" + +MANIFEST="$WORKDIR/telemetry-bundle.json" +HASHES="$WORKDIR/telemetry-bundle.sha256" + +if [[ ! -f "$MANIFEST" || ! -f "$HASHES" ]]; then + echo "Missing manifest or checksum file." >&2 + exit 21 +fi + +# Verify checksums +pushd "$WORKDIR" >/dev/null +if ! sha256sum --quiet --check telemetry-bundle.sha256; then + echo "Checksum mismatch." >&2 + exit 22 +fi +popd >/dev/null + +# JSON schema validation (optional if jsonschema not present). +if command -v python >/dev/null 2>&1; then + SCHEMA_DIR="$(cd "$(dirname "$0")/../../docs/modules/telemetry/schemas" && pwd)" + SCHEMA_FILE="$SCHEMA_DIR/telemetry-bundle.schema.json" + if [[ -f "$SCHEMA_FILE" ]]; then + python - "$MANIFEST" "$SCHEMA_FILE" <<'PY' +import json, sys +from jsonschema import validate, Draft202012Validator + +manifest_path = sys.argv[1] +schema_path = sys.argv[2] +with open(manifest_path, 'r', encoding='utf-8') as f: + manifest = json.load(f) +with open(schema_path, 'r', encoding='utf-8') as f: + schema = json.load(f) +Draft202012Validator.check_schema(schema) +validate(manifest, schema) +PY + if [[ $? -ne 0 ]]; then + echo "Schema validation failed." >&2 + exit 23 + fi + else + echo "Schema file not found ($SCHEMA_FILE); skipping validation." >&2 + fi +else + echo "jsonschema validation skipped (requires python + jsonschema)." >&2 +fi + +echo "Telemetry bundle verified." >&2 +exit 0 diff --git a/scripts/run-node-phase22-smoke.sh b/scripts/run-node-phase22-smoke.sh index 3f7baf809..33347b8e8 100644 --- a/scripts/run-node-phase22-smoke.sh +++ b/scripts/run-node-phase22-smoke.sh @@ -4,14 +4,22 @@ ROOT_DIR="$(cd "$(dirname "$0")/.." && pwd)" export DOTNET_CLI_HOME="${DOTNET_CLI_HOME:-${ROOT_DIR}/.dotnet-cli}" export DOTNET_SKIP_FIRST_TIME_EXPERIENCE=1 export DOTNET_CLI_TELEMETRY_OPTOUT=1 +export DOTNET_NOLOGO=1 +export DOTNET_MULTILEVEL_LOOKUP=0 +export MSBUILDDISABLENODEREUSE=1 +export DOTNET_HOST_DISABLE_RESOLVER_FALLBACK=1 PROJECT="${ROOT_DIR}/src/Scanner/__Tests/StellaOps.Scanner.Analyzers.Lang.Node.SmokeTests/StellaOps.Scanner.Analyzers.Lang.Node.SmokeTests.csproj" RESTORE_SRC="${ROOT_DIR}/local-nugets" mkdir -p "$DOTNET_CLI_HOME" -DOTNET_RESTORE_ARGS=("restore" "$PROJECT" "--no-cache" "--disable-parallel" "/p:RestoreSources=${RESTORE_SRC}") -DOTNET_TEST_ARGS=("test" "$PROJECT" "-c" "Release" "--no-build" "--no-restore" "--filter" "Phase22_Fixture_Matches_Golden" "--logger" "trx" "--results-directory" "${ROOT_DIR}/TestResults/phase22-smoke" "/p:RestoreSources=${RESTORE_SRC}" "/p:DisableSdkResolverCache=true") +DOTNET_RESTORE_ARGS=("restore" "$PROJECT" "--no-cache" "--disable-parallel" "/p:RestoreSources=${RESTORE_SRC}" "/p:DisableSdkResolverCache=true" "/p:DisableImplicitNuGetFallbackFolder=true" "/p:RestoreNoCache=true") +DOTNET_BUILD_ARGS=("build" "$PROJECT" "-c" "Release" "--no-restore" "/p:RestoreSources=${RESTORE_SRC}" "/p:DisableSdkResolverCache=true" "/p:DisableImplicitNuGetFallbackFolder=true") +DOTNET_TEST_ARGS=("test" "$PROJECT" "-c" "Release" "--no-build" "--no-restore" "--filter" "Phase22_Fixture_Matches_Golden" "--logger" "trx" "--results-directory" "${ROOT_DIR}/TestResults/phase22-smoke" "/p:RestoreSources=${RESTORE_SRC}" "/p:DisableSdkResolverCache=true" "/p:DisableImplicitNuGetFallbackFolder=true") echo "[phase22-smoke] restoring from ${RESTORE_SRC} ..." dotnet "${DOTNET_RESTORE_ARGS[@]}" +echo "[phase22-smoke] building smoke project ..." +dotnet "${DOTNET_BUILD_ARGS[@]}" + echo "[phase22-smoke] running test ..." dotnet "${DOTNET_TEST_ARGS[@]}" diff --git a/src/Bench/StellaOps.Bench/Graph/run_graph_bench.sh b/src/Bench/StellaOps.Bench/Graph/run_graph_bench.sh new file mode 100644 index 000000000..58a87c8db --- /dev/null +++ b/src/Bench/StellaOps.Bench/Graph/run_graph_bench.sh @@ -0,0 +1,24 @@ +#!/usr/bin/env bash +set -euo pipefail + +ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +# Repo root is four levels up from Graph/ +REPO_ROOT="$(cd "${ROOT}/../../../.." && pwd)" +FIXTURES_ROOT="${FIXTURES_ROOT:-${REPO_ROOT}/samples/graph/interim}" +OUT_DIR="${OUT_DIR:-$ROOT/results}" +SAMPLES="${SAMPLES:-100}" + +mkdir -p "${OUT_DIR}" + +run_one() { + local fixture="$1" + local name + name="$(basename "${fixture}")" + local out_file="${OUT_DIR}/${name}.json" + python "${ROOT}/graph_bench.py" --fixture "${fixture}" --output "${out_file}" --samples "${SAMPLES}" +} + +run_one "${FIXTURES_ROOT}/graph-50k" +run_one "${FIXTURES_ROOT}/graph-100k" + +echo "Graph bench complete. Results in ${OUT_DIR}" diff --git a/src/Bench/StellaOps.Bench/Graph/ui_bench_driver.mjs b/src/Bench/StellaOps.Bench/Graph/ui_bench_driver.mjs new file mode 100644 index 000000000..274ce4dc6 --- /dev/null +++ b/src/Bench/StellaOps.Bench/Graph/ui_bench_driver.mjs @@ -0,0 +1,50 @@ +#!/usr/bin/env node +/** + * ui_bench_driver.mjs + * + * Reads scenarios and fixture manifest, and emits a deterministic run plan. + * This is browser-free; intended to be wrapped by Playwright later. + */ +import fs from "fs"; +import path from "path"; + +function readJson(p) { + return JSON.parse(fs.readFileSync(p, "utf-8")); +} + +function buildPlan(scenarios, manifest, fixtureName) { + const now = new Date().toISOString(); + return { + version: "1.0.0", + fixture: fixtureName, + manifestHash: manifest?.hashes || {}, + timestamp: now, + steps: scenarios.map((s, idx) => ({ + order: idx + 1, + id: s.id, + name: s.name, + actions: s.steps, + })), + }; +} + +function main() { + const fixtureDir = process.argv[2]; + const scenariosPath = process.argv[3]; + const outputPath = process.argv[4]; + if (!fixtureDir || !scenariosPath || !outputPath) { + console.error("usage: ui_bench_driver.mjs "); + process.exit(1); + } + + const manifestPath = path.join(fixtureDir, "manifest.json"); + const manifest = fs.existsSync(manifestPath) ? readJson(manifestPath) : {}; + const scenarios = readJson(scenariosPath).scenarios || []; + + const plan = buildPlan(scenarios, manifest, path.basename(fixtureDir)); + fs.mkdirSync(path.dirname(outputPath), { recursive: true }); + fs.writeFileSync(outputPath, JSON.stringify(plan, null, 2)); + console.log(`Wrote plan to ${outputPath}`); +} + +main(); diff --git a/src/Bench/StellaOps.Bench/Graph/ui_bench_plan.md b/src/Bench/StellaOps.Bench/Graph/ui_bench_plan.md new file mode 100644 index 000000000..26686a27b --- /dev/null +++ b/src/Bench/StellaOps.Bench/Graph/ui_bench_plan.md @@ -0,0 +1,30 @@ +# Graph UI Bench Plan (BENCH-GRAPH-21-002) + +Purpose: provide a deterministic, headless flow for measuring graph UI interactions over large fixtures (50k/100k nodes). + +## Scope +- Use synthetic fixtures under `samples/graph/interim/` until canonical SAMPLES-GRAPH-24-003 lands. +- Drive a deterministic sequence of interactions: + 1) Load graph canvas with specified fixture. + 2) Pan to node `pkg-000001`. + 3) Zoom in 2×, zoom out 1×. + 4) Apply filter `name contains "package-0001"`. + 5) Select node, expand neighbors (depth 1), collapse. + 6) Toggle overlay layer (once available). +- Capture timings: initial render, filter apply, expand/collapse, overlay toggle. + +## Determinism rules +- Fixed seed for any randomized layouts (seed `424242`). +- Disable animations/transitions where possible; otherwise measure after `requestAnimationFrame` settle. +- No network calls; fixtures loaded from local file served by test harness. +- Stable viewport (width=1280, height=720), device scale factor 1. + +## Artifacts +- `ui_bench_scenarios.json` — canonical scenario list with step ids and notes. +- `ui_bench_driver.mjs` — helper that reads scenario + fixture manifest and emits a run plan (no browser dependency). Intended to be wrapped by a Playwright script later. +- Results format (proposed): NDJSON with `{stepId, name, durationMs, fixture, timestamp}`. + +## Next steps +- Bind `ui_bench_driver.mjs` into a Playwright harness when Graph UI build/serve target is available. +- Swap fixtures to SAMPLES-GRAPH-24-003 + overlay once schema finalized; keep scenario ids stable. +- Add CI slice to run the driver and validate scenario/fixture bindings (no browser) to keep determinism checked in commits. diff --git a/src/Bench/StellaOps.Bench/Graph/ui_bench_scenarios.json b/src/Bench/StellaOps.Bench/Graph/ui_bench_scenarios.json new file mode 100644 index 000000000..69c41fa7b --- /dev/null +++ b/src/Bench/StellaOps.Bench/Graph/ui_bench_scenarios.json @@ -0,0 +1,35 @@ +{ + "version": "1.0.0", + "scenarios": [ + { + "id": "load", + "name": "Load graph canvas", + "steps": ["navigate", "waitForRender"] + }, + { + "id": "pan-start-node", + "name": "Pan to pkg-000001", + "steps": ["panTo:pkg-000001"] + }, + { + "id": "zoom-in-out", + "name": "Zoom in twice, out once", + "steps": ["zoomIn", "zoomIn", "zoomOut"] + }, + { + "id": "filter-name", + "name": "Filter name contains package-0001", + "steps": ["setFilter:name=package-0001", "waitForRender"] + }, + { + "id": "expand-collapse", + "name": "Expand neighbors then collapse", + "steps": ["select:pkg-000001", "expandDepth:1", "collapseSelection"] + }, + { + "id": "overlay-toggle", + "name": "Toggle overlay layer", + "steps": ["toggleOverlay:on", "toggleOverlay:off"] + } + ] +} diff --git a/src/Bench/StellaOps.Bench/TASKS.md b/src/Bench/StellaOps.Bench/TASKS.md index be0bcfeb9..a8a26306a 100644 --- a/src/Bench/StellaOps.Bench/TASKS.md +++ b/src/Bench/StellaOps.Bench/TASKS.md @@ -4,3 +4,4 @@ | --- | --- | --- | --- | --- | | BENCH-DETERMINISM-401-057 | DONE (2025-11-26) | SPRINT_0512_0001_0001_bench | Determinism harness and mock scanner added under `src/Bench/StellaOps.Bench/Determinism`; manifests + sample inputs included. | `src/Bench/StellaOps.Bench/Determinism/results` (generated) | | BENCH-GRAPH-21-001 | DOING (2025-12-01) | SPRINT_0512_0001_0001_bench | Added interim graph bench harness (`Graph/graph_bench.py`) using synthetic 50k/100k fixtures; measures adjacency build + depth-3 reach; pending overlay schema for final fixture integration. | `src/Bench/StellaOps.Bench/Graph` | +| BENCH-GRAPH-21-002 | DOING (2025-12-01) | SPRINT_0512_0001_0001_bench | Added Graph UI bench scaffold (scenarios JSON + driver + plan) using interim fixtures; awaits overlay schema/UI target for Playwright binding and timing collection. | `src/Bench/StellaOps.Bench/Graph` | diff --git a/src/Cli/StellaOps.Cli/Commands/CommandHandlers.cs b/src/Cli/StellaOps.Cli/Commands/CommandHandlers.cs index 8eb6fd7c3..2738e45c4 100644 --- a/src/Cli/StellaOps.Cli/Commands/CommandHandlers.cs +++ b/src/Cli/StellaOps.Cli/Commands/CommandHandlers.cs @@ -48,17 +48,43 @@ namespace StellaOps.Cli.Commands; internal static class CommandHandlers { private const string KmsPassphraseEnvironmentVariable = "STELLAOPS_KMS_PASSPHRASE"; - private static readonly JsonSerializerOptions KmsJsonOptions = new(JsonSerializerDefaults.Web) - { - WriteIndented = true - }; + private static readonly JsonSerializerOptions KmsJsonOptions = new(JsonSerializerDefaults.Web) + { + WriteIndented = true + }; + + private static async Task VerifyBundleAsync(string path, ILogger logger, CancellationToken cancellationToken) + { + // Simple SHA256 check using sidecar .sha256 file if present; fail closed on mismatch. + var shaPath = path + ".sha256"; + if (!File.Exists(shaPath)) + { + logger.LogError("Checksum file missing for bundle {Bundle}. Expected sidecar {Sidecar}.", path, shaPath); + Environment.ExitCode = 21; + throw new InvalidOperationException("Checksum file missing"); + } + + var expected = (await File.ReadAllTextAsync(shaPath, cancellationToken).ConfigureAwait(false)).Trim(); + using var stream = File.OpenRead(path); + var hash = await SHA256.HashDataAsync(stream, cancellationToken).ConfigureAwait(false); + var actual = Convert.ToHexString(hash).ToLowerInvariant(); + + if (!string.Equals(expected, actual, StringComparison.OrdinalIgnoreCase)) + { + logger.LogError("Checksum mismatch for {Bundle}. Expected {Expected} but found {Actual}", path, expected, actual); + Environment.ExitCode = 22; + throw new InvalidOperationException("Checksum verification failed"); + } + + logger.LogInformation("Checksum verified for {Bundle}", path); + } - public static async Task HandleScannerDownloadAsync( - IServiceProvider services, - string channel, - string? output, - bool overwrite, - bool install, + public static async Task HandleScannerDownloadAsync( + IServiceProvider services, + string channel, + string? output, + bool overwrite, + bool install, bool verbose, CancellationToken cancellationToken) { @@ -88,24 +114,29 @@ internal static class CommandHandlers CliMetrics.RecordScannerDownload(channel, result.FromCache); - if (install) - { - var installer = scope.ServiceProvider.GetRequiredService(); - await installer.InstallAsync(result.Path, verbose, cancellationToken).ConfigureAwait(false); - CliMetrics.RecordScannerInstall(channel); - } + if (install) + { + await VerifyBundleAsync(result.Path, logger, cancellationToken).ConfigureAwait(false); + + var installer = scope.ServiceProvider.GetRequiredService(); + await installer.InstallAsync(result.Path, verbose, cancellationToken).ConfigureAwait(false); + CliMetrics.RecordScannerInstall(channel); + } Environment.ExitCode = 0; - } - catch (Exception ex) - { - logger.LogError(ex, "Failed to download scanner bundle."); - Environment.ExitCode = 1; - } - finally - { - verbosity.MinimumLevel = previousLevel; - } + } + catch (Exception ex) + { + logger.LogError(ex, "Failed to download scanner bundle."); + if (Environment.ExitCode == 0) + { + Environment.ExitCode = 1; + } + } + finally + { + verbosity.MinimumLevel = previousLevel; + } } public static async Task HandleTaskRunnerSimulateAsync( diff --git a/src/Cli/StellaOps.Cli/Configuration/CliProfile.cs b/src/Cli/StellaOps.Cli/Configuration/CliProfile.cs index d60c4aeec..9347a4d05 100644 --- a/src/Cli/StellaOps.Cli/Configuration/CliProfile.cs +++ b/src/Cli/StellaOps.Cli/Configuration/CliProfile.cs @@ -107,9 +107,9 @@ public sealed class CliProfileStore public Dictionary Profiles { get; init; } = new(StringComparer.OrdinalIgnoreCase); /// - /// Default telemetry opt-in status. + /// Default telemetry opt-in status. Defaults to false for privacy. /// - public bool? TelemetryEnabled { get; set; } + public bool TelemetryEnabled { get; set; } = false; } /// @@ -225,7 +225,7 @@ public sealed class CliProfileManager public async Task SetTelemetryEnabledAsync(bool? enabled, CancellationToken cancellationToken = default) { var store = await GetStoreAsync(cancellationToken).ConfigureAwait(false); - store.TelemetryEnabled = enabled; + store.TelemetryEnabled = enabled ?? false; await SaveStoreAsync(store, cancellationToken).ConfigureAwait(false); } @@ -247,7 +247,18 @@ public sealed class CliProfileManager { await using var stream = File.OpenRead(_profilesFilePath); var store = await JsonSerializer.DeserializeAsync(stream, JsonOptions, cancellationToken).ConfigureAwait(false); - return store ?? new CliProfileStore(); + if (store is null) + { + return new CliProfileStore(); + } + + // Ensure default-off if older files had telemetryEnabled missing/null. + if (!store.TelemetryEnabled) + { + store.TelemetryEnabled = false; + } + + return store; } catch (JsonException) { diff --git a/src/Cli/StellaOps.Cli/Services/BackendOperationsClient.cs b/src/Cli/StellaOps.Cli/Services/BackendOperationsClient.cs index cc14bbb90..a40a7e4fb 100644 --- a/src/Cli/StellaOps.Cli/Services/BackendOperationsClient.cs +++ b/src/Cli/StellaOps.Cli/Services/BackendOperationsClient.cs @@ -4512,11 +4512,11 @@ internal sealed class BackendOperationsClient : IBackendOperationsClient } // CLI-SDK-64-001: SDK update operations - public async Task CheckSdkUpdatesAsync(SdkUpdateRequest request, CancellationToken cancellationToken) - { - ArgumentNullException.ThrowIfNull(request); - - EnsureBackendConfigured(); + public async Task CheckSdkUpdatesAsync(SdkUpdateRequest request, CancellationToken cancellationToken) + { + ArgumentNullException.ThrowIfNull(request); + + EnsureBackendConfigured(); OfflineModeGuard.ThrowIfOffline("sdk update"); var queryParams = new List(); @@ -4554,9 +4554,9 @@ internal sealed class BackendOperationsClient : IBackendOperationsClient }; } - var result = await response.Content.ReadFromJsonAsync(JsonOptions, cancellationToken).ConfigureAwait(false); - return result ?? new SdkUpdateResponse { Success = false, Error = "Empty response" }; - } + var result = await response.Content.ReadFromJsonAsync(JsonOptions, cancellationToken).ConfigureAwait(false); + return result ?? new SdkUpdateResponse { Success = false, Error = "Empty response" }; + } public async Task ListInstalledSdksAsync(string? language, string? tenant, CancellationToken cancellationToken) { diff --git a/src/Cli/__Tests/StellaOps.Cli.Tests/Commands/ScannerDownloadVerifyTests.cs b/src/Cli/__Tests/StellaOps.Cli.Tests/Commands/ScannerDownloadVerifyTests.cs new file mode 100644 index 000000000..e165756f1 --- /dev/null +++ b/src/Cli/__Tests/StellaOps.Cli.Tests/Commands/ScannerDownloadVerifyTests.cs @@ -0,0 +1,60 @@ +using System; +using System.IO; +using System.Threading; +using System.Threading.Tasks; +using Microsoft.Extensions.Logging.Abstractions; +using StellaOps.Cli.Commands; +using Xunit; + +namespace StellaOps.Cli.Tests.Commands; + +public sealed class ScannerDownloadVerifyTests +{ + [Fact] + public async Task VerifyBundleAsync_Succeeds_WhenHashMatches() + { + var tmp = Path.Combine(Path.GetTempPath(), $"stellaops-cli-{Guid.NewGuid():N}"); + Directory.CreateDirectory(tmp); + var bundle = Path.Combine(tmp, "scanner.tgz"); + await File.WriteAllTextAsync(bundle, "hello"); + + var hash = Convert.ToHexString(System.Security.Cryptography.SHA256.HashData(File.ReadAllBytes(bundle))).ToLowerInvariant(); + await File.WriteAllTextAsync(bundle + ".sha256", hash); + + await CommandHandlersTestShim.VerifyBundlePublicAsync(bundle, NullLogger.Instance, CancellationToken.None); + } + + [Fact] + public async Task VerifyBundleAsync_Throws_WhenHashMismatch() + { + var tmp = Path.Combine(Path.GetTempPath(), $"stellaops-cli-{Guid.NewGuid():N}"); + Directory.CreateDirectory(tmp); + var bundle = Path.Combine(tmp, "scanner.tgz"); + await File.WriteAllTextAsync(bundle, "hello"); + await File.WriteAllTextAsync(bundle + ".sha256", "deadbeef"); + + await Assert.ThrowsAsync(() => + CommandHandlersTestShim.VerifyBundlePublicAsync(bundle, NullLogger.Instance, CancellationToken.None)); + } + + [Fact] + public async Task VerifyBundleAsync_Throws_WhenChecksumMissing() + { + var tmp = Path.Combine(Path.GetTempPath(), $"stellaops-cli-{Guid.NewGuid():N}"); + Directory.CreateDirectory(tmp); + var bundle = Path.Combine(tmp, "scanner.tgz"); + await File.WriteAllTextAsync(bundle, "hello"); + + await Assert.ThrowsAsync(() => + CommandHandlersTestShim.VerifyBundlePublicAsync(bundle, NullLogger.Instance, CancellationToken.None)); + } +} + +internal static class CommandHandlersTestShim +{ + public static Task VerifyBundlePublicAsync(string path, ILogger logger, CancellationToken token) + => typeof(CommandHandlers) + .GetMethod(\"VerifyBundleAsync\", System.Reflection.BindingFlags.NonPublic | System.Reflection.BindingFlags.Static)! + .Invoke(null, new object[] { path, logger, token }) as Task + ?? Task.CompletedTask; +} diff --git a/src/Cli/__Tests/StellaOps.Cli.Tests/Configuration/TelemetryDefaultsTests.cs b/src/Cli/__Tests/StellaOps.Cli.Tests/Configuration/TelemetryDefaultsTests.cs new file mode 100644 index 000000000..dca097cc4 --- /dev/null +++ b/src/Cli/__Tests/StellaOps.Cli.Tests/Configuration/TelemetryDefaultsTests.cs @@ -0,0 +1,35 @@ +using System.IO; +using System.Threading.Tasks; +using StellaOps.Cli.Configuration; +using Xunit; + +namespace StellaOps.Cli.Tests.Configuration; + +public sealed class TelemetryDefaultsTests +{ + [Fact] + public async Task NewStore_DefaultsTelemetryToOff() + { + var tempDir = Path.Combine(Path.GetTempPath(), $"stellaops-cli-telemetry-{Path.GetRandomFileName()}"); + Directory.CreateDirectory(tempDir); + + var manager = new CliProfileManager(tempDir); + var store = await manager.GetStoreAsync(); + + Assert.False(store.TelemetryEnabled); + } + + [Fact] + public async Task SetTelemetryEnabled_PersistsValue() + { + var tempDir = Path.Combine(Path.GetTempPath(), $"stellaops-cli-telemetry-{Path.GetRandomFileName()}"); + Directory.CreateDirectory(tempDir); + + var manager = new CliProfileManager(tempDir); + + await manager.SetTelemetryEnabledAsync(true); + var store = await manager.GetStoreAsync(); + + Assert.True(store.TelemetryEnabled); + } +} diff --git a/src/Cli/__Tests/StellaOps.Cli.Tests/Contracts/CliSpecTests.cs b/src/Cli/__Tests/StellaOps.Cli.Tests/Contracts/CliSpecTests.cs new file mode 100644 index 000000000..f80ef4f91 --- /dev/null +++ b/src/Cli/__Tests/StellaOps.Cli.Tests/Contracts/CliSpecTests.cs @@ -0,0 +1,41 @@ +using System.IO; +using System.Linq; +using System.Threading.Tasks; +using Xunit; + +namespace StellaOps.Cli.Tests.Contracts; + +public sealed class CliSpecTests +{ + private static readonly string SpecPath = Path.Combine("docs", "modules", "cli", "contracts", "cli-spec-v1.yaml"); + + [Fact] + public async Task Spec_Exists_And_Has_PrivacyDefaults() + { + Assert.True(File.Exists(SpecPath), $"Spec file missing: {SpecPath}"); + + var text = await File.ReadAllTextAsync(SpecPath); + + Assert.Contains("defaultEnabled: false", text); + Assert.Contains("checksumRequired: true", text); + Assert.Contains("cosignVerifyDefault: true", text); + } + + [Fact] + public async Task Spec_Has_Pinned_Buildx_Digest() + { + var text = await File.ReadAllLinesAsync(SpecPath); + var digestLine = text.FirstOrDefault(l => l.TrimStart().StartsWith("imageDigest:")); + + Assert.False(string.IsNullOrWhiteSpace(digestLine)); + Assert.DoesNotContain("TO-BE-PINNED", digestLine); + } + + [Fact] + public async Task Spec_Declares_Install_ExitCodes() + { + var text = await File.ReadAllTextAsync(SpecPath); + Assert.Contains("21: checksum-file-missing", text); + Assert.Contains("22: checksum-mismatch", text); + } +} diff --git a/src/Orchestrator/StellaOps.Orchestrator/StellaOps.Orchestrator.Core/Domain/AuditEntry.cs b/src/Orchestrator/StellaOps.Orchestrator/StellaOps.Orchestrator.Core/Domain/AuditEntry.cs index 856cc2a2a..9a596f92e 100644 --- a/src/Orchestrator/StellaOps.Orchestrator/StellaOps.Orchestrator.Core/Domain/AuditEntry.cs +++ b/src/Orchestrator/StellaOps.Orchestrator/StellaOps.Orchestrator.Core/Domain/AuditEntry.cs @@ -1,3 +1,5 @@ +using StellaOps.Orchestrator.Core.Hashing; + namespace StellaOps.Orchestrator.Core.Domain; /// @@ -90,9 +92,22 @@ public sealed record AuditEntry( var entryId = Guid.NewGuid(); var occurredAt = DateTimeOffset.UtcNow; - // Compute content hash from entry data - var contentToHash = $"{entryId}|{tenantId}|{eventType}|{resourceType}|{resourceId}|{actorId}|{actorType}|{description}|{oldState}|{newState}|{occurredAt:O}|{sequenceNumber}"; - var contentHash = ComputeSha256(contentToHash); + // Compute canonical hash from immutable content + var contentHash = CanonicalJsonHasher.ComputeCanonicalSha256(new + { + entryId, + tenantId, + eventType, + resourceType, + resourceId, + actorId, + actorType, + description, + oldState, + newState, + occurredAt, + sequenceNumber + }); return new AuditEntry( EntryId: entryId, @@ -122,8 +137,21 @@ public sealed record AuditEntry( /// public bool VerifyIntegrity() { - var contentToHash = $"{EntryId}|{TenantId}|{EventType}|{ResourceType}|{ResourceId}|{ActorId}|{ActorType}|{Description}|{OldState}|{NewState}|{OccurredAt:O}|{SequenceNumber}"; - var computed = ComputeSha256(contentToHash); + var computed = CanonicalJsonHasher.ComputeCanonicalSha256(new + { + EntryId, + TenantId, + EventType, + ResourceType, + ResourceId, + ActorId, + ActorType, + Description, + OldState, + NewState, + OccurredAt, + SequenceNumber + }); return string.Equals(ContentHash, computed, StringComparison.OrdinalIgnoreCase); } diff --git a/src/Orchestrator/StellaOps.Orchestrator/StellaOps.Orchestrator.Core/Domain/Replay/ReplayManifest.cs b/src/Orchestrator/StellaOps.Orchestrator/StellaOps.Orchestrator.Core/Domain/Replay/ReplayManifest.cs new file mode 100644 index 000000000..d265bf109 --- /dev/null +++ b/src/Orchestrator/StellaOps.Orchestrator/StellaOps.Orchestrator.Core/Domain/Replay/ReplayManifest.cs @@ -0,0 +1,72 @@ +using System.Collections.Immutable; +using System.Text.Json.Serialization; +using StellaOps.Orchestrator.Core.Hashing; + +namespace StellaOps.Orchestrator.Core.Domain.Replay; + +/// +/// Deterministic replay manifest that captures all inputs required to faithfully re-run a job. +/// Aligns with replay-manifest.schema.json and is hashed via canonical JSON. +/// +public sealed record ReplayManifest( + [property: JsonPropertyName("schemaVersion")] string SchemaVersion, + [property: JsonPropertyName("jobId")] string JobId, + [property: JsonPropertyName("replayOf")] string ReplayOf, + [property: JsonPropertyName("createdAt")] DateTimeOffset CreatedAt, + [property: JsonPropertyName("reason")] string? Reason, + [property: JsonPropertyName("inputs")] ReplayInputs Inputs, + [property: JsonPropertyName("artifacts")] ImmutableArray Artifacts) +{ + public static ReplayManifest Create( + string jobId, + string replayOf, + ReplayInputs inputs, + IEnumerable? artifacts = null, + string schemaVersion = "orch.replay.v1", + string? reason = null, + DateTimeOffset? createdAt = null) + { + ArgumentException.ThrowIfNullOrWhiteSpace(jobId); + ArgumentException.ThrowIfNullOrWhiteSpace(replayOf); + ArgumentNullException.ThrowIfNull(inputs); + + return new ReplayManifest( + SchemaVersion: schemaVersion, + JobId: jobId, + ReplayOf: replayOf, + CreatedAt: createdAt ?? DateTimeOffset.UtcNow, + Reason: string.IsNullOrWhiteSpace(reason) ? null : reason, + Inputs: inputs, + Artifacts: artifacts is null ? ImmutableArray.Empty : ImmutableArray.CreateRange(artifacts)); + } + + /// + /// Deterministic SHA-256 over canonical JSON representation of the manifest. + /// + public string ComputeHash() => CanonicalJsonHasher.ComputeCanonicalSha256(this); +} + +public sealed record ReplayInputs( + [property: JsonPropertyName("policyHash")] string PolicyHash, + [property: JsonPropertyName("graphRevisionId")] string GraphRevisionId, + [property: JsonPropertyName("latticeHash")] string? LatticeHash, + [property: JsonPropertyName("toolImages")] ImmutableArray ToolImages, + [property: JsonPropertyName("seeds")] ReplaySeeds Seeds, + [property: JsonPropertyName("timeSource")] ReplayTimeSource TimeSource, + [property: JsonPropertyName("env")] ImmutableDictionary Env); + +public sealed record ReplaySeeds( + [property: JsonPropertyName("rng")] int? Rng, + [property: JsonPropertyName("sampling")] int? Sampling); + +public sealed record ReplayArtifact( + [property: JsonPropertyName("name")] string Name, + [property: JsonPropertyName("digest")] string Digest, + [property: JsonPropertyName("mediaType")] string? MediaType); + +[JsonConverter(typeof(JsonStringEnumConverter))] +public enum ReplayTimeSource +{ + monotonic, + wall +} diff --git a/src/Orchestrator/StellaOps.Orchestrator/StellaOps.Orchestrator.Core/Hashing/CanonicalJsonHasher.cs b/src/Orchestrator/StellaOps.Orchestrator/StellaOps.Orchestrator.Core/Hashing/CanonicalJsonHasher.cs new file mode 100644 index 000000000..21286b67c --- /dev/null +++ b/src/Orchestrator/StellaOps.Orchestrator/StellaOps.Orchestrator.Core/Hashing/CanonicalJsonHasher.cs @@ -0,0 +1,66 @@ +using System.Security.Cryptography; +using System.Text; +using System.Text.Json; +using System.Text.Json.Nodes; +using System.Text.Json.Serialization; + +namespace StellaOps.Orchestrator.Core.Hashing; + +/// +/// Produces deterministic, canonical JSON and hashes for orchestrator payloads (events, audit, manifests). +/// Keys are sorted lexicographically; arrays preserve order; nulls are retained; timestamps remain ISO 8601 with offsets. +/// +public static class CanonicalJsonHasher +{ + private static readonly JsonSerializerOptions SerializerOptions = new() + { + DefaultIgnoreCondition = JsonIgnoreCondition.Never, + WriteIndented = false, + PropertyNamingPolicy = null, + Converters = { new JsonStringEnumConverter(JsonNamingPolicy.CamelCase) } + }; + + /// + /// Serialize the value to canonical JSON (sorted object keys, stable formatting). + /// + public static string ToCanonicalJson(T value) + { + var node = JsonSerializer.SerializeToNode(value, SerializerOptions) ?? new JsonObject(); + var ordered = OrderNode(node); + return ordered.ToJsonString(SerializerOptions); + } + + /// + /// Compute SHA-256 over canonical JSON (lowercase hex). + /// + public static string ComputeCanonicalSha256(T value) + { + var canonicalJson = ToCanonicalJson(value); + var bytes = Encoding.UTF8.GetBytes(canonicalJson); + var hash = SHA256.HashData(bytes); + return Convert.ToHexString(hash).ToLowerInvariant(); + } + + private static JsonNode OrderNode(JsonNode node) + { + switch (node) + { + case JsonObject obj: + var orderedObj = new JsonObject(); + foreach (var kvp in obj.OrderBy(x => x.Key, StringComparer.Ordinal)) + { + orderedObj.Add(kvp.Key, kvp.Value is null ? null : OrderNode(kvp.Value)); + } + return orderedObj; + case JsonArray arr: + var orderedArr = new JsonArray(); + foreach (var item in arr) + { + orderedArr.Add(item is null ? null : OrderNode(item)); + } + return orderedArr; + default: + return node; // primitives stay as-is + } + } +} diff --git a/src/Orchestrator/StellaOps.Orchestrator/StellaOps.Orchestrator.Core/Schemas/audit-bundle.schema.json b/src/Orchestrator/StellaOps.Orchestrator/StellaOps.Orchestrator.Core/Schemas/audit-bundle.schema.json new file mode 100644 index 000000000..af33973f3 --- /dev/null +++ b/src/Orchestrator/StellaOps.Orchestrator/StellaOps.Orchestrator.Core/Schemas/audit-bundle.schema.json @@ -0,0 +1,51 @@ +{ + "$id": "https://stellaops.dev/orchestrator/schemas/audit-bundle.schema.json", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Orchestrator Audit Bundle", + "type": "object", + "additionalProperties": false, + "required": ["manifestVersion", "bundleId", "createdAt", "entries"], + "properties": { + "manifestVersion": { "type": "string", "pattern": "^orch\.audit\.v[0-9]+$" }, + "bundleId": { "type": "string", "minLength": 1 }, + "createdAt": { "type": "string", "format": "date-time" }, + "tenantId": { "type": "string" }, + "hashAlgorithm": { "type": "string", "enum": ["sha256"] }, + "entries": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": ["entryId", "contentHash", "sequenceNumber", "occurredAt"], + "properties": { + "entryId": { "type": "string" }, + "contentHash": { "type": "string", "pattern": "^[a-f0-9]{64}$" }, + "previousEntryHash": { "type": "string", "pattern": "^[a-f0-9]{64}$" }, + "sequenceNumber": { "type": "integer", "minimum": 1 }, + "occurredAt": { "type": "string", "format": "date-time" }, + "resourceType": { "type": "string" }, + "resourceId": { "type": "string" }, + "eventType": { "type": "string" }, + "actorId": { "type": "string" }, + "actorType": { "type": "string" }, + "oldState": { "type": "string" }, + "newState": { "type": "string" }, + "metadata": { "type": "string" } + } + } + }, + "attachments": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": ["uri", "digest"], + "properties": { + "uri": { "type": "string" }, + "digest": { "type": "string", "pattern": "^[a-z0-9]+:[A-Fa-f0-9]+$" }, + "mediaType": { "type": "string" } + } + } + } + } +} diff --git a/src/Orchestrator/StellaOps.Orchestrator/StellaOps.Orchestrator.Core/Schemas/event-envelope.schema.json b/src/Orchestrator/StellaOps.Orchestrator/StellaOps.Orchestrator.Core/Schemas/event-envelope.schema.json new file mode 100644 index 000000000..21e74d4d1 --- /dev/null +++ b/src/Orchestrator/StellaOps.Orchestrator/StellaOps.Orchestrator.Core/Schemas/event-envelope.schema.json @@ -0,0 +1,95 @@ +{ + "$id": "https://stellaops.dev/orchestrator/schemas/event-envelope.schema.json", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Orchestrator Event Envelope", + "type": "object", + "additionalProperties": false, + "required": [ + "schemaVersion", + "eventId", + "eventType", + "occurredAt", + "idempotencyKey", + "tenantId", + "actor", + "job" + ], + "properties": { + "schemaVersion": { "type": "string", "pattern": "^orch\.event\.v[0-9]+$" }, + "eventId": { "type": "string", "minLength": 1 }, + "eventType": { "type": "string", "minLength": 1 }, + "occurredAt": { "type": "string", "format": "date-time" }, + "idempotencyKey": { "type": "string", "minLength": 1 }, + "correlationId": { "type": "string" }, + "tenantId": { "type": "string", "minLength": 1 }, + "projectId": { "type": "string" }, + "actor": { + "type": "object", + "additionalProperties": false, + "required": ["subject", "scopes"], + "properties": { + "subject": { "type": "string", "minLength": 1 }, + "scopes": { "type": "array", "items": { "type": "string" } } + } + }, + "job": { + "type": "object", + "additionalProperties": false, + "required": ["id", "type", "attempt", "status"], + "properties": { + "id": { "type": "string", "minLength": 1 }, + "type": { "type": "string", "minLength": 1 }, + "runId": { "type": "string" }, + "attempt": { "type": "integer", "minimum": 0 }, + "leaseId": { "type": "string" }, + "taskRunnerId": { "type": "string" }, + "status": { "type": "string" }, + "reason": { "type": "string" }, + "payloadDigest": { "type": "string" }, + "artifacts": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": ["uri", "digest"], + "properties": { + "uri": { "type": "string" }, + "digest": { "type": "string" }, + "mime": { "type": "string" } + } + } + }, + "provenance": { + "type": "object", + "additionalProperties": { "type": "string" } + } + } + }, + "metrics": { + "type": "object", + "additionalProperties": false, + "properties": { + "durationSeconds": { "type": "number", "minimum": 0 }, + "logStreamLagSeconds": { "type": "number", "minimum": 0 }, + "backoffSeconds": { "type": "number", "minimum": 0 } + } + }, + "notifier": { + "type": "object", + "additionalProperties": false, + "properties": { + "channel": { "type": "string" }, + "delivery": { "type": "string" }, + "replay": { + "type": "object", + "additionalProperties": false, + "required": ["ordinal", "total"], + "properties": { + "ordinal": { "type": "integer", "minimum": 0 }, + "total": { "type": "integer", "minimum": 1 } + } + } + } + } + } +} diff --git a/src/Orchestrator/StellaOps.Orchestrator/StellaOps.Orchestrator.Core/Schemas/replay-manifest.schema.json b/src/Orchestrator/StellaOps.Orchestrator/StellaOps.Orchestrator.Core/Schemas/replay-manifest.schema.json new file mode 100644 index 000000000..55ccab528 --- /dev/null +++ b/src/Orchestrator/StellaOps.Orchestrator/StellaOps.Orchestrator.Core/Schemas/replay-manifest.schema.json @@ -0,0 +1,55 @@ +{ + "$id": "https://stellaops.dev/orchestrator/schemas/replay-manifest.schema.json", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "Replay Manifest", + "type": "object", + "additionalProperties": false, + "required": ["schemaVersion", "jobId", "replayOf", "inputs"], + "properties": { + "schemaVersion": { "type": "string", "pattern": "^orch\.replay\.v[0-9]+$" }, + "jobId": { "type": "string" }, + "replayOf": { "type": "string" }, + "createdAt": { "type": "string", "format": "date-time" }, + "reason": { "type": "string" }, + "inputs": { + "type": "object", + "additionalProperties": false, + "required": ["policyHash", "graphRevisionId", "toolImages", "seeds"], + "properties": { + "policyHash": { "type": "string" }, + "graphRevisionId": { "type": "string" }, + "latticeHash": { "type": "string" }, + "toolImages": { + "type": "array", + "items": { "type": "string" } + }, + "seeds": { + "type": "object", + "additionalProperties": false, + "properties": { + "rng": { "type": "integer", "minimum": 0 }, + "sampling": { "type": "integer", "minimum": 0 } + } + }, + "timeSource": { "type": "string", "enum": ["monotonic", "wall"] }, + "env": { + "type": "object", + "additionalProperties": { "type": "string" } + } + } + }, + "artifacts": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": ["name", "digest"], + "properties": { + "name": { "type": "string" }, + "digest": { "type": "string" }, + "mediaType": { "type": "string" } + } + } + } + } +} diff --git a/src/Orchestrator/StellaOps.Orchestrator/StellaOps.Orchestrator.Core/Schemas/taskrunner-integrity.schema.json b/src/Orchestrator/StellaOps.Orchestrator/StellaOps.Orchestrator.Core/Schemas/taskrunner-integrity.schema.json new file mode 100644 index 000000000..4d50c191e --- /dev/null +++ b/src/Orchestrator/StellaOps.Orchestrator/StellaOps.Orchestrator.Core/Schemas/taskrunner-integrity.schema.json @@ -0,0 +1,51 @@ +{ + "$id": "https://stellaops.dev/orchestrator/schemas/taskrunner-integrity.schema.json", + "$schema": "https://json-schema.org/draft/2020-12/schema", + "title": "TaskRunner Integrity Capsule", + "type": "object", + "additionalProperties": false, + "required": ["jobId", "runId", "attempt", "artifacts", "logs"], + "properties": { + "jobId": { "type": "string" }, + "runId": { "type": "string" }, + "attempt": { "type": "integer", "minimum": 0 }, + "workerImage": { "type": "string" }, + "configHash": { "type": "string" }, + "artifacts": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": ["name", "digest", "sizeBytes"], + "properties": { + "name": { "type": "string" }, + "digest": { "type": "string", "pattern": "^[a-z0-9]+:[A-Fa-f0-9]+$" }, + "sizeBytes": { "type": "integer", "minimum": 0 }, + "mediaType": { "type": "string" } + } + } + }, + "logs": { + "type": "object", + "additionalProperties": false, + "required": ["digest", "sizeBytes"], + "properties": { + "digest": { "type": "string", "pattern": "^[a-z0-9]+:[A-Fa-f0-9]+$" }, + "sizeBytes": { "type": "integer", "minimum": 0 } + } + }, + "timeline": { + "type": "array", + "items": { + "type": "object", + "additionalProperties": false, + "required": ["type", "occurredAt"], + "properties": { + "type": { "type": "string" }, + "occurredAt": { "type": "string", "format": "date-time" }, + "traceId": { "type": "string" } + } + } + } + } +} diff --git a/src/Orchestrator/StellaOps.Orchestrator/StellaOps.Orchestrator.Tests/CanonicalJsonHasherTests.cs b/src/Orchestrator/StellaOps.Orchestrator/StellaOps.Orchestrator.Tests/CanonicalJsonHasherTests.cs new file mode 100644 index 000000000..262d79d71 --- /dev/null +++ b/src/Orchestrator/StellaOps.Orchestrator/StellaOps.Orchestrator.Tests/CanonicalJsonHasherTests.cs @@ -0,0 +1,54 @@ +using System.Text.Json; +using StellaOps.Orchestrator.Core.Domain; +using StellaOps.Orchestrator.Core.Hashing; + +namespace StellaOps.Orchestrator.Tests; + +public class CanonicalJsonHasherTests +{ + [Fact] + public void ProducesStableHash_WhenObjectPropertyOrderDiffers() + { + var first = new { b = 1, a = 2 }; + var second = new { a = 2, b = 1 }; + + var firstHash = CanonicalJsonHasher.ComputeCanonicalSha256(first); + var secondHash = CanonicalJsonHasher.ComputeCanonicalSha256(second); + + Assert.Equal(firstHash, secondHash); + } + + [Fact] + public void CanonicalJson_SortsKeysAndPreservesArrays() + { + var value = new + { + meta = new { z = 1, a = 2 }, + items = new[] { "first", "second" } + }; + + var json = CanonicalJsonHasher.ToCanonicalJson(value); + + // keys sorted inside meta + Assert.Equal("{\"items\":[\"first\",\"second\"],\"meta\":{\"a\":2,\"z\":1}}", json); + } + + [Fact] + public void AuditEntry_UsesCanonicalHash() + { + var entry = AuditEntry.Create( + tenantId: "tenant-1", + eventType: AuditEventType.JobCreated, + resourceType: "job", + resourceId: Guid.Parse("aaaaaaaa-bbbb-cccc-dddd-eeeeeeeeeeee"), + actorId: "user-1", + actorType: ActorType.User, + description: "created job"); + + Assert.True(entry.VerifyIntegrity()); + + // Changing description should invalidate hash + var tampered = entry with { Description = "tampered" }; + Assert.False(tampered.VerifyIntegrity()); + } +} diff --git a/src/Orchestrator/StellaOps.Orchestrator/StellaOps.Orchestrator.Tests/PackRun/PackRunStreamCoordinatorTests.cs b/src/Orchestrator/StellaOps.Orchestrator/StellaOps.Orchestrator.Tests/PackRun/PackRunStreamCoordinatorTests.cs index aa84e484e..d54b17698 100644 --- a/src/Orchestrator/StellaOps.Orchestrator/StellaOps.Orchestrator.Tests/PackRun/PackRunStreamCoordinatorTests.cs +++ b/src/Orchestrator/StellaOps.Orchestrator/StellaOps.Orchestrator.Tests/PackRun/PackRunStreamCoordinatorTests.cs @@ -203,7 +203,6 @@ public sealed class PackRunStreamCoordinatorTests public override void Dispose() { _state = WebSocketState.Closed; - base.Dispose(); } public override Task ReceiveAsync(ArraySegment buffer, CancellationToken cancellationToken) diff --git a/src/Orchestrator/StellaOps.Orchestrator/StellaOps.Orchestrator.Tests/ReplayManifestTests.cs b/src/Orchestrator/StellaOps.Orchestrator/StellaOps.Orchestrator.Tests/ReplayManifestTests.cs new file mode 100644 index 000000000..6e9301aa4 --- /dev/null +++ b/src/Orchestrator/StellaOps.Orchestrator/StellaOps.Orchestrator.Tests/ReplayManifestTests.cs @@ -0,0 +1,39 @@ +using System.Collections.Immutable; +using StellaOps.Orchestrator.Core.Domain.Replay; + +namespace StellaOps.Orchestrator.Tests; + +public class ReplayManifestTests +{ + [Fact] + public void ComputeHash_IsStableWithCanonicalOrdering() + { + var inputs = new ReplayInputs( + PolicyHash: "sha256:policy", + GraphRevisionId: "graph-42", + LatticeHash: "sha256:lattice", + ToolImages: ImmutableArray.Create("img:1", "img:2"), + Seeds: new ReplaySeeds(Rng: 1234, Sampling: 99), + TimeSource: ReplayTimeSource.monotonic, + Env: ImmutableDictionary.Create().Add("TZ", "UTC")); + + var manifestA = ReplayManifest.Create( + jobId: "job-123", + replayOf: "job-original", + inputs: inputs, + artifacts: new[] { new ReplayArtifact("ledger.ndjson", "sha256:abc", "application/x-ndjson") }, + createdAt: new DateTimeOffset(2025, 12, 1, 0, 0, 0, TimeSpan.Zero)); + + var manifestB = ReplayManifest.Create( + jobId: "job-123", + replayOf: "job-original", + inputs: inputs, + artifacts: new[] { new ReplayArtifact("ledger.ndjson", "sha256:abc", "application/x-ndjson") }, + createdAt: new DateTimeOffset(2025, 12, 1, 0, 0, 0, TimeSpan.Zero)); + + var hashA = manifestA.ComputeHash(); + var hashB = manifestB.ComputeHash(); + + Assert.Equal(hashA, hashB); + } +} diff --git a/src/Orchestrator/StellaOps.Orchestrator/StellaOps.Orchestrator.Tests/SchemaSmokeTests.cs b/src/Orchestrator/StellaOps.Orchestrator/StellaOps.Orchestrator.Tests/SchemaSmokeTests.cs new file mode 100644 index 000000000..6ecfcf87e --- /dev/null +++ b/src/Orchestrator/StellaOps.Orchestrator/StellaOps.Orchestrator.Tests/SchemaSmokeTests.cs @@ -0,0 +1,56 @@ +using System.Collections.Immutable; +using System.Text.Json; +using StellaOps.Orchestrator.Core; +using StellaOps.Orchestrator.Core.Hashing; + +namespace StellaOps.Orchestrator.Tests; + +public class SchemaSmokeTests +{ + [Theory] + [InlineData("event-envelope.schema.json")] + [InlineData("audit-bundle.schema.json")] + [InlineData("replay-manifest.schema.json")] + [InlineData("taskrunner-integrity.schema.json")] + public void Schemas_AreWellFormedJson(string schemaFile) + { + var path = Path.Combine(AppContext.BaseDirectory, "../../../StellaOps.Orchestrator.Core/Schemas", schemaFile); + Assert.True(File.Exists(path), $"Schema missing: {schemaFile}"); + + var text = File.ReadAllText(path); + using var doc = JsonDocument.Parse(text); + + Assert.True(doc.RootElement.TryGetProperty("$id", out _)); + Assert.True(doc.RootElement.TryGetProperty("title", out _)); + } + + [Fact] + public void CanonicalHash_For_EventEnvelope_IsStable() + { + var envelope = EventEnvelope.Create( + eventType: "job.completed", + tenantId: "tenant-a", + job: new EventJob( + Id: "job-123", + Type: "pack-run", + RunId: "run-1", + Attempt: 1, + LeaseId: "lease-9", + TaskRunnerId: "tr-1", + Status: "completed", + Reason: null, + PayloadDigest: "sha256:abc", + Artifacts: ImmutableArray.Create(), + Provenance: ImmutableDictionary.Empty), + actor: new EventActor("worker-go", ImmutableArray.Create("orch:job")), + occurredAt: new DateTimeOffset(2025, 12, 1, 12, 0, 0, TimeSpan.Zero), + eventId: "evt-1", + idempotencyKey: "fixed"); + + var hash1 = CanonicalJsonHasher.ComputeCanonicalSha256(envelope); + var hash2 = CanonicalJsonHasher.ComputeCanonicalSha256(envelope); + + Assert.Equal(hash1, hash2); + Assert.Equal(64, hash1.Length); + } +} diff --git a/src/Scanner/StellaOps.Scanner.Worker/Processing/Surface/SurfaceManifestPublisher.cs b/src/Scanner/StellaOps.Scanner.Worker/Processing/Surface/SurfaceManifestPublisher.cs index 4905a13b0..20d4b7c57 100644 --- a/src/Scanner/StellaOps.Scanner.Worker/Processing/Surface/SurfaceManifestPublisher.cs +++ b/src/Scanner/StellaOps.Scanner.Worker/Processing/Surface/SurfaceManifestPublisher.cs @@ -257,6 +257,7 @@ internal sealed class SurfaceManifestPublisher : ISurfaceManifestPublisher ArtifactDocumentFormat.ComponentFragmentJson => "layer.fragments", ArtifactDocumentFormat.ObservationJson => "observation.json", ArtifactDocumentFormat.SurfaceManifestJson => "surface.manifest", + ArtifactDocumentFormat.CompositionRecipeJson => "composition.recipe", ArtifactDocumentFormat.CycloneDxJson => "cdx-json", ArtifactDocumentFormat.CycloneDxProtobuf => "cdx-protobuf", ArtifactDocumentFormat.SpdxJson => "spdx-json", diff --git a/src/Scanner/StellaOps.Scanner.Worker/Processing/Surface/SurfaceManifestStageExecutor.cs b/src/Scanner/StellaOps.Scanner.Worker/Processing/Surface/SurfaceManifestStageExecutor.cs index 54b122273..c5bbe9464 100644 --- a/src/Scanner/StellaOps.Scanner.Worker/Processing/Surface/SurfaceManifestStageExecutor.cs +++ b/src/Scanner/StellaOps.Scanner.Worker/Processing/Surface/SurfaceManifestStageExecutor.cs @@ -265,8 +265,8 @@ internal sealed class SurfaceManifestStageExecutor : IScanStageExecutor pins["policy"] = policy; } - var (artifactHashes, merkle) = ComputeDeterminismHashes(payloads); - merkleRoot = merkle; + var (artifactHashes, recipeBytes, recipeSha256) = BuildCompositionRecipe(payloads); + merkleRoot = recipeSha256; var report = new { @@ -277,12 +277,26 @@ internal sealed class SurfaceManifestStageExecutor : IScanStageExecutor concurrencyLimit = _determinism.ConcurrencyLimit, pins = pins, artifacts = artifactHashes, - merkleRoot = merkle + merkleRoot = recipeSha256 }; - var evidence = new Determinism.DeterminismEvidence(artifactHashes, merkle); + var evidence = new Determinism.DeterminismEvidence(artifactHashes, recipeSha256); context.Analysis.Set(ScanAnalysisKeys.DeterminismEvidence, evidence); + // Publish composition recipe as a manifest artifact for offline replay. + payloads = payloads.ToList(); + ((List)payloads).Add(new SurfaceManifestPayload( + ArtifactDocumentType.CompositionRecipe, + ArtifactDocumentFormat.CompositionRecipeJson, + Kind: "composition.recipe", + MediaType: "application/vnd.stellaops.composition.recipe+json", + Content: recipeBytes, + Metadata: new Dictionary + { + ["schema"] = "stellaops.composition.recipe@1", + ["merkleRoot"] = recipeSha256, + })); + var json = JsonSerializer.Serialize(report, JsonOptions); return new SurfaceManifestPayload( ArtifactDocumentType.SurfaceObservation, @@ -293,9 +307,9 @@ internal sealed class SurfaceManifestStageExecutor : IScanStageExecutor View: "replay"); } - private static (Dictionary Hashes, string MerkleRoot) ComputeDeterminismHashes(IEnumerable payloads) + private static (Dictionary Hashes, byte[] RecipeBytes, string RecipeSha256) BuildCompositionRecipe(IEnumerable payloads) { - var map = new Dictionary(StringComparer.OrdinalIgnoreCase); + var map = new SortedDictionary(StringComparer.Ordinal); using var sha = SHA256.Create(); foreach (var payload in payloads.OrderBy(p => p.Kind, StringComparer.Ordinal)) @@ -304,18 +318,18 @@ internal sealed class SurfaceManifestStageExecutor : IScanStageExecutor map[payload.Kind] = digest; } - // Build Merkle-like root by hashing the ordered list of kind:digest lines. - var builder = new StringBuilder(); - foreach (var kvp in map.OrderBy(kv => kv.Key, StringComparer.Ordinal)) + var recipe = new { - builder.Append(kvp.Key).Append(':').Append(kvp.Value).Append('\n'); - } + schema = "stellaops.composition.recipe@1", + artifacts = map, // already sorted + }; - var rootBytes = Encoding.UTF8.GetBytes(builder.ToString()); - var rootHash = sha.ComputeHash(rootBytes); + var recipeJson = JsonSerializer.Serialize(recipe, JsonOptions); + var recipeBytes = Encoding.UTF8.GetBytes(recipeJson); + var rootHash = sha.ComputeHash(recipeBytes); var merkleRoot = Convert.ToHexString(rootHash).ToLowerInvariant(); - return (map, merkleRoot); + return (new Dictionary(map, StringComparer.OrdinalIgnoreCase), recipeBytes, merkleRoot); } private static string? GetReplayBundleUri(ScanJobContext context) diff --git a/src/Scanner/__Libraries/StellaOps.Scanner.Emit/Composition/CycloneDxComposer.cs b/src/Scanner/__Libraries/StellaOps.Scanner.Emit/Composition/CycloneDxComposer.cs index 3bacb45f3..570f0714e 100644 --- a/src/Scanner/__Libraries/StellaOps.Scanner.Emit/Composition/CycloneDxComposer.cs +++ b/src/Scanner/__Libraries/StellaOps.Scanner.Emit/Composition/CycloneDxComposer.cs @@ -24,11 +24,11 @@ public sealed class CycloneDxComposer private const string InventoryMediaTypeProtobuf = "application/vnd.cyclonedx+protobuf; version=1.6"; private const string UsageMediaTypeProtobuf = "application/vnd.cyclonedx+protobuf; version=1.6; view=usage"; - public SbomCompositionResult Compose(SbomCompositionRequest request) - { - ArgumentNullException.ThrowIfNull(request); - if (request.LayerFragments.IsDefaultOrEmpty) - { + public SbomCompositionResult Compose(SbomCompositionRequest request) + { + ArgumentNullException.ThrowIfNull(request); + if (request.LayerFragments.IsDefaultOrEmpty) + { throw new ArgumentException("At least one layer fragment is required.", nameof(request)); } @@ -48,9 +48,9 @@ public sealed class CycloneDxComposer .Where(static component => component.Usage.UsedByEntrypoint) .ToImmutableArray(); - CycloneDxArtifact? usageArtifact = null; - if (!usageComponents.IsEmpty) - { + CycloneDxArtifact? usageArtifact = null; + if (!usageComponents.IsEmpty) + { usageArtifact = BuildArtifact( request, graph, @@ -59,15 +59,36 @@ public sealed class CycloneDxComposer generatedAt, UsageMediaTypeJson, UsageMediaTypeProtobuf); - } - - return new SbomCompositionResult - { - Inventory = inventoryArtifact, - Usage = usageArtifact, - Graph = graph, - }; - } + } + + var compositionRecipeJson = BuildCompositionRecipeJson(graph, generatedAt); + var compositionRecipeSha = ComputeSha256(compositionRecipeJson); + var compositionRecipeUri = $"cas://sbom/composition/{compositionRecipeSha}.json"; + + inventoryArtifact = inventoryArtifact with + { + MerkleRoot = compositionRecipeSha, + CompositionRecipeUri = compositionRecipeUri, + }; + + if (usageArtifact is not null) + { + usageArtifact = usageArtifact with + { + MerkleRoot = compositionRecipeSha, + CompositionRecipeUri = compositionRecipeUri, + }; + } + + return new SbomCompositionResult + { + Inventory = inventoryArtifact, + Usage = usageArtifact, + Graph = graph, + CompositionRecipeJson = compositionRecipeJson, + CompositionRecipeSha256 = compositionRecipeSha, + }; + } private CycloneDxArtifact BuildArtifact( SbomCompositionRequest request, @@ -92,6 +113,7 @@ public sealed class CycloneDxComposer : null; request.AdditionalProperties?.TryGetValue("stellaops:composition.manifest", out var compositionUri); + request.AdditionalProperties?.TryGetValue("stellaops:composition.recipe", out var compositionRecipeUri); return new CycloneDxArtifact { @@ -104,12 +126,38 @@ public sealed class CycloneDxComposer ContentHash = jsonHash, MerkleRoot = merkleRoot, CompositionUri = compositionUri, + CompositionRecipeUri = compositionRecipeUri, JsonMediaType = jsonMediaType, ProtobufBytes = protobufBytes, ProtobufSha256 = protobufHash, ProtobufMediaType = protobufMediaType, }; } + + private static byte[] BuildCompositionRecipeJson(ComponentGraph graph, DateTimeOffset generatedAt) + { + var recipe = new + { + schema = "stellaops.composition.recipe@1", + generatedAt = ScannerTimestamps.ToIso8601(generatedAt), + layers = graph.Layers.Select(layer => new + { + layer.LayerDigest, + components = layer.Components + .Select(component => component.Identity.Key) + .OrderBy(key => key, StringComparer.Ordinal) + .ToArray(), + }).OrderBy(entry => entry.LayerDigest, StringComparer.Ordinal).ToArray(), + }; + + var json = JsonSerializer.Serialize(recipe, new JsonSerializerOptions + { + PropertyNamingPolicy = JsonNamingPolicy.CamelCase, + WriteIndented = false, + }); + + return Encoding.UTF8.GetBytes(json); + } private Bom BuildBom( SbomCompositionRequest request, diff --git a/src/Scanner/__Libraries/StellaOps.Scanner.Emit/Composition/SbomCompositionResult.cs b/src/Scanner/__Libraries/StellaOps.Scanner.Emit/Composition/SbomCompositionResult.cs index 3d7a3bdf7..a76890e43 100644 --- a/src/Scanner/__Libraries/StellaOps.Scanner.Emit/Composition/SbomCompositionResult.cs +++ b/src/Scanner/__Libraries/StellaOps.Scanner.Emit/Composition/SbomCompositionResult.cs @@ -33,6 +33,11 @@ public sealed record CycloneDxArtifact /// public string? CompositionUri { get; init; } + /// + /// CAS URI of the layer composition recipe (_composition.json) if emitted. + /// + public string? CompositionRecipeUri { get; init; } + public required string JsonMediaType { get; init; } public required byte[] ProtobufBytes { get; init; } @@ -42,11 +47,21 @@ public sealed record CycloneDxArtifact public required string ProtobufMediaType { get; init; } } -public sealed record SbomCompositionResult -{ - public required CycloneDxArtifact Inventory { get; init; } - - public CycloneDxArtifact? Usage { get; init; } - - public required ComponentGraph Graph { get; init; } -} +public sealed record SbomCompositionResult +{ + public required CycloneDxArtifact Inventory { get; init; } + + public CycloneDxArtifact? Usage { get; init; } + + public required ComponentGraph Graph { get; init; } + + /// + /// Composition recipe JSON bytes (canonical) capturing fragment ordering and hashes. + /// + public required byte[] CompositionRecipeJson { get; init; } + + /// + /// SHA256 hex of the composition recipe JSON. + /// + public required string CompositionRecipeSha256 { get; init; } +} diff --git a/src/Scanner/__Libraries/StellaOps.Scanner.Emit/Packaging/ScannerArtifactPackageBuilder.cs b/src/Scanner/__Libraries/StellaOps.Scanner.Emit/Packaging/ScannerArtifactPackageBuilder.cs index 86cd6419d..aeef5a94a 100644 --- a/src/Scanner/__Libraries/StellaOps.Scanner.Emit/Packaging/ScannerArtifactPackageBuilder.cs +++ b/src/Scanner/__Libraries/StellaOps.Scanner.Emit/Packaging/ScannerArtifactPackageBuilder.cs @@ -88,18 +88,26 @@ public sealed class ScannerArtifactPackageBuilder descriptors.Add(CreateDescriptor(ArtifactDocumentType.ImageBom, ArtifactDocumentFormat.CycloneDxProtobuf, composition.Usage.ProtobufMediaType, composition.Usage.ProtobufBytes, composition.Usage.ProtobufSha256, SbomView.Usage)); } - descriptors.Add(CreateDescriptor(ArtifactDocumentType.Index, ArtifactDocumentFormat.BomIndex, "application/vnd.stellaops.bom-index.v1+binary", bomIndex.Bytes, bomIndex.Sha256, null)); + descriptors.Add(CreateDescriptor(ArtifactDocumentType.Index, ArtifactDocumentFormat.BomIndex, "application/vnd.stellaops.bom-index.v1+binary", bomIndex.Bytes, bomIndex.Sha256, null)); + + descriptors.Add(CreateDescriptor( + ArtifactDocumentType.CompositionRecipe, + ArtifactDocumentFormat.CompositionRecipeJson, + "application/vnd.stellaops.composition.recipe+json", + composition.CompositionRecipeJson, + composition.CompositionRecipeSha256, + null)); - var manifest = new ScannerArtifactManifest - { - ImageDigest = imageDigest.Trim(), - GeneratedAt = generatedAt, - Artifacts = descriptors - .Select(ToManifestEntry) - .OrderBy(entry => entry.Kind, StringComparer.Ordinal) - .ThenBy(entry => entry.Format) - .ToImmutableArray(), - }; + var manifest = new ScannerArtifactManifest + { + ImageDigest = imageDigest.Trim(), + GeneratedAt = generatedAt, + Artifacts = descriptors + .Select(ToManifestEntry) + .OrderBy(entry => entry.Kind, StringComparer.Ordinal) + .ThenBy(entry => entry.Format) + .ToImmutableArray(), + }; return new ScannerArtifactPackage { @@ -136,9 +144,10 @@ public sealed class ScannerArtifactPackageBuilder ArtifactDocumentType.ImageBom => "sbom-inventory", ArtifactDocumentType.LayerBom => "layer-sbom", ArtifactDocumentType.Diff => "diff", - ArtifactDocumentType.Attestation => "attestation", - _ => descriptor.Type.ToString().ToLowerInvariant(), - }; + ArtifactDocumentType.Attestation => "attestation", + ArtifactDocumentType.CompositionRecipe => "composition-recipe", + _ => descriptor.Type.ToString().ToLowerInvariant(), + }; return new ScannerArtifactManifestEntry { diff --git a/src/Scanner/__Libraries/StellaOps.Scanner.Storage/Catalog/ArtifactDocument.cs b/src/Scanner/__Libraries/StellaOps.Scanner.Storage/Catalog/ArtifactDocument.cs index a6c90cb5f..fbb0d62c9 100644 --- a/src/Scanner/__Libraries/StellaOps.Scanner.Storage/Catalog/ArtifactDocument.cs +++ b/src/Scanner/__Libraries/StellaOps.Scanner.Storage/Catalog/ArtifactDocument.cs @@ -12,7 +12,8 @@ public enum ArtifactDocumentType SurfaceManifest, SurfaceEntryTrace, SurfaceLayerFragment, - SurfaceObservation + SurfaceObservation, + CompositionRecipe } public enum ArtifactDocumentFormat @@ -26,7 +27,8 @@ public enum ArtifactDocumentFormat EntryTraceNdjson, EntryTraceGraphJson, ComponentFragmentJson, - ObservationJson + ObservationJson, + CompositionRecipeJson } [BsonIgnoreExtraElements] diff --git a/src/Scanner/__Tests/StellaOps.Scanner.Analyzers.Lang.Node.SmokeTests/StellaOps.Scanner.Analyzers.Lang.Node.SmokeTests.csproj b/src/Scanner/__Tests/StellaOps.Scanner.Analyzers.Lang.Node.SmokeTests/StellaOps.Scanner.Analyzers.Lang.Node.SmokeTests.csproj index a9c55c833..86ac4f6f6 100644 --- a/src/Scanner/__Tests/StellaOps.Scanner.Analyzers.Lang.Node.SmokeTests/StellaOps.Scanner.Analyzers.Lang.Node.SmokeTests.csproj +++ b/src/Scanner/__Tests/StellaOps.Scanner.Analyzers.Lang.Node.SmokeTests/StellaOps.Scanner.Analyzers.Lang.Node.SmokeTests.csproj @@ -8,6 +8,8 @@ false $(StellaOpsLocalNuGetSource) + true + true diff --git a/src/Scanner/__Tests/StellaOps.Scanner.Emit.Tests/Composition/CycloneDxComposerTests.cs b/src/Scanner/__Tests/StellaOps.Scanner.Emit.Tests/Composition/CycloneDxComposerTests.cs index 014a19a60..3b9b112a5 100644 --- a/src/Scanner/__Tests/StellaOps.Scanner.Emit.Tests/Composition/CycloneDxComposerTests.cs +++ b/src/Scanner/__Tests/StellaOps.Scanner.Emit.Tests/Composition/CycloneDxComposerTests.cs @@ -79,8 +79,9 @@ public sealed class CycloneDxComposerTests Assert.Equal(first.Inventory.ContentHash, first.Inventory.JsonSha256); Assert.Equal(first.Inventory.ProtobufSha256, second.Inventory.ProtobufSha256); Assert.Equal(first.Inventory.SerialNumber, second.Inventory.SerialNumber); - Assert.Null(first.Inventory.MerkleRoot); + Assert.False(string.IsNullOrWhiteSpace(first.Inventory.MerkleRoot)); Assert.Null(first.Inventory.CompositionUri); + Assert.Null(first.Inventory.CompositionRecipeUri); Assert.NotNull(first.Usage); Assert.NotNull(second.Usage); @@ -88,8 +89,15 @@ public sealed class CycloneDxComposerTests Assert.Equal(first.Usage.ContentHash, first.Usage.JsonSha256); Assert.Equal(first.Usage.ProtobufSha256, second.Usage.ProtobufSha256); Assert.Equal(first.Usage.SerialNumber, second.Usage.SerialNumber); - Assert.Null(first.Usage.MerkleRoot); + Assert.False(string.IsNullOrWhiteSpace(first.Usage.MerkleRoot)); Assert.Null(first.Usage.CompositionUri); + Assert.Null(first.Usage.CompositionRecipeUri); + + Assert.Equal(first.Inventory.MerkleRoot, first.Usage.MerkleRoot); + Assert.Equal(first.Inventory.MerkleRoot, result.CompositionRecipeSha256); + Assert.Equal(first.Inventory.ContentHash.Length, first.Inventory.MerkleRoot!.Length); + Assert.Equal(result.CompositionRecipeSha256.Length, 64); + Assert.NotEmpty(result.CompositionRecipeJson); } private static SbomCompositionRequest BuildRequest() diff --git a/src/Scanner/__Tests/StellaOps.Scanner.Emit.Tests/Packaging/ScannerArtifactPackageBuilderTests.cs b/src/Scanner/__Tests/StellaOps.Scanner.Emit.Tests/Packaging/ScannerArtifactPackageBuilderTests.cs index e961634dc..00d180916 100644 --- a/src/Scanner/__Tests/StellaOps.Scanner.Emit.Tests/Packaging/ScannerArtifactPackageBuilderTests.cs +++ b/src/Scanner/__Tests/StellaOps.Scanner.Emit.Tests/Packaging/ScannerArtifactPackageBuilderTests.cs @@ -64,16 +64,16 @@ public sealed class ScannerArtifactPackageBuilderTests var packageBuilder = new ScannerArtifactPackageBuilder(); var package = packageBuilder.Build(request.Image.ImageDigest, request.GeneratedAt, composition, bomIndex); - Assert.Equal(5, package.Artifacts.Length); // inventory JSON+PB, usage JSON+PB, index - - var kinds = package.Manifest.Artifacts.Select(entry => entry.Kind).ToArray(); - Assert.Equal(new[] { "bom-index", "sbom-inventory", "sbom-inventory", "sbom-usage", "sbom-usage" }, kinds); - - var manifestJson = package.Manifest.ToJsonBytes(); - using var document = JsonDocument.Parse(manifestJson); - var root = document.RootElement; - Assert.Equal("sha256:image", root.GetProperty("imageDigest").GetString()); - Assert.Equal(5, root.GetProperty("artifacts").GetArrayLength()); + Assert.Equal(6, package.Artifacts.Length); // inventory JSON+PB, usage JSON+PB, index, composition recipe + + var kinds = package.Manifest.Artifacts.Select(entry => entry.Kind).ToArray(); + Assert.Equal(new[] { "bom-index", "composition-recipe", "sbom-inventory", "sbom-inventory", "sbom-usage", "sbom-usage" }, kinds); + + var manifestJson = package.Manifest.ToJsonBytes(); + using var document = JsonDocument.Parse(manifestJson); + var root = document.RootElement; + Assert.Equal("sha256:image", root.GetProperty("imageDigest").GetString()); + Assert.Equal(6, root.GetProperty("artifacts").GetArrayLength()); var usageEntry = root.GetProperty("artifacts").EnumerateArray().First(element => element.GetProperty("kind").GetString() == "sbom-usage"); Assert.Equal("application/vnd.cyclonedx+json; version=1.6; view=usage", usageEntry.GetProperty("mediaType").GetString()); diff --git a/src/Scanner/__Tests/StellaOps.Scanner.Worker.Tests/SurfaceManifestStageExecutorTests.cs b/src/Scanner/__Tests/StellaOps.Scanner.Worker.Tests/SurfaceManifestStageExecutorTests.cs index 6c175f1e5..dfe068f35 100644 --- a/src/Scanner/__Tests/StellaOps.Scanner.Worker.Tests/SurfaceManifestStageExecutorTests.cs +++ b/src/Scanner/__Tests/StellaOps.Scanner.Worker.Tests/SurfaceManifestStageExecutorTests.cs @@ -102,10 +102,12 @@ public sealed class SurfaceManifestStageExecutorTests Assert.Equal(publisher.LastManifestDigest, result!.ManifestDigest); Assert.Equal(result.DeterminismMerkleRoot, publisher.LastRequest!.DeterminismMerkleRoot); - Assert.Equal(4, cache.Entries.Count); + Assert.Equal(6, cache.Entries.Count); Assert.Contains(cache.Entries.Keys, key => key.Namespace == "surface.artifacts.entrytrace.graph" && key.Tenant == "tenant-a"); Assert.Contains(cache.Entries.Keys, key => key.Namespace == "surface.artifacts.entrytrace.ndjson" && key.Tenant == "tenant-a"); Assert.Contains(cache.Entries.Keys, key => key.Namespace == "surface.artifacts.layer.fragments" && key.Tenant == "tenant-a"); + Assert.Contains(cache.Entries.Keys, key => key.Namespace == "surface.artifacts.determinism.json" && key.Tenant == "tenant-a"); + Assert.Contains(cache.Entries.Keys, key => key.Namespace == "surface.artifacts.composition.recipe" && key.Tenant == "tenant-a"); Assert.Contains(cache.Entries.Keys, key => key.Namespace == "surface.manifests" && key.Tenant == "tenant-a"); var publishedMetrics = listener.Measurements @@ -114,7 +116,7 @@ public sealed class SurfaceManifestStageExecutorTests Assert.Single(publishedMetrics); Assert.Equal(1, publishedMetrics[0].Value); Assert.Equal("published", publishedMetrics[0]["surface.result"]); - Assert.Equal(3, Convert.ToInt32(publishedMetrics[0]["surface.payload_count"])); + Assert.Equal(5, Convert.ToInt32(publishedMetrics[0]["surface.payload_count"])); var payloadMetrics = listener.Measurements .Where(m => m.InstrumentName == "scanner_worker_surface_payload_persisted_total") @@ -608,7 +610,8 @@ public sealed class SurfaceManifestStageExecutorTests WorkerInstance = request.WorkerInstance, Attempt = request.Attempt }, - Artifacts = artifacts + Artifacts = artifacts, + DeterminismMerkleRoot = request.DeterminismMerkleRoot }; var manifestBytes = JsonSerializer.SerializeToUtf8Bytes(document, _options); diff --git a/src/Web/StellaOps.Web/TASKS.md b/src/Web/StellaOps.Web/TASKS.md index e681128cf..8186daa8a 100644 --- a/src/Web/StellaOps.Web/TASKS.md +++ b/src/Web/StellaOps.Web/TASKS.md @@ -5,4 +5,8 @@ | WEB-AOC-19-002 | DONE (2025-11-30) | Added provenance builder, checksum utilities, and DSSE/CMS signature verification helpers with unit tests. | | WEB-AOC-19-003 | DONE (2025-11-30) | Added client-side guard validator (forbidden/derived/unknown fields, provenance/signature checks) with unit fixtures. | | WEB-CONSOLE-23-002 | DOING (2025-12-01) | Console status polling + SSE run stream client/store/UI added; tests pending once env fixed. | +| WEB-RISK-66-001 | DOING (2025-12-01) | Added risk gateway mock client/models + tests; wire to real gateway once endpoints land. | | WEB-EXC-25-001 | TODO | Exceptions workflow CRUD pending policy scopes. | +| WEB-TEN-47-CONTRACT | DONE (2025-12-01) | Gateway tenant auth/ABAC contract doc v1.0 published (`docs/api/gateway/tenant-auth.md`). | +| WEB-VULN-29-LEDGER-DOC | DONE (2025-12-01) | Findings Ledger proxy contract doc v1.0 with idempotency + retries (`docs/api/gateway/findings-ledger-proxy.md`). | +| WEB-RISK-68-NOTIFY-DOC | DONE (2025-12-01) | Notifications severity transition event schema v1.0 published (`docs/api/gateway/notifications-severity.md`). | diff --git a/src/Web/StellaOps.Web/src/app/app.config.ts b/src/Web/StellaOps.Web/src/app/app.config.ts index e74d4d054..be30dfc06 100644 --- a/src/Web/StellaOps.Web/src/app/app.config.ts +++ b/src/Web/StellaOps.Web/src/app/app.config.ts @@ -20,6 +20,8 @@ import { NOTIFY_TENANT_ID, } from './core/api/notify.client'; import { CONSOLE_API_BASE_URL } from './core/api/console-status.client'; +import { RISK_API } from './core/api/risk.client'; +import { RISK_API_BASE_URL, RiskHttpClient } from './core/api/risk-http.client'; import { AppConfigService } from './core/config/app-config.service'; import { AuthHttpInterceptor } from './core/auth/auth-http.interceptor'; import { OperatorMetadataInterceptor } from './core/orchestrator/operator-metadata.interceptor'; @@ -65,11 +67,31 @@ export const appConfig: ApplicationConfig = { } }, }, - AuthorityConsoleApiHttpClient, - { - provide: AUTHORITY_CONSOLE_API, - useExisting: AuthorityConsoleApiHttpClient, - }, + AuthorityConsoleApiHttpClient, + { + provide: AUTHORITY_CONSOLE_API, + useExisting: AuthorityConsoleApiHttpClient, + }, + { + provide: RISK_API_BASE_URL, + deps: [AppConfigService], + useFactory: (config: AppConfigService) => { + const authorityBase = config.config.apiBaseUrls.authority; + try { + return new URL('/risk', authorityBase).toString(); + } catch { + const normalized = authorityBase.endsWith('/') + ? authorityBase.slice(0, -1) + : authorityBase; + return `${normalized}/risk`; + } + }, + }, + RiskHttpClient, + { + provide: RISK_API, + useExisting: RiskHttpClient, + }, { provide: NOTIFY_API_BASE_URL, useValue: '/api/v1/notify', diff --git a/src/Web/StellaOps.Web/src/app/core/api/risk-http.client.ts b/src/Web/StellaOps.Web/src/app/core/api/risk-http.client.ts new file mode 100644 index 000000000..dad59f845 --- /dev/null +++ b/src/Web/StellaOps.Web/src/app/core/api/risk-http.client.ts @@ -0,0 +1,62 @@ +import { HttpClient, HttpHeaders, HttpParams } from '@angular/common/http'; +import { Inject, Injectable, InjectionToken } from '@angular/core'; +import { Observable, map } from 'rxjs'; + +import { AuthSessionStore } from '../auth/auth-session.store'; +import { RiskApi } from './risk.client'; +import { RiskQueryOptions, RiskResultPage, RiskStats } from './risk.models'; + +export const RISK_API_BASE_URL = new InjectionToken('RISK_API_BASE_URL'); + +@Injectable({ providedIn: 'root' }) +export class RiskHttpClient implements RiskApi { + constructor( + private readonly http: HttpClient, + private readonly authSession: AuthSessionStore, + @Inject(RISK_API_BASE_URL) private readonly baseUrl: string + ) {} + + list(options: RiskQueryOptions): Observable { + const tenant = this.resolveTenant(options.tenantId); + const headers = this.buildHeaders(tenant, options.projectId, options.traceId); + + let params = new HttpParams(); + if (options.page) params = params.set('page', options.page); + if (options.pageSize) params = params.set('pageSize', options.pageSize); + if (options.severity) params = params.set('severity', options.severity); + if (options.search) params = params.set('search', options.search); + + return this.http + .get(`${this.baseUrl}/risk`, { headers, params }) + .pipe(map((page) => ({ ...page, page: page.page ?? 1, pageSize: page.pageSize ?? 20 }))); + } + + stats(options: Pick): Observable { + const tenant = this.resolveTenant(options.tenantId); + const headers = this.buildHeaders(tenant, options.projectId, options.traceId); + + return this.http + .get(`${this.baseUrl}/risk/status`, { headers }) + .pipe( + map((stats) => ({ + countsBySeverity: stats.countsBySeverity, + lastComputation: stats.lastComputation ?? '1970-01-01T00:00:00Z', + })) + ); + } + + private buildHeaders(tenantId: string, projectId?: string, traceId?: string): HttpHeaders { + let headers = new HttpHeaders({ 'X-Stella-Tenant': tenantId }); + if (projectId) headers = headers.set('X-Stella-Project', projectId); + if (traceId) headers = headers.set('X-Stella-Trace-Id', traceId); + return headers; + } + + private resolveTenant(tenantId?: string): string { + const tenant = (tenantId && tenantId.trim()) || this.authSession.getActiveTenantId(); + if (!tenant) { + throw new Error('RiskHttpClient requires an active tenant identifier.'); + } + return tenant; + } +} diff --git a/src/Web/StellaOps.Web/src/app/core/api/risk.client.spec.ts b/src/Web/StellaOps.Web/src/app/core/api/risk.client.spec.ts new file mode 100644 index 000000000..6f63b214c --- /dev/null +++ b/src/Web/StellaOps.Web/src/app/core/api/risk.client.spec.ts @@ -0,0 +1,40 @@ +import { MockRiskApi } from './risk.client'; + +describe('MockRiskApi', () => { + let api: MockRiskApi; + + beforeEach(() => { + api = new MockRiskApi(); + }); + + it('requires tenantId for list', () => { + expect(() => api.list({ tenantId: '' })).toThrow('tenantId is required'); + }); + + it('returns deterministic ordering by score then id', (done) => { + api.list({ tenantId: 'acme-tenant', pageSize: 10 }).subscribe((page) => { + const scores = page.items.map((r) => r.score); + expect(scores).toEqual([...scores].sort((a, b) => b - a)); + done(); + }); + }); + + it('filters by project and severity', (done) => { + api + .list({ tenantId: 'acme-tenant', projectId: 'proj-ops', severity: 'high' }) + .subscribe((page) => { + expect(page.items.every((r) => r.projectId === 'proj-ops')).toBeTrue(); + expect(page.items.every((r) => r.severity === 'high')).toBeTrue(); + done(); + }); + }); + + it('computes stats with zeroed severities present', (done) => { + api.stats({ tenantId: 'acme-tenant' }).subscribe((stats) => { + expect(stats.countsBySeverity.none).toBe(0); + expect(stats.countsBySeverity.critical).toBeGreaterThan(0); + expect(stats.lastComputation).toMatch(/T/); + done(); + }); + }); +}); diff --git a/src/Web/StellaOps.Web/src/app/core/api/risk.client.ts b/src/Web/StellaOps.Web/src/app/core/api/risk.client.ts new file mode 100644 index 000000000..c1e54c597 --- /dev/null +++ b/src/Web/StellaOps.Web/src/app/core/api/risk.client.ts @@ -0,0 +1,112 @@ +import { Injectable, InjectionToken } from '@angular/core'; +import { Observable, delay, map, of } from 'rxjs'; + +import { RiskProfile, RiskQueryOptions, RiskResultPage, RiskStats, RiskSeverity } from './risk.models'; + +export interface RiskApi { + list(options: RiskQueryOptions): Observable; + stats(options: Pick): Observable; +} + +export const RISK_API = new InjectionToken('RISK_API'); + +const MOCK_RISKS: RiskProfile[] = [ + { + id: 'risk-001', + title: 'RCE on internet-facing API', + description: 'Critical RCE on public API gateway impacting tenants acme, globally exposed.', + severity: 'critical', + score: 97, + lastEvaluatedAt: '2025-11-30T12:00:00Z', + tenantId: 'acme-tenant', + }, + { + id: 'risk-002', + title: 'Expired token audience', + description: 'Tokens minted without correct audience allow cross-tenant reuse.', + severity: 'high', + score: 81, + lastEvaluatedAt: '2025-11-30T11:00:00Z', + tenantId: 'acme-tenant', + projectId: 'proj-ops', + }, + { + id: 'risk-003', + title: 'Missing SBOM attestation', + description: 'Builds lack SBOM attestations; export blocked in sealed mode.', + severity: 'medium', + score: 55, + lastEvaluatedAt: '2025-11-29T19:30:00Z', + tenantId: 'acme-tenant', + }, +]; + +@Injectable({ providedIn: 'root' }) +export class MockRiskApi implements RiskApi { + list(options: RiskQueryOptions): Observable { + if (!options.tenantId) { + throw new Error('tenantId is required'); + } + + const page = options.page ?? 1; + const pageSize = options.pageSize ?? 20; + const filtered = MOCK_RISKS.filter((r) => { + if (r.tenantId !== options.tenantId) { + return false; + } + if (options.projectId && r.projectId !== options.projectId) { + return false; + } + if (options.severity && r.severity !== options.severity) { + return false; + } + if (options.search && !r.title.toLowerCase().includes(options.search.toLowerCase())) { + return false; + } + return true; + }); + + const start = (page - 1) * pageSize; + const items = filtered + .slice() + .sort((a, b) => b.score - a.score || a.id.localeCompare(b.id)) + .slice(start, start + pageSize); + + const response: RiskResultPage = { + items, + total: filtered.length, + page, + pageSize, + }; + + return of(response).pipe(delay(50)); + } + + stats(options: Pick): Observable { + if (!options.tenantId) { + throw new Error('tenantId is required'); + } + + const relevant = MOCK_RISKS.filter((r) => r.tenantId === options.tenantId); + const emptyCounts: Record = { + none: 0, + info: 0, + low: 0, + medium: 0, + high: 0, + critical: 0, + }; + + const counts = relevant.reduce((acc, curr) => { + acc[curr.severity] = (acc[curr.severity] ?? 0) + 1; + return acc; + }, { ...emptyCounts }); + + const lastEvaluatedAt = relevant + .map((r) => r.lastEvaluatedAt) + .sort() + .reverse()[0] ?? '1970-01-01T00:00:00Z'; + + return of({ countsBySeverity: counts, lastComputation: lastEvaluatedAt }).pipe(delay(25)); + } +} diff --git a/src/Web/StellaOps.Web/src/app/core/api/risk.models.ts b/src/Web/StellaOps.Web/src/app/core/api/risk.models.ts new file mode 100644 index 000000000..6fa093757 --- /dev/null +++ b/src/Web/StellaOps.Web/src/app/core/api/risk.models.ts @@ -0,0 +1,34 @@ +export type RiskSeverity = 'none' | 'info' | 'low' | 'medium' | 'high' | 'critical'; + +export interface RiskProfile { + id: string; + title: string; + description: string; + severity: RiskSeverity; + score: number; + lastEvaluatedAt: string; // UTC ISO-8601 + tenantId: string; + projectId?: string; +} + +export interface RiskResultPage { + items: RiskProfile[]; + total: number; + page: number; + pageSize: number; +} + +export interface RiskQueryOptions { + tenantId: string; + projectId?: string; + page?: number; + pageSize?: number; + severity?: RiskSeverity; + search?: string; + traceId?: string; +} + +export interface RiskStats { + countsBySeverity: Record; + lastComputation: string; // UTC ISO-8601 +}