153 lines
9.3 KiB
Markdown
153 lines
9.3 KiB
Markdown
# Policy Exception Effects
|
||
|
||
> **Audience:** Policy authors, reviewers, operators, and governance owners.
|
||
> **Scope:** How exception definitions are authored, resolved, and surfaced by the Policy Engine during evaluation, including precedence rules, metadata flow, and simulation/diff behaviour.
|
||
|
||
Exception effects let teams codify governed waivers without compromising determinism. This guide explains the artefacts involved, how the evaluator selects a single winning exception, and where downstream consumers observe the applied override.
|
||
|
||
---
|
||
|
||
## 1 · Exception Building Blocks
|
||
|
||
| Artefact | Description |
|
||
|----------|-------------|
|
||
| **Exception Effect** | Declared inside a policy pack (`exceptions.effects`). Defines the override behaviour plus governance metadata. See effect fields in §2. |
|
||
| **Routing Template** | Optional mapping (`exceptions.routingTemplates`) used by Authority to route approvals/MFA. Effects reference templates by id. |
|
||
| **Exception Instance** | Stored outside the policy pack (Authority/API). Captures who requested the waiver, scope filters, metadata, and creation time. |
|
||
|
||
Effects are validated at bind time (`PolicyBinder`), while instances are ingested alongside policy evaluation inputs. Both are normalized to case-insensitive identifiers to avoid duplicate conflicts.
|
||
|
||
---
|
||
|
||
## 2 · Effect Fields
|
||
|
||
| Field | Required | Purpose | Notes |
|
||
|-------|----------|---------|-------|
|
||
| `id` | ✅ | Stable identifier (`[A-Za-z0-9-_]+`). | Must be unique per policy pack. |
|
||
| `name` | — | Friendly label for consoles/reports. | Forwarded to verdict metadata if present. |
|
||
| `effect` | ✅ | Behaviour enum: `suppress`, `defer`, `downgrade`, `requireControl`. | Case-insensitive. |
|
||
| `downgradeSeverity` | ⚠️ | Target severity for `downgrade`. | Must map to DSL severities (`high`, `medium`, etc.). Validation enforced in `PolicyBinder` (`policy.exceptions.effect.downgrade.missingSeverity`). |
|
||
| `requiredControlId` | ⚠️ | Control catalogue key for `requireControl`. | Required when effect is `requireControl`. |
|
||
| `routingTemplate` | — | Connects to an Authority approval flow. | CLI/Console resolve to `authorityRouteId`. |
|
||
| `maxDurationDays` | — | Soft limit for temporary waivers. | Must be > 0 when provided. |
|
||
| `description` | — | Rich-text rationale. | Displayed in approvals centre (optional). |
|
||
|
||
Authoring invalid combinations returns structured errors with JSON paths, preventing packs from compiling (see `src/Policy/__Tests/StellaOps.Policy.Tests/PolicyBinderTests.cs:33`). Routing templates additionally declare `authorityRouteId` and `requireMfa` flags for governance routing.
|
||
|
||
---
|
||
|
||
## 3 · Exception Instances & Scope
|
||
|
||
Instances are resolved from Authority or API collections and injected into the evaluation context (`PolicyEvaluationExceptions`). Each instance contains:
|
||
|
||
| Field | Source | Usage |
|
||
|-------|--------|-------|
|
||
| `id` | Authority storage | Propagated to annotations and `appliedException.exceptionId`. |
|
||
| `effectId` | Links to pack-defined effect | Must resolve to a known effect; otherwise ignored. |
|
||
| `scope.ruleNames` | Optional list | Limits to specific rule identifiers. |
|
||
| `scope.severities` | Optional list (`severity.normalized`) | Normalized against the evaluator’s severity string. |
|
||
| `scope.sources` | Optional advisory sources (`GHSA`, `NVD`, …) | Compared against the advisory context. |
|
||
| `scope.tags` | Optional SBOM tags | Matched using `sbom.has_tag(...)`. |
|
||
| `createdAt` | RFC3339 UTC timestamp | Used as tie-breaker when specificity scores match. |
|
||
| `metadata` | Arbitrary key/value bag | Copied to verdict annotations (`exception.meta.*`). |
|
||
|
||
Scopes are case-insensitive and trimmed. Empty scopes behave as global waivers but still require routing and metadata supplied by Authority workflows.
|
||
|
||
---
|
||
|
||
## 4 · Resolution & Specificity
|
||
|
||
Only one exception effect is applied per finding. Evaluation proceeds as follows:
|
||
|
||
1. Filter instances whose `effectId` resolves to a known effect.
|
||
2. Discard instances whose scope does not match the candidate finding (rule name, severity, advisory source, SBOM tags).
|
||
3. Score remaining instances for **specificity**:
|
||
- `ruleNames` ⇒ `1000 + (count × 25)`
|
||
- `severities` ⇒ `500 + (count × 10)`
|
||
- `sources` ⇒ `250 + (count × 10)`
|
||
- `tags` ⇒ `100 + (count × 5)`
|
||
4. Highest score wins. Ties fall back to the newest `createdAt`, then lexical `id` (stable sorting).
|
||
|
||
These rules guarantee deterministic selection even when multiple waivers overlap. See `src/Policy/__Tests/StellaOps.Policy.Engine.Tests/PolicyEvaluatorTests.cs:209` for tie-break coverage.
|
||
|
||
---
|
||
|
||
## 5 · Effect Behaviours
|
||
|
||
| Effect | Status impact | Severity impact | Warnings / metadata |
|
||
|--------|---------------|-----------------|---------------------|
|
||
| `suppress` | Forces status `suppressed`. | No change. | `exception.status=suppressed`. |
|
||
| `defer` | Forces status `deferred`. | No change. | `exception.status=deferred`. |
|
||
| `downgrade` | No change. | Sets severity to configured `downgradeSeverity`. | `exception.severity` annotation. |
|
||
| `requireControl` | No change. | No change. | Adds warning `Exception '<id>' requires control '<requiredControlId>'`. Annotation `exception.requiredControl`. |
|
||
|
||
All effects stamp shared annotations: `exception.id`, `exception.effectId`, `exception.effectType`, optional `exception.effectName`, optional `exception.routingTemplate`, plus `exception.maxDurationDays`. Instance metadata is surfaced both in annotations (`exception.meta.<key>`) and the structured `AppliedException.Metadata` payload for downstream APIs. Behaviour is validated by unit tests (`src/Policy/__Tests/StellaOps.Policy.Engine.Tests/PolicyEvaluatorTests.cs:130` & `src/Policy/__Tests/StellaOps.Policy.Engine.Tests/PolicyEvaluatorTests.cs:169`).
|
||
|
||
---
|
||
|
||
## 6 · Explain, Simulation & Outputs
|
||
|
||
- **Explain traces / CLI simulate** – Verdict payloads include `appliedException` capturing original vs applied status/severity, enabling diff visualisation in Console and CLI previews.
|
||
- **Annotations** – Deterministic keys make it trivial for exports or alerting pipelines to flag waived findings.
|
||
- **Warnings** – `requireControl` adds runtime warnings so operators can enforce completion of compensating controls.
|
||
- **Routing** – When `routingTemplate` is populated, verdict metadata includes `routingTemplate`, allowing UI surfaces to deep-link into the approvals centre.
|
||
|
||
Example verdict excerpt (JSON):
|
||
|
||
```json
|
||
{
|
||
"status": "suppressed",
|
||
"severity": "Critical",
|
||
"annotations": {
|
||
"exception.id": "exc-001",
|
||
"exception.effectId": "suppress-critical",
|
||
"exception.effectType": "Suppress",
|
||
"exception.status": "suppressed",
|
||
"exception.meta.requestedBy": "alice"
|
||
},
|
||
"appliedException": {
|
||
"exceptionId": "exc-001",
|
||
"effectId": "suppress-critical",
|
||
"effectType": "Suppress",
|
||
"originalStatus": "blocked",
|
||
"appliedStatus": "suppressed",
|
||
"metadata": {
|
||
"effectName": "Rule Critical Suppress",
|
||
"requestedBy": "alice"
|
||
}
|
||
}
|
||
}
|
||
```
|
||
|
||
---
|
||
|
||
## 7 · Operational Notes
|
||
|
||
- **Authoring** – Policy packs must ship effect definitions before Authority can issue instances. CLI validation (`stella policy lint`) fails if required fields are missing.
|
||
- **Approvals & MFA** – Effects referencing routing templates inherit `requireMfa` rules from `exceptions.routingTemplates`. When a template requires MFA, Authority will refuse to mint tokens containing `exceptions:approve` unless the authenticating identity provider exposes MFA capability; the failure is logged as `authority.password.grant` with `reason="Exception approval scope requires an MFA-capable identity provider."` Review `/docs/security/authority-scopes.md` for scope/role assignments and `/docs/11_AUTHORITY.md` for configuration samples.
|
||
- **Presence in exports** – Even when an exception suppresses a finding, explain traces and effective findings retain the applied exception metadata for audit parity.
|
||
- **Determinism** – Specificity scoring plus tie-breakers ensure repeatable outcomes across runs, supporting sealed/offline replay.
|
||
|
||
---
|
||
|
||
## 8 · Testing References
|
||
|
||
- `src/Policy/__Tests/StellaOps.Policy.Tests/PolicyBinderTests.cs:33` – Validates schema rules for defining effects, routing templates, and downgrade guardrails.
|
||
- `src/Policy/__Tests/StellaOps.Policy.Engine.Tests/PolicyEvaluatorTests.cs:130` – Covers suppression, downgrade, and metadata propagation.
|
||
- `src/Policy/__Tests/StellaOps.Policy.Engine.Tests/PolicyEvaluatorTests.cs:209` – Confirms specificity ordering and metadata forwarding for competing exceptions.
|
||
|
||
---
|
||
|
||
## 9 · Compliance Checklist
|
||
|
||
- [ ] **Effect catalogue maintained:** Each policy pack documents available effects and routing templates for auditors.
|
||
- [ ] **Authority alignment:** Approval routes in Authority mirror `routingTemplate` definitions and enforce MFA where required.
|
||
- [ ] **Explain coverage:** Console/CLI surfaces display `appliedException` details and `exception.*` annotations for every waived verdict.
|
||
- [ ] **Simulation parity:** `stella policy simulate` outputs include exception metadata, ensuring PR/CI reviews catch unintended waivers.
|
||
- [ ] **Audit retention:** Effective findings history retains `appliedException` payloads so exception lifecycle reviews remain replayable.
|
||
- [ ] **Tests locked:** Binder and evaluator tests covering exception paths remain green before publishing documentation updates.
|
||
|
||
---
|
||
|
||
*Last updated: 2025-10-27 (Sprint 25).*
|