Hedronite · Ops Lesson · Cloud track · Tue 2026-10-13 · BACKFILL

Nickel contracts for a Lambda canary deploy config

Type the deploy config; let export refuse 150 percent.

Lesson Class: Ops (T7 · phase C)
Referent: Nickel contracts · merge priorities · export
Tool: nickel 1.18.0 via nixpkgs
Paired Cert: AWS DOP Lambda traffic shifting
Contract at the Field
Enums and predicates.
Merge with Priorities
default yields, force overrides.
Export Is the Gate
Nonzero exit stops the pipeline.
Config is code that nobody tests.

Config is code that nobody tests. Give it a contract, export JSON only when the contract holds, and let the export fail loudly when someone types 150 percent.

§I. Frame

C-days bridge Ops to config languages. Nickel is the first row: a configuration language with contracts (checks attached to fields), merging with priorities, and export to JSON, YAML, or TOML. The anchor names HedronOS/Facet config as the long target; today's referent is smaller and real: the deploy settings for a Lambda canary, which the paired DOP lesson walks through on the AWS side.

Done bar: one Nickel file holding prod and staging deploy settings that share a base, a contract that rejects bad values at export, and a JSON file a pipeline can read.

§II. Three techniques

Contract at the Field (named technique). Attach the check to the field, not to a later script.

let Percent = std.contract.from_predicate (fun n => std.is_number n && n > 0 && n < 100) in
let Strategy = [| 'Canary, 'Linear, 'AllAtOnce |] in
let Deploy = {
  function_name | String,
  alias | String | default = "live",
  strategy | Strategy,
  step_percent | Percent,
  interval_minutes | Number | default = 10,
  alarms | Array String,
  memory_mb | Number | default = 512,
} in

Strategy is an enum: a typo like 'Canery fails. Percent is a predicate contract.

Merge with Priorities (named technique). & merges records. A field marked default yields to any plain value; force overrides a plain value. Environments become small overlays on one base:

let base | Deploy = {
  function_name = "orders-api", strategy = 'Canary,
  step_percent = 10, alarms = ["orders-api-5xx", "orders-api-p99"],
} in
{
  prod | Deploy = base & { memory_mb | force = 1024 },
  staging | Deploy = base & { alarms | force = [], strategy | force = 'AllAtOnce,
                              step_percent | force = 50 },
}

Contrast Nix //: it replaces right-biased, silently, with no types.

Export Is the Gate (named technique). nickel export evaluates, applies every contract, and only then prints. A contract failure is a nonzero exit, so the pipeline stops before CodeDeploy ever sees the value.

§III. Run it

nickel export deploy.ncl (nickel 1.18.0, real, exit 0, trimmed to prod):

"prod": {
  "alarms": ["orders-api-5xx", "orders-api-p99"],
  "alias": "live",
  "function_name": "orders-api",
  "interval_minutes": 10,
  "memory_mb": 1024,
  "step_percent": 10,
  "strategy": "Canary"
}

staging came out with memory_mb: 512 (the default held), alarms: [], strategy: "AllAtOnce", step_percent: 50. Defaults filled in, overrides applied, contracts passed.

bad.ncl sets step_percent = 150. Real output, exit 1:

error: contract broken by the value of `step_percent`
  ┌─ .../nickel/bad.ncl:4:66
3 │ let Deploy = { function_name | String, step_percent | Percent, alarms | Array String } in
  │                                                       ------- expected type
4 │ { prod | Deploy = { function_name = "orders-api", step_percent = 150, alarms = [] } }
  │                                                                  ^^^ applied to this expression

The error names the field, the contract, and the line.

§IV. Into the pipeline

§V. Drills

  1. Someone writes strategy = 'Canery. Where does it fail? (At export: not a member of the enum.)
  2. Base sets memory_mb as a default and prod writes memory_mb = 1024 with no annotation. Who wins? (Prod: a plain value beats a default.)
  3. Why is exit code 1 on export useful? (The pipeline stops before deploy.)
  4. Name one rule a contract could add for prod. (Non-empty alarms when strategy is Canary or Linear.)

Close

Contract at the Field, Merge with Priorities, Export Is the Gate. Type the deploy config, build environments as overlays, and let nickel export refuse bad values before AWS sees them.

Related