v0.7.16 — Per-Target OperatorBox and MuxReconciler

4 min read

Per-target operatorBox

Each serve.target.entries entry can now declare its own operatorBox — resources, lifecycle hooks, and preReconcile gates (enqueueGate / reconcileGate). The reconciler selects the active box from serve-alias / serve-target annotations on the CR at reconcile time.

serve:
  target:
    entries:
      v2-enabled:
        operatorBox:
          enqueueGate: "{{ isBusinessHours }}"
          reconciler:
            hooks: true
      v2-ctor:
        operatorBox:
          reconciler:
            default: false   # use a dedicated constructor from ReconcilerRegistry

reconciler.default: false wires the target’s constructor from ReconcilerRegistry at load time. A missing registry entry is a load-time error — the Runtime refuses to start rather than falling back silently.

Surface switches are detected via orkestra.orkspace.io/last-surface and cleaned up with a label-selector sweep (SweepOwnedNamespaced / SweepOwnedClusterScoped) rather than template expansion, which is immune to spec fields being cleared before cleanup runs. keepPreviousSurface: true skips the sweep when set.

EffectiveOwnerKey stamps and checks ownership as <name>.<alias> for target-mode CRs, allowing per-surface resource isolation.


MuxReconciler and pkg/intent/target/

MuxReconciler holds one domain.Reconciler per registered target. At reconcile time the kordinator reads the target annotation and dispatches to the matching reconciler, falling back to the CRD-level reconciler when no target-specific one is registered. The kordinator sees one reconciler; the routing is internal.

Target resolution and CR construction move into a dedicated pkg/intent/target/ package, separating the intent layer from the gateway API package. target.go and its tests follow.


ork serve apply --override

Routing conflict detection (409) is still enforced when a target switch is attempted without an explicit override. Pass --override (or ?override=true on the API) to route to the new target and trigger surface cleanup of the old one.


Fixture: 03-hooks-targets

pkg/kubeclient/fixture/03-hooks-targets — three targets on one CRD proving each dispatch path end-to-end:

  • v2-enabled — hooks with an enqueueGate (business hours), featureEnabled: true
  • v2-disabled — same hook binary, featureEnabled: false, no gate
  • v2-ctorreconciler.default: false, dedicated constructor

Documentation

documentation/concepts/reusability/ — new section covering Reusability and Composition in Orkestra.


lifecycle: block — maturity, deprecation, compatibility, and platform policy

A new top-level lifecycle: block on every Katalog and Komposer file replaces the old metadata.deprecation: approach with a first-class lifecycle model.

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"

Maturity — four levels: alpha, beta, stable, deprecated. The presence of a deprecation: block is the primary signal; maturity: deprecated without a block emits a warning rather than an error. maturity: deprecated with a block is always valid.

Deprecation — a deprecated Katalog always blocks startup when run directly. Consumers acknowledge it via lifecycle.accept.patterns on their Komposer, not by a field on the Katalog itself.

Compatibility — declares minimum orkestra and kubernetes semver constraints. ork validate rejects patterns that declare versions below the installed runtime.

Kind boundarylifecycle.accept.patterns belongs on a Komposer. Declaring lifecycle.accept on a Katalog is a validation error.


Komposer-level lifecycle acceptance — lifecycle.accept.patterns

Komposers accept deprecated Katalogs at the point of composition:

lifecycle:
  accept:
    patterns:
      - name: webapp-operator
        version: ">= 1.0.0, < 2.0.0"   # optional semver range
      - name: cache-operator

version: scopes acceptance to a semver range. Acceptance without a range applies to any version of that pattern.


Platform policy — policy.lifecycle.minMaturity

Operators can declare a minimum maturity floor for all imported patterns:

policy:
  lifecycle:
    minMaturity: beta   # alpha | beta | stable; deprecated is rejected

ork validate rejects any Katalog whose maturity is below the declared floor. minMaturity: deprecated is itself a validation error — the policy is a quality floor, not a filter.

policy: is structured as policy.<area>.* so security, registry, and user-defined policy categories can grow alongside lifecycle: without flattening.


Registry guide examples 13–16

Four new self-contained steps extend the registry guide:

  • 13-deprecation-accept — accept a deprecated Katalog via Komposer lifecycle.accept.patterns; scoped version acceptance
  • 14-lifecycle-maturity — maturity progression from alpha through stable; ork inspect output at each stage
  • 15-lifecycle-compatibility — declaring orkestra and kubernetes version constraints; validation rejection behaviour
  • 16-komposer-acceptlifecycle.accept.patterns on a Komposer composing both a deprecated and an alpha Katalog; scoped version: range