You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
decisions: read operations stay "proposed" forever, and abandoned proposals are never superseded #26
Every operations propose writes a decision record, which is right for an audit trail. Two things make the trail noisy:
A risk: read operation has already run when it is recorded, but its status stays proposed forever
A mutating proposal the agent abandons, or proposes again, stays proposed with nothing marking it superseded or stale
One disk-full incident handled by an agent on a single host produced 47 decision records for cloudfall why --host hz1, most of them read runs and repeated proposals of the same operation.
Reproduce
$ cloudfall operations propose disk-report --target hz1 # read
$ cloudfall operations propose shop-logrotate --target hz1 # mutating
$ cloudfall operations propose shop-logrotate --target hz1 # again
$ cloudfall decisions list
# three records, all "proposed"; the read one ran, the first logrotate one is superseded
Expected
read operations get a terminal status (for example ran) with their exit code
a newer proposal of the same operation and targets marks the older open one superseded
open proposals older than their evidence basis can be marked stale, and decisions list / why can filter on status
The staleness check is already noted as a future improvement in the M8/M9 proving runs.
decision.propose (sdk/src/cloudfall/decision.py) saves every record as proposed, read operations included. For a risk: read operation the gate is runs-freely and operations_propose already returns the check-mode output as the run's result, so the record says proposed for something that has finished
Nothing links a new proposal to an older open one of the same operation and targets, so repeats pile up as proposed
decisions list has no filter; it returns every record
Design. This changes stored state and a published schema, so it goes through the design gate. status is an enum in config/schemas/v1/operation-decision.schema.json (proposed, approved, rejected, executed, verified, failed). No decisions log exists yet, so nothing settled is departed from. The plan:
Read operations: propose records them with a terminal status and the check run's exit code. Records already on disk stay proposed
Supersede: when propose records an operation, any open (proposed) record with the same operation id, targets and inputs gets superseded, with superseded_by naming the new decision id. Approving a superseded record is refused
decisions list --status <s> and cloudfall why --status <s> (repeatable) filter on status
Compatibility: the new enum values are additive in the v1 schema, but a record written by the new version fails to load in an older cloudfall (DecisionStatus(...) is strict). That only matters on a downgrade
Postponed:stale needs a rule for when the evidence basis is "older" (the observation timestamp, the repository commit, or both) and a trigger (on read, or a sweep). That is a feature for a spec, not this fix; the issue stays open for it after the first PR.
@romamo Decision needed: how should a decision record show a read run and a replaced proposal?
New status values in the v1 schema (recommended): a read run is recorded as ran with its exit code, and an older open proposal of the same operation, targets and inputs becomes superseded with superseded_by; decisions list and why get --status. Additive, but a newer record fails to load in an older cloudfall
Same as 1, but reuse the existing values: a read run is executed (or failed on a non-zero exit) and a replaced proposal is rejected with a rejected_reason: superseded. No schema enum change, but rejected then means two things in the record
Don't record read runs as decisions at all: they go to the observation log instead, and only supersede is added. The fewest records, but cloudfall why loses the reads an agent ran before proposing a fix
Either way stale is postponed to a spec. Reply here with a number or your own answer; shipmill takes this up on the tick after your reply.
1 — new status values in the v1 schema: ran with its exit code for a read run, superseded with superseded_by for a replaced proposal, and --status on decisions list and why
(decision by @romamo, given in chat; posted by Claude on his behalf)
Triage: implement, taking up the answer above (option 1), recorded as D-1 in #51.
Plan, unchanged from the triage comment: propose records a risk: read operation as ran with the check run's exit code; a new proposal of the same operation, targets and inputs marks the older proposed record superseded with superseded_by, and approving a superseded record is refused; decisions list --status and cloudfall why --status filter on status. Records already on disk stay as they are. stale stays postponed, so this issue stays open after the PR (it says "Part of #26").
#36 also changes operation-decision.schema.json; the same implementer builds both on separate branches, and whichever merges second rebases on the other.
PR: #53 (Part of #26). A read run is recorded as ran with ranExitCode; a repeat of the same operation, targets and inputs marks the older open record superseded with supersededBy (camelCase like every other key in the record; D-1's superseded_by is the Python attribute); decisions approve refuses both; decisions list --status and why --status filter. Trade-offs: operations propose now reads the existing records first, so a broken record stops it with RECORD_INVALID; an old read record still saying proposed can still be approved. stale stays open here.
Decision by @romamo, given in chat; posted by Claude on his behalf.
1. New status values in the v1 schema: a read run is recorded as ran with its exit code, and an older open proposal of the same operation, targets and inputs becomes superseded with superseded_by; decisions list and why get --status. stale is postponed to a spec.
Triage: #53 merged parts 1 and 2 and the --status filter. The rest, stale, is postponed: it needs a rule for when the evidence basis is older (observation timestamp, repository commit, or both) and a trigger (on read or a sweep), which is a spec, not a fix. Labelled postponed; it comes back for a decision after the next stable release.
Triage: feature, back after the 0.6.0 stable release (REVISIT). You filed this, so the stale part goes through the spec gate rather than staying postponed.
Spec: #71 (docs/specs/001-stale-proposals.md). In short: a proposed decision is stale when a host its basis names has a newer observation; it's worked out on read and never stored (no schema change); decisions list, show and why report stale and staleHosts; --stale filters; decisions approve names the stale hosts in its prompt but doesn't refuse. A commit-based rule and a stored status are out of scope, with reasons in the spec.
On hold: waits on #71. Implementers start once the spec merges; merging it is the approval.
Update: the spec merged in #71 with status: approved, which is the approval. Its one build issue is #97 (all of S-001-1 to S-001-9), linked as a sub-issue; its PR sets the spec to built and closes this issue too.
Fixed by #105, merged with every CI job green: the stale part, built as spec S-001 (now status: built). Parts 1 and 2 and --status landed earlier in #53. Every sub-issue is closed:
Released in v0.8.0rc1 (pre-release). uv tool install --prerelease allow cloudfall==0.8.0rc1
A plain install gets it with the next stable release.
If you filed this: react 👍 on this comment once it works for you, or reply if it doesn't.
Activity
shipmill-romamo commentedon Oct 8, 2026
Triage: implement parts 1 and 2 and the status filter; postpone part 3 (
stale). The implement part waits on your decision below.Confirmed on
main(38fb7c1):decision.propose(sdk/src/cloudfall/decision.py) saves every record asproposed, read operations included. For arisk: readoperation the gate isruns-freelyandoperations_proposealready returns the check-mode output as the run's result, so the record saysproposedfor something that has finishedproposeddecisions listhas no filter; it returns every recordDesign. This changes stored state and a published schema, so it goes through the design gate.
statusis an enum inconfig/schemas/v1/operation-decision.schema.json(proposed,approved,rejected,executed,verified,failed). No decisions log exists yet, so nothing settled is departed from. The plan:proposerecords them with a terminal status and the check run's exit code. Records already on disk stayproposedproposerecords an operation, any open (proposed) record with the same operation id, targets and inputs getssuperseded, withsuperseded_bynaming the new decision id. Approving a superseded record is refuseddecisions list --status <s>andcloudfall why --status <s>(repeatable) filter on statuscloudfall(DecisionStatus(...)is strict). That only matters on a downgradePostponed:
staleneeds a rule for when the evidence basis is "older" (the observation timestamp, the repository commit, or both) and a trigger (on read, or a sweep). That is a feature for a spec, not this fix; the issue stays open for it after the first PR.shipmill-romamo commentedon Oct 8, 2026
@romamo Decision needed: how should a decision record show a read run and a replaced proposal?
ranwith its exit code, and an older open proposal of the same operation, targets and inputs becomessupersededwithsuperseded_by;decisions listandwhyget--status. Additive, but a newer record fails to load in an oldercloudfallexecuted(orfailedon a non-zero exit) and a replaced proposal isrejectedwith arejected_reason: superseded. No schema enum change, butrejectedthen means two things in the recordcloudfall whyloses the reads an agent ran before proposing a fixEither way
staleis postponed to a spec. Reply here with a number or your own answer; shipmill takes this up on the tick after your reply.romamo commentedon Oct 9, 2026
1 — new status values in the v1 schema:
ranwith its exit code for a read run,supersededwithsuperseded_byfor a replaced proposal, and--statusondecisions listandwhy(decision by @romamo, given in chat; posted by Claude on his behalf)
shipmill-romamo commentedon Oct 9, 2026
Triage: implement, taking up the answer above (option 1), recorded as D-1 in #51.
Plan, unchanged from the triage comment:
proposerecords arisk: readoperation asranwith the check run's exit code; a new proposal of the same operation, targets and inputs marks the olderproposedrecordsupersededwithsuperseded_by, and approving a superseded record is refused;decisions list --statusandcloudfall why --statusfilter on status. Records already on disk stay as they are.stalestays postponed, so this issue stays open after the PR (it says "Part of #26").#36 also changes
operation-decision.schema.json; the same implementer builds both on separate branches, and whichever merges second rebases on the other.shipmill-romamo commentedon Oct 9, 2026
PR: #53 (Part of #26). A read run is recorded as
ranwithranExitCode; a repeat of the same operation, targets and inputs marks the older open recordsupersededwithsupersededBy(camelCase like every other key in the record; D-1'ssuperseded_byis the Python attribute);decisions approverefuses both;decisions list --statusandwhy --statusfilter. Trade-offs:operations proposenow reads the existing records first, so a broken record stops it withRECORD_INVALID; an old read record still sayingproposedcan still be approved.stalestays open here.romamo commentedon Oct 9, 2026
Decision by @romamo, given in chat; posted by Claude on his behalf.
1. New status values in the v1 schema: a read run is recorded as
ranwith its exit code, and an older open proposal of the same operation, targets and inputs becomessupersededwithsuperseded_by;decisions listandwhyget--status.staleis postponed to a spec.Do not tell an operator to approve a read that already ran
shipmill-romamo commentedon Oct 9, 2026
Triage: #53 merged parts 1 and 2 and the
--statusfilter. The rest,stale, is postponed: it needs a rule for when the evidence basis is older (observation timestamp, repository commit, or both) and a trigger (on read or a sweep), which is a spec, not a fix. Labelledpostponed; it comes back for a decision after the next stable release.shipmill-romamo commentedon Oct 10, 2026
Triage: feature, back after the 0.6.0 stable release (REVISIT). You filed this, so the
stalepart goes through the spec gate rather than staying postponed.Spec: #71 (
docs/specs/001-stale-proposals.md). In short: aproposeddecision is stale when a host its basis names has a newer observation; it's worked out on read and never stored (no schema change);decisions list,showandwhyreportstaleandstaleHosts;--stalefilters;decisions approvenames the stale hosts in its prompt but doesn't refuse. A commit-based rule and a stored status are out of scope, with reasons in the spec.On hold: waits on #71. Implementers start once the spec merges; merging it is the approval.
Spec stale proposals for #26 (#71)
shipmill-romamo commentedon Oct 20, 2026
Update: the spec merged in #71 with
status: approved, which is the approval. Its one build issue is #97 (all of S-001-1 to S-001-9), linked as a sub-issue; its PR sets the spec tobuiltand closes this issue too.shipmill-romamo commentedon Oct 20, 2026
Fixed by #105, merged with every CI job green: the
stalepart, built as spec S-001 (nowstatus: built). Parts 1 and 2 and--statuslanded earlier in #53. Every sub-issue is closed:Closing this as done.
shipmill-romamo commentedon Oct 20, 2026
Released in v0.8.0rc1 (pre-release).
uv tool install --prerelease allow cloudfall==0.8.0rc1A plain install gets it with the next stable release.
If you filed this: react 👍 on this comment once it works for you, or reply if it doesn't.