Validate a versioned authority policy¶
The AuthorityPolicy v1alpha1 format, available from v0.6.0, describes finite
authority for one operation kind: support-case status changes or GitHub Actions
workflow_dispatch. Validation and compilation run offline and create no
grants, approvals, seals, credentials or ledger records.
opaque authority-policy validate examples/authority-policy/support.yaml --json
opaque authority-policy compile examples/authority-policy/staging-dispatch.yaml --json
opaque authority-policy schema
Both examples select named tenant, connector and reviewer references. A manifest cannot supply an endpoint, credential, principal or signing key. Validating the dispatch example with a v0.6.0 build prints:
{
"digest": "7d16e99044c5ed63f71f77841701840d9cb08818707f7fc5e2f280b01865a790",
"identity": {
"apiVersion": "policy.opaque.dev/v1alpha1",
"connectorRef": "github",
"kind": "AuthorityPolicy",
"name": "staging-dispatch",
"namespace": "release",
"reviewerRef": "release-approver",
"tenantRef": "example"
},
"schemaVersion": 1,
"valid": true
}
Operation kinds¶
spec.authority.operation selects the kind. Each kind has exactly one
kind-specific field; a manifest that carries the other kind's field fails
validation instead of being pruned, with exit status 2:
allowedStatuses belongs to support.case.setStatus and workflows to github.workflow.dispatch; supply exactly the selected kind's field
operation |
Broker operation | Kind field | Scope resource | Written field |
|---|---|---|---|---|
support.case.setStatus |
support.case.set_status |
allowedStatuses: 1 to 3 of open, resolved, closed |
case identifier | status |
github.workflow.dispatch |
github.dispatch_staging_workflow |
workflows: 1 to 100 targets of repository, path, ref |
{repository}:{path}:{ref} |
ref |
For the dispatch kind, workflows is the complete set of targets any scope may
name. A scope plan that names a repository, workflow file or branch outside it
is refused before a review round exists. The example at
examples/authority-policy/staging-dispatch.yaml pins one target:
authority:
operation: github.workflow.dispatch
workflows:
- repository: example-org/service
path: .github/workflows/staging.yml
ref: main
maxResources: 1
maxAttempts: 5
maxDuration: 1h
repository is owner/repo. path is one file directly under
.github/workflows/ ending in .yml or .yaml. ref is a branch name: never a
tag, a 40-hex commit, a refs/ path or a name containing ... The scope
resource string joins the three with colons, which none of them may contain.
Governing a staging dispatch
describes what the broker reads, sends and records for this kind.
approval.scope: Required is mandatory. approval.action defaults to
EveryAction; WithinApprovedScope explicitly permits automatic dispatch only
inside a separately human-approved, finite scope. A policy is never that
approval. spec.mode supports only Enforce; Shadow and nonempty
spec.evaluators fail validation. This version does not invoke Jev.
Parsing and compiler contract¶
The parser accepts one UTF-8 YAML or JSON document, at most 64 KiB, 16 nested
levels and 4,096 values/keys. Duplicate keys, YAML aliases, anchors, explicit
tags, merge keys, complex keys and unknown fields are rejected. YAML plain
scalars use JSON boolean/number/null interpretation; quote references that look
like those values. Names, namespaces and references are lowercase DNS labels,
at most 63 bytes. Allowed statuses are open, resolved and closed;
duplicate statuses and duplicate workflow targets are rejected. Resource limits
are 1 to 100 and attempts 1 to 10,000. maxDuration accepts a positive whole
s, m or h duration up to 24 hours.
The compiler fills defaults, sorts statuses or workflow targets, and normalizes
duration to seconds. The portable JSON Schema expresses the two kinds as a
oneOf; git-specific refusals such as refs/ prefixes, .. and bare commit
SHAs are compiler-only, so the compiler remains authoritative.
Its lowercase hex digest is SHA-256 over opaque.authority-policy.v1, one NUL
byte, then compact UTF-8 JSON of the canonical policy with every object's keys
sorted lexicographically and no whitespace. There are no floating-point policy
fields. Name, namespace, references, approval requirements and bounds are
included. Comments, property order and equivalent durations do not change it.
compile returns {schemaVersion: 1, digest, identity, policy}; validate
returns {schemaVersion: 1, valid: true, digest, identity}. Errors exit 2 with no
successful JSON result. Recompiling the returned policy yields the same
digest; hashing the pretty-printed output does not.
Bind a policy at broker startup¶
In the isolated broker's operator-controlled, sealed config.toml, pin the exact
compiler digest and identity. References must match the manifest and
tenant_ref must also match the broker's configured tenant ID:
[authority_policy]
path = "/etc/opaque/authority-policy/authority-policy.json"
digest = "REPLACE_WITH_COMPILER_DIGEST"
name = "support-status"
namespace = "support"
tenant_ref = "example"
connector_ref = "support-api"
reviewer_ref = "support-approver"
reviewer_id = "REPLACE_WITH_ENROLLED_HUMAN_PRINCIPAL"
reviewer_public_key = "REPLACE_WITH_ENROLLED_PUBLIC_KEY"
generation = 1
[authority_policy.profile]
endpoint = "https://support.example.invalid/v1/"
token_file = "/var/lib/opaque/private/support.token"
For the github.workflow.dispatch kind the same section applies with the GitHub
API as the profile: endpoint = "https://api.github.com/" (or a GitHub
Enterprise Server https://HOST/api/v3/ base) and a token_file holding a token
that can read the target repository's branches and workflows and create
workflow_dispatch events for it. The broker loads that token, binds it into the
provider profile digest under a dispatch-specific domain, and never exposes it to
the agent. The legacy [scope_workflow] TOML section stays support-only; the
dispatch kind is configured through a manifest.
The local profile and enrolled reviewer remain trusted configuration, bound into
the runtime policy digest. Startup still requires native approval, required
identity, sealed configuration and enforced custody/tenant isolation. Setting
both [authority_policy] and [scope_workflow] is rejected. Manifest identity
and digest checks precede opening scope ledgers. Bounded regular-file reads
reject FIFOs without blocking. Policy content is nonsecret: root-owned read-only
files and Kubernetes projected-volume symlinks work. Credential custody checks
remain separate.
Activation occurs only on operator-controlled restart after the ordinary seal
process. These commands never seal or activate automatically. File changes do
not alter a running broker; a changed file with an old pin fails startup. The
read-only opaque scope snapshot reports authority_policy.digest and
authority_policy.identity for the running broker. A staged file alone does not
prove activation.
Keep the same tenant, generation and private custody directory across policy
edits, removal and reapplication. Generation identifies the ledger owner, not a
policy revision; changing it against existing custody is unsupported. Never
delete a ledger or create fresh custody to activate an update. Consumed attempts,
revoked scopes and UNKNOWN outcomes survive restart and rollback. Semantic
changes make old grants/reviews fail current policy checks. An exact rollback
can make otherwise-valid unused authority match again; use explicit scope
revocation for permanent retirement. Neither rollback nor redeployment restores
consumed or revoked authority. New scopes still require human approval.
Historical outcome queries can still return the retained result; that never
authorizes another dispatch.
Migrate existing TOML without changing the broker¶
opaque authority-policy migrate --config /path/to/config.toml \
--name support-status --namespace support --tenant-ref example \
--connector-ref support-api --reviewer-ref support-approver > policy.json
opaque authority-policy validate policy.json --json
Migration preserves [scope_workflow] limits, explicit exact_action and omitted
legacy defaults. It emits manifest JSON only on stdout. It does not read the
credential, copy endpoint/key material, edit configuration, change generation,
contact a broker or issue authority. Review the result, retain the existing local
connector/reviewer/custody bindings, replace the selected configuration section,
then use the ordinary operator seal/restart procedure. Existing TOML and its
runtime digest remain unchanged when no manifest is selected. Switching to a
manifest deliberately requires newly reviewed scopes.
Kubernetes adapters¶
Core supplies a portable schema and compiler, without a Kubernetes controller.
A staging controller can embed the schema's strict spec subtree and invoke
the compiler. Kubernetes owns metadata such as UID, resourceVersion, generation,
creationTimestamp and managedFields, plus status. An adapter must remove only
its documented API-server metadata/status before passing authored
name/namespace/spec to the strict compiler. Reject unknown spec fields before
pruning, or preserve them for compiler rejection; never silently discard them
into a valid policy. The standalone parser rejects extra metadata.
An immutable ConfigMap is staged content, not activation or a grant. Mounting or deleting it cannot reset budgets, mint approvals or prove a broker loaded it. Broker-side sealed digest and identity checks remain authoritative.