v0.7.14 — Aliases and Intent Provenance
Serve aliases
A CRD can now expose multiple named entry points — aliases — alongside its primary target. Each alias shares the same CRD, operator, and Kubernetes resource, but can restrict which tokens are valid for it and shape its response independently.
Aliases are declared in serve.target. The entry with primary: true is the primary surface; all other entries are aliases:
serve:
target:
apifixture:
primary: true
preview:
include: ./serve/aliases/preview.yaml
internal:
tokens:
platform-team:
permissions:
global: ["*"]
Callers use aliases the same way they use the primary target — by name in POST /api/v1/apply or via ork serve:
curl -X POST /api/v1/apply \
-H "Authorization: Bearer $TOKEN" \
-d '{"target": "preview", "name": "my-service", ...}'
CRD-level serve.tokens and serve.config serve as fallback for any alias that does not declare its own. Each entry accepts enabled: false to close a surface without removing its config block.
Intent provenance annotations
Every CR applied through the gateway is stamped with three annotations by the apply handler:
| Annotation | Value |
|---|---|
orkestra.orkspace.io/serve-target | The primary target name (e.g. apifixture) |
orkestra.orkspace.io/serve-alias | The alias name, or "" for the primary target |
orkestra.orkspace.io/serve-source | Verified OIDC sub claim of the caller, or "" for static token auth |
Seven built-in notes expose provenance in any template expression:
| Note | Returns |
|---|---|
getServeTarget . | Primary target name |
getServeAlias . | Alias name — "" for primary target |
getServeSource . | Delivery source |
hasServeTarget . | true when submitted via the Gateway API |
hasServeAlias . | true when a named alias was used |
hasServeSource . | true when a webhook source integration was used |
isDirectApply . | true when none of the three annotations are present |
isDirectApply is true when a CR arrived via kubectl or CI direct apply, bypassing all gateway routing.
These notes work in when: conditions at both reconcile time (operatorBox resource templates) and admission time (validation and mutation rules).
Immutable routing surface
A resource’s routing surface — the target or alias it was created through — is immutable without explicit intent. Once a CR is applied via preview, only preview can update it:
{
"accepted": false,
"message": "routing surface conflict: resource was created via \"preview\", cannot update via \"apifixture\" without ?override=true"
}
Pass ?override=true to intentionally change the surface. The gateway logs a warning with the before/after values. New CRs and CRs created by direct kubectl apply are not subject to this check.
Alias-aware response shaping
Each alias can configure its own response config — what the apply response includes and what GET /api/v1/resources/... returns:
serve:
target:
apifixture:
primary: true
preview:
config:
response:
default: false
payload:
phase: '{{ .status.phase }}'
alias: '{{ getServeAlias . }}'
On GET, the gateway reads the serve-alias annotation stamped on the stored CR and applies the matching alias response config automatically — the caller does not need to pass ?target=. CRs applied via the primary target use the CRD-level response config.
Alias token permission model
Token permissions resolve in a three-layer chain, most specific first:
- Alias tokens — when the alias declares
tokens:, only listed tokens are checked. A token absent from the alias map is denied, even if valid at the CRD level. - CRD tokens — when no alias tokens are declared, the CRD-level restrictions apply.
- Allow all — when neither level declares restrictions.
serve.target[*].include
Each target entry supports include: — a path relative to the katalog file pointing to a YAML file with tokens: and/or config: at the top level. Inline fields take precedence on merge. Convention: ./serve/aliases/<name>.yaml.
Admission-time gating by alias or target
Provenance notes are available in when: conditions on validation.rules and mutation.rules. The gateway stamps the annotations before the SSA patch, so the webhook sees them on every create and update:
validation:
rules:
- field: spec.replicas
lessThanOrEqualTo: 10
message: "preview environments are capped at 10 replicas"
action: deny
when:
- field: '{{ eq (getServeAlias .) "preview" }}'
equals: "true"
--alias flag on ork serve subcommands
ork serve schema, ork serve fields, ork serve aliases, and ork serve can-i all accept --alias as an alternative to --target. Passing an alias name resolves the CRD through that surface and shows alias-specific context in the output header.
Gateway schema API includes aliases
GET /api/v1/schema and GET /api/v1/schema?target=<t> now include an aliases array in each response entry. GET /api/v1/resources/... accepts alias names in the {kind} path segment — aliases are routable the same way targets are.
Control Center surface selector
When a CRD has aliases, the create form in the Control Center shows a surface tab strip before the fields. Selecting a tab routes the submission through that alias. Single-surface CRDs stamp the target silently with no UI change.
CRDEntry.AliasNames()
A new AliasNames() []string method on CRDEntry returns a sorted slice of alias names. Used by the gateway schema response, the kordinator /katalog health endpoint, and the CC.
OIDC token authentication
gateway.api.auth.tokens now supports short-lived OIDC tokens as a credential source. Three named presets and a generic oidc: type are available:
githubOIDC:— GitHub Actions tokens. Issuer is hardcoded totoken.actions.githubusercontent.com. Theallow:block constrains byrepository,repositoryOwner,ref,workflow,environment, andjobWorkflowRef.- name: gh-ci githubOIDC: allow: repository: myorg/payments ref: refs/heads/maingitlabOIDC:— GitLab CI tokens. Issuer hardcoded togitlab.com. Allow fields:namespacePath,refProtected,environment.- name: gl-ci gitlabOIDC: allow: namespacePath: mygroup/infra refProtected: "true"vaultOIDC:— HashiCorp Vault identity OIDC tokens.url:is the Vault server root; the effective issuer and JWKS discovery base are{url}/v1/identity/oidc. Allow fields:entityName,entityID,namespace, plus a free-formallow:map for custom claim templates.- name: vault-ci vaultOIDC: url: https://vault.myorg.io allow: entityName: ci-agentoidc:— any OIDC-compliant provider.issuer:is required. Discovery follows the standard{issuer}/.well-known/openid-configurationpath. Free-formallow:map.- name: internal-ci oidc: issuer: https://auth.myorg.io audience: orkestra allow: sub: "system:serviceaccount:ci:runner"
All four types are mutually exclusive with token: and secretRef:. When an OIDC token authenticates a request, the verified sub claim is stamped as orkestra.orkspace.io/serve-source and available in templates via getServeSource ..
ork token — inspect and verify gateway tokens
New CLI namespace for working with gateway.api.auth.tokens entries without a running cluster.
ork token list— tabular list of all configured token entries: name, type (oidc/static), provider, and allow summary.ork token verify— verify a JWT against the katalog locally. Fetches JWKS from the real provider, checks signature, expiry, issuer, and claim matching. Reports which entry matched and prints the verified claims.ork token probe -n <name>— probe the OIDC discovery endpoint and JWKS for a named entry. Reports reachability,jwks_uri, key count, and algorithms. Useful for confirming Vault’s non-standard discovery path is reachable before deploying.
ork token list
ork token verify -t token.jwt
ork token probe -n vault-ci
ork token verify --api http://localhost:8443 -t token.jwt # live mode via ork proxy
Gateway token validation at ork validate
gateway.api.auth.tokens entries are now validated statically at ork validate time:
- Each
token:value must be an${ENV_VAR}reference — literals are rejected. - Each
secretRef:entry must supply bothnameandkey. tokenandsecretRefare mutually exclusive per entry.oidc.issueris required for the genericoidc:type.githubOIDC.allow,gitlabOIDC.allow, andvaultOIDC.allowmust not be empty — an empty block accepts any valid token from that issuer.vaultOIDC.urlis required.
ork serve validate --full — aliases block
ork serve validate --full prints the aliases block per CRD, showing token restriction status and response config presence for each alias.
ork serve play
New subcommand that runs the full gateway apply chain locally from a flat intent file — no cluster, no running gateway required.
ork serve play --token control-center
Reads intent.yaml (or intent.json) from the current directory and runs six stages in-process: target resolution, token permission check, CR construction from serve field declarations, provenance annotation stamping, admission rule evaluation, and response payload evaluation. Each stage prints its result; the trace stops at the first failure with a clear error.
The intent file is the same flat key-value document you would POST to /api/v1/apply:
target: apifixture
name: my-payment-service
workloadType: app
team: platform
environment: staging
repoURL: https://github.com/myorg/payments
Stage 5 (admission validation) evaluates validation.rules and mutation.rules — including synthesized rules from serve.fields marked required: true — using the same EvaluateConditions + EvaluateValidationRule logic as the webhook and reconciler. A deny-action violation stops the chain before simulate handoff. Mutation rules that would fire are previewed inline.
--simulate hands the built CR to ork simulate after all six stages pass. --simulate simulate.yaml uses an existing simulate spec for katalog, cycles, and expect: assertions while substituting the play-built CR. This makes a simulate spec a full contract from caller intent to child resource ops — testable locally in one command.
Useful for testing token permissions, verifying serve.name/serve.namespace expression resolution, confirming field routing, catching admission violations, and previewing the response payload — all before wiring up a real delivery surface or GitOps webhook.
ork gate
New command that evaluates admission rules locally against a CR — no cluster, no webhook server required.
ork gate -f katalog.yaml --cr cr.yaml
Runs EvaluateConditions + EvaluateValidationRule for every validation.rule in the Katalog against the provided CR. Deny-action violations exit non-zero; warn-action violations are printed as advisories and exit zero. mutation.rules are also evaluated and previewed — showing which fields would be defaulted or overridden and what value they would receive.
When mutateFirst: true is set, ork gate applies mutation rules to a copy of the CR before running validation — matching the real webhook pipeline order. A CR with absent fields that mutation would fill in passes validation locally just as it would at admission time.
Two operators are skipped in local mode — unique: (needs an informer cache) and external: (needs a real endpoint) — and are noted in the output.
Multi-document CR files are supported; each document is matched to a CRD by kind.
The gateway is an intent runner. The runtime is a CR runner. ork gate is what closes the local loop on the admission side: the same validation and mutation logic the webhook enforces, runnable anywhere without a cluster. Combined with ork serve play --simulate simulate.yaml, the full path from intent to child resource ops is testable locally end to end.
ork serve apply
New subcommand that applies an intent or CR to a live gateway via POST /api/v1/apply.
ork serve apply -f intent.yaml --api https://gateway.myorg.io --token "$ORK_TOKEN"
Accepts a flat intent file (target mode) or a full CR (apiVersion + kind). Both YAML and JSON are supported. Defaults to intent.yaml or intent.json in the current directory when --file is not set.
The gateway handles everything on the other side — target resolution, token validation, admission, provenance stamping, SSA delivery. The command sends the body, prints the structured response, and exits non-zero on rejection.
--dry-run runs the full admission pipeline at the gateway without writing to etcd — useful for validating a token’s permissions or an intent’s shape before committing.
Dependency updates
golang.org/x/netv0.55.0 → v0.56.0 (CVE-2026-46600)golang.org/x/cryptov0.52.0 → v0.53.0
ork push --add-intent
ork push now accepts --add-intent <file> (YAML or JSON). Before pushing, it runs the full ork serve play chain — target resolution, token check (if token: is set in the intent file), CR construction, and admission validation — and bakes the result into the artifact as OCI annotations:
| Annotation | Value |
|---|---|
io.orkestra.intent.status | passed / failed |
io.orkestra.intent.target | Target name played |
io.orkestra.intent.tested_at | RFC3339 timestamp |
Both intent.yaml and intent.json are recognised as optional files in the artifact bundle. ork inspect shows the intent play result alongside Simulate and E2E.
Deprecation timeline and runtime enforcement
metadata.deprecation now supports a scheduled deprecation window and two-gate runtime enforcement.
Timeline — timeline.from opens the deprecation warning window with a days-until-EOL countdown; timeline.to marks the end-of-life date. Both are YYYY-MM-DD strings. ork validate rejects missing message, invalid dates, and from ≥ to.
Accept gates — ork run and ork gate refuse to start a deprecated or EOL Katalog unless the operator has explicitly acknowledged it in the file:
deprecation:
accept:
beforeEol: true # required during the deprecation warning window
eol: true # additionally required after the end-of-life date
eol: true alone is not accepted — beforeEol must also be set.
Touchpoints — the deprecation state is surfaced at ork push (author sees the exact consumer message before upload), ork validate, ork inspect, and ork pull. Display state is computed from today vs the timeline at each call site.
Validator — ork validate warns without blocking; enforcement is at runtime startup only, after validation passes and before the operator begins reconciling.
Serve field translation — value, values, .request
The serve layer can now transform what a caller submits before it reaches the CRD. Two new fields on serve.fields.<name> control this:
value — single transform. One intent field → one spec field, expression-evaluated:
serve:
fields:
image:
value: '{{ trimPrefix "docker.io/" .value }}'
.value is the raw submitted value. The result replaces it at the declared spec path (or spec.<fieldName> when no path: is set).
values — fanout. One intent field → multiple spec fields:
serve:
fields:
schedule:
label: "Schedule (cron)"
required: true
values:
schedule.minute: '{{ cronMinute .value }}'
schedule.hour: '{{ cronHour .value }}'
schedule.dayOfMonth: '{{ cronDom .value }}'
schedule.month: '{{ cronMonth .value }}'
schedule.dayOfWeek: '{{ cronDow .value }}'
The caller submits "0 2 * * 1-5". The CR receives five structured fields. Neither side sees the other’s format. The Katalog is the contract between them.
Both value and values expressions have access to .value (the submitted field value) and .request (the full raw intent payload), so cross-field references work naturally:
values:
image.tag: '{{ .request.version }}'
Expression failures are hard errors. If a value or values expression fails at serve time (template error, missing function, unresolvable reference), the apply is rejected immediately — the CR is never built with a partial spec.
Flat keys in values are supported. A values key does not need to be dotted — imageTag: '{{ .value }}' writes to spec.imageTag.
.request available throughout the reconciler
The raw serve intent (from the orkestra.orkspace.io/serve-intent annotation) is now available as .request.<field> in all template contexts during reconcile — operatorBox templates, mutation.rules, validation.rules, and status.fields. Previously it was only injected for validation at the webhook boundary.
Intent gating in validation rules — fires.reconcile: false
validation.rules and mutation.rules now support fires.reconcile: false, limiting a rule to the admission path only:
validation:
rules:
- field: "{{ cronValid .request.schedule }}"
equals: true
link: schedule
fires:
reconcile: false
message: 'schedule must be a valid cron expression'
action: deny
Use this for rules that read .request.* — the raw intent annotation is present at admission but not guaranteed at every reconcile. Rules without fires: continue to fire at both admission and reconcile (existing behavior unchanged).
fires.reconcile on ExternalCall (under validation.external and mutation.external) predates this; the same field is now available on individual inline rules as well.
Example pack — use-cases/crd-api-evolution
New example pack showing how to evolve a CRD’s API surface without breaking existing consumers:
- Demonstrates adding fields, changing field shapes, and renaming spec paths across operator versions using the serve layer as the translation boundary
- Each step is runnable with
ork serve playlocally before deploying
ork init --pack use-cases/crd-api-evolution
Example pack — use-cases/crd-conversion
New example pack demonstrating CRD conversion patterns without a conversion webhook:
basic/— single-version CronJob operator; no conversion, no serve layerwith-serve-translation/— same operator withserve.fields.valuesfanout: callers submit a flat cron string, the serve layer fans it out to five structured schedule fields before the CR reaches the API server
ork init --pack use-cases/crd-conversion
The with-serve-translation variant includes ork serve play and ork simulate as local test steps — runnable before any cluster is involved.
ork validate — field translation checks
New static checks for serve.fields at ork validate time:
valueandvaluesare mutually exclusive per fieldpathandvaluesare mutually exclusive per field — write full paths invaluesdirectly- Field names must not contain hyphens — they cannot be used as Go template identifiers in
.request.<name>expressions; use camelCase or underscores valuesexpressions must compile against the full FuncMap (including user-defined notes)