| name | halt-state |
| description | Manage halt-condition state across a dispatch lifecycle (ARMED → CHECKED → FIRED → ATTESTED) per ADR-017 §2.4 — arm halts pre-flight via tools/halt-arm.sh (Task 3.2), query/fire mid-dispatch via tools/halt-check.sh (Task 3.3 TBD). |
Halt-state
When to invoke
Per ADR-017 §2.4 lifecycle:
DECLARED ── halt-arm.sh ──► ARMED ── halt-check.sh ──► CHECKED ── halt-check.sh ──► FIRED ── closure-attest.sh ──► ATTESTED
- halt-arm.sh (Task 3.2): pre-flight arming — AFTER
dispatch-validate.sh passes, BEFORE Implementer dispatch fires. Reads contract halt_classes:; emits ARMED state file.
- halt-check.sh (Task 3.3 TBD): mid-dispatch — query a specific halt's state OR fire it; advances ARMED → CHECKED → FIRED.
What it does (halt-arm.sh)
Invokes tools/halt-arm.sh to:
- Parse the dispatch contract YAML at
--contract <path>.
- Extract the
halt_classes: field (list of {name, severity} entries).
- Verify all 7 ADR-017 §2.4 canonical halt-classes are present (additive-only constraint per OQ#3; block on missing).
- Compose canonical + per-dispatch additions into a list of
HaltState records with lifecycle: "ARMED".
- Write a state file to
<state-dir>/<dispatch-id>.json (default state-dir: .amnesia/halt-state/; gitignored).
- Emit a JSON diagnostic to stdout.
Canonical halt-state file shape (authoritative)
Schema version 1.0.0. Consumed by halt-check.sh (Task 3.3) and closure-attest.sh (post-dispatch). schema_version is forward-compat preventive (future state-shape changes don't break downstream consumers).
{
"schema_version": "1.0.0",
"dispatch_id": "<id>",
"contract_path": "<absolute-path>",
"armed_at": "<ISO-8601 UTC>",
"halts": [
{
"name": "halt-anchor-resolution-failure",
"severity": "block",
"lifecycle": "ARMED",
"evidence": null,
"armed_at": "<ISO-8601 UTC>",
"fired_at": null
}
],
"canonical_halt_count": 7,
"per_dispatch_addition_count": 0
}
Field semantics:
schema_version: forward-compat indicator; consumers MUST verify before parsing.
dispatch_id: traceability (which dispatch armed this state).
contract_path: absolute path to the source contract YAML.
armed_at: top-level ISO-8601 UTC timestamp (matches per-halt armed_at).
halts: list of HaltState records (canonical + per-dispatch additions in contract order).
canonical_halt_count: count of halts matching ADR-017 §2.4 canonical 7.
per_dispatch_addition_count: count of halts beyond canonical 7.
What it does (halt-check.sh)
Invokes tools/halt-check.sh to query or fire a named halt in an existing state file (produced by tools/halt-arm.sh). Per ADR-017 §2.4 state machine:
- Read state file at
<state-dir>/<dispatch-id>.json.
- Locate the halt entry matching
--condition <name>.
- Reconstruct
HaltState (Pydantic validation; P2 port halt.py:HaltState L59-81).
- Branch on mode:
- Query mode (no
--fire): advance ARMED → CHECKED per Task 3.3 DO Stage 1 Q4 disposition (ADR-017 §2.4 lifecycle); CHECKED → CHECKED idempotent on repeat-query; FIRED → FIRED no-op read.
- Fire mode (
--fire --evidence "<text>"): transition current lifecycle → FIRED with evidence + fired_at timestamp (P2 port halt.py:HaltState.transition() L83-132).
- Write updated state file back (atomic; same shape as Task 3.2 authoritative
schema_version: "1.0.0").
- Emit JSON diagnostic to stdout.
Sticky-block-severity enforcement (ADR-017 §2.4 + P2 port halt.py L119-122): re-fire on lifecycle=FIRED, severity=block is rejected with halt-check-sticky-rejection; explicit operator rearm required via separate command (see "Future / Open" below; deferred per Task 3.3 DO Stage 1 Q5).
Contract with primitive (halt-check.sh)
- Primitive:
tools/halt-check.sh
- Flags / inputs:
--dispatch-id <id> (required; state file basename)
--condition <name> (required; halt class name to query or fire)
--fire (optional; transitions current lifecycle → FIRED; requires --evidence)
--evidence "<text>" (required if --fire; recorded with FIRED state)
--state-dir <path> (optional; default .amnesia/halt-state)
--json (machine-readable output; default behavior)
--pretty (pretty-printed JSON via indent=2)
-h / --help (usage)
Success stdout shapes
Query mode (no transition or non-FIRED transition):
{
"checked": true,
"dispatch_id": "<id>",
"condition": "halt-anchor-resolution-failure",
"state_file": "<absolute-path>",
"current_state": {
"name": "halt-anchor-resolution-failure",
"severity": "block",
"lifecycle": "CHECKED",
"evidence": null,
"armed_at": "<ISO-8601 UTC>",
"fired_at": null
},
"transition_applied": true,
"violations": [],
"warnings": []
}
Fire mode (block-severity successful fire → exit 1):
{
"fired": true,
"dispatch_id": "<id>",
"condition": "halt-anchor-resolution-failure",
"state_file": "<absolute-path>",
"current_state": {
"name": "halt-anchor-resolution-failure",
"severity": "block",
"lifecycle": "FIRED",
"evidence": {"detail": "stale anchor at L42"},
"armed_at": "<ISO-8601 UTC>",
"fired_at": "<ISO-8601 UTC>"
},
"transition": {
"from": "CHECKED",
"to": "FIRED",
"evidence": {"detail": "stale anchor at L42"},
"fired_at": "<ISO-8601 UTC>"
},
"violations": [{"halt_class": "halt-anchor-resolution-failure", "severity": "block", "detail": "stale anchor at L42"}],
"warnings": []
}
Fire mode (warn/log severity successful fire → exit 0 + warnings[]): same shape with severity ∈ {warn, log}, exit code 0, violations[] empty, warnings[] populated.
Failure stdout shape
{
"checked": false,
"fired": false,
"dispatch_id": "<id>",
"condition": "...",
"state_file": "<absolute-path>",
"violations": [
{"halt_class": "halt-check-sticky-rejection", "severity": "block", "detail": "transition rejected: Block-severity halt ... is FIRED and sticky"}
],
"warnings": [],
"current_state": { }
}
- Exit semantics: 0 = query success OR fire success with warn/log severity; 1 = fire success with block-severity (halt-and-surface signal) OR failure (state file missing / malformed / condition not found / sticky-rejection / transition rejected / evidence missing on
--fire); 2 = invocation error.
Example invocation
tools/halt-check.sh --dispatch-id amnesia-o4fp.1-task-3.5 --condition halt-anchor-resolution-failure --json
tools/halt-check.sh --dispatch-id amnesia-o4fp.1-task-3.5 --condition halt-anchor-resolution-failure --fire --evidence "stale ADR-017 §2.4 anchor at L99" --pretty
Contract with primitive (halt-arm.sh)
- Primitive:
tools/halt-arm.sh
- Flags / inputs:
--contract <path> (required; YAML contract path)
--dispatch-id <id> (required; state file basename)
--state-dir <path> (optional; default .amnesia/halt-state)
--json (machine-readable output; default behavior)
--pretty (pretty-printed JSON via indent=2)
-h / --help (usage)
Success stdout shape
{
"armed": true,
"dispatch_id": "<id>",
"contract_path": "<absolute-path>",
"state_file": "<absolute-path>",
"armed_at": "<ISO-8601 UTC>",
"canonical_halt_count": 7,
"per_dispatch_addition_count": 0,
"halts_armed": ["halt-anchor-resolution-failure", "..."]
}
Failure stdout shape
{
"armed": false,
"dispatch_id": "<id>",
"contract_path": "<absolute-path>",
"violations": [
{
"halt_class": "halt-halt-classes-missing",
"severity": "block",
"detail": "contract has no `halt_classes:` field (or non-list value)"
}
]
}
- Exit semantics: 0 = armed (success); 1 = not armed (contract malformed / halt_classes missing or incomplete / state file write failed); 2 = invocation error.
Example invocation
tools/halt-arm.sh --contract /tmp/my-dispatch-contract.yaml --dispatch-id amnesia-o4fp.1-task-3.5 --json
tools/halt-arm.sh --contract /tmp/my-dispatch-contract.yaml --dispatch-id test-001 --state-dir /tmp/halts --pretty
Halt conditions (halt-arm.sh; tool-local; do NOT propagate to ADR-017 §2.4 canonical 7-class taxonomy)
halt-contract-yaml-malformed (block): contract YAML parse failure or top-level is not a mapping.
halt-halt-classes-missing (block): contract has no halt_classes: field (or non-list value).
halt-canonical-halt-incomplete (block): contract's halt_classes: does not include all 7 ADR-017 §2.4 canonical halts.
halt-state-construction-failure (block): per-halt Pydantic HaltState construction failed (e.g., invalid severity literal).
halt-state-file-write-failure (block): state directory create or write failure (filesystem error).
Halt conditions (halt-check.sh; tool-local; do NOT propagate to ADR-017 §2.4 canonical 7-class taxonomy)
halt-check-state-file-missing (block): state file at <state-dir>/<dispatch-id>.json does not exist (halt-arm.sh not yet invoked for this dispatch).
halt-check-state-file-malformed (block): state file JSON parse failure or halts field missing/non-list.
halt-check-condition-not-found (block): --condition <name> not present in state file's halts list (typo or armed under different name).
halt-check-state-construction-failure (block): per-halt Pydantic HaltState reconstruction from state file failed (state file corruption).
halt-check-sticky-rejection (block): re-fire attempt on a halt already in lifecycle=FIRED, severity=block (ADR-017 §2.4 sticky-block enforcement; explicit operator rearm required — see "Future / Open" below).
halt-check-transition-rejected (block): P2 port HaltState.transition() rejected the requested lifecycle transition for a non-sticky reason (backward transition, unknown lifecycle, FIRED-without-prior-rearm for warn/log severity, etc.).
halt-check-state-file-write-failure (block): state file write failure post-transition (filesystem error).
halt-check-fire-evidence-missing (block): --fire invoked without --evidence "<text>" (evidence is mandatory for FIRED transitions).
Future / Open
Rearm mechanism (CHECKED/FIRED → ARMED) deferred per plan §Phase 3 L554 "explicit operator rearm required via separate command"; specify when sticky-block-after-resolution workflows concretize. Candidate paths:
- Separate
tools/halt-rearm.sh primitive (mirrors halt-arm/halt-check structure)
--rearm flag on tools/halt-check.sh (current tool extension)
- New cli.py subcommand
halt-rearm
Design considerations to settle when the workflow concretizes: which roles can rearm? when (closure attestation? operator override?)? does rearm require evidence (e.g., remediation proof)? does rearm preserve original armed_at or generate a new one?
Per Task 3.3 DO Stage 1 Q5 disposition: plan AC #3 only requires sticky-REJECTION of second-fire, not a rearm path. Task 3.3 implements rejection; rearm is a future task (open for re-prioritization).
Related
- ADR-017 §2.2 (canonical packet shape;
halt_classes: field).
- ADR-017 §2.4 (canonical halt-class taxonomy; lifecycle; sticky-block-severity).
tools/role_schema/ports/halt.py (P2 port: HaltState + CANONICAL_HALT_CLASSES + state-machine transition() method).
tools/role_schema/cli.py:halt_arm (Python implementation; this skill's underlying mechanism; 4th cli.py subcommand beyond plan §Task 1.8's enumerated 3 — legitimate Phase 3 CLI surface extension per Task 3.2 DO Stage 1 Q2 disposition).
tools/dispatch-validate.sh (sibling primitive — pre-launch validation; runs BEFORE halt-arm).
- Future siblings:
tools/halt-check.sh (Task 3.3 — extends this skill), tools/closure-attest.sh (consumes halt-state at closure attestation).