Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Concelier Attestation Tests / attestation-tests (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Export Center CI / export-ci (push) Has been cancelled
devportal-offline / build-offline (push) Has been cancelled
6.1 KiB
6.1 KiB
Contract: POLICY-CONSOLE-23-001 — Console findings/export & simulation surfaces
Status: Draft → Proposed (2025-12-02)
Scope
- Provide deterministic, tenant-scoped APIs from Policy Engine to StellaOps Console for findings browse/export and simulation/explain experiences.
- Replace legacy ad-hoc Console queries with cursor-based, RBAC-aware endpoints that expose provenance and aggregation hints.
- Keep all responses deterministic (stable ordering, explicit timestamps, no wall-clock/default time windows).
Versioning & Compatibility
schemaVersion:console-policy-23-001(bumped on breaking changes).- Media type:
application/vnd.stellaops.console-policy-23-001+json(clients MUST sendAcceptand SHOULD sendContent-Type). - Backward-compatible additions follow additive fields; ordering and cursor format remain stable.
Authentication & RBAC
- Required scopes:
policy:read,effective:read,explain:read(all tenant-scoped). - Optional
findings:exportto enable NDJSON bulk export. - All endpoints require
X-Tenant-Id; server enforces tenant filter and rejects cross-tenant cursor reuse.
Determinism Rules
- Ordering:
policyVersion DESC,artifactDigest ASC,purl ASC,ruleId ASC,findingId ASC. - Cursor: opaque, URL-safe base64 of the last tuple above; contains
policyVersion|artifactDigest|purl|ruleId|findingIdplusschemaVersion. No server clocks in cursors. - Timestamps: clients MUST provide
evaluationTimestamportimeWindowStart/End; server never injectsDateTime.UtcNowdefaults. - Randomness/network access disallowed; sampling ratios must be provided by the client or policy config.
Endpoints
1) List findings (paged)
- GET
/policy/console/findings - Query params
cursor(string, optional)limit(int, 1–500, default 100)severityBand[](enum: critical|high|medium|low|unknown)ruleId[],policyId,policyVersionartifactDigest[],purl[],namespace[]advisoryId[],vexStatement[]state[](open|waived|fixed|not_applicable)timeWindowStart,timeWindowEnd(ISO-8601, optional)sort(one ofdefault,severity_desc,artifact,rule); default respects deterministic tuple above.
- Response
{ "schemaVersion": "console-policy-23-001", "items": [ { "findingId": "ulid", "policyVersion": "2025.11.24", "artifactDigest": "sha256:...", "purl": "pkg:maven/org.example/foo@1.2.3", "ruleId": "RULE-1234", "severity": "high", "state": "open", "explainSummary": { "hitRules": ["RULE-1234"], "traceSampleId": "ulid", "rationale": ["package matches advisory CVE-2025-1234"] }, "provenance": { "evaluationTimestamp": "2025-11-28T00:00:00Z", "effectiveFindingHash": "be...", "source": "materialized" } } ], "cursor": { "next": "b64...", "prev": "b64..." }, "aggregates": { "countsBySeverity": {"critical": 1, "high": 5, "medium": 12, "low": 3, "unknown": 0}, "countsByRule": [{"ruleId": "RULE-1234", "count": 4}], "countsByPolicyVersion": [{"policyVersion": "2025.11.24", "count": 25}] } }
2) Finding explain trace (summary)
- GET
/policy/console/findings/{findingId}/explain - Returns deterministic trace summary for UI drawer (no full trace fan-out): hit rules, key facts, sampled trace token, policyVersion, evaluationTimestamp, hashes.
- Optional
format(jsondefault,markdownfor UI preview); output ordering stable.
3) Simulation/export diff (used by POLICY-CONSOLE-23-002)
- POST
/policy/console/simulations/diff - Body
{ "baselinePolicyVersion": "2025.11.24", "candidatePolicyVersion": "2025.12.02", "artifactScope": [{"artifactDigest": "sha256:..."}], "budget": {"maxFindings": 2000, "maxExplainSamples": 50}, "filters": {"severityBand": ["high","critical"]} } - Response
{ "schemaVersion": "console-policy-23-001", "summary": { "before": {"total": 120, "severity": {"critical":4,"high":30,"medium":60,"low":26}}, "after": {"total": 98, "severity": {"critical":3,"high":22,"medium":55,"low":18}}, "delta": {"added":12,"removed":34,"regressed":2} }, "ruleImpact": [ {"ruleId":"RULE-1234","added":3,"removed":10,"severityShift":{"high→medium":6}}, {"ruleId":"RULE-2000","added":1,"removed":0} ], "samples": { "explain": ["trace-token-1","trace-token-2"], "findings": ["finding-ulid-1","finding-ulid-2"] }, "provenance": { "baselinePolicyVersion": "2025.11.24", "candidatePolicyVersion": "2025.12.02", "evaluationTimestamp": "2025-12-02T00:00:00Z" } } - Ordering of ruleImpact array:
ruleId ASC; samples ordered by hash.
4) Bulk export (NDJSON)
- POST
/policy/console/findings/export - Body accepts same filters as list endpoint plus
format(ndjsononly) andmaxRows(hard cap 50k). - Response streams NDJSON of finding records in deterministic ordering with content hashes.
Error Model
- 400 with machine-readable code (
invalid_filter,unsupported_schemaVersion,budget_exceeded). - 401/403 for auth/scope failures; 409 when
schemaVersionmismatch. - 429 when budget limits tripped; include
retryAfterSecondsbut never implicit sleep in server.
Non-Goals
- No mutable state or approvals exposed here; status transitions remain in Console backend via existing endpoints.
- No live wall-clock filtering; clients must pass explicit windows.
Testing Hooks
- Provide
X-Dry-Run: trueto validate filters and budgets without executing evaluation. X-Debug-Sampling: <0..1>allowed in non-production tenants only; otherwise rejected.
Implementation Notes
- Reuse batch evaluation pipeline for simulation diff; reuse materialized
effective_finding_*collections for listing/export. - Enforce deterministic
evaluationTimestampsupplied by caller; reject missing timestamp whenbaselinePolicyVersion != candidatePolicyVersion. - All aggregates computed in-memory over deterministically ordered result sets; no sampling unless explicitly requested.