Repository navigation
feat(mission-control): an opt-in decision revision on the settling verbs - #2448
Merged
Merged
Conversation
Tests only (#2446). A client reads an ask's decisionRevision from the single-row queue read, and an answer, resolve, approve or deny sent with it acts only on that decision: - an unchanged revision acts and returns the saved revision and row; - a stale one (changed question text under the same ids, another person's partial answer, a re-park) conflicts with nothing written; - of two concurrent answers on one revision, one commits and one conflicts; - a retry of the same committed operation is reconciled from its record, and another verb or payload under the same operation id is not; - a revision on a verb that doesn't settle a decision is refused; - a summary, a wake or a claim leaves the revision alone; - the route passes the revision to the write contract, and a registered-host forward carries it to the origin. A caller that sends no revision keeps today's behaviour and result fields.
|
Caution Review failedThe pull request is closed.
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. CodeRabbit Verify
|
Refs #2446. GET /api/queue/:qitemId carries decisionRevision, an opaque hash of what a person decides on: the body, intent and questions, and the decision's state (open, blocked or terminal; who it's addressed to or parked on; its answers, a decision made on their behalf, the current park and the replies returned). A summary, a wake or a claim doesn't change it. POST /api/mission-control/action takes an optional expectedRevision on answer, resolve, approve and deny. The compare runs at the start of the act, before any branch that writes, with no await before the first write. A stale revision is 409 decision_revision_changed with nothing written, and the current revision, state and latest action. A retry of the caller's own committed operation (the same operationId in auditNotes, verb, sender and payload) is answered from its record. On other verbs it's 400 decision_revision_unsupported. Without it, every act and result is as before. A registered-host forward carries it to the origin.
added 4 commits
November 3, 2026 15:06
… gate, and an unchecked revision Tests only, #2448's delta: - two concurrent typed answers to a runner's permission ask on one revision: one closes it and the other conflicts, with one audit row; - a typed-gate park resolved and parked again on the same gate conflicts with the old revision, while a note on the park leaves the revision unchanged; - a revision sent with hold isn't checked: the hold runs, and the result says so (replacing the earlier refusal).
… new park is a new revision, and an unchecked revision is noted #2448's delta (#2446): - A typed answer to a runner's permission ask writes nothing until its close, after an await, so a guarded answer compares its revision again just before that close, with no await in between. - The revision includes the transition that entered the current park, so a typed-gate park resolved and parked again on the same gate is a new revision. A note on a parked row doesn't change it. - A revision sent with a verb that doesn't settle a decision is no longer refused: the act runs as today, and the result's revisionNote says it wasn't checked.
…ts when a deny lands during its return Tests only, #2448 round 3. A runner's permission ask still pending with its answers already recorded (an interrupted return): a guarded choice writes nothing before its return, a deny lands during it, and the choice must conflict with only the deny recorded. A control pins that a choice which records its answer still closes the ask.
… the call wrote nothing first #2448 round 3. On a runner's permission ask (no reply row), the re-compare before the close now keys on whether this call changed the ask before its await, not on choice versus typed. A choice whose answer set was already complete (an interrupted return) writes nothing, so it compares again like typed words. A choice that recorded its answer was compared before that write and keeps its behaviour, so the nothing-was-written conflict stays true.
mvschwarz
marked this pull request as ready for review
November 3, 2026 17:37
openrig-review
approved these changes
Nov 3, 2026
openrig-review
left a comment
Collaborator
There was a problem hiding this comment.
Approved at aebb57c. An opt-in decision revision on the four settling verbs: GET /api/queue/:qitemId returns decisionRevision, and answer, resolve, approve and deny take an optional expectedRevision. A stale one answers 409 decision_revision_changed with nothing written; a caller that sends none acts exactly as before. The compare runs before the act's first write, with no await between, and again before a runner ask's close when the call wrote nothing first. Required CI passes at this head.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What a person gets
A client, such as an outside GUI, can settle exactly the decision its person reviewed. It reads the decision's revision, and sends that revision with the answer. If the question or the decision changed in between, nothing is written: the client hears that it changed and gets the current state.
It's opt-in. A caller that sends no revision acts exactly as before, with the same result fields. The CLI, TUI and Slack don't send one.
What changed
GET /api/queue/:qitemIdgainsdecisionRevision. It's an opaque token, computed on read and never stored, covering what a person decides on:POST /api/mission-control/actiontakes an optionalexpectedRevisionforanswer,resolve,approveanddeny:decisionRevision, the revision as saved.decision_revision_changed, and nothing is written. That means no answer, closure, reply, transition, audit row or wake. The response carriescurrentRevision, the row's state and answers, and its most recent action.revisionNotesays the revision wasn't checked.operationIdin the existingauditNotes.reconciled: trueand the originalactionId. Nothing is written again.expectedRevision, which is compared there. Forwarding still strips authority and the claimed actor, as before.Tests
The red commit adds only
decision-revision.test.ts. It runs on real SQLite with all migrations, the real queue, the Mission Control route and its write contract, and the single-row read.answerand forapprove.holdisn't checked: the hold runs, and the result says the revision wasn't checked.Not covered
Cost
Source +90 across four files, comments included. Tests +239.
Refs #2446
🤖 Generated with Claude Code