Skip to content

Report stale proposals on read (S-001) - #105

Merged
shipmill-romamo[bot] merged 1 commit into
mainfrom
feat/97-stale-proposals
Oct 20, 2026
Merged

shipmill-romamo[bot] merged 1 commit into
mainfrom
feat/97-stale-proposals

Conversation

@shipmill-romamo

Copy link
Copy Markdown
Contributor

Summary

Builds spec S-001 (docs/specs/001-stale-proposals.md): an open proposal is stale when a host named in its basis.observedAt has a later spec.observedAt in its observation file in the folder basis.observations names. Worked out on read, never stored; no status value, no schema change.

  • ObservationClock and Staleness in sdk/src/cloudfall/decision.py: a relative basis folder resolves against the repository (as propose writes it), each folder is read once per command, and times compare as datetimes (a naive time is UTC)
  • decisions list, decisions show and cloudfall why add stale (and staleHosts when true) beside apiVersion/spec in each proposed decision document; every other status carries neither key. In why the keys sit in answers[].decision, the same place as in decisions list
  • --stale on decisions list and why keeps only the stale proposals and combines with --status; the MCP tools generated from both get the flag
  • decisions approve on a stale proposal prefixes its attestation prompt with each stale host, its newer observedAt and the time the proposal saw, then runs as before
  • An observation file in the basis folder that isn't valid JSON (or a basis "folder" that is a file, or an unparseable time) stops the command with RECORD_INVALID, context code decision_observation_invalid, naming the file
  • Spec status set to built, with its Issues and Verification sections filled

Test plan

  • New sdk/tests/test_stale.py, one or more tests per criterion S-001-1 to S-001-9 (specs.py coverage --spec 001 lists them all), including a byte and mtime compare of the record (S-001-7) and a real ansible-playbook approve at a simulated terminal (S-001-8)
  • Ran by hand on a tmp project: decisions list (plus --stale, --stale --status proposed), decisions show, why, why --stale, a git hash-object before/after compare, and a broken observation file giving exit 86 RECORD_INVALID
  • uv run ruff check ., uv run mypy, uv run pytest (538 passed), uv run cloudfall config validate --project config/examples, specs.py check, specs.py coverage --spec 001: all green

Fixes #97
Fixes #26

🤖 Generated with Claude Code

An open proposal is stale when a host its basis names has a later
observedAt in the basis folder than the basis records. decisions list,
decisions show and why add stale and staleHosts to proposed decisions
only; --stale filters list and why; approve names the stale hosts and
their newer times in its prompt and still runs. Nothing is stored: the
record and its schema stay as they were. An observation file that is
not valid JSON stops the command with RECORD_INVALID.

Builds spec S-001.

Fixes #97
Fixes #26

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@shipmill-romamo
shipmill-romamo Bot merged commit 3473464 into main Oct 20, 2026
1 check passed
@shipmill-romamo
shipmill-romamo Bot deleted the feat/97-stale-proposals branch October 20, 2026 09:58
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.

Stale proposals (S-001-1 to S-001-9) decisions: read operations stay "proposed" forever, and abandoned proposals are never superseded

0 participants