Restructure solution layout by module
This commit is contained in:
@@ -1,294 +1,294 @@
|
||||
# Stella Policy DSL (`stella-dsl@1`)
|
||||
|
||||
> **Audience:** Policy authors, reviewers, and tooling engineers building lint/compile flows for the Policy Engine v2 rollout (Sprint 20).
|
||||
|
||||
This document specifies the `stella-dsl@1` grammar, semantics, and guardrails used by Stella Ops to transform SBOM facts, Concelier advisories, and Excititor VEX statements into effective findings. Use it with the [Policy Engine Overview](overview.md) for architectural context and the upcoming lifecycle/run guides for operational workflows.
|
||||
|
||||
---
|
||||
|
||||
## 1 · Design Goals
|
||||
|
||||
- **Deterministic:** Same policy + same inputs ⇒ identical findings on every machine.
|
||||
- **Declarative:** No arbitrary loops, network calls, or clock access.
|
||||
- **Explainable:** Every decision records the rule, inputs, and rationale in the explain trace.
|
||||
- **Lean authoring:** Common precedence, severity, and suppression patterns are first-class.
|
||||
- **Offline-friendly:** Grammar and built-ins avoid cloud dependencies, run the same in sealed deployments.
|
||||
|
||||
---
|
||||
|
||||
## 2 · Document Structure
|
||||
|
||||
Policy packs ship one or more `.stella` files. Each file contains exactly one `policy` block:
|
||||
|
||||
```dsl
|
||||
policy "Default Org Policy" syntax "stella-dsl@1" {
|
||||
metadata {
|
||||
description = "Baseline severity + VEX precedence"
|
||||
tags = ["baseline","vex"]
|
||||
}
|
||||
|
||||
profile severity {
|
||||
map vendor_weight {
|
||||
source "GHSA" => +0.5
|
||||
source "OSV" => +0.0
|
||||
source "VendorX" => -0.2
|
||||
}
|
||||
env exposure_adjustments {
|
||||
if env.runtime == "serverless" then -0.5
|
||||
if env.exposure == "internal-only" then -1.0
|
||||
}
|
||||
}
|
||||
|
||||
rule vex_precedence priority 10 {
|
||||
when vex.any(status in ["not_affected","fixed"])
|
||||
and vex.justification in ["component_not_present","vulnerable_code_not_present"]
|
||||
then status := vex.status
|
||||
because "Strong vendor justification prevails";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
High-level layout:
|
||||
|
||||
| Section | Purpose |
|
||||
|---------|---------|
|
||||
| `metadata` | Optional descriptive fields surfaced in Console/CLI. |
|
||||
| `imports` | Reserved for future reuse (not yet implemented in `@1`). |
|
||||
| `profile` blocks | Declarative scoring modifiers (`severity`, `trust`, `reachability`). |
|
||||
| `rule` blocks | When/then logic applied to each `(component, advisory, vex[])` tuple. |
|
||||
| `settings` | Optional evaluation toggles (sampling, default status overrides). |
|
||||
|
||||
---
|
||||
|
||||
## 3 · Lexical Rules
|
||||
|
||||
- **Case sensitivity:** Keywords are lowercase; identifiers are case-sensitive.
|
||||
- **Whitespace:** Space, tab, newline act as separators. Indentation is cosmetic.
|
||||
- **Comments:** `// inline` and `/* block */` are ignored.
|
||||
- **Literals:**
|
||||
- Strings use double quotes (`"text"`); escape with `\"`, `\n`, `\t`.
|
||||
- Numbers are decimal; suffix `%` allowed for percentage weights (`-2.5%` becomes `-0.025`).
|
||||
- Booleans: `true`, `false`.
|
||||
- Lists: `[1, 2, 3]`, `["a","b"]`.
|
||||
- **Identifiers:** Start with letter or underscore, continue with letters, digits, `_`.
|
||||
- **Operators:** `=`, `==`, `!=`, `<`, `<=`, `>`, `>=`, `in`, `not in`, `and`, `or`, `not`, `:=`.
|
||||
|
||||
---
|
||||
|
||||
## 4 · Grammar (EBNF)
|
||||
|
||||
```ebnf
|
||||
policy = "policy", string, "syntax", string, "{", policy-body, "}" ;
|
||||
policy-body = { metadata | profile | settings | rule | helper } ;
|
||||
|
||||
metadata = "metadata", "{", { meta-entry }, "}" ;
|
||||
meta-entry = identifier, "=", (string | list) ;
|
||||
|
||||
profile = "profile", identifier, "{", { profile-item }, "}" ;
|
||||
profile-item= map | env-map | scalar ;
|
||||
map = "map", identifier, "{", { "source", string, "=>", number, ";" }, "}" ;
|
||||
env-map = "env", identifier, "{", { "if", expression, "then", number, ";" }, "}" ;
|
||||
scalar = identifier, "=", (number | string | list), ";" ;
|
||||
|
||||
settings = "settings", "{", { setting-entry }, "}" ;
|
||||
setting-entry = identifier, "=", (number | string | boolean), ";" ;
|
||||
|
||||
rule = "rule", identifier, [ "priority", integer ], "{",
|
||||
"when", predicate,
|
||||
{ "and", predicate },
|
||||
"then", { action },
|
||||
[ "else", { action } ],
|
||||
[ "because", string ],
|
||||
"}" ;
|
||||
|
||||
predicate = expression ;
|
||||
expression = term, { ("and" | "or"), term } ;
|
||||
term = ["not"], factor ;
|
||||
factor = comparison | membership | function-call | literal | identifier | "(" expression ")" ;
|
||||
comparison = value, comparator, value ;
|
||||
membership = value, ("in" | "not in"), list ;
|
||||
value = identifier | literal | function-call | field-access ;
|
||||
field-access= identifier, { ".", identifier | "[" literal "]" } ;
|
||||
function-call = identifier, "(", [ arg-list ], ")" ;
|
||||
arg-list = expression, { ",", expression } ;
|
||||
literal = string | number | boolean | list ;
|
||||
|
||||
action = assignment | ignore | escalate | require | warn | defer | annotate ;
|
||||
assignment = target, ":=", expression, ";" ;
|
||||
target = identifier, { ".", identifier } ;
|
||||
ignore = "ignore", [ "until", expression ], [ "because", string ], ";" ;
|
||||
escalate = "escalate", [ "to", expression ], [ "when", expression ], ";" ;
|
||||
require = "requireVex", "{", require-fields, "}", ";" ;
|
||||
warn = "warn", [ "message", string ], ";" ;
|
||||
defer = "defer", [ "until", expression ], ";" ;
|
||||
annotate = "annotate", identifier, ":=", expression, ";" ;
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- `helper` is reserved for shared calculcations (not yet implemented in `@1`).
|
||||
- `else` branch executes only if `when` predicates evaluate truthy **and** no prior rule earlier in priority handled the tuple.
|
||||
- Semicolons inside rule bodies are optional when each clause is on its own line; the compiler emits canonical semicolons in IR.
|
||||
|
||||
---
|
||||
|
||||
## 5 · Evaluation Context
|
||||
|
||||
Within predicates and actions you may reference the following namespaces:
|
||||
|
||||
| Namespace | Fields | Description |
|
||||
|-----------|--------|-------------|
|
||||
| `sbom` | `purl`, `name`, `version`, `licenses`, `layerDigest`, `tags`, `usedByEntrypoint` | Component metadata from Scanner. |
|
||||
| `advisory` | `id`, `source`, `aliases`, `severity`, `cvss`, `publishedAt`, `modifiedAt`, `content.raw` | Canonical Concelier advisory view. |
|
||||
| `vex` | `status`, `justification`, `statementId`, `timestamp`, `scope` | Current VEX statement when iterating; aggregator helpers available. |
|
||||
| `vex.any(...)`, `vex.all(...)`, `vex.count(...)` | Functions operating over all matching statements. |
|
||||
| `run` | `policyId`, `policyVersion`, `tenant`, `timestamp` | Metadata for explain annotations. |
|
||||
| `env` | Arbitrary key/value pairs injected per run (e.g., `environment`, `runtime`). |
|
||||
| `telemetry` | Optional reachability signals; missing fields evaluate to `unknown`. |
|
||||
| `profile.<name>` | Values computed inside profile blocks (maps, scalars). |
|
||||
|
||||
Missing fields evaluate to `null`, which is falsey in boolean context and propagates through comparisons unless explicitly checked.
|
||||
|
||||
---
|
||||
|
||||
## 6 · Built-ins (v1)
|
||||
|
||||
| Function / Property | Signature | Description |
|
||||
|---------------------|-----------|-------------|
|
||||
| `normalize_cvss(advisory)` | `Advisory → SeverityScalar` | Parses `advisory.content.raw` for CVSS data; falls back to policy maps. |
|
||||
| `cvss(score, vector)` | `double × string → SeverityScalar` | Constructs a severity object manually. |
|
||||
| `severity_band(value)` | `string → SeverityBand` | Normalises strings like `"critical"`, `"medium"`. |
|
||||
| `risk_score(base, modifiers...)` | Variadic | Multiplies numeric modifiers (severity × trust × reachability). |
|
||||
| `vex.any(predicate)` | `(Statement → bool) → bool` | `true` if any statement satisfies predicate. |
|
||||
| `vex.all(predicate)` | `(Statement → bool) → bool` | `true` if all statements satisfy predicate. |
|
||||
| `vex.latest()` | `→ Statement` | Lexicographically newest statement. |
|
||||
| `advisory.has_tag(tag)` | `string → bool` | Checks advisory metadata tags. |
|
||||
| `advisory.matches(pattern)` | `string → bool` | Glob match against advisory identifiers. |
|
||||
| `sbom.has_tag(tag)` | `string → bool` | Uses SBOM inventory tags (usage vs inventory). |
|
||||
| `exists(expression)` | `→ bool` | `true` when value is non-null/empty. |
|
||||
| `coalesce(a, b, ...)` | `→ value` | First non-null argument. |
|
||||
| `days_between(dateA, dateB)` | `→ int` | Absolute day difference (UTC). |
|
||||
| `percent_of(part, whole)` | `→ double` | Fractions for scoring adjustments. |
|
||||
| `lowercase(text)` | `string → string` | Normalises casing deterministically (InvariantCulture). |
|
||||
|
||||
All built-ins are pure; if inputs are null the result is null unless otherwise noted.
|
||||
|
||||
---
|
||||
|
||||
## 7 · Rule Semantics
|
||||
|
||||
1. **Ordering:** Rules execute in ascending `priority`. When priorities tie, lexical order defines precedence.
|
||||
2. **Short-circuit:** Once a rule sets `status`, subsequent rules only execute if they use `combine`. Use this sparingly to avoid ambiguity.
|
||||
3. **Actions:**
|
||||
- `status := <string>` – Allowed values: `affected`, `not_affected`, `fixed`, `suppressed`, `under_investigation`, `escalated`.
|
||||
- `severity := <SeverityScalar>` – Either from `normalize_cvss`, `cvss`, or numeric map; ensures `normalized` and `score`.
|
||||
- `ignore until <ISO-8601>` – Temporarily treats finding as suppressed until timestamp; recorded in explain trace.
|
||||
- `warn message "<text>"` – Adds warn verdict and deducts `warnPenalty`.
|
||||
- `escalate to severity_band("critical") when condition` – Forces verdict severity upward when condition true.
|
||||
- `requireVex { vendors = ["VendorX"], justifications = ["component_not_present"] }` – Fails evaluation if matching VEX evidence absent.
|
||||
- `annotate reason := "text"` – Adds free-form key/value pairs to explain payload.
|
||||
4. **Because clause:** Mandatory for actions changing status or severity; captured verbatim in explain traces.
|
||||
|
||||
---
|
||||
|
||||
## 8 · Scoping Helpers
|
||||
|
||||
- **Maps:** Use `profile severity { map vendor_weight { ... } }` to declare additive factors. Retrieve with `profile.severity.vendor_weight["GHSA"]`.
|
||||
- **Environment overrides:** `env` profiles allow conditional adjustments based on runtime metadata.
|
||||
- **Tenancy:** `run.tenant` ensures policies remain tenant-aware; avoid hardcoding single-tenant IDs.
|
||||
- **Default values:** Use `settings { default_status = "affected"; }` to override built-in defaults.
|
||||
|
||||
---
|
||||
|
||||
## 9 · Examples
|
||||
|
||||
### 9.1 Baseline Severity Normalisation
|
||||
|
||||
```dsl
|
||||
rule advisory_normalization {
|
||||
when advisory.source in ["GHSA","OSV"]
|
||||
then severity := normalize_cvss(advisory)
|
||||
because "Align vendor severity to CVSS baseline";
|
||||
}
|
||||
```
|
||||
|
||||
### 9.2 VEX Override with Quiet Mode
|
||||
|
||||
```dsl
|
||||
rule vex_strong_claim priority 5 {
|
||||
when vex.any(status == "not_affected")
|
||||
and vex.justification in ["component_not_present","vulnerable_code_not_present"]
|
||||
then status := vex.status
|
||||
annotate winning_statement := vex.latest().statementId
|
||||
warn message "VEX override applied"
|
||||
because "Strong VEX justification";
|
||||
}
|
||||
```
|
||||
|
||||
### 9.3 Environment-Specific Escalation
|
||||
|
||||
```dsl
|
||||
rule internet_exposed_guard {
|
||||
when env.exposure == "internet"
|
||||
and severity.normalized >= "High"
|
||||
then escalate to severity_band("Critical")
|
||||
because "Internet-exposed assets require critical posture";
|
||||
}
|
||||
```
|
||||
|
||||
### 9.4 Anti-pattern (flagged by linter)
|
||||
|
||||
```dsl
|
||||
rule catch_all {
|
||||
when true
|
||||
then status := "suppressed"
|
||||
because "Suppress everything" // ❌ Fails lint: unbounded suppression
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10 · Validation & Tooling
|
||||
|
||||
- `stella policy lint` ensures:
|
||||
- Grammar compliance and canonical formatting.
|
||||
- Static determinism guard (no forbidden namespaces).
|
||||
- Anti-pattern detection (e.g., unconditional suppression, missing `because`).
|
||||
- `stella policy compile` emits IR (`.stella.ir.json`) and SHA-256 digest used in `policy_runs`.
|
||||
- CI pipelines (see `DEVOPS-POLICY-20-001`) compile sample packs and fail on lint violations.
|
||||
- Simulation harnesses (`stella policy simulate`) highlight provided/queried fields so policy authors affirm assumptions before promotion.
|
||||
|
||||
---
|
||||
|
||||
## 11 · Anti-patterns & Mitigations
|
||||
|
||||
| Anti-pattern | Risk | Mitigation |
|
||||
|--------------|------|------------|
|
||||
| Catch-all suppress/ignore without scope | Masks all findings | Linter blocks rules with `when true` unless `priority` > 1000 and justification includes remediation plan. |
|
||||
| Comparing strings with inconsistent casing | Missed matches | Wrap comparisons in `lowercase(value)` to align casing or normalise metadata during ingest. |
|
||||
| Referencing `telemetry` without fallback | Null propagation | Wrap access in `exists(telemetry.reachability)`. |
|
||||
| Hardcoding tenant IDs | Breaks multi-tenant | Prefer `env.tenantTag` or metadata-sourced predicates. |
|
||||
| Duplicated rule names | Explain trace ambiguity | Compiler enforces unique `rule` identifiers within a policy. |
|
||||
|
||||
---
|
||||
|
||||
## 12 · Versioning & Compatibility
|
||||
|
||||
- `syntax "stella-dsl@1"` is mandatory.
|
||||
- Future revisions (`@2`, …) will be additive; existing packs continue to compile with their declared version.
|
||||
- The compiler canonicalises documents (sorted keys, normalised whitespace) before hashing to ensure reproducibility.
|
||||
|
||||
---
|
||||
|
||||
## 13 · Compliance Checklist
|
||||
|
||||
- [ ] **Grammar validated:** Policy compiles with `stella policy lint` and matches `syntax "stella-dsl@1"`.
|
||||
- [ ] **Deterministic constructs only:** No use of forbidden namespaces (`DateTime.Now`, `Guid.NewGuid`, external services).
|
||||
- [ ] **Rationales present:** Every status/severity change includes a `because` clause or `annotate` entry.
|
||||
- [ ] **Scoped suppressions:** Rules that ignore/suppress findings reference explicit components, vendors, or VEX justifications.
|
||||
- [ ] **Explain fields verified:** `annotate` keys align with Console/CLI expectations (documented in upcoming lifecycle guide).
|
||||
- [ ] **Offline parity tested:** Policy pack simulated in sealed mode (`--sealed`) to confirm absence of network dependencies.
|
||||
|
||||
---
|
||||
|
||||
*Last updated: 2025-10-26 (Sprint 20).*
|
||||
# Stella Policy DSL (`stella-dsl@1`)
|
||||
|
||||
> **Audience:** Policy authors, reviewers, and tooling engineers building lint/compile flows for the Policy Engine v2 rollout (Sprint 20).
|
||||
|
||||
This document specifies the `stella-dsl@1` grammar, semantics, and guardrails used by Stella Ops to transform SBOM facts, Concelier advisories, and Excititor VEX statements into effective findings. Use it with the [Policy Engine Overview](overview.md) for architectural context and the upcoming lifecycle/run guides for operational workflows.
|
||||
|
||||
---
|
||||
|
||||
## 1 · Design Goals
|
||||
|
||||
- **Deterministic:** Same policy + same inputs ⇒ identical findings on every machine.
|
||||
- **Declarative:** No arbitrary loops, network calls, or clock access.
|
||||
- **Explainable:** Every decision records the rule, inputs, and rationale in the explain trace.
|
||||
- **Lean authoring:** Common precedence, severity, and suppression patterns are first-class.
|
||||
- **Offline-friendly:** Grammar and built-ins avoid cloud dependencies, run the same in sealed deployments.
|
||||
|
||||
---
|
||||
|
||||
## 2 · Document Structure
|
||||
|
||||
Policy packs ship one or more `.stella` files. Each file contains exactly one `policy` block:
|
||||
|
||||
```dsl
|
||||
policy "Default Org Policy" syntax "stella-dsl@1" {
|
||||
metadata {
|
||||
description = "Baseline severity + VEX precedence"
|
||||
tags = ["baseline","vex"]
|
||||
}
|
||||
|
||||
profile severity {
|
||||
map vendor_weight {
|
||||
source "GHSA" => +0.5
|
||||
source "OSV" => +0.0
|
||||
source "VendorX" => -0.2
|
||||
}
|
||||
env exposure_adjustments {
|
||||
if env.runtime == "serverless" then -0.5
|
||||
if env.exposure == "internal-only" then -1.0
|
||||
}
|
||||
}
|
||||
|
||||
rule vex_precedence priority 10 {
|
||||
when vex.any(status in ["not_affected","fixed"])
|
||||
and vex.justification in ["component_not_present","vulnerable_code_not_present"]
|
||||
then status := vex.status
|
||||
because "Strong vendor justification prevails";
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
High-level layout:
|
||||
|
||||
| Section | Purpose |
|
||||
|---------|---------|
|
||||
| `metadata` | Optional descriptive fields surfaced in Console/CLI. |
|
||||
| `imports` | Reserved for future reuse (not yet implemented in `@1`). |
|
||||
| `profile` blocks | Declarative scoring modifiers (`severity`, `trust`, `reachability`). |
|
||||
| `rule` blocks | When/then logic applied to each `(component, advisory, vex[])` tuple. |
|
||||
| `settings` | Optional evaluation toggles (sampling, default status overrides). |
|
||||
|
||||
---
|
||||
|
||||
## 3 · Lexical Rules
|
||||
|
||||
- **Case sensitivity:** Keywords are lowercase; identifiers are case-sensitive.
|
||||
- **Whitespace:** Space, tab, newline act as separators. Indentation is cosmetic.
|
||||
- **Comments:** `// inline` and `/* block */` are ignored.
|
||||
- **Literals:**
|
||||
- Strings use double quotes (`"text"`); escape with `\"`, `\n`, `\t`.
|
||||
- Numbers are decimal; suffix `%` allowed for percentage weights (`-2.5%` becomes `-0.025`).
|
||||
- Booleans: `true`, `false`.
|
||||
- Lists: `[1, 2, 3]`, `["a","b"]`.
|
||||
- **Identifiers:** Start with letter or underscore, continue with letters, digits, `_`.
|
||||
- **Operators:** `=`, `==`, `!=`, `<`, `<=`, `>`, `>=`, `in`, `not in`, `and`, `or`, `not`, `:=`.
|
||||
|
||||
---
|
||||
|
||||
## 4 · Grammar (EBNF)
|
||||
|
||||
```ebnf
|
||||
policy = "policy", string, "syntax", string, "{", policy-body, "}" ;
|
||||
policy-body = { metadata | profile | settings | rule | helper } ;
|
||||
|
||||
metadata = "metadata", "{", { meta-entry }, "}" ;
|
||||
meta-entry = identifier, "=", (string | list) ;
|
||||
|
||||
profile = "profile", identifier, "{", { profile-item }, "}" ;
|
||||
profile-item= map | env-map | scalar ;
|
||||
map = "map", identifier, "{", { "source", string, "=>", number, ";" }, "}" ;
|
||||
env-map = "env", identifier, "{", { "if", expression, "then", number, ";" }, "}" ;
|
||||
scalar = identifier, "=", (number | string | list), ";" ;
|
||||
|
||||
settings = "settings", "{", { setting-entry }, "}" ;
|
||||
setting-entry = identifier, "=", (number | string | boolean), ";" ;
|
||||
|
||||
rule = "rule", identifier, [ "priority", integer ], "{",
|
||||
"when", predicate,
|
||||
{ "and", predicate },
|
||||
"then", { action },
|
||||
[ "else", { action } ],
|
||||
[ "because", string ],
|
||||
"}" ;
|
||||
|
||||
predicate = expression ;
|
||||
expression = term, { ("and" | "or"), term } ;
|
||||
term = ["not"], factor ;
|
||||
factor = comparison | membership | function-call | literal | identifier | "(" expression ")" ;
|
||||
comparison = value, comparator, value ;
|
||||
membership = value, ("in" | "not in"), list ;
|
||||
value = identifier | literal | function-call | field-access ;
|
||||
field-access= identifier, { ".", identifier | "[" literal "]" } ;
|
||||
function-call = identifier, "(", [ arg-list ], ")" ;
|
||||
arg-list = expression, { ",", expression } ;
|
||||
literal = string | number | boolean | list ;
|
||||
|
||||
action = assignment | ignore | escalate | require | warn | defer | annotate ;
|
||||
assignment = target, ":=", expression, ";" ;
|
||||
target = identifier, { ".", identifier } ;
|
||||
ignore = "ignore", [ "until", expression ], [ "because", string ], ";" ;
|
||||
escalate = "escalate", [ "to", expression ], [ "when", expression ], ";" ;
|
||||
require = "requireVex", "{", require-fields, "}", ";" ;
|
||||
warn = "warn", [ "message", string ], ";" ;
|
||||
defer = "defer", [ "until", expression ], ";" ;
|
||||
annotate = "annotate", identifier, ":=", expression, ";" ;
|
||||
```
|
||||
|
||||
Notes:
|
||||
|
||||
- `helper` is reserved for shared calculcations (not yet implemented in `@1`).
|
||||
- `else` branch executes only if `when` predicates evaluate truthy **and** no prior rule earlier in priority handled the tuple.
|
||||
- Semicolons inside rule bodies are optional when each clause is on its own line; the compiler emits canonical semicolons in IR.
|
||||
|
||||
---
|
||||
|
||||
## 5 · Evaluation Context
|
||||
|
||||
Within predicates and actions you may reference the following namespaces:
|
||||
|
||||
| Namespace | Fields | Description |
|
||||
|-----------|--------|-------------|
|
||||
| `sbom` | `purl`, `name`, `version`, `licenses`, `layerDigest`, `tags`, `usedByEntrypoint` | Component metadata from Scanner. |
|
||||
| `advisory` | `id`, `source`, `aliases`, `severity`, `cvss`, `publishedAt`, `modifiedAt`, `content.raw` | Canonical Concelier advisory view. |
|
||||
| `vex` | `status`, `justification`, `statementId`, `timestamp`, `scope` | Current VEX statement when iterating; aggregator helpers available. |
|
||||
| `vex.any(...)`, `vex.all(...)`, `vex.count(...)` | Functions operating over all matching statements. |
|
||||
| `run` | `policyId`, `policyVersion`, `tenant`, `timestamp` | Metadata for explain annotations. |
|
||||
| `env` | Arbitrary key/value pairs injected per run (e.g., `environment`, `runtime`). |
|
||||
| `telemetry` | Optional reachability signals; missing fields evaluate to `unknown`. |
|
||||
| `profile.<name>` | Values computed inside profile blocks (maps, scalars). |
|
||||
|
||||
Missing fields evaluate to `null`, which is falsey in boolean context and propagates through comparisons unless explicitly checked.
|
||||
|
||||
---
|
||||
|
||||
## 6 · Built-ins (v1)
|
||||
|
||||
| Function / Property | Signature | Description |
|
||||
|---------------------|-----------|-------------|
|
||||
| `normalize_cvss(advisory)` | `Advisory → SeverityScalar` | Parses `advisory.content.raw` for CVSS data; falls back to policy maps. |
|
||||
| `cvss(score, vector)` | `double × string → SeverityScalar` | Constructs a severity object manually. |
|
||||
| `severity_band(value)` | `string → SeverityBand` | Normalises strings like `"critical"`, `"medium"`. |
|
||||
| `risk_score(base, modifiers...)` | Variadic | Multiplies numeric modifiers (severity × trust × reachability). |
|
||||
| `vex.any(predicate)` | `(Statement → bool) → bool` | `true` if any statement satisfies predicate. |
|
||||
| `vex.all(predicate)` | `(Statement → bool) → bool` | `true` if all statements satisfy predicate. |
|
||||
| `vex.latest()` | `→ Statement` | Lexicographically newest statement. |
|
||||
| `advisory.has_tag(tag)` | `string → bool` | Checks advisory metadata tags. |
|
||||
| `advisory.matches(pattern)` | `string → bool` | Glob match against advisory identifiers. |
|
||||
| `sbom.has_tag(tag)` | `string → bool` | Uses SBOM inventory tags (usage vs inventory). |
|
||||
| `exists(expression)` | `→ bool` | `true` when value is non-null/empty. |
|
||||
| `coalesce(a, b, ...)` | `→ value` | First non-null argument. |
|
||||
| `days_between(dateA, dateB)` | `→ int` | Absolute day difference (UTC). |
|
||||
| `percent_of(part, whole)` | `→ double` | Fractions for scoring adjustments. |
|
||||
| `lowercase(text)` | `string → string` | Normalises casing deterministically (InvariantCulture). |
|
||||
|
||||
All built-ins are pure; if inputs are null the result is null unless otherwise noted.
|
||||
|
||||
---
|
||||
|
||||
## 7 · Rule Semantics
|
||||
|
||||
1. **Ordering:** Rules execute in ascending `priority`. When priorities tie, lexical order defines precedence.
|
||||
2. **Short-circuit:** Once a rule sets `status`, subsequent rules only execute if they use `combine`. Use this sparingly to avoid ambiguity.
|
||||
3. **Actions:**
|
||||
- `status := <string>` – Allowed values: `affected`, `not_affected`, `fixed`, `suppressed`, `under_investigation`, `escalated`.
|
||||
- `severity := <SeverityScalar>` – Either from `normalize_cvss`, `cvss`, or numeric map; ensures `normalized` and `score`.
|
||||
- `ignore until <ISO-8601>` – Temporarily treats finding as suppressed until timestamp; recorded in explain trace.
|
||||
- `warn message "<text>"` – Adds warn verdict and deducts `warnPenalty`.
|
||||
- `escalate to severity_band("critical") when condition` – Forces verdict severity upward when condition true.
|
||||
- `requireVex { vendors = ["VendorX"], justifications = ["component_not_present"] }` – Fails evaluation if matching VEX evidence absent.
|
||||
- `annotate reason := "text"` – Adds free-form key/value pairs to explain payload.
|
||||
4. **Because clause:** Mandatory for actions changing status or severity; captured verbatim in explain traces.
|
||||
|
||||
---
|
||||
|
||||
## 8 · Scoping Helpers
|
||||
|
||||
- **Maps:** Use `profile severity { map vendor_weight { ... } }` to declare additive factors. Retrieve with `profile.severity.vendor_weight["GHSA"]`.
|
||||
- **Environment overrides:** `env` profiles allow conditional adjustments based on runtime metadata.
|
||||
- **Tenancy:** `run.tenant` ensures policies remain tenant-aware; avoid hardcoding single-tenant IDs.
|
||||
- **Default values:** Use `settings { default_status = "affected"; }` to override built-in defaults.
|
||||
|
||||
---
|
||||
|
||||
## 9 · Examples
|
||||
|
||||
### 9.1 Baseline Severity Normalisation
|
||||
|
||||
```dsl
|
||||
rule advisory_normalization {
|
||||
when advisory.source in ["GHSA","OSV"]
|
||||
then severity := normalize_cvss(advisory)
|
||||
because "Align vendor severity to CVSS baseline";
|
||||
}
|
||||
```
|
||||
|
||||
### 9.2 VEX Override with Quiet Mode
|
||||
|
||||
```dsl
|
||||
rule vex_strong_claim priority 5 {
|
||||
when vex.any(status == "not_affected")
|
||||
and vex.justification in ["component_not_present","vulnerable_code_not_present"]
|
||||
then status := vex.status
|
||||
annotate winning_statement := vex.latest().statementId
|
||||
warn message "VEX override applied"
|
||||
because "Strong VEX justification";
|
||||
}
|
||||
```
|
||||
|
||||
### 9.3 Environment-Specific Escalation
|
||||
|
||||
```dsl
|
||||
rule internet_exposed_guard {
|
||||
when env.exposure == "internet"
|
||||
and severity.normalized >= "High"
|
||||
then escalate to severity_band("Critical")
|
||||
because "Internet-exposed assets require critical posture";
|
||||
}
|
||||
```
|
||||
|
||||
### 9.4 Anti-pattern (flagged by linter)
|
||||
|
||||
```dsl
|
||||
rule catch_all {
|
||||
when true
|
||||
then status := "suppressed"
|
||||
because "Suppress everything" // ❌ Fails lint: unbounded suppression
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 10 · Validation & Tooling
|
||||
|
||||
- `stella policy lint` ensures:
|
||||
- Grammar compliance and canonical formatting.
|
||||
- Static determinism guard (no forbidden namespaces).
|
||||
- Anti-pattern detection (e.g., unconditional suppression, missing `because`).
|
||||
- `stella policy compile` emits IR (`.stella.ir.json`) and SHA-256 digest used in `policy_runs`.
|
||||
- CI pipelines (see `DEVOPS-POLICY-20-001`) compile sample packs and fail on lint violations.
|
||||
- Simulation harnesses (`stella policy simulate`) highlight provided/queried fields so policy authors affirm assumptions before promotion.
|
||||
|
||||
---
|
||||
|
||||
## 11 · Anti-patterns & Mitigations
|
||||
|
||||
| Anti-pattern | Risk | Mitigation |
|
||||
|--------------|------|------------|
|
||||
| Catch-all suppress/ignore without scope | Masks all findings | Linter blocks rules with `when true` unless `priority` > 1000 and justification includes remediation plan. |
|
||||
| Comparing strings with inconsistent casing | Missed matches | Wrap comparisons in `lowercase(value)` to align casing or normalise metadata during ingest. |
|
||||
| Referencing `telemetry` without fallback | Null propagation | Wrap access in `exists(telemetry.reachability)`. |
|
||||
| Hardcoding tenant IDs | Breaks multi-tenant | Prefer `env.tenantTag` or metadata-sourced predicates. |
|
||||
| Duplicated rule names | Explain trace ambiguity | Compiler enforces unique `rule` identifiers within a policy. |
|
||||
|
||||
---
|
||||
|
||||
## 12 · Versioning & Compatibility
|
||||
|
||||
- `syntax "stella-dsl@1"` is mandatory.
|
||||
- Future revisions (`@2`, …) will be additive; existing packs continue to compile with their declared version.
|
||||
- The compiler canonicalises documents (sorted keys, normalised whitespace) before hashing to ensure reproducibility.
|
||||
|
||||
---
|
||||
|
||||
## 13 · Compliance Checklist
|
||||
|
||||
- [ ] **Grammar validated:** Policy compiles with `stella policy lint` and matches `syntax "stella-dsl@1"`.
|
||||
- [ ] **Deterministic constructs only:** No use of forbidden namespaces (`DateTime.Now`, `Guid.NewGuid`, external services).
|
||||
- [ ] **Rationales present:** Every status/severity change includes a `because` clause or `annotate` entry.
|
||||
- [ ] **Scoped suppressions:** Rules that ignore/suppress findings reference explicit components, vendors, or VEX justifications.
|
||||
- [ ] **Explain fields verified:** `annotate` keys align with Console/CLI expectations (documented in upcoming lifecycle guide).
|
||||
- [ ] **Offline parity tested:** Policy pack simulated in sealed mode (`--sealed`) to confirm absence of network dependencies.
|
||||
|
||||
---
|
||||
|
||||
*Last updated: 2025-10-26 (Sprint 20).*
|
||||
|
||||
@@ -32,7 +32,7 @@ Effects are validated at bind time (`PolicyBinder`), while instances are ingeste
|
||||
| `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/StellaOps.Policy.Tests/PolicyBinderTests.cs:33`). Routing templates additionally declare `authorityRouteId` and `requireMfa` flags for governance routing.
|
||||
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.
|
||||
|
||||
---
|
||||
|
||||
@@ -68,7 +68,7 @@ Only one exception effect is applied per finding. Evaluation proceeds as follows
|
||||
- `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/StellaOps.Policy.Engine.Tests/PolicyEvaluatorTests.cs:209` for tie-break coverage.
|
||||
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.
|
||||
|
||||
---
|
||||
|
||||
@@ -81,7 +81,7 @@ These rules guarantee deterministic selection even when multiple waivers overlap
|
||||
| `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/StellaOps.Policy.Engine.Tests/PolicyEvaluatorTests.cs:130` & `src/StellaOps.Policy.Engine.Tests/PolicyEvaluatorTests.cs:169`).
|
||||
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`).
|
||||
|
||||
---
|
||||
|
||||
@@ -132,9 +132,9 @@ Example verdict excerpt (JSON):
|
||||
|
||||
## 8 · Testing References
|
||||
|
||||
- `src/StellaOps.Policy.Tests/PolicyBinderTests.cs:33` – Validates schema rules for defining effects, routing templates, and downgrade guardrails.
|
||||
- `src/StellaOps.Policy.Engine.Tests/PolicyEvaluatorTests.cs:130` – Covers suppression, downgrade, and metadata propagation.
|
||||
- `src/StellaOps.Policy.Engine.Tests/PolicyEvaluatorTests.cs:209` – Confirms specificity ordering and metadata forwarding for competing exceptions.
|
||||
- `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.
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,124 +1,124 @@
|
||||
# Policy Gateway
|
||||
|
||||
> **Delivery scope:** `StellaOps.Policy.Gateway` minimal API service fronting Policy Engine pack CRUD + activation endpoints for UI/CLI clients. Sender-constrained with DPoP and tenant headers, suitable for online and Offline Kit deployments.
|
||||
|
||||
## 1 · Responsibilities
|
||||
|
||||
- Proxy policy pack CRUD and activation requests to Policy Engine while enforcing scope policies (`policy:read`, `policy:author`, `policy:review`, `policy:operate`, `policy:activate`).
|
||||
- Normalise responses (DTO + `ProblemDetails`) so Console, CLI, and automation receive consistent payloads.
|
||||
- Guard activation actions with structured logging and metrics so approvals are auditable.
|
||||
- Support dual auth modes:
|
||||
- Forwarded caller tokens (Console/CLI) with DPoP proofs + `X-Stella-Tenant` header.
|
||||
- Gateway client credentials (DPoP) for service automation or Offline Kit flows when no caller token is present.
|
||||
|
||||
## 2 · Endpoints
|
||||
|
||||
| Route | Method | Description | Required scope(s) |
|
||||
|-------|--------|-------------|-------------------|
|
||||
| `/api/policy/packs` | `GET` | List policy packs and revisions for the active tenant. | `policy:read` |
|
||||
| `/api/policy/packs` | `POST` | Create a policy pack shell or upsert display metadata. | `policy:author` |
|
||||
| `/api/policy/packs/{packId}/revisions` | `POST` | Create or update a policy revision (draft/approved). | `policy:author` |
|
||||
| `/api/policy/packs/{packId}/revisions/{version}:activate` | `POST` | Activate a revision, enforcing single/two-person approvals. | `policy:operate`, `policy:activate` |
|
||||
|
||||
### Response shapes
|
||||
|
||||
- Successful responses return camel-case DTOs matching `PolicyPackDto`, `PolicyRevisionDto`, or `PolicyRevisionActivationDto` as described in the Policy Engine API doc (`/docs/api/policy.md`).
|
||||
- Errors always return RFC 7807 `ProblemDetails` with deterministic fields (`title`, `detail`, `status`). Missing caller credentials now surface `401` with `"Upstream authorization missing"` detail.
|
||||
|
||||
## 3 · Authentication & headers
|
||||
|
||||
| Header | Source | Notes |
|
||||
|--------|--------|-------|
|
||||
| `Authorization` | Forwarded caller token *or* gateway client credentials. | Caller tokens must include tenant scope; gateway tokens default to `DPoP` scheme. |
|
||||
| `DPoP` | Caller or gateway. | Required when Authority mandates proof-of-possession (default). Generated per request; gateway keeps ES256/ES384 key material under `etc/policy-gateway-dpop.pem`. |
|
||||
| `X-Stella-Tenant` | Caller | Tenant isolation header. Forwarded unchanged; gateway automation omits it. |
|
||||
|
||||
Gateway client credentials are configured in `policy-gateway.yaml`:
|
||||
|
||||
```yaml
|
||||
policyEngine:
|
||||
baseAddress: "https://policy-engine.internal"
|
||||
audience: "api://policy-engine"
|
||||
clientCredentials:
|
||||
enabled: true
|
||||
clientId: "policy-gateway"
|
||||
clientSecret: "<secret>"
|
||||
scopes:
|
||||
- policy:read
|
||||
- policy:author
|
||||
- policy:review
|
||||
- policy:operate
|
||||
- policy:activate
|
||||
dpop:
|
||||
enabled: true
|
||||
keyPath: "../etc/policy-gateway-dpop.pem"
|
||||
algorithm: "ES256"
|
||||
```
|
||||
|
||||
> 🔐 **DPoP key** – store the private key alongside Offline Kit secrets; rotate it whenever the gateway identity or Authority configuration changes.
|
||||
|
||||
## 4 · Metrics & logging
|
||||
|
||||
All activation calls emit:
|
||||
|
||||
- `policy_gateway_activation_requests_total{outcome,source}` – counter labelled with `outcome` (`activated`, `pending_second_approval`, `already_active`, `bad_request`, `not_found`, `unauthorized`, `forbidden`, `error`) and `source` (`caller`, `service`).
|
||||
- `policy_gateway_activation_latency_ms{outcome,source}` – histogram measuring proxy latency.
|
||||
|
||||
Structured logs (category `StellaOps.Policy.Gateway.Activation`) include `PackId`, `Version`, `Outcome`, `Source`, and upstream status code for audit trails.
|
||||
|
||||
## 5 · Sample `curl` workflows
|
||||
|
||||
Assuming you already obtained a DPoP-bound access token (`$TOKEN`) for tenant `acme`:
|
||||
|
||||
```bash
|
||||
# Generate a DPoP proof for GET via the CLI helper
|
||||
DPoP_PROOF=$(stella auth dpop proof \
|
||||
--htu https://gateway.example.com/api/policy/packs \
|
||||
--htm GET \
|
||||
--token "$TOKEN")
|
||||
|
||||
curl -sS https://gateway.example.com/api/policy/packs \
|
||||
-H "Authorization: DPoP $TOKEN" \
|
||||
-H "DPoP: $DPoP_PROOF" \
|
||||
-H "X-Stella-Tenant: acme"
|
||||
|
||||
# Draft a new revision
|
||||
DPoP_PROOF=$(stella auth dpop proof \
|
||||
--htu https://gateway.example.com/api/policy/packs/policy.core/revisions \
|
||||
--htm POST \
|
||||
--token "$TOKEN")
|
||||
|
||||
curl -sS https://gateway.example.com/api/policy/packs/policy.core/revisions \
|
||||
-H "Authorization: DPoP $TOKEN" \
|
||||
-H "DPoP: $DPoP_PROOF" \
|
||||
-H "X-Stella-Tenant: acme" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"version":5,"requiresTwoPersonApproval":true,"initialStatus":"Draft"}'
|
||||
|
||||
# Activate revision 5 (returns 202 when awaiting the second approver)
|
||||
DPoP_PROOF=$(stella auth dpop proof \
|
||||
--htu https://gateway.example.com/api/policy/packs/policy.core/revisions/5:activate \
|
||||
--htm POST \
|
||||
--token "$TOKEN")
|
||||
|
||||
curl -sS https://gateway.example.com/api/policy/packs/policy.core/revisions/5:activate \
|
||||
-H "Authorization: DPoP $TOKEN" \
|
||||
-H "DPoP: $DPoP_PROOF" \
|
||||
-H "X-Stella-Tenant: acme" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"comment":"Rollout baseline"}'
|
||||
```
|
||||
|
||||
For air-gapped environments, bundle `policy-gateway.yaml` and the DPoP key in the Offline Kit (see `/docs/24_OFFLINE_KIT.md` §5.7).
|
||||
|
||||
> **DPoP proof helper:** Use `stella auth dpop proof` to mint sender-constrained proofs locally. The command accepts `--htu`, `--htm`, and `--token` arguments and emits a ready-to-use header value. Teams maintaining alternate tooling (for example, `scripts/make-dpop.sh`) can substitute it as long as the inputs and output match the CLI behaviour.
|
||||
|
||||
## 6 · Offline Kit guidance
|
||||
|
||||
- Include `policy-gateway.yaml.sample` and the resolved runtime config in the Offline Kit’s `config/` tree.
|
||||
- Place the DPoP private key under `secrets/policy-gateway-dpop.pem` with restricted permissions; document rotation steps in the manifest.
|
||||
- When building verification scripts, use the gateway endpoints above instead of hitting Policy Engine directly. The Offline Kit validator now expects `policy_gateway_activation_requests_total` metrics in the Prometheus snapshot.
|
||||
|
||||
## 7 · Change log
|
||||
|
||||
- **2025-10-27 – Sprint 18.5**: Initial gateway bootstrap + activation metrics + DPoP client credentials.
|
||||
# Policy Gateway
|
||||
|
||||
> **Delivery scope:** `StellaOps.Policy.Gateway` minimal API service fronting Policy Engine pack CRUD + activation endpoints for UI/CLI clients. Sender-constrained with DPoP and tenant headers, suitable for online and Offline Kit deployments.
|
||||
|
||||
## 1 · Responsibilities
|
||||
|
||||
- Proxy policy pack CRUD and activation requests to Policy Engine while enforcing scope policies (`policy:read`, `policy:author`, `policy:review`, `policy:operate`, `policy:activate`).
|
||||
- Normalise responses (DTO + `ProblemDetails`) so Console, CLI, and automation receive consistent payloads.
|
||||
- Guard activation actions with structured logging and metrics so approvals are auditable.
|
||||
- Support dual auth modes:
|
||||
- Forwarded caller tokens (Console/CLI) with DPoP proofs + `X-Stella-Tenant` header.
|
||||
- Gateway client credentials (DPoP) for service automation or Offline Kit flows when no caller token is present.
|
||||
|
||||
## 2 · Endpoints
|
||||
|
||||
| Route | Method | Description | Required scope(s) |
|
||||
|-------|--------|-------------|-------------------|
|
||||
| `/api/policy/packs` | `GET` | List policy packs and revisions for the active tenant. | `policy:read` |
|
||||
| `/api/policy/packs` | `POST` | Create a policy pack shell or upsert display metadata. | `policy:author` |
|
||||
| `/api/policy/packs/{packId}/revisions` | `POST` | Create or update a policy revision (draft/approved). | `policy:author` |
|
||||
| `/api/policy/packs/{packId}/revisions/{version}:activate` | `POST` | Activate a revision, enforcing single/two-person approvals. | `policy:operate`, `policy:activate` |
|
||||
|
||||
### Response shapes
|
||||
|
||||
- Successful responses return camel-case DTOs matching `PolicyPackDto`, `PolicyRevisionDto`, or `PolicyRevisionActivationDto` as described in the Policy Engine API doc (`/docs/api/policy.md`).
|
||||
- Errors always return RFC 7807 `ProblemDetails` with deterministic fields (`title`, `detail`, `status`). Missing caller credentials now surface `401` with `"Upstream authorization missing"` detail.
|
||||
|
||||
## 3 · Authentication & headers
|
||||
|
||||
| Header | Source | Notes |
|
||||
|--------|--------|-------|
|
||||
| `Authorization` | Forwarded caller token *or* gateway client credentials. | Caller tokens must include tenant scope; gateway tokens default to `DPoP` scheme. |
|
||||
| `DPoP` | Caller or gateway. | Required when Authority mandates proof-of-possession (default). Generated per request; gateway keeps ES256/ES384 key material under `etc/policy-gateway-dpop.pem`. |
|
||||
| `X-Stella-Tenant` | Caller | Tenant isolation header. Forwarded unchanged; gateway automation omits it. |
|
||||
|
||||
Gateway client credentials are configured in `policy-gateway.yaml`:
|
||||
|
||||
```yaml
|
||||
policyEngine:
|
||||
baseAddress: "https://policy-engine.internal"
|
||||
audience: "api://policy-engine"
|
||||
clientCredentials:
|
||||
enabled: true
|
||||
clientId: "policy-gateway"
|
||||
clientSecret: "<secret>"
|
||||
scopes:
|
||||
- policy:read
|
||||
- policy:author
|
||||
- policy:review
|
||||
- policy:operate
|
||||
- policy:activate
|
||||
dpop:
|
||||
enabled: true
|
||||
keyPath: "../etc/policy-gateway-dpop.pem"
|
||||
algorithm: "ES256"
|
||||
```
|
||||
|
||||
> 🔐 **DPoP key** – store the private key alongside Offline Kit secrets; rotate it whenever the gateway identity or Authority configuration changes.
|
||||
|
||||
## 4 · Metrics & logging
|
||||
|
||||
All activation calls emit:
|
||||
|
||||
- `policy_gateway_activation_requests_total{outcome,source}` – counter labelled with `outcome` (`activated`, `pending_second_approval`, `already_active`, `bad_request`, `not_found`, `unauthorized`, `forbidden`, `error`) and `source` (`caller`, `service`).
|
||||
- `policy_gateway_activation_latency_ms{outcome,source}` – histogram measuring proxy latency.
|
||||
|
||||
Structured logs (category `StellaOps.Policy.Gateway.Activation`) include `PackId`, `Version`, `Outcome`, `Source`, and upstream status code for audit trails.
|
||||
|
||||
## 5 · Sample `curl` workflows
|
||||
|
||||
Assuming you already obtained a DPoP-bound access token (`$TOKEN`) for tenant `acme`:
|
||||
|
||||
```bash
|
||||
# Generate a DPoP proof for GET via the CLI helper
|
||||
DPoP_PROOF=$(stella auth dpop proof \
|
||||
--htu https://gateway.example.com/api/policy/packs \
|
||||
--htm GET \
|
||||
--token "$TOKEN")
|
||||
|
||||
curl -sS https://gateway.example.com/api/policy/packs \
|
||||
-H "Authorization: DPoP $TOKEN" \
|
||||
-H "DPoP: $DPoP_PROOF" \
|
||||
-H "X-Stella-Tenant: acme"
|
||||
|
||||
# Draft a new revision
|
||||
DPoP_PROOF=$(stella auth dpop proof \
|
||||
--htu https://gateway.example.com/api/policy/packs/policy.core/revisions \
|
||||
--htm POST \
|
||||
--token "$TOKEN")
|
||||
|
||||
curl -sS https://gateway.example.com/api/policy/packs/policy.core/revisions \
|
||||
-H "Authorization: DPoP $TOKEN" \
|
||||
-H "DPoP: $DPoP_PROOF" \
|
||||
-H "X-Stella-Tenant: acme" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"version":5,"requiresTwoPersonApproval":true,"initialStatus":"Draft"}'
|
||||
|
||||
# Activate revision 5 (returns 202 when awaiting the second approver)
|
||||
DPoP_PROOF=$(stella auth dpop proof \
|
||||
--htu https://gateway.example.com/api/policy/packs/policy.core/revisions/5:activate \
|
||||
--htm POST \
|
||||
--token "$TOKEN")
|
||||
|
||||
curl -sS https://gateway.example.com/api/policy/packs/policy.core/revisions/5:activate \
|
||||
-H "Authorization: DPoP $TOKEN" \
|
||||
-H "DPoP: $DPoP_PROOF" \
|
||||
-H "X-Stella-Tenant: acme" \
|
||||
-H "Content-Type: application/json" \
|
||||
-d '{"comment":"Rollout baseline"}'
|
||||
```
|
||||
|
||||
For air-gapped environments, bundle `policy-gateway.yaml` and the DPoP key in the Offline Kit (see `/docs/24_OFFLINE_KIT.md` §5.7).
|
||||
|
||||
> **DPoP proof helper:** Use `stella auth dpop proof` to mint sender-constrained proofs locally. The command accepts `--htu`, `--htm`, and `--token` arguments and emits a ready-to-use header value. Teams maintaining alternate tooling (for example, `scripts/make-dpop.sh`) can substitute it as long as the inputs and output match the CLI behaviour.
|
||||
|
||||
## 6 · Offline Kit guidance
|
||||
|
||||
- Include `policy-gateway.yaml.sample` and the resolved runtime config in the Offline Kit’s `config/` tree.
|
||||
- Place the DPoP private key under `secrets/policy-gateway-dpop.pem` with restricted permissions; document rotation steps in the manifest.
|
||||
- When building verification scripts, use the gateway endpoints above instead of hitting Policy Engine directly. The Offline Kit validator now expects `policy_gateway_activation_requests_total` metrics in the Prometheus snapshot.
|
||||
|
||||
## 7 · Change log
|
||||
|
||||
- **2025-10-27 – Sprint 18.5**: Initial gateway bootstrap + activation metrics + DPoP client credentials.
|
||||
|
||||
@@ -1,238 +1,238 @@
|
||||
# Policy Lifecycle & Approvals
|
||||
|
||||
> **Audience:** Policy authors, reviewers, security approvers, release engineers.
|
||||
> **Scope:** End-to-end flow for `stella-dsl@1` policies from draft through archival, including CLI/Console touch-points, Authority scopes, audit artefacts, and offline considerations.
|
||||
|
||||
This guide explains how a policy progresses through Stella Ops, which roles are involved, and the artefacts produced at every step. Pair it with the [Policy Engine Overview](overview.md), [DSL reference](dsl.md), and upcoming run documentation to ensure consistent authoring and rollout.
|
||||
|
||||
---
|
||||
|
||||
## 1 · Protocol Summary
|
||||
|
||||
- Policies are **immutable versions** attached to a stable `policy_id`.
|
||||
- Lifecycle states: `draft → submitted → approved → active → archived`.
|
||||
- Every transition requires explicit Authority scopes and produces structured events + storage artefacts (`policies`, `policy_runs`, audit log collections).
|
||||
- Simulation and CI gating happen **before** approvals can be granted.
|
||||
- Activation triggers (runs, bundle exports, CLI `promote`) operate on the **latest approved** version per tenant.
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> Draft
|
||||
Draft --> Draft: edit/save (policy:author)
|
||||
Draft --> Submitted: submit(reviewers) (policy:author)
|
||||
Submitted --> Draft: requestChanges (policy:review)
|
||||
Submitted --> Approved: approve (policy:approve)
|
||||
Approved --> Active: activate/run (policy:operate)
|
||||
Active --> Archived: archive (policy:operate)
|
||||
Approved --> Archived: superseded/explicit archive
|
||||
Archived --> [*]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2 · Roles & Authority Scopes
|
||||
|
||||
| Role (suggested) | Required scopes | Responsibilities |
|
||||
|------------------|-----------------|------------------|
|
||||
| **Policy Author** | `policy:author`, `policy:simulate`, `findings:read` | Draft DSL, run local/CI simulations, submit for review. |
|
||||
| **Policy Reviewer** | `policy:review`, `policy:simulate`, `findings:read` | Comment on submissions, demand additional simulations, request changes. |
|
||||
| **Policy Approver** | `policy:approve`, `policy:audit`, `findings:read` | Grant final approval, ensure sign-off evidence captured. |
|
||||
| **Policy Operator** | `policy:operate`, `policy:run`, `policy:activate`, `findings:read` | Trigger full/incremental runs, monitor results, roll back to previous version. |
|
||||
| **Policy Auditor** | `policy:audit`, `findings:read` | Review past versions, verify attestations, respond to compliance requests. |
|
||||
| **Policy Engine Service** | `effective:write`, `findings:read` | Materialise effective findings during runs; no approval capabilities. |
|
||||
|
||||
> Scopes are issued by Authority (`AUTH-POLICY-20-001`). Tenants may map organisational roles (e.g., `secops.approver`) to these scopes via issuer policy.
|
||||
|
||||
---
|
||||
|
||||
## 3 · Lifecycle Stages in Detail
|
||||
|
||||
### 3.1 Draft
|
||||
|
||||
- **Who:** Authors (`policy:author`).
|
||||
- **Tools:** Console editor, `stella policy edit`, policy DSL files.
|
||||
- **Actions:**
|
||||
- Author DSL leveraging [stella-dsl@1](dsl.md).
|
||||
- Run `stella policy lint` and `stella policy simulate --sbom <fixtures>` locally.
|
||||
- Attach rationale metadata (`metadata.description`, tags).
|
||||
- **Artefacts:**
|
||||
- `policies` document with `status=draft`, `version=n`, `provenance.created_by`.
|
||||
- Local IR cache (`.stella.ir.json`) generated by CLI compile.
|
||||
- **Guards:**
|
||||
- Draft versions never run in production.
|
||||
- CI must lint drafts before allowing submission PRs (see `DEVOPS-POLICY-20-001`).
|
||||
|
||||
### 3.2 Submission
|
||||
|
||||
- **Who:** Authors (`policy:author`).
|
||||
- **Tools:** Console “Submit for review” button, `stella policy submit <policyId> --reviewers ...`.
|
||||
- **Actions:**
|
||||
- Provide review notes and required simulations (CLI uploads attachments).
|
||||
- Choose reviewer groups; Authority records them in submission metadata.
|
||||
- **Artefacts:**
|
||||
- Policy document transitions to `status=submitted`, capturing `submitted_by`, `submitted_at`, reviewer list, simulation digest references.
|
||||
- Audit event `policy.submitted` (Authority timeline / Notifier integration).
|
||||
- **Guards:**
|
||||
- Submission blocked unless latest lint + compile succeed (<24 h freshness).
|
||||
- Must reference at least one simulation artefact (CLI enforces via `--attach`).
|
||||
|
||||
### 3.3 Review (Submitted)
|
||||
|
||||
- **Who:** Reviewers (`policy:review`), optionally authors responding.
|
||||
- **Tools:** Console review pane (line comments, overall verdict), `stella policy review`.
|
||||
- **Actions:**
|
||||
- Inspect DSL diff vs previous approved version.
|
||||
- Run additional `simulate` jobs (UI button or CLI).
|
||||
- Request changes → policy returns to `draft` with comment log.
|
||||
- **Artefacts:**
|
||||
- Comments stored in `policy_reviews` collection with timestamps, resolved flag.
|
||||
- Additional simulation run records appended to submission metadata.
|
||||
- **Guards:**
|
||||
- Approval cannot proceed until all blocking comments resolved.
|
||||
- Required reviewers (Authority rule) must vote before approver sees “Approve” button.
|
||||
|
||||
### 3.4 Approval
|
||||
|
||||
- **Who:** Approvers (`policy:approve`).
|
||||
- **Tools:** Console “Approve”, CLI `stella policy approve <id> --version n --note "rationale"`.
|
||||
- **Actions:**
|
||||
- Confirm compliance checks (see §6) all green.
|
||||
- Provide approval note (mandatory string captured in audit trail).
|
||||
- **Artefacts:**
|
||||
- Policy `status=approved`, `approved_by`, `approved_at`, `approval_note`.
|
||||
- Audit event `policy.approved` plus optional Notifier broadcast.
|
||||
- Immutable approval record stored in `policy_history`.
|
||||
- **Guards:**
|
||||
- Approver cannot be same identity as author (enforced by Authority config).
|
||||
- Approver must attest to successful simulation diff review (`--attach diff.json`).
|
||||
|
||||
### 3.5 Activation & Runs
|
||||
|
||||
- **Who:** Operators (`policy:operate`, `policy:run`, `policy:activate`).
|
||||
- **Tools:** Console “Promote to active”, CLI `stella policy activate <id> --version n`, `stella policy run`.
|
||||
- **Actions:**
|
||||
- Mark approved version as tenant’s active policy.
|
||||
- Trigger full run or rely on orchestrator for incremental runs.
|
||||
- Monitor results via Console dashboards or CLI run logs.
|
||||
- **Artefacts:**
|
||||
- `policy_runs` entries with `mode=full|incremental`, `policy_version=n`.
|
||||
- Effective findings collections updated; explain traces stored.
|
||||
- Activation event `policy.activated` with `runId`.
|
||||
- **Guards:**
|
||||
- Activation blocked if previous full run <24 h old failed or is pending.
|
||||
- Selection of SBOM/advisory snapshots uses consistent cursors recorded for reproducibility.
|
||||
|
||||
### 3.6 Archival / Rollback
|
||||
|
||||
- **Who:** Approvers or Operators with `policy:archive`.
|
||||
- **Tools:** Console menu, CLI `stella policy archive <id> --version n --reason`.
|
||||
- **Actions:**
|
||||
- Retire policies superseded by newer versions or revert to older approved version (`stella policy activate <id> --version n-1`).
|
||||
- Export archived version for audit bundles (Offline Kit integration).
|
||||
- **Artefacts:**
|
||||
- Policy `status=archived`, `archived_by`, `archived_at`, reason.
|
||||
- Audit event `policy.archived`.
|
||||
- Exported DSSE-signed policy pack stored if requested.
|
||||
- **Guards:**
|
||||
- Archival cannot proceed while runs using that version are in-flight.
|
||||
- Rollback requires documented incident reference.
|
||||
|
||||
---
|
||||
|
||||
## 4 · Tooling Touchpoints
|
||||
|
||||
| Stage | Console | CLI | API |
|
||||
|-------|---------|-----|-----|
|
||||
| Draft | Inline linting, simulation panel | `stella policy lint`, `edit`, `simulate` | `POST /policies`, `PUT /policies/{id}/versions/{v}` |
|
||||
| Submit | Submit modal (attach simulations) | `stella policy submit` | `POST /policies/{id}/submit` |
|
||||
| Review | Comment threads, diff viewer | `stella policy review --approve/--request-changes` | `POST /policies/{id}/reviews` |
|
||||
| Approve | Approve dialog | `stella policy approve` | `POST /policies/{id}/approve` |
|
||||
| Activate | Promote button, run scheduler | `stella policy activate`, `run`, `simulate` | `POST /policies/{id}/run`, `POST /policies/{id}/activate` |
|
||||
| Archive | Archive / rollback menu | `stella policy archive` | `POST /policies/{id}/archive` |
|
||||
|
||||
All CLI commands emit structured JSON by default; use `--format table` for human review.
|
||||
|
||||
---
|
||||
|
||||
## 5 · Audit & Observability
|
||||
|
||||
- **Storage:**
|
||||
- `policies` retains all versions with provenance metadata.
|
||||
- `policy_reviews` stores reviewer comments, timestamps, attachments.
|
||||
- `policy_history` summarises transitions (state, actor, note, diff digest).
|
||||
- `policy_runs` retains input cursors and determinism hash per run.
|
||||
- **Events:**
|
||||
- `policy.submitted`, `policy.review.requested`, `policy.approved`, `policy.activated`, `policy.archived`, `policy.rollback`.
|
||||
- Routed to Notifier + Timeline Indexer; offline deployments log to local event store.
|
||||
- **Logs & metrics:**
|
||||
- Policy Engine logs include `policyId`, `policyVersion`, `runId`, `approvalNote`.
|
||||
- Observability dashboards (see forthcoming `/docs/observability/policy.md`) highlight pending approvals, run SLA, VEX overrides.
|
||||
- **Reproducibility:**
|
||||
- Each state transition stores IR checksum and simulation diff digests, enabling offline audit replay.
|
||||
|
||||
---
|
||||
|
||||
## 6 · Compliance Gates
|
||||
|
||||
| Gate | Stage | Enforced by | Requirement |
|
||||
|------|-------|-------------|-------------|
|
||||
| **DSL lint** | Draft → Submit | CLI/CI | `stella policy lint` successful within 24 h. |
|
||||
| **Simulation evidence** | Submit | CLI/Console | Attach diff from `stella policy simulate` covering baseline SBOM set. |
|
||||
| **Reviewer quorum** | Submit → Approve | Authority | Minimum approver/reviewer count configurable per tenant. |
|
||||
| **Determinism CI** | Approve | DevOps job | Twin run diff passes (`DEVOPS-POLICY-20-003`). |
|
||||
| **Activation health** | Approve → Activate | Policy Engine | Last run status succeeded; orchestrator queue healthy. |
|
||||
| **Export validation** | Archive | Offline Kit | DSSE-signed policy pack generated for long-term retention. |
|
||||
|
||||
Failure of any gate emits a `policy.lifecycle.violation` event and blocks transition until resolved.
|
||||
|
||||
---
|
||||
|
||||
## 7 · Offline / Air-Gap Considerations
|
||||
|
||||
- Offline Kit bundles include:
|
||||
- Approved policy packs (`.policy.bundle` + DSSE signatures).
|
||||
- Submission/approval audit logs.
|
||||
- Simulation diff JSON for reproducibility.
|
||||
- Air-gapped sites operate with the same lifecycle:
|
||||
- Approvals happen locally; Authority runs in enclave.
|
||||
- Rollout requires manual import of policy packs from connected environment via signed bundles.
|
||||
- `stella policy simulate --sealed` ensures no outbound calls; required before approval in sealed mode.
|
||||
|
||||
---
|
||||
|
||||
## 8 · Incident Response & Rollback
|
||||
|
||||
- Incident mode (triggered via `policy incident activate`) forces:
|
||||
- Immediate incremental run to evaluate mitigation policies.
|
||||
- Expanded trace retention for affected runs.
|
||||
- Automatic snapshot of currently active policies for evidence locker.
|
||||
- Rollback path:
|
||||
1. `stella policy activate <id> --version <previous>` with incident note.
|
||||
2. Orchestrator schedules full run to ensure findings align.
|
||||
3. Archive problematic version with reason referencing incident ticket.
|
||||
- Post-incident review must confirm new version passes gates before re-activation.
|
||||
|
||||
---
|
||||
|
||||
## 9 · CI/CD Integration (Reference)
|
||||
|
||||
- **Pre-merge:** run lint + simulation jobs against golden SBOM fixtures.
|
||||
- **Post-merge (main):** compile, compute IR checksum, stage for Offline Kit.
|
||||
- **Nightly:** determinism replay, `policy simulate` diff drift alerts, backlog of pending approvals.
|
||||
- **Notifications:** Slack/Email via Notifier when submissions await review > SLA or approvals succeed.
|
||||
|
||||
---
|
||||
|
||||
## 10 · Compliance Checklist
|
||||
|
||||
- [ ] **Role mapping validated:** Authority issuer config maps organisational roles to required `policy:*` scopes (per tenant).
|
||||
- [ ] **Submission evidence attached:** Latest simulation diff and lint artefacts linked to submission.
|
||||
- [ ] **Reviewer quorum met:** All required reviewers approved or acknowledged; no unresolved blocking comments.
|
||||
- [ ] **Approval note logged:** Approver justification recorded in audit trail alongside IR checksum.
|
||||
- [ ] **Activation guard passed:** Latest run status success, orchestrator queue healthy, determinism job green.
|
||||
- [ ] **Archive bundles produced:** When archiving, DSSE-signed policy pack exported and stored for offline retention.
|
||||
- [ ] **Offline parity proven:** For sealed deployments, `--sealed` simulations executed and logged before approval.
|
||||
|
||||
---
|
||||
|
||||
*Last updated: 2025-10-26 (Sprint 20).*
|
||||
# Policy Lifecycle & Approvals
|
||||
|
||||
> **Audience:** Policy authors, reviewers, security approvers, release engineers.
|
||||
> **Scope:** End-to-end flow for `stella-dsl@1` policies from draft through archival, including CLI/Console touch-points, Authority scopes, audit artefacts, and offline considerations.
|
||||
|
||||
This guide explains how a policy progresses through Stella Ops, which roles are involved, and the artefacts produced at every step. Pair it with the [Policy Engine Overview](overview.md), [DSL reference](dsl.md), and upcoming run documentation to ensure consistent authoring and rollout.
|
||||
|
||||
---
|
||||
|
||||
## 1 · Protocol Summary
|
||||
|
||||
- Policies are **immutable versions** attached to a stable `policy_id`.
|
||||
- Lifecycle states: `draft → submitted → approved → active → archived`.
|
||||
- Every transition requires explicit Authority scopes and produces structured events + storage artefacts (`policies`, `policy_runs`, audit log collections).
|
||||
- Simulation and CI gating happen **before** approvals can be granted.
|
||||
- Activation triggers (runs, bundle exports, CLI `promote`) operate on the **latest approved** version per tenant.
|
||||
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> Draft
|
||||
Draft --> Draft: edit/save (policy:author)
|
||||
Draft --> Submitted: submit(reviewers) (policy:author)
|
||||
Submitted --> Draft: requestChanges (policy:review)
|
||||
Submitted --> Approved: approve (policy:approve)
|
||||
Approved --> Active: activate/run (policy:operate)
|
||||
Active --> Archived: archive (policy:operate)
|
||||
Approved --> Archived: superseded/explicit archive
|
||||
Archived --> [*]
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2 · Roles & Authority Scopes
|
||||
|
||||
| Role (suggested) | Required scopes | Responsibilities |
|
||||
|------------------|-----------------|------------------|
|
||||
| **Policy Author** | `policy:author`, `policy:simulate`, `findings:read` | Draft DSL, run local/CI simulations, submit for review. |
|
||||
| **Policy Reviewer** | `policy:review`, `policy:simulate`, `findings:read` | Comment on submissions, demand additional simulations, request changes. |
|
||||
| **Policy Approver** | `policy:approve`, `policy:audit`, `findings:read` | Grant final approval, ensure sign-off evidence captured. |
|
||||
| **Policy Operator** | `policy:operate`, `policy:run`, `policy:activate`, `findings:read` | Trigger full/incremental runs, monitor results, roll back to previous version. |
|
||||
| **Policy Auditor** | `policy:audit`, `findings:read` | Review past versions, verify attestations, respond to compliance requests. |
|
||||
| **Policy Engine Service** | `effective:write`, `findings:read` | Materialise effective findings during runs; no approval capabilities. |
|
||||
|
||||
> Scopes are issued by Authority (`AUTH-POLICY-20-001`). Tenants may map organisational roles (e.g., `secops.approver`) to these scopes via issuer policy.
|
||||
|
||||
---
|
||||
|
||||
## 3 · Lifecycle Stages in Detail
|
||||
|
||||
### 3.1 Draft
|
||||
|
||||
- **Who:** Authors (`policy:author`).
|
||||
- **Tools:** Console editor, `stella policy edit`, policy DSL files.
|
||||
- **Actions:**
|
||||
- Author DSL leveraging [stella-dsl@1](dsl.md).
|
||||
- Run `stella policy lint` and `stella policy simulate --sbom <fixtures>` locally.
|
||||
- Attach rationale metadata (`metadata.description`, tags).
|
||||
- **Artefacts:**
|
||||
- `policies` document with `status=draft`, `version=n`, `provenance.created_by`.
|
||||
- Local IR cache (`.stella.ir.json`) generated by CLI compile.
|
||||
- **Guards:**
|
||||
- Draft versions never run in production.
|
||||
- CI must lint drafts before allowing submission PRs (see `DEVOPS-POLICY-20-001`).
|
||||
|
||||
### 3.2 Submission
|
||||
|
||||
- **Who:** Authors (`policy:author`).
|
||||
- **Tools:** Console “Submit for review” button, `stella policy submit <policyId> --reviewers ...`.
|
||||
- **Actions:**
|
||||
- Provide review notes and required simulations (CLI uploads attachments).
|
||||
- Choose reviewer groups; Authority records them in submission metadata.
|
||||
- **Artefacts:**
|
||||
- Policy document transitions to `status=submitted`, capturing `submitted_by`, `submitted_at`, reviewer list, simulation digest references.
|
||||
- Audit event `policy.submitted` (Authority timeline / Notifier integration).
|
||||
- **Guards:**
|
||||
- Submission blocked unless latest lint + compile succeed (<24 h freshness).
|
||||
- Must reference at least one simulation artefact (CLI enforces via `--attach`).
|
||||
|
||||
### 3.3 Review (Submitted)
|
||||
|
||||
- **Who:** Reviewers (`policy:review`), optionally authors responding.
|
||||
- **Tools:** Console review pane (line comments, overall verdict), `stella policy review`.
|
||||
- **Actions:**
|
||||
- Inspect DSL diff vs previous approved version.
|
||||
- Run additional `simulate` jobs (UI button or CLI).
|
||||
- Request changes → policy returns to `draft` with comment log.
|
||||
- **Artefacts:**
|
||||
- Comments stored in `policy_reviews` collection with timestamps, resolved flag.
|
||||
- Additional simulation run records appended to submission metadata.
|
||||
- **Guards:**
|
||||
- Approval cannot proceed until all blocking comments resolved.
|
||||
- Required reviewers (Authority rule) must vote before approver sees “Approve” button.
|
||||
|
||||
### 3.4 Approval
|
||||
|
||||
- **Who:** Approvers (`policy:approve`).
|
||||
- **Tools:** Console “Approve”, CLI `stella policy approve <id> --version n --note "rationale"`.
|
||||
- **Actions:**
|
||||
- Confirm compliance checks (see §6) all green.
|
||||
- Provide approval note (mandatory string captured in audit trail).
|
||||
- **Artefacts:**
|
||||
- Policy `status=approved`, `approved_by`, `approved_at`, `approval_note`.
|
||||
- Audit event `policy.approved` plus optional Notifier broadcast.
|
||||
- Immutable approval record stored in `policy_history`.
|
||||
- **Guards:**
|
||||
- Approver cannot be same identity as author (enforced by Authority config).
|
||||
- Approver must attest to successful simulation diff review (`--attach diff.json`).
|
||||
|
||||
### 3.5 Activation & Runs
|
||||
|
||||
- **Who:** Operators (`policy:operate`, `policy:run`, `policy:activate`).
|
||||
- **Tools:** Console “Promote to active”, CLI `stella policy activate <id> --version n`, `stella policy run`.
|
||||
- **Actions:**
|
||||
- Mark approved version as tenant’s active policy.
|
||||
- Trigger full run or rely on orchestrator for incremental runs.
|
||||
- Monitor results via Console dashboards or CLI run logs.
|
||||
- **Artefacts:**
|
||||
- `policy_runs` entries with `mode=full|incremental`, `policy_version=n`.
|
||||
- Effective findings collections updated; explain traces stored.
|
||||
- Activation event `policy.activated` with `runId`.
|
||||
- **Guards:**
|
||||
- Activation blocked if previous full run <24 h old failed or is pending.
|
||||
- Selection of SBOM/advisory snapshots uses consistent cursors recorded for reproducibility.
|
||||
|
||||
### 3.6 Archival / Rollback
|
||||
|
||||
- **Who:** Approvers or Operators with `policy:archive`.
|
||||
- **Tools:** Console menu, CLI `stella policy archive <id> --version n --reason`.
|
||||
- **Actions:**
|
||||
- Retire policies superseded by newer versions or revert to older approved version (`stella policy activate <id> --version n-1`).
|
||||
- Export archived version for audit bundles (Offline Kit integration).
|
||||
- **Artefacts:**
|
||||
- Policy `status=archived`, `archived_by`, `archived_at`, reason.
|
||||
- Audit event `policy.archived`.
|
||||
- Exported DSSE-signed policy pack stored if requested.
|
||||
- **Guards:**
|
||||
- Archival cannot proceed while runs using that version are in-flight.
|
||||
- Rollback requires documented incident reference.
|
||||
|
||||
---
|
||||
|
||||
## 4 · Tooling Touchpoints
|
||||
|
||||
| Stage | Console | CLI | API |
|
||||
|-------|---------|-----|-----|
|
||||
| Draft | Inline linting, simulation panel | `stella policy lint`, `edit`, `simulate` | `POST /policies`, `PUT /policies/{id}/versions/{v}` |
|
||||
| Submit | Submit modal (attach simulations) | `stella policy submit` | `POST /policies/{id}/submit` |
|
||||
| Review | Comment threads, diff viewer | `stella policy review --approve/--request-changes` | `POST /policies/{id}/reviews` |
|
||||
| Approve | Approve dialog | `stella policy approve` | `POST /policies/{id}/approve` |
|
||||
| Activate | Promote button, run scheduler | `stella policy activate`, `run`, `simulate` | `POST /policies/{id}/run`, `POST /policies/{id}/activate` |
|
||||
| Archive | Archive / rollback menu | `stella policy archive` | `POST /policies/{id}/archive` |
|
||||
|
||||
All CLI commands emit structured JSON by default; use `--format table` for human review.
|
||||
|
||||
---
|
||||
|
||||
## 5 · Audit & Observability
|
||||
|
||||
- **Storage:**
|
||||
- `policies` retains all versions with provenance metadata.
|
||||
- `policy_reviews` stores reviewer comments, timestamps, attachments.
|
||||
- `policy_history` summarises transitions (state, actor, note, diff digest).
|
||||
- `policy_runs` retains input cursors and determinism hash per run.
|
||||
- **Events:**
|
||||
- `policy.submitted`, `policy.review.requested`, `policy.approved`, `policy.activated`, `policy.archived`, `policy.rollback`.
|
||||
- Routed to Notifier + Timeline Indexer; offline deployments log to local event store.
|
||||
- **Logs & metrics:**
|
||||
- Policy Engine logs include `policyId`, `policyVersion`, `runId`, `approvalNote`.
|
||||
- Observability dashboards (see forthcoming `/docs/observability/policy.md`) highlight pending approvals, run SLA, VEX overrides.
|
||||
- **Reproducibility:**
|
||||
- Each state transition stores IR checksum and simulation diff digests, enabling offline audit replay.
|
||||
|
||||
---
|
||||
|
||||
## 6 · Compliance Gates
|
||||
|
||||
| Gate | Stage | Enforced by | Requirement |
|
||||
|------|-------|-------------|-------------|
|
||||
| **DSL lint** | Draft → Submit | CLI/CI | `stella policy lint` successful within 24 h. |
|
||||
| **Simulation evidence** | Submit | CLI/Console | Attach diff from `stella policy simulate` covering baseline SBOM set. |
|
||||
| **Reviewer quorum** | Submit → Approve | Authority | Minimum approver/reviewer count configurable per tenant. |
|
||||
| **Determinism CI** | Approve | DevOps job | Twin run diff passes (`DEVOPS-POLICY-20-003`). |
|
||||
| **Activation health** | Approve → Activate | Policy Engine | Last run status succeeded; orchestrator queue healthy. |
|
||||
| **Export validation** | Archive | Offline Kit | DSSE-signed policy pack generated for long-term retention. |
|
||||
|
||||
Failure of any gate emits a `policy.lifecycle.violation` event and blocks transition until resolved.
|
||||
|
||||
---
|
||||
|
||||
## 7 · Offline / Air-Gap Considerations
|
||||
|
||||
- Offline Kit bundles include:
|
||||
- Approved policy packs (`.policy.bundle` + DSSE signatures).
|
||||
- Submission/approval audit logs.
|
||||
- Simulation diff JSON for reproducibility.
|
||||
- Air-gapped sites operate with the same lifecycle:
|
||||
- Approvals happen locally; Authority runs in enclave.
|
||||
- Rollout requires manual import of policy packs from connected environment via signed bundles.
|
||||
- `stella policy simulate --sealed` ensures no outbound calls; required before approval in sealed mode.
|
||||
|
||||
---
|
||||
|
||||
## 8 · Incident Response & Rollback
|
||||
|
||||
- Incident mode (triggered via `policy incident activate`) forces:
|
||||
- Immediate incremental run to evaluate mitigation policies.
|
||||
- Expanded trace retention for affected runs.
|
||||
- Automatic snapshot of currently active policies for evidence locker.
|
||||
- Rollback path:
|
||||
1. `stella policy activate <id> --version <previous>` with incident note.
|
||||
2. Orchestrator schedules full run to ensure findings align.
|
||||
3. Archive problematic version with reason referencing incident ticket.
|
||||
- Post-incident review must confirm new version passes gates before re-activation.
|
||||
|
||||
---
|
||||
|
||||
## 9 · CI/CD Integration (Reference)
|
||||
|
||||
- **Pre-merge:** run lint + simulation jobs against golden SBOM fixtures.
|
||||
- **Post-merge (main):** compile, compute IR checksum, stage for Offline Kit.
|
||||
- **Nightly:** determinism replay, `policy simulate` diff drift alerts, backlog of pending approvals.
|
||||
- **Notifications:** Slack/Email via Notifier when submissions await review > SLA or approvals succeed.
|
||||
|
||||
---
|
||||
|
||||
## 10 · Compliance Checklist
|
||||
|
||||
- [ ] **Role mapping validated:** Authority issuer config maps organisational roles to required `policy:*` scopes (per tenant).
|
||||
- [ ] **Submission evidence attached:** Latest simulation diff and lint artefacts linked to submission.
|
||||
- [ ] **Reviewer quorum met:** All required reviewers approved or acknowledged; no unresolved blocking comments.
|
||||
- [ ] **Approval note logged:** Approver justification recorded in audit trail alongside IR checksum.
|
||||
- [ ] **Activation guard passed:** Latest run status success, orchestrator queue healthy, determinism job green.
|
||||
- [ ] **Archive bundles produced:** When archiving, DSSE-signed policy pack exported and stored for offline retention.
|
||||
- [ ] **Offline parity proven:** For sealed deployments, `--sealed` simulations executed and logged before approval.
|
||||
|
||||
---
|
||||
|
||||
*Last updated: 2025-10-26 (Sprint 20).*
|
||||
|
||||
@@ -1,173 +1,173 @@
|
||||
# Policy Engine Overview
|
||||
|
||||
> **Goal:** Evaluate organisation policies deterministically against scanner SBOMs, Concelier advisories, and Excititor VEX evidence, then publish effective findings that downstream services can trust.
|
||||
|
||||
This document introduces the v2 Policy Engine: how the service fits into Stella Ops, the artefacts it produces, the contracts it honours, and the guardrails that keep policy decisions reproducible across air-gapped and connected deployments.
|
||||
|
||||
---
|
||||
|
||||
## 1 · Role in the Platform
|
||||
|
||||
- **Purpose:** Compose policy verdicts by reconciling SBOM inventory, advisory metadata, VEX statements, and organisation rules.
|
||||
- **Form factor:** Dedicated `.NET 10` Minimal API host (`StellaOps.Policy.Engine`) plus worker orchestration. Policies are defined in `stella-dsl@1` packs compiled to an intermediate representation (IR) with a stable SHA-256 digest.
|
||||
- **Tenancy:** All workloads run under Authority-enforced scopes (`policy:*`, `findings:read`, `effective:write`). Only the Policy Engine identity may materialise effective findings collections.
|
||||
- **Consumption:** Findings ledger, Console, CLI, and Notify read the published `effective_finding_{policyId}` materialisations and policy run ledger (`policy_runs`).
|
||||
- **Offline parity:** Bundled policies import/export alongside advisories and VEX. In sealed mode the engine degrades gracefully, annotating explanations whenever cached signals replace live lookups.
|
||||
|
||||
---
|
||||
|
||||
## 2 · High-Level Architecture
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph Inputs
|
||||
A[Scanner SBOMs<br/>Inventory & Usage]
|
||||
B[Concelier Advisories<br/>Canonical linksets]
|
||||
C[Excititor VEX<br/>Consensus status]
|
||||
D[Policy Packs<br/>stella-dsl@1]
|
||||
end
|
||||
subgraph PolicyEngine["StellaOps.Policy.Engine"]
|
||||
P1[DSL Compiler<br/>IR + Digest]
|
||||
P2[Joiners<br/>SBOM ↔ Advisory ↔ VEX]
|
||||
P3[Deterministic Evaluator<br/>Rule hits + scoring]
|
||||
P4[Materialisers<br/>effective findings]
|
||||
P5[Run Orchestrator<br/>Full & incremental]
|
||||
end
|
||||
subgraph Outputs
|
||||
O1[Effective Findings Collections]
|
||||
O2[Explain Traces<br/>Rule hit lineage]
|
||||
O3[Metrics & Traces<br/>policy_run_seconds,<br/>rules_fired_total]
|
||||
O4[Simulation/Preview Feeds<br/>CLI & Studio]
|
||||
end
|
||||
|
||||
A --> P2
|
||||
B --> P2
|
||||
C --> P2
|
||||
D --> P1 --> P3
|
||||
P2 --> P3 --> P4 --> O1
|
||||
P3 --> O2
|
||||
P5 --> P3
|
||||
P3 --> O3
|
||||
P3 --> O4
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3 · Core Concepts
|
||||
|
||||
| Concept | Description |
|
||||
|---------|-------------|
|
||||
| **Policy Pack** | Versioned bundle of DSL documents, metadata, and checksum manifest. Packs import/export via CLI and Offline Kit bundles. |
|
||||
| **Policy Digest** | SHA-256 of the canonical IR; used for caching, explain trace attribution, and audit proofs. |
|
||||
| **Effective Findings** | Append-only Mongo collections (`effective_finding_{policyId}`) storing the latest verdict per finding, plus history sidecars. |
|
||||
| **Policy Run** | Execution record persisted in `policy_runs` capturing inputs, run mode, timings, and determinism hash. |
|
||||
| **Explain Trace** | Structured tree showing rule matches, data provenance, and scoring components for UI/CLI explain features. |
|
||||
| **Simulation** | Dry-run evaluation that compares a candidate pack against the active pack and produces verdict diffs without persisting results. |
|
||||
| **Incident Mode** | Elevated sampling/trace capture toggled automatically when SLOs breach; emits events for Notifier and Timeline Indexer. |
|
||||
|
||||
---
|
||||
|
||||
## 4 · Inputs & Pre-processing
|
||||
|
||||
### 4.1 SBOM Inventory
|
||||
|
||||
- **Source:** Scanner.WebService publishes inventory/usage SBOMs plus BOM-Index (roaring bitmap) metadata.
|
||||
- **Consumption:** Policy joiners use the index to expand candidate components quickly, keeping evaluation under the `< 5 s` warm path budget.
|
||||
- **Schema:** CycloneDX Protobuf + JSON views; Policy Engine reads canonical projections via shared SBOM adapters.
|
||||
|
||||
### 4.2 Advisory Corpus
|
||||
|
||||
- **Source:** Concelier exports canonical advisories with deterministic identifiers, linksets, and equivalence tables.
|
||||
- **Contract:** Policy Engine only consumes raw `content.raw`, `identifiers`, and `linkset` fields per Aggregation-Only Contract (AOC); derived precedence remains a policy concern.
|
||||
|
||||
### 4.3 VEX Evidence
|
||||
|
||||
- **Source:** Excititor consensus service resolves OpenVEX / CSAF statements, preserving conflicts.
|
||||
- **Usage:** Policy rules can require specific VEX vendors or justification codes; evaluator records when cached evidence substitutes for live statements (sealed mode).
|
||||
|
||||
### 4.4 Policy Packs
|
||||
|
||||
- Authored in Policy Studio or CLI, validated against the `stella-dsl@1` schema.
|
||||
- Compiler performs canonicalisation (ordering, defaulting) before emitting IR and digest.
|
||||
- Packs bundle scoring profiles, allowlist metadata, and optional reachability weighting tables.
|
||||
|
||||
---
|
||||
|
||||
## 5 · Evaluation Flow
|
||||
|
||||
1. **Run selection** – Orchestrator accepts `full`, `incremental`, or `simulate` jobs. Incremental runs listen to change streams from Concelier, Excititor, and SBOM imports to scope re-evaluation.
|
||||
2. **Input staging** – Candidates fetched in deterministic batches; identity graph from Concelier strengthens PURL lookups.
|
||||
3. **Rule execution** – Evaluator walks rules in lexical order (first-match wins). Actions available: `block`, `ignore`, `warn`, `defer`, `escalate`, `requireVex`, each supporting quieting semantics where permitted.
|
||||
4. **Scoring** – `PolicyScoringConfig` applies severity, trust, reachability weights plus penalties (`warnPenalty`, `ignorePenalty`, `quietPenalty`).
|
||||
5. **Verdict and explain** – Engine constructs `PolicyVerdict` records with inputs, quiet flags, unknown confidence bands, and provenance markers; explain trees capture rule lineage.
|
||||
6. **Materialisation** – Effective findings collections are upserted append-only, stamped with run identifier, policy digest, and tenant.
|
||||
7. **Publishing** – Completed run writes to `policy_runs`, emits metrics (`policy_run_seconds`, `rules_fired_total`, `vex_overrides_total`), and raises events for Console/Notify subscribers.
|
||||
|
||||
---
|
||||
|
||||
## 6 · Run Modes
|
||||
|
||||
| Mode | Trigger | Scope | Persistence | Typical Use |
|
||||
|------|---------|-------|-------------|-------------|
|
||||
| **Full** | Manual CLI (`stella policy run`), scheduled nightly, or emergency rebaseline | Entire tenant | Writes effective findings and run record | After policy publish or major advisory/VEX import |
|
||||
| **Incremental** | Change-stream queue driven by Concelier/Excititor/SBOM deltas | Only affected artefacts | Writes effective findings and run record | Continuous upkeep; ensures SLA ≤ 5 min from source change |
|
||||
| **Simulate** | CLI/Studio preview, CI pipelines | Candidate subset (diff against baseline) | No materialisation; produces explain & diff payloads | Policy authoring, CI regression suites |
|
||||
|
||||
All modes are cancellation-aware and checkpoint progress for replay in case of deployment restarts.
|
||||
|
||||
---
|
||||
|
||||
## 7 · Outputs & Integrations
|
||||
|
||||
- **APIs** – Minimal API exposes policy CRUD, run orchestration, explain fetches, and cursor-based listing of effective findings (see `/docs/api/policy.md` once published).
|
||||
- **CLI** – `stella policy simulate/run/show` commands surface JSON verdicts, exit codes, and diff summaries suitable for CI gating.
|
||||
- **Console / Policy Studio** – UI reads explain traces, policy metadata, approval workflow status, and simulation diffs to guide reviewers.
|
||||
- **Findings Ledger** – Effective findings feed downstream export, Notify, and risk scoring jobs.
|
||||
- **Air-gap bundles** – Offline Kit includes policy packs, scoring configs, and explain indexes; export commands generate DSSE-signed bundles for transfer.
|
||||
|
||||
---
|
||||
|
||||
## 8 · Determinism & Guardrails
|
||||
|
||||
- **Deterministic inputs** – All joins rely on canonical linksets and equivalence tables; batches are sorted, and random/wall-clock APIs are blocked by static analysis plus runtime guards (`ERR_POL_004`).
|
||||
- **Stable outputs** – Canonical JSON serializers sort keys; digests recorded in run metadata enable reproducible diffs across machines.
|
||||
- **Idempotent writes** – Materialisers upsert using `{policyId, findingId, tenant}` keys and retain prior versions with append-only history.
|
||||
- **Sandboxing** – Policy evaluation executes in-process with timeouts; restart-only plug-ins guarantee no runtime DLL injection.
|
||||
- **Compliance proof** – Every run stores digest of inputs (policy, SBOM batch, advisory snapshot) so auditors can replay decisions offline.
|
||||
|
||||
---
|
||||
|
||||
## 9 · Security, Tenancy & Offline Notes
|
||||
|
||||
- **Authority scopes:** Gateway enforces `policy:read`, `policy:write`, `policy:simulate`, `policy:runs`, `findings:read`, `effective:write`. Service identities must present DPoP-bound tokens.
|
||||
- **Tenant isolation:** Collections partition by tenant identifier; cross-tenant queries require explicit admin scopes and return audit warnings.
|
||||
- **Sealed mode:** In air-gapped deployments the engine surfaces `sealed=true` hints in explain traces, warning about cached EPSS/KEV data and suggesting bundle refreshes (see `docs/airgap/EPIC_16_AIRGAP_MODE.md` §3.7).
|
||||
- **Observability:** Structured logs carry correlation IDs matching orchestrator job IDs; metrics integrate with OpenTelemetry exporters; sampled rule-hit logs redact policy secrets.
|
||||
- **Incident response:** Incident mode can be forced via API, boosting trace retention and notifying Notifier through `policy.incident.activated` events.
|
||||
|
||||
---
|
||||
|
||||
## 10 · Working with Policy Packs
|
||||
|
||||
1. **Author** in Policy Studio or edit DSL files locally. Validate with `stella policy lint`.
|
||||
2. **Simulate** against golden SBOM fixtures (`stella policy simulate --sbom fixtures/*.json`). Inspect explain traces for unexpected overrides.
|
||||
3. **Publish** via API or CLI; Authority enforces review/approval workflows (`draft → review → approve → rollout`).
|
||||
4. **Monitor** the subsequent incremental runs; if determinism diff fails in CI, roll back pack while investigating digests.
|
||||
5. **Bundle** packs for offline sites with `stella policy bundle export` and distribute via Offline Kit.
|
||||
|
||||
---
|
||||
|
||||
## 11 · Compliance Checklist
|
||||
|
||||
- [ ] **Scopes enforced:** Confirm gateway policy requires `policy:*` and `effective:write` scopes for all mutating endpoints.
|
||||
- [ ] **Determinism guard active:** Static analyzer blocks clock/RNG usage; CI determinism job diffing repeated runs passes.
|
||||
- [ ] **Materialisation audit:** Effective findings collections use append-only writers and retain history per policy run.
|
||||
- [ ] **Explain availability:** UI/CLI expose explain traces for every verdict; sealed-mode warnings display when cached evidence is used.
|
||||
- [ ] **Offline parity:** Policy bundles (import/export) tested in sealed environment; air-gap degradations documented for operators.
|
||||
- [ ] **Observability wired:** Metrics (`policy_run_seconds`, `rules_fired_total`, `vex_overrides_total`) and sampled rule hit logs emit to the shared telemetry pipeline with correlation IDs.
|
||||
- [ ] **Documentation synced:** API (`/docs/api/policy.md`), DSL grammar (`/docs/policy/dsl.md`), lifecycle (`/docs/policy/lifecycle.md`), and run modes (`/docs/policy/runs.md`) cross-link back to this overview.
|
||||
|
||||
---
|
||||
|
||||
*Last updated: 2025-10-26 (Sprint 20).*
|
||||
|
||||
# Policy Engine Overview
|
||||
|
||||
> **Goal:** Evaluate organisation policies deterministically against scanner SBOMs, Concelier advisories, and Excititor VEX evidence, then publish effective findings that downstream services can trust.
|
||||
|
||||
This document introduces the v2 Policy Engine: how the service fits into Stella Ops, the artefacts it produces, the contracts it honours, and the guardrails that keep policy decisions reproducible across air-gapped and connected deployments.
|
||||
|
||||
---
|
||||
|
||||
## 1 · Role in the Platform
|
||||
|
||||
- **Purpose:** Compose policy verdicts by reconciling SBOM inventory, advisory metadata, VEX statements, and organisation rules.
|
||||
- **Form factor:** Dedicated `.NET 10` Minimal API host (`StellaOps.Policy.Engine`) plus worker orchestration. Policies are defined in `stella-dsl@1` packs compiled to an intermediate representation (IR) with a stable SHA-256 digest.
|
||||
- **Tenancy:** All workloads run under Authority-enforced scopes (`policy:*`, `findings:read`, `effective:write`). Only the Policy Engine identity may materialise effective findings collections.
|
||||
- **Consumption:** Findings ledger, Console, CLI, and Notify read the published `effective_finding_{policyId}` materialisations and policy run ledger (`policy_runs`).
|
||||
- **Offline parity:** Bundled policies import/export alongside advisories and VEX. In sealed mode the engine degrades gracefully, annotating explanations whenever cached signals replace live lookups.
|
||||
|
||||
---
|
||||
|
||||
## 2 · High-Level Architecture
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph Inputs
|
||||
A[Scanner SBOMs<br/>Inventory & Usage]
|
||||
B[Concelier Advisories<br/>Canonical linksets]
|
||||
C[Excititor VEX<br/>Consensus status]
|
||||
D[Policy Packs<br/>stella-dsl@1]
|
||||
end
|
||||
subgraph PolicyEngine["StellaOps.Policy.Engine"]
|
||||
P1[DSL Compiler<br/>IR + Digest]
|
||||
P2[Joiners<br/>SBOM ↔ Advisory ↔ VEX]
|
||||
P3[Deterministic Evaluator<br/>Rule hits + scoring]
|
||||
P4[Materialisers<br/>effective findings]
|
||||
P5[Run Orchestrator<br/>Full & incremental]
|
||||
end
|
||||
subgraph Outputs
|
||||
O1[Effective Findings Collections]
|
||||
O2[Explain Traces<br/>Rule hit lineage]
|
||||
O3[Metrics & Traces<br/>policy_run_seconds,<br/>rules_fired_total]
|
||||
O4[Simulation/Preview Feeds<br/>CLI & Studio]
|
||||
end
|
||||
|
||||
A --> P2
|
||||
B --> P2
|
||||
C --> P2
|
||||
D --> P1 --> P3
|
||||
P2 --> P3 --> P4 --> O1
|
||||
P3 --> O2
|
||||
P5 --> P3
|
||||
P3 --> O3
|
||||
P3 --> O4
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3 · Core Concepts
|
||||
|
||||
| Concept | Description |
|
||||
|---------|-------------|
|
||||
| **Policy Pack** | Versioned bundle of DSL documents, metadata, and checksum manifest. Packs import/export via CLI and Offline Kit bundles. |
|
||||
| **Policy Digest** | SHA-256 of the canonical IR; used for caching, explain trace attribution, and audit proofs. |
|
||||
| **Effective Findings** | Append-only Mongo collections (`effective_finding_{policyId}`) storing the latest verdict per finding, plus history sidecars. |
|
||||
| **Policy Run** | Execution record persisted in `policy_runs` capturing inputs, run mode, timings, and determinism hash. |
|
||||
| **Explain Trace** | Structured tree showing rule matches, data provenance, and scoring components for UI/CLI explain features. |
|
||||
| **Simulation** | Dry-run evaluation that compares a candidate pack against the active pack and produces verdict diffs without persisting results. |
|
||||
| **Incident Mode** | Elevated sampling/trace capture toggled automatically when SLOs breach; emits events for Notifier and Timeline Indexer. |
|
||||
|
||||
---
|
||||
|
||||
## 4 · Inputs & Pre-processing
|
||||
|
||||
### 4.1 SBOM Inventory
|
||||
|
||||
- **Source:** Scanner.WebService publishes inventory/usage SBOMs plus BOM-Index (roaring bitmap) metadata.
|
||||
- **Consumption:** Policy joiners use the index to expand candidate components quickly, keeping evaluation under the `< 5 s` warm path budget.
|
||||
- **Schema:** CycloneDX Protobuf + JSON views; Policy Engine reads canonical projections via shared SBOM adapters.
|
||||
|
||||
### 4.2 Advisory Corpus
|
||||
|
||||
- **Source:** Concelier exports canonical advisories with deterministic identifiers, linksets, and equivalence tables.
|
||||
- **Contract:** Policy Engine only consumes raw `content.raw`, `identifiers`, and `linkset` fields per Aggregation-Only Contract (AOC); derived precedence remains a policy concern.
|
||||
|
||||
### 4.3 VEX Evidence
|
||||
|
||||
- **Source:** Excititor consensus service resolves OpenVEX / CSAF statements, preserving conflicts.
|
||||
- **Usage:** Policy rules can require specific VEX vendors or justification codes; evaluator records when cached evidence substitutes for live statements (sealed mode).
|
||||
|
||||
### 4.4 Policy Packs
|
||||
|
||||
- Authored in Policy Studio or CLI, validated against the `stella-dsl@1` schema.
|
||||
- Compiler performs canonicalisation (ordering, defaulting) before emitting IR and digest.
|
||||
- Packs bundle scoring profiles, allowlist metadata, and optional reachability weighting tables.
|
||||
|
||||
---
|
||||
|
||||
## 5 · Evaluation Flow
|
||||
|
||||
1. **Run selection** – Orchestrator accepts `full`, `incremental`, or `simulate` jobs. Incremental runs listen to change streams from Concelier, Excititor, and SBOM imports to scope re-evaluation.
|
||||
2. **Input staging** – Candidates fetched in deterministic batches; identity graph from Concelier strengthens PURL lookups.
|
||||
3. **Rule execution** – Evaluator walks rules in lexical order (first-match wins). Actions available: `block`, `ignore`, `warn`, `defer`, `escalate`, `requireVex`, each supporting quieting semantics where permitted.
|
||||
4. **Scoring** – `PolicyScoringConfig` applies severity, trust, reachability weights plus penalties (`warnPenalty`, `ignorePenalty`, `quietPenalty`).
|
||||
5. **Verdict and explain** – Engine constructs `PolicyVerdict` records with inputs, quiet flags, unknown confidence bands, and provenance markers; explain trees capture rule lineage.
|
||||
6. **Materialisation** – Effective findings collections are upserted append-only, stamped with run identifier, policy digest, and tenant.
|
||||
7. **Publishing** – Completed run writes to `policy_runs`, emits metrics (`policy_run_seconds`, `rules_fired_total`, `vex_overrides_total`), and raises events for Console/Notify subscribers.
|
||||
|
||||
---
|
||||
|
||||
## 6 · Run Modes
|
||||
|
||||
| Mode | Trigger | Scope | Persistence | Typical Use |
|
||||
|------|---------|-------|-------------|-------------|
|
||||
| **Full** | Manual CLI (`stella policy run`), scheduled nightly, or emergency rebaseline | Entire tenant | Writes effective findings and run record | After policy publish or major advisory/VEX import |
|
||||
| **Incremental** | Change-stream queue driven by Concelier/Excititor/SBOM deltas | Only affected artefacts | Writes effective findings and run record | Continuous upkeep; ensures SLA ≤ 5 min from source change |
|
||||
| **Simulate** | CLI/Studio preview, CI pipelines | Candidate subset (diff against baseline) | No materialisation; produces explain & diff payloads | Policy authoring, CI regression suites |
|
||||
|
||||
All modes are cancellation-aware and checkpoint progress for replay in case of deployment restarts.
|
||||
|
||||
---
|
||||
|
||||
## 7 · Outputs & Integrations
|
||||
|
||||
- **APIs** – Minimal API exposes policy CRUD, run orchestration, explain fetches, and cursor-based listing of effective findings (see `/docs/api/policy.md` once published).
|
||||
- **CLI** – `stella policy simulate/run/show` commands surface JSON verdicts, exit codes, and diff summaries suitable for CI gating.
|
||||
- **Console / Policy Studio** – UI reads explain traces, policy metadata, approval workflow status, and simulation diffs to guide reviewers.
|
||||
- **Findings Ledger** – Effective findings feed downstream export, Notify, and risk scoring jobs.
|
||||
- **Air-gap bundles** – Offline Kit includes policy packs, scoring configs, and explain indexes; export commands generate DSSE-signed bundles for transfer.
|
||||
|
||||
---
|
||||
|
||||
## 8 · Determinism & Guardrails
|
||||
|
||||
- **Deterministic inputs** – All joins rely on canonical linksets and equivalence tables; batches are sorted, and random/wall-clock APIs are blocked by static analysis plus runtime guards (`ERR_POL_004`).
|
||||
- **Stable outputs** – Canonical JSON serializers sort keys; digests recorded in run metadata enable reproducible diffs across machines.
|
||||
- **Idempotent writes** – Materialisers upsert using `{policyId, findingId, tenant}` keys and retain prior versions with append-only history.
|
||||
- **Sandboxing** – Policy evaluation executes in-process with timeouts; restart-only plug-ins guarantee no runtime DLL injection.
|
||||
- **Compliance proof** – Every run stores digest of inputs (policy, SBOM batch, advisory snapshot) so auditors can replay decisions offline.
|
||||
|
||||
---
|
||||
|
||||
## 9 · Security, Tenancy & Offline Notes
|
||||
|
||||
- **Authority scopes:** Gateway enforces `policy:read`, `policy:write`, `policy:simulate`, `policy:runs`, `findings:read`, `effective:write`. Service identities must present DPoP-bound tokens.
|
||||
- **Tenant isolation:** Collections partition by tenant identifier; cross-tenant queries require explicit admin scopes and return audit warnings.
|
||||
- **Sealed mode:** In air-gapped deployments the engine surfaces `sealed=true` hints in explain traces, warning about cached EPSS/KEV data and suggesting bundle refreshes (see `docs/airgap/EPIC_16_AIRGAP_MODE.md` §3.7).
|
||||
- **Observability:** Structured logs carry correlation IDs matching orchestrator job IDs; metrics integrate with OpenTelemetry exporters; sampled rule-hit logs redact policy secrets.
|
||||
- **Incident response:** Incident mode can be forced via API, boosting trace retention and notifying Notifier through `policy.incident.activated` events.
|
||||
|
||||
---
|
||||
|
||||
## 10 · Working with Policy Packs
|
||||
|
||||
1. **Author** in Policy Studio or edit DSL files locally. Validate with `stella policy lint`.
|
||||
2. **Simulate** against golden SBOM fixtures (`stella policy simulate --sbom fixtures/*.json`). Inspect explain traces for unexpected overrides.
|
||||
3. **Publish** via API or CLI; Authority enforces review/approval workflows (`draft → review → approve → rollout`).
|
||||
4. **Monitor** the subsequent incremental runs; if determinism diff fails in CI, roll back pack while investigating digests.
|
||||
5. **Bundle** packs for offline sites with `stella policy bundle export` and distribute via Offline Kit.
|
||||
|
||||
---
|
||||
|
||||
## 11 · Compliance Checklist
|
||||
|
||||
- [ ] **Scopes enforced:** Confirm gateway policy requires `policy:*` and `effective:write` scopes for all mutating endpoints.
|
||||
- [ ] **Determinism guard active:** Static analyzer blocks clock/RNG usage; CI determinism job diffing repeated runs passes.
|
||||
- [ ] **Materialisation audit:** Effective findings collections use append-only writers and retain history per policy run.
|
||||
- [ ] **Explain availability:** UI/CLI expose explain traces for every verdict; sealed-mode warnings display when cached evidence is used.
|
||||
- [ ] **Offline parity:** Policy bundles (import/export) tested in sealed environment; air-gap degradations documented for operators.
|
||||
- [ ] **Observability wired:** Metrics (`policy_run_seconds`, `rules_fired_total`, `vex_overrides_total`) and sampled rule hit logs emit to the shared telemetry pipeline with correlation IDs.
|
||||
- [ ] **Documentation synced:** API (`/docs/api/policy.md`), DSL grammar (`/docs/policy/dsl.md`), lifecycle (`/docs/policy/lifecycle.md`), and run modes (`/docs/policy/runs.md`) cross-link back to this overview.
|
||||
|
||||
---
|
||||
|
||||
*Last updated: 2025-10-26 (Sprint 20).*
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ All modes record their status in `policy_runs` with deterministic metadata:
|
||||
}
|
||||
```
|
||||
|
||||
> **Schemas & samples:** see `src/StellaOps.Scheduler.Models/docs/SCHED-MODELS-20-001-POLICY-RUNS.md` and the fixtures in `samples/api/scheduler/policy-*.json` for canonical payloads consumed by CLI/UI/worker integrations.
|
||||
> **Schemas & samples:** see `src/Scheduler/__Libraries/StellaOps.Scheduler.Models/docs/SCHED-MODELS-20-001-POLICY-RUNS.md` and the fixtures in `samples/api/scheduler/policy-*.json` for canonical payloads consumed by CLI/UI/worker integrations.
|
||||
|
||||
---
|
||||
|
||||
|
||||
Reference in New Issue
Block a user