Groundskeeper

Policy bundle specification

Goals

Policy bundles make guardrail behavior reusable, reviewable, deterministic, and deployable without request-time compilation. YAML is the authoring representation; canonical JSON is the compiled and signed representation. The initial authoring schema is policy-bundle.schema.json.

This specification is proposed pending OD-011 through OD-013.

Authoring model

A bundle declares:

  • a stable reverse-domain name and SemVer version;
  • explicit digest-pinned imports;
  • exported named profiles;
  • check instances and operator-approved configuration;
  • typed, pure CEL match predicates;
  • composition and per-check error behavior;
  • stable rule IDs and whether a platform rule is overridable;
  • bundle tests containing synthetic input facts and expected outcomes.

Detector credentials, endpoints, trust roots, mandatory platform defaults, and audit-redaction controls are operator configuration and cannot appear in tenant bundles.

CEL profile

CEL is limited to deciding whether a rule applies. The compiler exposes a typed environment containing event metadata, trusted context labels, and content metadata—not raw detector credentials or arbitrary host objects.

Allowed expressions are side-effect-free and deterministic. The environment:

  • provides an allowlist of pure functions;
  • performs static type checking;
  • forbids I/O, network, filesystem, reflection, secret access, and dynamic plugins;
  • limits AST size and estimated/runtime cost;
  • enforces an evaluation deadline;
  • rejects unbounded or unsafe regular expressions and oversized literals.

Detection and transformation do not execute inside CEL.

Imports, layering, and overrides

Imports are explicit and pinned to a sha256: digest. SemVer tags assist discovery but never select production dependencies at runtime. Shared bundles export namespaced profiles.

The compiler applies layers in this order:

  1. mandatory platform baseline;
  2. tenant/workspace policy;
  3. application/deployment policy.

There is no implicit inheritance and no general deep merge. An override targets a stable rule ID and uses one typed operation:

  • enable or disable an overridable rule;
  • replace a schema-approved parameter;
  • append a rule with a unique ID.

A lower layer cannot weaken a non-overridable platform rule. Duplicate rule IDs, unknown override targets, and ambiguous ordering are compile errors.

Each rule's overridable flag defaults to false. Only a rule marked overridable: true can be the target of a lower layer's typed override.

Current runtime slice

The runtime loads one bundle and one profile. It has no layered compiler yet, so:

  • overridable is parsed, defaults to false, and is retained in the snapshot for the layered compiler. With a single layer there is nothing that could override a rule, so the flag cannot weaken anything.
  • imports and overrides are rejected with an explicit error rather than ignored, because applying neither would silently change the policy's meaning.
  • Bundle tests are rejected until the compiler runs them, so a bundle cannot appear tested when it is not.
  • match expressions are rejected until the CEL environment exists.
  • check.config is accepted by the schema but not passed to built-in detectors.
  • Each detector referenced by several enabled rules runs once per request. Its findings are reported once, and each rule judges them and gets its own coverage entry.

Compiler contract

Compilation is all-or-nothing:

  1. Parse YAML and reject unknown fields.
  2. Validate JSON Schema and supported format version.
  3. Resolve the complete import DAG; reject cycles, missing digests, and name/version inconsistencies.
  4. Validate detector capabilities, configuration schemas, categories, stages, and parameter ranges.
  5. Type-check and cost-check CEL.
  6. Apply layers and typed overrides.
  7. Run bundle unit tests and corpus replay/diff.
  8. Emit canonical JSON, a dependency lockfile, capability requirements, and a content digest.
  9. Sign the artefact and provenance after the approval gate.

Canonicalization uses UTF-8 JSON with lexicographically sorted object keys, no insignificant whitespace, and unchanged array order. Number encoding must be specified before the compiled format is frozen; authoring currently avoids floating-point policy values where exact decimal handling matters.

Snapshot and activation lifecycle

Publish immutable OCI artefacts. The evaluator verifies digest, expected signer, provenance, schema compatibility, and available capabilities before installation. It never activates a partial graph. A complete snapshot is shadowed, moved through a deterministic sticky canary, and atomically promoted. Last-known-good and prior digests are retained for rollback. Signing proves provenance, not policy safety.

Composition

V1 composition is deny-wins. Each check is required or optional and declares an on_error action of block, allow_unjudged, or continue_unjudged as permitted by the policy schema. The exact permitted values by risk tier remain an open risk decision. Quorum and weighted voting are deliberately deferred.

The au-baseline example

policies/examples/au-baseline.yaml is an experimental, non-production policy that loads in the runtime. Tests check every profile.

Decision (3 October 2026). The earlier version of this file passed schema validation but could never load. It referenced four detectors that do not exist (builtin.pii.au, builtin.credentials, builtin.jailbreak, and builtin.safety), a CEL match expression, and detector config that no built-in reads. The file was corrected rather than skipped:

  • The overridable: false flags are kept and are now modelled by the loader.
  • The rules for detectors that do not exist were replaced with the two implemented Australian structured detectors. The match expression selected every phase, so removing it changes nothing.
  • The file now has an observe profile and a redact-experimental profile. Both use required: false and on_error: continue_unjudged, and neither uses minimum_confidence.

The removed families come back as rules once their detectors exist. The file is limited to the owner-approved posture recorded in section 17 of the micro-slice specification: observe or redact only up to G2, with no blocking or production enforcement.

On this page