The canonical reference for the decree.schema.yaml format — every top-level key, every field-level option, every constraint, every cross-field rule. For the higher-level concepts (lifecycle, drafts vs published versions, strict mode), see Schemas & Fields. For the JSON Schema 2020-12 meta-schema that validates this format mechanically, see Meta-schema.
Alpha Software — OpenDecree is under active development. APIs, proto definitions, and the schema format may change without notice between versions. Not recommended for production use yet.
| Filename | Purpose |
|---|---|
decree.schema.yaml |
Canonical name when a repo holds one schema. |
*.decree.schema.yaml |
Generic glob when a repo holds multiple (e.g. payments.decree.schema.yaml). |
Place the modeline on line 1 of every file so editors with yaml-language-server auto-apply schema validation and IntelliSense:
# yaml-language-server: $schema=https://schemas.opendecree.dev/schema/v0.1.0/decree-schema.jsonThe CLI is filename-agnostic — decree apply some-other-name.yaml keeps working. The convention drives editor discovery only.
# yaml-language-server: $schema=https://schemas.opendecree.dev/schema/v0.1.0/decree-schema.json
spec_version: v1 # required, const "v1"
$schema: https://schemas.opendecree.dev/schema/v0.1.0/decree-schema.json # optional
$id: urn:decree:schema:payments:v3 # optional URN
name: payments # required slug
description: Payment service configuration # optional
version: 3 # optional informational integer
version_description: Added refund_window field # optional
info: # optional
title: Payments
author: Platform team
contact: { name: Pat, email: pat@example.com, url: https://wiki/team }
labels: { team: platform }
fields: # required, ≥1 entry
payments.fee:
type: number
constraints: { minimum: 0, maximum: 1 }
dependentRequired: # optional
payments.refunds_enabled: [payments.refund_window]
validations: # optional, reserved for Phase 2
- path: payments
rule: "self.payments.min < self.payments.max"
message: "min must be less than max"
x-vendor-key: "any extension data" # x-* allowed at every level| Key | Required | Notes |
|---|---|---|
spec_version |
required | Must be the literal string "v1". |
name |
required | Slug — ^[a-z0-9]([a-z0-9-]*[a-z0-9])?$, 1–63 chars. |
fields |
required | ≥1 entry. |
$schema |
optional | HTTPS URL pointing at the meta-schema. |
$id |
optional | URN of the form urn:decree:schema:<segment>(:<segment>)*. |
description |
optional | Free-form text. |
version |
optional | Informational integer ≥ 1. The server assigns the actual version. |
version_description |
optional | Description of what changed in this version. |
info |
optional | Ownership metadata — see Info Object. |
dependentRequired |
optional | Cross-field "B required when A present" rules — see Cross-field rules. |
validations |
optional | Reserved for CEL expression rules; engine ships in Phase 2 (issue #76). |
Unknown top-level keys are rejected at import unless they begin with the x- prefix.
fields: is a map keyed on field path with one entry per field.
A field path matches the regex ^[a-zA-Z_][a-zA-Z0-9_.-]*$. Dots within the path act as conventional grouping prefixes (payments.fee, payments.refunds.window) — the convention drives UI grouping and tag-vs-path orthogonality, but the parser treats the path as one opaque key.
Two field paths cannot collide such that one is a strict prefix of the other (e.g. payments and payments.fee cannot coexist) — see issue #194.
Every field declares one of eight types. The type drives wire encoding, runtime validation, and which constraints apply.
| YAML type | Proto type | Go type | Example value |
|---|---|---|---|
integer |
int64 |
int64 |
42, -1 |
number |
double |
float64 |
3.14, 0.025 |
string |
string |
string |
"hello", "USD" |
bool |
bool |
bool |
true, false |
time |
google.protobuf.Timestamp |
time.Time |
2025-01-15T09:30:00Z |
duration |
google.protobuf.Duration |
time.Duration |
24h, 30s, 500ms |
url |
string |
string |
https://example.com/hook |
json |
string (JSON-encoded) |
string |
{"key": "value"} |
url fields are always validated for absolute-URL form, even without explicit constraints. json fields are always parsed as valid JSON.
Type safety is enforced at the wire level — sending a string to an integer field is rejected by the server.
| Option | Type | Default | Description |
|---|---|---|---|
type |
enum | required | One of the 8 types above. |
description |
string | — | Human-readable description of the field's purpose. |
default |
string | — | Default value, encoded as a string per the field's type. |
nullable |
bool | false |
Whether the field accepts null values. See Typed Values — Null. |
deprecated |
bool | false |
Mark the field as deprecated. Reads still work. |
redirect_to |
string | — | When deprecated, reads can transparently follow this path. Existence checked at import. |
title |
string | — | Display name (e.g. "Fee Rate" for path payments.fee_rate). |
example |
string | — | Single example value, encoded as a string per the field's type. |
examples |
map | — | Named examples — see Named examples. |
format |
string | — | Semantic format hint (advisory, not enforced) — see Format hints. |
tags |
list | — | Cross-cutting categories — see Tags. |
readOnly |
bool | false |
System-managed; not user-editable. |
writeOnce |
bool | false |
Settable once; immutable after first write. |
sensitive |
bool | false |
Mask in logs and UI. |
externalDocs |
object | — | Link to external documentation: { url: <required>, description: <optional> }. |
constraints |
object | — | Validation rules — see Constraints. |
x-* extension keys are accepted at every level.
format: is a free-form string — the parser does not enforce any format value. Authors signal semantic intent for tooling and humans; tooling chooses whether to use it.
Recommended values (advisory only):
format: |
Meaning |
|---|---|
email |
RFC 5322 email address. |
uri / url |
RFC 3986 URI — note url as a TYPE is enforced; format hint differentiates "URL string" within a string type. |
uuid |
RFC 4122 UUID. |
ipv4 / ipv6 / ipv6-zone |
IP address forms. |
hostname / idn-hostname |
Hostname / internationalized hostname. |
date / date-time / time |
RFC 3339 forms (use time field type for full datetime). |
regex |
A field whose value is itself a regular expression. |
semver |
SemVer 2.0.0. |
percentage |
A percentage value (string or number). |
color |
CSS color string (#rrggbb, rgb(...), etc.). |
currency |
ISO 4217 three-letter code. |
Vendor-specific formats should use the x- prefix (e.g. x-stripe-customer-id).
Constraints attach to a field via the constraints: map. Available constraints depend on the field's type — applying an incompatible constraint is rejected at import.
| Constraint | numeric (integer, number, duration) | string | json | other (bool, time, url) |
|---|---|---|---|---|
minimum / maximum |
✓ | ✗ | ✗ | ✗ |
exclusiveMinimum / exclusiveMaximum |
✓ | ✗ | ✗ | ✗ |
minLength / maxLength |
✗ | ✓ | ✗ | ✗ |
pattern |
✗ | ✓ | ✗ | ✗ |
json_schema |
✗ | ✗ | ✓ | ✗ |
enum |
✓ | ✓ | ✓ | ✓ |
Range sanity is checked at import — minimum > maximum, minLength > maxLength, and exclusiveMinimum >= exclusiveMaximum are all rejected.
# Integer range, inclusive.
constraints:
minimum: 0
maximum: 100
# Number range, exclusive — value must be > 0 and < 1.
constraints:
exclusiveMinimum: 0
exclusiveMaximum: 1
# String length + pattern.
constraints:
minLength: 3
maxLength: 50
pattern: '^[A-Z]+$' # uppercase only (RE2)
# Enum on any type.
constraints:
enum: [dev, staging, prod]
# Embedded JSON Schema for json fields.
constraints:
json_schema: |
{"type": "object", "required": ["name"], "properties": {"name": {"type": "string"}}}The pattern constraint uses RE2 syntax — a subset of PCRE without backtracking. ReDoS-safe by construction. An invalid pattern is rejected at import time with InvalidArgument; the schema is not stored.
Two complementary mechanisms cover cross-field invariants. Pick the simpler one whenever it suffices.
Declarative "if field A is set, field B must also be set". Free, no expression engine. Matches the JSON Schema 2020-12 keyword of the same name.
fields:
payments.refunds_enabled: { type: bool }
payments.refund_window: { type: duration, nullable: true }
dependentRequired:
payments.refunds_enabled: [payments.refund_window]Semantics:
- Triggers on non-null. A rule fires only when the trigger has a non-null value in the post-merge configuration. Setting the trigger to null clears the requirement.
- Lint at import.
ImportSchemarejects rules where the trigger or any dependent does not name a defined field, where a trigger lists itself as a dependent, or where a dependent appears twice under the same trigger. - Runtime enforcement. Every config write (
SetField,SetFields,ImportConfig,RollbackToVersion) evaluates all rules against the post-merge state inside the same transaction. A rule violation rejects the write withInvalidArgument.
Reserved for cross-field rules expressed in Common Expression Language. The schema-format key is reserved in v0.1.0; the runtime engine ships in Phase 2 (issue #76). v0.1.0 schemas with validations: parse and persist; rules become no-ops at write time until the engine ships.
validations:
- path: payments
rule: "self.payments.min_amount < self.payments.max_amount"
message: "min_amount must be less than max_amount"
severity: error # optional, default error
reason: MIN_GE_MAX # optional machine codeReach for validations: when dependentRequired: cannot express the rule — typically arithmetic comparisons (min < max), conditional requirement based on a value (if env == "prod" then audit_url required), or multi-field invariants. See .agents/context/cel-validation.md for the full design.
example: is a single value; examples: is a named map for showing multiple realistic shapes.
fields:
rate_limits:
type: json
description: Per-tenant rate limits.
examples:
basic:
value: '{"requests_per_second": 100}'
summary: Default rate limit for free-tier tenants.
strict:
value: '{"requests_per_second": 10, "burst": 5}'
summary: Strict limit for trial accounts.Tags provide cross-cutting categorization independent of the path hierarchy. Useful for UI grouping, filtering, and documentation.
fields:
api.webhook_url:
type: url
tags: [integrations]
payments.fee_rate:
type: number
tags: [billing, compliance] # multi-tag
payments.currency:
type: string
tags: [compliance]Path prefixes (api, payments) and tags are independent dimensions. Real schemas often tag fields across multiple path groups to support views like "all compliance-related fields" or "all integrations".
info:
title: Payments
author: Platform team
contact:
name: Pat
email: pat@example.com
url: https://wiki/teams/platform
labels:
team: platform
cost-center: "1234"| Key | Type | Notes |
|---|---|---|
title |
string | Display title (free-form, human-friendly). |
author |
string | Owner name (person, team, or service). |
contact.name |
string | Contact name. |
contact.email |
string | Contact email (RFC 5322). |
contact.url |
string | Contact URL (e.g. team wiki). |
labels |
map<string,string> | Free-form key/value labels for filtering and categorization. |
Any object level — top-level, field, constraints, info, externalDocs, examples, validations entries — accepts keys with the x- prefix. The parser preserves them on round-trip but does not interpret them.
x-internal-team: payments
fields:
payments.fee:
type: number
x-monitoring-alert-threshold: 0.05Use x-* for vendor- or team-specific metadata that doesn't belong in the core format.
- Schema lookup by
name:- Schema doesn't exist → creates a new schema with v1.
- Schema exists, fields differ → creates the next version as a draft.
- Schema exists, fields identical → returns
AlreadyExists(no-op).
- Imported versions are drafts by default. Use
--publish(CLI) orauto_publish(API) to auto-publish on import. - The
versionfield in YAML is informational — the server assigns the actual next version. - Full-replace semantics: the YAML defines the complete field set, not a diff against the previous version.
- Exports a specific version (or latest) as YAML.
- Server-generated fields excluded from export:
id,checksum,published,created_at. - The exported document includes the
# yaml-language-server:modeline and$schemapointer for round-trip editor support.
When writing config values against a schema, the server operates in strict mode: writes to field paths not defined in the tenant's schema are rejected. This prevents typos and undeclared fields from entering the config. There is no "permissive" mode in v0.1.0.
- Schemas & Fields — higher-level concepts: lifecycle, drafts vs published versions, evolution.
- Meta-schema — the JSON Schema 2020-12 meta-schema that mechanically validates files in this format.
- Typed Values — how
type:maps to the wireTypedValueoneof. - Tenants — how schemas are bound to tenants for config writes.
- API Reference — SchemaService — the gRPC RPC surface.
- CLI —
decree schema— managing schemas from the command line.