# schemas/lib/keeper_policy.ncl — Keeper auto-sign policy schema (ADR-038) # # Declarative-only closed shape parsed by the keeper-daemon Rust matcher. # Policy files (policy-/policy.ncl) MUST conform to PolicyDef and # MUST NOT contain Nickel function definitions or imports beyond this schema. # Constraint: policy-files-are-declarative-only (ADR-038). # # Usage: # let kp = import "schemas/lib/keeper_policy.ncl" in # { policy | kp.PolicyDef = { auto_sign = [...], require_manual = [...] } } # Op type wildcard contract — superset of ops_contract.ncl OpsType that also accepts "*" let OpTypeOrAny = std.contract.custom ( fun label => fun value => let valid = ["deploy", "scale", "restart", "secret_update", "drain", "*"] in if std.array.any (fun x => x == value) valid then 'Ok value else 'Error { message = "Invalid op_type '%{value}'.\nValid values: deploy | scale | restart | secret_update | drain | *" } ) in # A single match rule — all fields are glob patterns applied by the Rust matcher. # Absent / defaulted-to-"*" field means "match any value for this dimension". # The matcher evaluates rules top-to-bottom; first matching rule wins. let _MatchRule = { op_type | OpTypeOrAny | doc "Op type this rule applies to; '*' matches any op type" | default = "*", image_patterns | Array String | doc "Glob patterns matched against OCI image reference in the op payload (deploy ops only)" | default = ["*"], target_patterns | Array String | doc "Glob patterns matched against the op target name (e.g., 'staging-*', 'vapora')" | default = ["*"], scope_patterns | Array String | doc "Glob patterns matched against JWT scope entries (:)" | default = ["*"], } in # Top-level policy file schema. Evaluation order: auto_sign rules checked first (top-to-bottom), # then require_manual. If no rule matches, the op is held pending for manual review. let _PolicyDef = { version | Number | doc "Schema version — keeper-daemon rejects files with unknown versions" | default = 1, auto_sign | Array _MatchRule | doc "Rules for operations the keeper-daemon may sign automatically" | default = [], require_manual | Array _MatchRule | doc "Rules for operations that must be signed interactively via keeper-cli" | default = [], } in { OpTypeOrAny = OpTypeOrAny, MatchRule = _MatchRule, PolicyDef = _PolicyDef, make_policy | not_exported = fun data => data | _PolicyDef, }