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:
- Builds a graph from
hookswheredemand_job != ''(edge: dependent → upstream). - Seeds jobs with at least one command having empty demand at level 0.
- Recursively assigns level N+1 to jobs demanding a job at level N.
- Sets each job’s
deployment_seqto 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 build — post_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 |