Runbook: Policy Engine - Policy Compilation Errors
For platform on-call and policy authors. Use this when a Stella Ops policy version fails to compile or activate — a :compile endpoint returning 400 ERR_POL_001, or stella policy compile/lint reporting POLICY-DSL-* diagnostics. It maps the diagnostic codes to their causes and shows how to fix the source and re-activate. Compilation targets the native stella-dsl@1 DSL; OPA/Rego is a secondary interop layer, not the compiler.
Sprint: SPRINT_20260117_029_DOCS_runbook_coverage Task: RUN-003 - Policy Engine Runbooks
Reconciliation note (2026-05-30): This runbook was originally written as if the Policy Engine were OPA/Rego-native. That is incorrect. The primary policy language is StellaOps’ own DSL (
stella-dsl@1), compiled by thePolicyCompiler(src/Policy/StellaOps.PolicyDsl/) into a deterministic intermediate representation (IR) with a SHA-256 checksum. OPA/Rego is a secondary interop/gate layer, not the compilation engine — Rego can be exported/imported alongside the native engine, and an optionalOpaGateAdaptercan call an external OPA server for attestation gates. Seedocs/modules/policy/architecture.md§13 (“The C# engine remains primary; Rego serves as an interoperability adapter”). Commands, error codes, and checks below have been corrected to matchsrc/.
Metadata
| Field | Value |
|---|---|
| Component | Policy Engine (StellaOps.Policy.Engine, StellaOps.PolicyDsl) |
| Severity | High |
| On-call scope | Platform team |
| Last updated | 2026-05-31 |
| Doctor check | check.policy.engine (src/Doctor/__Plugins/StellaOps.Doctor.Plugin.Policy/Checks/PolicyEngineHealthCheck.cs) |
Note on the Doctor check: there is a single aggregate check
check.policy.enginethat covers compilation, evaluation, and storage health. The previously documentedcheck.policy.compilation-healthdoes not exist in source. The check only runs when a policy-engine URL is configured (Policy:Engine:Url/PolicyEngine:BaseUrl, defaulthttp://localhost:8181).Scope caveat: this check probes an OPA HTTP server — its “compilation” sub-check lists policies via OPA’s
/v1/policies, reads/v1/status, and exercises/v1/data, reportingengine_typeasopa(PolicyEngineHealthCheck.cs). It therefore validates the OPA gate/interop layer’s reachability, not the nativestella-dsl@1compiler. A native-DSL compilation failure surfaces throughstella policy lint/compileand the:compileHTTP endpoint (below), not through this Doctor check.
Symptoms
- [ ] Policy version compilation/activation failing with a compilation error
- [ ] HTTP
POST .../:compilereturning400 Bad Requestwith problem codeERR_POL_001 - [ ]
stella policy compile <file>/stella policy lint <file>reportingPOLICY-DSL-*diagnostics - [ ] Diagnostic codes such as
POLICY-DSL-LEX-001(unexpected character) orPOLICY-DSL-PARSE-001(unexpected token) - [ ] New policy versions not taking effect (only the last successfully compiled source is active)
- [ ] (Interop only)
stella policy import/validaterejecting a JSON or Rego PolicyPack
Impact
| Impact Type | Description |
|---|---|
| User-facing | New policy versions cannot be compiled/activated; the engine keeps evaluating the last successfully compiled source. |
| Data integrity | Existing active policies continue to evaluate; new rules are not enforced. Compilation is deterministic — the same source always yields the same IR checksum. |
| SLA impact | Policy updates blocked; security posture may be outdated until the source is fixed. |
Diagnosis
Quick checks
Check Doctor diagnostics:
stella doctor --check check.policy.engineVerify the exact CLI flag against
stella doctor --help. The check aggregatescompilation,evaluation, andstoragesub-results and reportsengine_type,engine_version,policy_count,compilation_time_ms, andlast_compilation_error.Lint the policy source locally (native DSL):
stella policy lint <policy-file>.stella # JSON output for tooling: stella policy lint <policy-file>.stella --output jsonlintparses the source and reports everyPOLICY-DSL-LEX-*/POLICY-DSL-PARSE-*diagnostic with its code, path, and message. In the default text output it exits1if any error-severity diagnostic is present; note that the--output jsonpath currently always exits0and signals failure via the"success": falsefield in the payload instead (PolicyCliCommandModule.cs— for CI gating on JSON output, branch onsuccess, not the exit code).Compile the source to IR (native DSL):
stella policy compile <policy-file>.stella # Emit only the deterministic checksum: stella policy compile <policy-file>.stella --checksum-only # Write IR to a file: stella policy compile <policy-file>.stella -o compiled-ir.jsonOn failure,
compilewrites the error-severity diagnostics to stderr and exits1.Two
policy compileimplementations exist and their flags differ slightly. The CLI plugin module (StellaOps.Cli.Plugins.Policy/PolicyCliCommandModule.cs) offers--checksum-onlyand-o <file>. The core CLI build (CommandFactory.cs~line 3458) offers--output/-o,--no-ir,--no-digest,--optimize,--strict, and--format. Both compile via the samePolicyCompilerand emit the same SHA-256 IR digest. If--checksum-onlyis not recognized in your build, omit--no-digestto get the digest instead.
Deep diagnosis
Read the diagnostic code. Native DSL codes are defined in
src/Policy/StellaOps.PolicyDsl/DiagnosticCodes.cs:Code Meaning POLICY-DSL-LEX-001Unexpected character POLICY-DSL-LEX-002Unterminated string POLICY-DSL-LEX-003Invalid escape sequence POLICY-DSL-LEX-004Invalid number POLICY-DSL-PARSE-001Unexpected token POLICY-DSL-PARSE-002Duplicate section POLICY-DSL-PARSE-003Missing policyheaderPOLICY-DSL-PARSE-004Unsupported syntax version (must be stella-dsl@1)POLICY-DSL-PARSE-005Duplicate rule name POLICY-DSL-PARSE-006Missing becauseclausePOLICY-DSL-PARSE-007Missing terminator POLICY-DSL-PARSE-008Invalid action POLICY-DSL-PARSE-009Invalid literal POLICY-DSL-PARSE-010Unexpected section The diagnostic
path(e.g.policy.syntax,policy.body,policy.name) points at the offending construct.Verify the syntax version. The parser only accepts
syntax "stella-dsl@1"; any other value yieldsPOLICY-DSL-PARSE-004.Check the server-side compile endpoint (used during version activation):
POST /api/policy/policies/{policyId}/versions/{version}:compileRequires scope
policy:edit. A compilation failure returns400with problem codeERR_POL_001and the parse/lint diagnostics in thediagnosticsarray; a success returns the deterministicdigest, statistics, complexity report, and any warning-severity diagnostics. Source:PolicyCompilationEndpoints.cs.(Optional) Run determinism lint to catch non-deterministic constructs (wall-clock, RNG, network/filesystem access) before activation:
POST /api/v1/policy/lint/analyze # single source POST /api/v1/policy/lint/analyze-batch # bundle GET /api/v1/policy/lint/rules # list rulesThese require scope
policy:read. Source:PolicyLintEndpoints.cs. This is a separate concern from DSL syntax compilation.
Resolution
Immediate mitigation
There is no separate “deploy” step that can leave a half-broken policy live: a version only becomes active if its source recompiles successfully (runtime reload recompiles the persisted source text — see docs/modules/policy/architecture.md §“Reload recompiles persisted source text”). A failed compilation therefore leaves the last good source active. Mitigation is to stop attempting to activate the broken version and fix the source.
Keep the last good version active. Do not activate/publish the failing version; the previously compiled source continues to evaluate. Re-author and re-compile before re-activating.
Revert to a prior version (CLI, backend-backed). A
rollbacksubcommand does exist and drives the backend lifecycle (IBackendOperationsClient.RollbackPolicyAsync,CommandHandlers.HandlePolicyRollbackAsync):stella policy rollback <policy-id> --target-version <n> --reason "<why>" # optional: --env <environment> --incident <id> --tenant <id> --jsonWith no
--target-versionit rolls back to the previous version. The command is network-gated (refuses in offline mode). Source:CommandFactory.cs(policy rollback, ~line 3958) →CommandHandlers.cs:15420.Lifecycle subcommands (CLI, backend-backed). Pack/version lifecycle is also driven through the CLI in addition to the Policy Engine HTTP API / Policy Studio governance flow. The
policycommand exposes (among others)activate,publish,promote,sign,verify-signature, plus areviewgroup (withapprove):stella policy activate <policy-id> --version <n> [--note "..."] stella policy publish <policy-id> ... stella policy promote <policy-id> ...These call backend governance endpoints (
HandlePolicyActivateAsync/HandlePolicyPublishAsync/HandlePolicyPromoteAsyncinCommandHandlers.cs) and are gated server-side by thepolicy:activate/policy:publish/policy:promote/policy:approvescopes (all present inStellaOpsScopes.cs).
Genuinely NOT IMPLEMENTED in the CLI as of 2026-05-31: the original runbook referenced
stella policy disable <id>,stella policy reload, andstella policy bundle load --version <v>. None of these three subcommands exist. (Offline pack bundles are handled bystella policy export-bundle/import-bundle, notbundle load.) The earlierrollback --to-last-goodflag spelling is also wrong — userollback --target-version <n>as shown above.
Root cause fix
If a lex/parse error (POLICY-DSL-LEX-* / POLICY-DSL-PARSE-*):
- Re-run lint to get the exact code and path:
stella policy lint <policy-file>.stella - Common DSL issues:
- Missing
policy "<name>"header (POLICY-DSL-PARSE-003) - Wrong/absent
syntax "stella-dsl@1"(POLICY-DSL-PARSE-004) - Missing
becauseclause on a rule (POLICY-DSL-PARSE-006) - Unbalanced braces / missing terminator (
POLICY-DSL-PARSE-007) - Duplicate
rulename or duplicate section (POLICY-DSL-PARSE-005/-002) - Unterminated string or bad escape (
POLICY-DSL-LEX-002/-003)
- Missing
- Fix and re-validate:
stella policy lint <fixed-policy>.stella stella policy compile <fixed-policy>.stella --checksum-only
If the source compiles but evaluates unexpectedly:
- Simulate against a signal context to inspect matched rules and actions:
The output reportsstella policy simulate <policy-file>.stella --signals signals.json --output jsonpolicyChecksum, matched rules, and the resolved actions (includingelse-branch actions). Source:PolicyCliCommandModule.cs.
If working with OPA/Rego or JSON PolicyPacks (interop layer):
- Validate against the PolicyPack v2 schema (auto-detects
json/rego):stella policy validate --file <pack>.json stella policy validate --file <pack>.rego --format rego - Import (dry-run / validate-only first):
The import mapping reports which rules map to native gates vs. those that remain OPA-evaluated (no native equivalent). Source:stella policy import --file <pack>.rego --dry-runPolicyInteropCommandGroup.cs. - Evaluate a pack against evidence input:
Interop commands use exit codes:stella policy evaluate --policy <pack>.json --input evidence.json --output ci0success,1warnings,2block/errors,10input error,12policy error.
Verification
# Lint clean (no error-severity diagnostics)
stella policy lint <fixed-policy>.stella
# Compile succeeds and yields a stable checksum
stella policy compile <fixed-policy>.stella --checksum-only
# Simulate to confirm intended rule matches/actions
stella policy simulate <fixed-policy>.stella --signals signals.json
# Re-run the engine health check
stella doctor --check check.policy.engine
The previously documented
stella policy deploy --file ...command does not exist, andstella policy evaluatehas no--testflag (its options are--policy/--input/--format/--environment/--include-remediation/--output). Activation is performed through the Policy Engine HTTP API / Policy Studio governance flow or the backend-backedstella policy activate/publish/promotecommands, not a CLIdeploycommand.A
stella policy testcommand does exist (it runs coverage fixtures against a DSL file:policy test <file> [--fixtures <dir>] [--filter <pat>] [--fail-fast],CommandFactory.cs~line 3107 →CommandHandlers.HandlePolicyTestAsync). Use it in CI to exercise expected verdicts before activation:stella policy test <fixed-policy>.stella --fixtures tests/policy/<name>/cases
Prevention
- [ ] CI/CD: Run
stella policy lint(andstella policy compile --checksum-onlyto detect drift) on every policy change before activation. - [ ] Determinism gate: Use
POST /api/v1/policy/lint/analyze-batchin CI to catch non-deterministic constructs across a bundle. - [ ] Simulation: Exercise
stella policy simulatewith representative signal contexts to confirm intended matches before publishing. - [ ] Staging: Activate in a staging environment before production via the governance workflow.
Related Resources
- Architecture:
docs/modules/policy/architecture.md(see §13 for the OPA/Rego interop layer; the native DSL compiler is described under “DSL Compiler”) - DSL reference:
docs/modules/policy/guides/dsl.md(grammar:docs/modules/policy/dsl-grammar-specification.md) - Policy CLI guide:
docs/modules/cli/guides/policy.md - Related runbooks:
policy-incident.md,policy-opa-crash.md,policy-evaluation-slow.md,policy-version-mismatch.md,policy-storage-unavailable.md - Source of truth:
- DSL compiler:
src/Policy/StellaOps.PolicyDsl/(PolicyCompiler.cs,PolicyParser.cs,DslTokenizer.cs,DiagnosticCodes.cs) - HTTP compile endpoint:
src/Policy/StellaOps.Policy.Engine/Endpoints/PolicyCompilationEndpoints.cs - CLI (native DSL plugin:
lint/compile/simulate):src/Cli/__Libraries/StellaOps.Cli.Plugins.Policy/PolicyCliCommandModule.cs - CLI (core
policytree:compile/lint/test/activate/publish/promote/rollback/sign/review):src/Cli/StellaOps.Cli/Commands/CommandFactory.cs+ handlers insrc/Cli/StellaOps.Cli/Commands/CommandHandlers.cs - CLI (interop:
export/import/validate/evaluate):src/Cli/StellaOps.Cli/Commands/Policy/PolicyInteropCommandGroup.cs - OPA gate adapter:
src/Policy/__Libraries/StellaOps.Policy/Gates/Opa/OpaGateAdapter.cs
- DSL compiler:
- Rego reference (external — for the optional OPA interop layer only): https://www.openpolicyagent.org/docs/latest/policy-language/
