Deployment sequence and demands

How command demands express cross-job dependencies and how deployment_seq orders maand build (post_build) and maand deploy waves.

Manifest fields: manifest.md. Concepts: start/concepts.


Command demands

A demand says: the named upstream job must appear earlier in deployment order. Demands are declared per command; deployment_seq is computed per job.

{
  "hooks": {
    "hook_migrate": {
      "executed_on": ["pre_deploy"],
      "demands": {
        "job": "database",
        "hook": "hook_schema",
        "config": { "min_version": "2.0.0" }
      }
    }
  }
}
Field Role
demands.job Upstream job name
demands.command Upstream command on that job
demands.config JSON for runtime GET /demands; min_version / max_version validated at build

Both demands.job and demands.command must be set together or both empty. Self-reference is rejected.

Build validation

After all jobs sync, ValidateHookDemands runs:

Check Error
Partial demand pair ErrInvalidHookDemand
Unknown upstream job/hook ErrInvalidHookDemand
Missing version on dependency participants ErrInvalidJobVersion
Upstream version outside min/max ErrHookDemandVersionMismatch
Cycle in demand graph ErrCircularHookDependency

Runtime behavior

Demands do not auto-run the upstream command. Scripts can call GET /demands to see downstream dependents:

import maand
for d in maand.demands():
    print(d["job"], d["command"], d["demand_config"])

API details: hook-api.md.


deployment_seq

Integer assigned to every job (and copied to all allocations) during maand build. Lower numbers deploy first.

Computation

After BuildAllocations, BuildDeploymentSequence:

  1. Builds a graph from hooks where demand_job != '' (edge: dependent → upstream).
  2. Seeds jobs with at least one command having empty demand at level 0.
  3. Recursively assigns level N+1 to jobs demanding a job at level N.
  4. Sets each job’s deployment_seq to the maximum level among its commands.

Jobs with no commands stay at sequence 0.

Example chain

database (seq 0)  ──demand──►  api (seq 1)  ──demand──►  frontend (seq 2)
Job Command demands deployment_seq
database hook_schema (empty) 0
api hook_migrate database / hook_schema 1
frontend hook_assets api / hook_migrate 2

Multiple demands → max depth across chains. Cycles fail build.


Where sequence is used

maand deploy

reconcile once (all jobs): stop removed/disabled → after_allocation_stopped → cleanup

for seq in 0..max:
  jobs ← all jobs with deployment_seq = seq (respect --jobs filter)
  for each job in jobs:
    pre_deploy (if registered) → stage → update plan hash (skip detection)
    skip if deploy complete (--force overrides)

  when at least one job needs work in this wave:
    bump update_seq; prepare worker.json / jobs.json / bin/

  for each job to deploy:
    stage → rsync → update content hash
    rollout batches: lifecycle → after_allocation_started → promote batch → health_check
    post_deploy → promote sweep → post_deploy_status

final rsync per successful job; commit (partial deploy allowed)

Within one sequence value, jobs roll out independently. Each job applies its own max_concurrent_starts (starts) and max_concurrent_upgrades (upgrades: restart or reload). Worker order within batches uses KV rollout_order.

Deploy never runs a higher sequence before a lower one finishes its wave.

Rolling batch details: guides/rolling-deploy.md.

maand buildpost_build

After the main transaction commits, post_build hooks run in sequence order (0 first). Any failure fails the build.

Not ordered by deployment_seq

Command Ordering
maand job start/stop/restart/reload Manual
maand hooks (cli) Selected job only
maand health_check Per --jobs flag
maand run_command Unrelated
maand collect facts --generate-workers Updates workspace/workers.json only (no DB until maand build)

End-to-end example

Goal: Database schema before API migration.

workspace/jobs/database/manifest.json:

{
  "version": "1.0.0",
  "selectors": ["worker"],
  "hooks": {
    "hook_schema": { "executed_on": ["post_build", "cli"] }
  }
}

workspace/jobs/api/manifest.json:

{
  "version": "1.0.0",
  "selectors": ["worker"],
  "hooks": {
    "hook_migrate": {
      "executed_on": ["pre_deploy", "cli"],
      "demands": {
        "job": "database",
        "hook": "hook_schema",
        "config": { "min_version": "1.0.0" }
      }
    }
  }
}
maand build
maand cat jobs          # database seq 0, api seq 1
maand deploy            # wave 0: database; wave 1: api

Inspect and debug

maand cat jobs
maand cat hooks
maand cat allocations
maand deploy --dry-run

Quick reference

Concept Stored in Set by
Command + demand hooks maand build
deployment_seq allocations.deployment_seq BuildDeploymentSequence
Deploy wave order maand deploy (0 → max)
post_build order maand build (0 → max)
Rolling upgrades max_concurrent_upgrades Per job, per wave (restart or reload)
Rolling first deploy max_concurrent_starts Per job, per wave
Rollout worker order rollout_order KV Build sync; optional pre_deploy override