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
139 lines
6.1 KiB
Markdown
139 lines
6.1 KiB
Markdown
# 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 send `Accept` and SHOULD send `Content-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:export` to 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|findingId` plus `schemaVersion`. No server clocks in cursors.
|
||
- Timestamps: clients MUST provide `evaluationTimestamp` or `timeWindowStart/End`; server never injects `DateTime.UtcNow` defaults.
|
||
- 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`, `policyVersion`
|
||
- `artifactDigest[]`, `purl[]`, `namespace[]`
|
||
- `advisoryId[]`, `vexStatement[]`
|
||
- `state[]` (open|waived|fixed|not_applicable)
|
||
- `timeWindowStart`, `timeWindowEnd` (ISO-8601, optional)
|
||
- `sort` (one of `default`, `severity_desc`, `artifact`, `rule`); default respects deterministic tuple above.
|
||
- **Response**
|
||
```json
|
||
{
|
||
"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` (`json` default, `markdown` for UI preview); output ordering stable.
|
||
|
||
### 3) Simulation/export diff (used by POLICY-CONSOLE-23-002)
|
||
- **POST** `/policy/console/simulations/diff`
|
||
- **Body**
|
||
```json
|
||
{
|
||
"baselinePolicyVersion": "2025.11.24",
|
||
"candidatePolicyVersion": "2025.12.02",
|
||
"artifactScope": [{"artifactDigest": "sha256:..."}],
|
||
"budget": {"maxFindings": 2000, "maxExplainSamples": 50},
|
||
"filters": {"severityBand": ["high","critical"]}
|
||
}
|
||
```
|
||
- **Response**
|
||
```json
|
||
{
|
||
"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` (`ndjson` only) and `maxRows` (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 `schemaVersion` mismatch.
|
||
- 429 when budget limits tripped; include `retryAfterSeconds` but 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: true` to 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 `evaluationTimestamp` supplied by caller; reject missing timestamp when `baselinePolicyVersion != candidatePolicyVersion`.
|
||
- All aggregates computed in-memory over deterministically ordered result sets; no sampling unless explicitly requested.
|