feat: Add UI benchmark driver and scenarios for graph interactions
Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
devportal-offline / build-offline (push) Has been cancelled
Some checks failed
AOC Guard CI / aoc-guard (push) Has been cancelled
AOC Guard CI / aoc-verify (push) Has been cancelled
Docs CI / lint-and-preview (push) Has been cancelled
Policy Lint & Smoke / policy-lint (push) Has been cancelled
devportal-offline / build-offline (push) Has been cancelled
- Introduced `ui_bench_driver.mjs` to read scenarios and fixture manifest, generating a deterministic run plan. - Created `ui_bench_plan.md` outlining the purpose, scope, and next steps for the benchmark. - Added `ui_bench_scenarios.json` containing various scenarios for graph UI interactions. - Implemented tests for CLI commands, ensuring bundle verification and telemetry defaults. - Developed schemas for orchestrator components, including replay manifests and event envelopes. - Added mock API for risk management, including listing and statistics functionalities. - Implemented models for risk profiles and query options to support the new API.
This commit is contained in:
@@ -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 <jwt>`: RS256/ES256 service token; `aud=stellaops-ledger`; scopes `ledger:write ledger:read`.
|
||||
- `Content-Type: application/json`.
|
||||
| Name | Requirement | Notes |
|
||||
| --- | --- | --- |
|
||||
| `Authorization: Bearer <jwt>` | 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.
|
||||
|
||||
@@ -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.
|
||||
|
||||
@@ -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 <jwt>` — 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 <jwt>` | 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"
|
||||
}
|
||||
```
|
||||
|
||||
Reference in New Issue
Block a user