Skip to content

Answer RECORD_INVALID for an invalid decision record - #43

Merged
shipmill-romamo[bot] merged 1 commit into
mainfrom
fix/41-invalid-decision-record
Oct 8, 2026
Merged

shipmill-romamo[bot] merged 1 commit into
mainfrom
fix/41-invalid-decision-record

Conversation

@shipmill-romamo

Copy link
Copy Markdown
Contributor

What changed

DecisionStore.load (sdk/src/cloudfall/decision.py) now turns a broken record into a DecisionError with the new code decision_record_invalid (_ERROR_RECORD_INVALID):

  • A file that is not JSON (json.JSONDecodeError, or UnicodeDecodeError for bytes that are not UTF-8): the message names the file and the parser's error
  • A document that fails the decision schema (jsonschema ValidationError): the message names the file, the failing JSON path (e.g. spec.approval.via, (root) for the top level) and the validator's message

Only those exception types are caught, each chained with from error.

Behaviour changes

  • decisions show, decisions list, decisions approve and why answer RECORD_INVALID (exit 86) with context.code = decision_record_invalid for an invalid record, instead of crashing with exit 1 and no envelope. All four already declared RECORD_INVALID and mapped DecisionError to it, so no exit-code or command change was needed
  • decisions approve loads the record before asking the person to type the id, so a broken record is refused before any prompt or run
  • decisions list and why stay fail-fast: the first bad record answers RECORD_INVALID naming that file; nothing is skipped
  • Other callers checked: the MCP surface exposes decisions list/show through the same handlers, so it gets the same typed error. The operator commands and agent paths use ProposalStore, not DecisionStore, and are unaffected. propose/approve in decision.py only write (save/update), never load

Tests

sdk/tests/test_decision.py:

  • test_an_invalid_record_answers_record_invalid_naming_the_file: a non-JSON file, a bad spec.status and a bad spec.approval.via, each through show, list, approve and why (12 cases); asserts exit 86, RECORD_INVALID, decision_record_invalid, and that the message names the file and the failing field
  • test_a_store_refuses_an_invalid_record_with_a_decision_error: DecisionStore.list() raises DecisionError for a bad record

Checks run locally: uv sync, uv run ruff check ., uv run mypy, uv run pytest (416 passed), uv run cloudfall config validate --project config/examples. ruff format --check is not a CI step (51 files already differ on main), so it was not applied.

CHANGELOG: one line under [Unreleased] → Fixed.

Fixes #41

Decisions for you

None. The plan on the issue was followed as published. One small addition: a record that is not valid UTF-8 is treated like a non-JSON file (same code), since read_text would otherwise crash the same way.

🤖 Generated with Claude Code

DecisionStore.load let JSONDecodeError and the schema library's
ValidationError escape, so decisions show/list/approve and why crashed
with exit 1 and no envelope. Both now raise DecisionError with code
decision_record_invalid, naming the file and the failing field.

Fixes #41

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@shipmill-romamo
shipmill-romamo Bot merged commit 54c62de into main Oct 8, 2026
1 check passed
@shipmill-romamo
shipmill-romamo Bot deleted the fix/41-invalid-decision-record branch October 8, 2026 21:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

An invalid decision record crashes decisions show/approve/list with exit 1 instead of a typed error

0 participants