CLI contract
The neo4j-backup CLI is the interface a coding/ops agent drives without MCP (and the surface
CI schedules). For that to be safe and scriptable, every subcommand honours one contract: a stable
machine-readable result, documented exit codes, and explicit guards on anything destructive. The
optional MCP server (#58 Phase 5) is a thin
wrapper over this same contract, not a separate interface.
The contract is executable: neo4j_backup_core/cli_contract.py
is the single source of truth, and tests/cli_contract.py is
the conformance harness the CLI's tests call per subcommand. This document is its prose form.
Output: --json on every command
Human-readable output is the default. --json switches stdout to a single JSON object — the
envelope — so an agent or CI parses one thing:
{
"ok": true,
"op": "backup",
"group": "demo",
"result": { "key": "demo/orders/orders-20260711t120000/full.backup" },
"error": null
}
| Field | Type | Notes |
|---|---|---|
ok |
bool | Always present. |
op |
string | The operation, e.g. backup, restore, verify, prune. |
group |
string or null | The policy group acted on, when the command targets one. |
result |
object or null | Operation payload on success (keys, counts, blast radius). |
error |
{ "kind", "msg" } or null |
Present iff ok is false. kind is a stable token; msg is human text. |
Rules: error is an object when ok is false and null otherwise; group/result are the value or
null. Log lines go to stderr so stdout stays clean, parseable JSON. neo4j_backup_core.cli_contract.validate_envelope
enforces this and returns [] for a conformant object.
Exit codes
Documented classes (neo4j_backup_core.cli_contract.Exit), stable across commands, so CI and agents
gate on meaning rather than a bare non-zero:
| Code | Name | When |
|---|---|---|
0 |
OK |
The operation succeeded. |
1 |
FAILURE |
The operation ran and failed (backup errored, server refused a restore). |
2 |
USAGE |
Bad arguments / unknown command (argparse). |
3 |
GUARD |
A safety guard refused: a destructive op without --confirm, or a failed precondition. |
Safety: dry-run, blast radius, confirm
--dry-runon every mutating command: print the planned actions, exit0, touch neither Neo4j nor the object store.- Blast radius for destructive ops (
restore --replace,prune): before acting — and always under--dry-run— theresultechoes what will be dropped/created (physical names, alias swaps, artifact keys). The MCP layer surfaces this same structure. --confirm(or--yes) required for destructive ops. No interactive prompts, ever — an agent/CI can't answer one, so a missing confirmation is aGUARDrefusal, not a hang. Read-only is the default posture.
For implementers
Build each subcommand on envelope(...) / make_error(...) and return an Exit code; do not
hand-roll result dicts. In tests, call the harness (assert_conformant, assert_exit,
assert_dry_run_is_inert, assert_destructive_refused) rather than re-checking fields. See
#60 for scope and
#59 for the agent-facing AGENTS.md that
summarises this contract.