v0.7.16 — Per-target operatorBox, lifecycle:, controller-runtime compatibility
Breaking: anyOf: renamed to or:
Global rename across the entire schema — gates, autoscale conditions, validation rules, simulation specs, serve fields. The old name is rejected at load time.
# before
anyOf:
- field: spec.env
equals: production
# after
or:
- field: spec.env
equals: production
Breaking: resources: renamed to managedResources: in the constructor block
constructor.resources: and hooks.resources: are now managedResources:, which distinguishes managed resource kinds from the watch: block. Old name rejected at load time.
Breaking: domain.Reconciler interface signature
Typed operators implementing domain.Reconciler must update their Reconcile method:
// before
Reconcile(ctx context.Context, key string) error
// after
Reconcile(ctx context.Context, req domain.Request) (domain.Result, error)
req.Key is namespace/name. req.NamespacedName is available directly. domain.Result.RequeueAfter propagates to the work queue — ctrl.Result{RequeueAfter: X} returned from a domain.ReconcilerFrom-wrapped reconciler now works without any changes inside Reconcile.
ork migrate handles this automatically. Native mode operators must update manually.
Per-target operatorBox
Each serve target can now declare its own operatorBox — resources, hooks, and pre-reconcile gates scoped to that surface:
serve:
target:
standard:
operatorBox:
reconciler:
hooks: true
enterprise:
operatorBox:
preReconcile:
enqueueGate:
when:
- field: "{{ isBusinessHours }}"
equals: "true"
reconciler.default: false wires the target’s constructor from ReconcilerRegistry. A missing entry is a load-time error.
lifecycle: block
lifecycle:
maturity: beta # alpha | beta | stable | deprecated
deprecation:
message: "Replaced by task-runner"
migratedTo: task-runner:v1.0.0
timeline:
from: "2026-01-01"
to: "2027-01-01"
compatibility:
orkestra: ">= 0.7.0"
kubernetes: ">= 1.28"
A Komposer must declare lifecycle.accept.patterns to consume a deprecated Katalog. Without it, the deprecated operator blocks startup.
requeue: — per-object requeue scheduling
operatorBox:
requeue:
- when:
- field: .status.phase
equals: Pending
after: "{{ timeUntil .status.certExpiresAt }}"
Different objects get different requeue intervals based on their own state. Complements resync: (uniform, whole-CRD period) — requeue: is conditional and per-object.
timeUntil note
Returns the duration until a timestamp as a Go duration string ("72h0m0s"), or "0s" if past or invalid. Primary use: requeue.after: for cert-expiry-style scheduling.
failPolicy: on pre-reconcile gates
operatorBox:
preReconcile:
reconcileGate:
external:
- name: featureCheck
url: "{{ .spec.flagUrl }}"
failPolicy: closed # deny reconcile when external call fails
open (default) passes through when the external call fails. closed denies.
observe.events — Kubernetes Events as reconciliation triggers [EXPERIMENTAL]
observe.events extends the observe: block with a second observation mechanism alongside observe.watch. Kubernetes Event objects can now trigger primary CR reconciliation, with the event’s own properties available as resolver context at the enqueueGate.
operatorBox:
observe:
events:
dbReady:
reason: DatabaseReady
type: Normal
regarding:
kind: Database
name: my-database
enqueueGate:
when:
- field: "{{ .events.dbReady.reportingController }}"
equals: "database.myorg.io/controller"
The event entry matches on reason, action, type, reportingController, reportingInstance, regarding, and related.
Matched events resolve the primary CR key via regarding (default) or broadcast to all managed CRs when regarding is absent.
The enqueueGate has access to the full event context as .events.<name>.* — reason, action, type, reportingController,
reportingInstance, regarding, related. The gate can reason about what the event says, not just that it happened. The same when:/or: condition machinery applies — no new syntax.
observe.watch and observe.events are siblings under pkg/runtime/informer/observe. Both share the same enqueue path, the
same gate evaluation, and the same resolver construction. The only difference is what they observe and what context they inject.
This is marked experimental. ork validate emits a warning when event entries are declared. The regarding / keyFrom relationship and
edge case handling are being settled in follow-up PRs before the experimental flag is removed.
Use when a secondary controller already emits Events and you want to react without polling, without watching the secondary CRD, and without writing a bridge controller.