| name | write-canary-transformations |
| description | Write correct transform blocks for Mission Control canary checks including fan-out, inline, and generated canary patterns. Use when adding transformations to canary checks, splitting a single check into multiple results, modifying check output, or generating child canaries from discovered resources. |
Write Canary Transformations
Goal
Generate correct, production-ready Canary transform blocks from user intent.
Quick Decision Tree
Use this to pick the right transform pattern:
- Need one check result per item in response?
- Use fan-out transform (return JSON array of objects, each with
name).
- Need to modify only the current check result?
- Use inline transform (return one JSON object without
name).
- Need to generate new canary/canaries from discovered resources?
- Use canary transform (return object/array with top-level
spec).
Golden Rules
- Prefer
transform.expr (CEL) by default over transform.javascript and transform.template.
- This is a convention for consistency and easier review, not a strict requirement.
- If JS/template is a better fit for the case, use it.
- End CEL transforms with
.toJSON().
- For fan-out transforms, always set deterministic
name.
- Set
pass explicitly in transformed checks (do not rely on defaults).
- Use
deletedAt when events can resolve/disappear.
- Use
markFailOnEmpty: true when empty output should fail the check.
- Keep check names stable across runs to avoid churn.
Output Contracts
A) Fan-out transformed checks
Return a JSON array (or object) of transformed check entries.
| Field | Required | Description | Example |
|---|
name | Yes | Check name. Keep deterministic across runs. | db/instance-1 |
pass | Recommended | Explicit pass/fail status. | true |
message | No | User-facing summary. | Replication lag within threshold |
description | No | Longer explanation/context. | Replica is healthy |
labels | No | Labels for filtering/relationships. | { 'env': 'prod', 'team': 'platform' } |
namespace | No | Override check namespace. | production |
icon | No | UI icon override. | alert |
duration | No | Duration in milliseconds. | 1200 |
start | No | Start time. | 2026-02-13T12:00:00Z |
detail | No | Structured detail payload. | { 'raw': r } |
data | No | Additional arbitrary data. | { 'source': 'prometheus' } |
metrics | No | Emitted metrics from transform. | [{ 'name': 'lag', 'type': 'gauge', ... }] |
deletedAt | No | Mark transformed check as resolved/deleted at a time. | r.endsAt |
transformDeleteStrategy | No | Behavior when transformed check disappears (Mark*,Ignore). | MarkHealthy |
B) Inline transformed result
Return a single JSON object and omit name.
| Field | Required | Description | Example |
|---|
name | Must omit | If set, this becomes fan-out behavior. | omit |
pass | No | Override pass/fail status. | json.status == 'ok' |
message | No | Override message. | 'status=' + string(json.status) |
description | No | Override description. | 'Health summary' |
error | No | Override error text. | 'response missing key' |
detail | No | Replace detail payload. | { 'status': json.status } |
data | No | Merge additional data into result data. | { 'apiVersion': json.version } |
duration | No | Override duration (milliseconds). | 1200 |
metrics | No | Add metrics emitted from transform. | [{ 'name': 'items', 'type': 'gauge', 'value': 10 }] |
C) Transform into canary/canaries
Return either one canary object or an array of canary objects.
| Field | Required | Description | Example |
|---|
name | Yes | Child canary name. | generated-http-canary |
namespace | No | Child canary namespace. | default |
spec | Yes | Full child canary spec. | { 'schedule': '@every 5m', 'http': [...] } |
Canonical Snippets
1) Fan-out alerts into checks
transform:
expr: |
results.alerts.map(r, {
'name': r.name + r.fingerprint,
'labels': r.labels,
'icon': 'alert',
'pass': false,
'message': r.message,
'description': r.message,
'deletedAt': has(r.endsAt) ? r.endsAt : null
}).toJSON()
2) Prometheus series to checks
transform:
expr: |
dyn(results).map(r, {
'name': r.job,
'namespace': 'namespace' in r ? r.namespace : '',
'labels': r.omit(['value', '__name__']),
'pass': r.value > 0,
'message': 'job=' + r.job
}).toJSON()
3) Inline transform (no new checks)
transform:
expr: |
{
'pass': json.status == 'ok',
'message': 'status=' + string(json.status),
'detail': {
'status': json.status,
'checkedAt': string(time.Now())
}
}.toJSON()
4) Generate child canary from discovery
transform:
expr: |
{
'name': 'generated-http-canary',
'namespace': canary.namespace,
'spec': {
'schedule': '@every 5m',
'http': dyn(results).map(r, {
'name': r.Object.metadata.namespace + '/' + r.Object.metadata.name,
'url': 'https://' + r.Object.spec.rules[0].host
})
}
}.toJSON()
5) Generate multiple child canaries (array output)
transform:
expr: |
dyn(results).map(r, {
'name': 'http-' + r.Object.metadata.name,
'namespace': r.Object.metadata.namespace,
'spec': {
'schedule': '@every 5m',
'http': [{
'name': r.Object.metadata.name,
'url': 'https://' + r.Object.spec.rules[0].host
}]
}
}).toJSON()
6) Empty output should fail
markFailOnEmpty: true
transform:
expr: |
dyn(results.rows).map(r, {
'name': r.id,
'pass': true
}).toJSON()
Deletion / Lifecycle Controls
Use transformDeleteStrategy on the parent check when transformed checks may disappear.
Recommended values:
MarkHealthy
MarkUnhealthy
Ignore
Example:
transformDeleteStrategy: MarkHealthy
Stateful Transform Patterns
Use prior run state
last_result() gives previous result context.
- Useful for cursors/time windows.
Pattern:
transform:
expr: |
{
'detail': {
'max': string(results.?newest.modified.orValue(last_result().results.?max.orValue('now-60d')))
}
}.toJSON()
Use outputs from dependency checks
If checks use dependsOn, you can use outputs.<checkName>... in expressions/tests.
Common Mistakes to Avoid
- Returning multiple transformed entries without
name.
- Forgetting
.toJSON() in CEL expressions.
- Non-deterministic names (e.g., random UUID every run).
- Forgetting to set
pass for fan-out checks.
- Returning empty list unintentionally without
markFailOnEmpty.
- Mixing canary-transform output (
spec) with check-transform output in same object.
Authoring Workflow
When user asks for a transformation:
- Identify source payload shape (
results, json, rows, messages).
- Pick transform mode (fan-out / inline / canary).
- Build minimal output contract.
- Add
pass, message, stable name.
- Add lifecycle fields (
deletedAt, transformDeleteStrategy) if needed.
- Return full YAML block ready to paste.
If user provides sample payload, always base transform logic directly on that payload.
References