Skip to main content

Specs and actions

A checklist is carried by two entities:

  • Checklist specification, spec for short: the items themselves, plus how their results add up to an outcome.
  • Approval action: which requests the spec governs, which version of it they run against, and what happens when it fails.

This page is the contract for both: crafting, wiring, and permissions. If you haven't built a checklist yet, start with your first checklist instead; everything the skill did there is this API.

What a checklist spec looks like

A spec is a name, an NRN, and a definition holding the items:

{
"items": [
{
"id": "coverage_gate",
"type": "condition",
"behavior": "gate",
"severity": "major",
"title": "Build coverage above 80%",
"query": { "build.metadata.coverage.lines.percent": { "$gte": 80 } }
},
{
"id": "snyk_high_severity",
"type": "condition",
"behavior": "gate",
"severity": "critical",
"title": "No high-severity Snyk findings",
"query": { "build.metadata.snyk.high": { "$eq": 0 } }
},
{
"id": "security_signoff",
"type": "manual",
"behavior": "gate",
"severity": "major",
"title": "Security team sign-off",
"description": "Required for changes that touch auth or PII.",
"applies_when": { "scope.dimensions.environment": "production" }
},
{
"id": "cab_override",
"type": "manual",
"behavior": "override",
"title": "CAB emergency override",
"description": "Use only when a blocking item is misconfigured and the change must ship."
}
]
}

Conditions use the same query language and the same field paths as policies, so a predicate you already trust can move across unchanged.

note

Note the paths carry no context. prefix. The full item contract, including structured inputs, validations, and groups, is in the items reference.

Next to items, the definition takes an optional aggregation block that says how the outcome is computed:

aggregation.typeWhat it does
derived (the default)Nullplatform builds the expression from your items: every gate item must pass, or an override item must be approved
expressionYou supply the boolean expression yourself in an expression field. An escape hatch, not a normal crafting step

Leaving aggregation out entirely is the same as derived. See how the outcome is computed for the expression language.

Craft the specification

Describing the spec to the np-checklist skill is the quickest way to get one, and your first checklist walks through that. To craft it yourself:

  1. Go to Platform settings > Approvals > Checklists and click + New specification. The editor opens on a working one-item spec:

    {
    "name": "my-checklist-specification",
    "description": "",
    "definition": {
    "items": [
    {
    "id": "manual_approval",
    "type": "manual",
    "behavior": "gate",
    "severity": "major",
    "title": "Manual approval",
    "require_comment": true
    }
    ],
    "aggregation": {
    "type": "derived"
    }
    }
    }
  2. Build your definition on top of it. The Condition, Manual, External, and Group buttons drop ready-made blocks into the editor, so you can write a spec without keeping the schema in your head.

  3. Check it in the pane on the right, which revalidates while you type. Checklist preview renders the list a developer would see, and Preview as flips it through Not started, In progress, Everything passed, and A gate failed, none of which creates a run.

  4. Click Create. The spec appears in the Checklists list with its version, its status, and how many approval actions currently run on it.

Connect it to an approval action

A spec does nothing until an approval action points at it. An action runs on a checklist or on policies, never both, so nothing can end up with two sets of rules disagreeing about the same request.

Go to Platform settings > Approvals > Settings and click + New approval action. The form creates the action with the checklist already attached. Choose the resource, entity, and action to protect, pick Checklist along with the specification and the version to pin it to, and set what happens once the run is evaluated. The form explains every field as you fill it in.

The New approval action form, with What is protected asking for a resource, an entity and an action, How it is decided set to Checklist with a specification and a version to pick, and Outcome holding the pass and fail behaviors and the two timeouts

Change a spec that's already live

Updating a spec never edits it in place. Every update publishes a new version: a new spec id under the same name and NRN, with the version number bumped.

Go to Platform settings > Approvals > Checklists and open the checklist you want to change. In its detail view, click New version: the spec editor reopens on the current definition, with the same validation and preview panes you had when you created it. Edit whatever you need and click Save as new version.

The detail view doubles as the version history, and Compare shows any two versions side by side. It also lists the approval actions currently running on the spec, and the expression the engine evaluates when the checklist finishes.

The detail view of a checklist specification, with the New version button in the header, a preview of its items, the approval actions associated with it, the derived approval rule, and a version history listing v2 as current and v1 below it, each with a Compare link

Publishing doesn't switch anything over. Version 1 stays active and the action keeps evaluating the version id it holds, so a spec change is a two-step rollout: publish, then point the action at the new id. Pointing it back at the old id is the rollback, and runs already in flight finish on the version they snapshotted.

Compare puts the two definitions side by side, so you can see exactly what moved before you point an action at the new version.

The version history of the first-gate checklist next to a Compare versions view of v1 and v2, with the coverage threshold highlighted as the only change: gt 80 and a title of Build coverage above 80% on the left, gt 85 and Build coverage above 85% on the right

Dry-run a spec before it governs anything

A dry run resolves the same action a real request would resolve and previews the run without creating it. It is what you do right after connecting a spec, and again after every new version. Send a POST request describing the request you want previewed:

curl -L 'https://api.nullplatform.com/approval/dry-run' \
-H 'Authorization: Bearer <token>' \
-H 'Content-Type: application/json' \
-d '{
"nrn": "organization=1:account=2:namespace=3:application=4",
"action": "deployment:create",
"approval_mode": "checklist",
"entity_id": 9876,
"requested": { "scope_id": 5521, "release_id": 7710 }
}'

approval_mode takes auto (the default: the checklist if the action has one, policies otherwise), checklist, or policy. Forcing checklist on an action with no spec attached is an error rather than a silent fallback, which is what you want when the thing you are verifying is the wiring.

The response predicts the run item by item:

{
"mode": "checklist",
"specification_snapshot": {
"id": "spec_abc123def456ghij",
"name": "staging-deploy-gate",
"version": 2
},
"preview": {
"items": [
{ "id": "coverage_gate", "type": "condition", "behavior": "gate", "predicted_status": "failed", "would_run": true, "message": "condition_not_met" },
{ "id": "security_signoff", "type": "manual", "behavior": "gate", "predicted_status": "pending", "would_pend_for_user": true }
],
"aggregate_prediction": { "best_case": "fail", "worst_case": "fail", "status": "resolved" }
}
}

Conditions are genuinely evaluated, so their predicted_status is the real verdict. Everything asynchronous reports what it would wait for instead: a manual item comes back pending with would_pend_for_user, an on_demand external comes back latent, and an auto external is only dispatched when it declares dry_run_safe: true. aggregate_prediction closes the loop: best_case says whether the request can be approved at all, and when it matches worst_case the outcome is already decided.

Pass a context object in the body to preview against values of your choosing rather than the ones nullplatform would build from the request. That is how you check a threshold from both sides without producing a build that fails it.

Choose what a failed run does

By default, a failed blocking item doesn't page anyone. The request stays open and resumable: the developer reads which item failed, fixes the cause, and redeploys, or asks for a manual review when they genuinely need another human. Reviewers are only notified at that point.

If your checklist is something the requester can never resolve on their own, set checklist_fail_mode on the approval action:

checklist_fail_modeWhat a failed run does
on_request (default)The request stays open. The requester fixes and redeploys, cancels, or asks for manual review. Nobody is notified until they ask
autoReviewers are notified immediately and the run moves to manual review

Keep the default when the requester can fix what failed, like a coverage threshold or a compliance rule they can address in code. Reserve auto for checklists the requester has no way to resolve on their own.

checklist_fail_mode only comes into play when the action's on_policy_fail is manual, which is the Require a person to decide option in the form above. If on_policy_fail is reject, a failed run denies the request outright and neither mode applies.

Who can do what

Configuring guardrails and executing them are deliberately different jobs.

OperationRoles
Create, update, or delete checklist specssecops, ops, admin
Link a checklist spec to an approval actionsecops, ops, admin
Dry-run a checklist spec against a contextsecops, ops, admin, developer
Sign off a manual item or retry an external onethe team that owns the run's NRN
Read checklist specs and runsall roles, including member and troubleshooting

See authorization for how roles and grants work.

What's next

  • Items reference: the full contract of every item type, behavior, and validation rule
  • Approval actions: everything an action can govern beyond deployments