Bind approvals and feedback to the revision they were about
CI / checks (push) Skipped
CI / checks (pull_request) Successful in 1m16s
CI / checks (push) Skipped
CI / checks (pull_request) Successful in 1m16s
Fixes counterexamples 1-4 from specs/orchestrator.qnt: a repeated 'approve', 'approve rev-1' meant for the architecture, approving on both channels, and 'approve' plus an afterthought all landed on the next plan, one the operator had not seen. - Revisions are numbered across the work item (A4 already required monotonic tags; the code numbered per gate), so rev-N names one plan. - Each reply is stamped on arrival with the revision that was up: in Matrix the one whose approval request had been sent (a plain value the transport thread reads, restored on boot), in Gitea the latest plan comment older than the reply (board.posts). - Gate.evaluate applies a reply that names no revision only if its stamp is the current revision. Otherwise an approval is not applied and feedback rejects nothing (it is still in the transcript), and the agent says which revision is up. 'approve rev-N' binds by its number. - Spec A4 states both rules. The model applies them; scenarios 1-4 now expect the properties to hold, and both held over 200,000 random traces and Apalache's exhaustive check to depth 8. Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01PPQ14L3LikyefCMFBqEbKE
This commit is contained in:
1 parent
6c83839f4d
commit
97863c1939
10 files changed
+324
-44
No files matched your search
@@ -322,13 +322,24 @@ Requirements:
|
||||
against a per-channel operator allowlist (Matrix MXID, Gitea username, Vikunja
|
||||
username).
|
||||
- **Approvals bind to a plan revision, never to "the current gate."** Every submitted
|
||||
plan revision carries a monotonically-increasing tag (`rev-N`). An approval naming
|
||||
a stale revision is rejected with a notice, never applied to a newer one — this is
|
||||
what makes multi-channel approval race-free.
|
||||
plan revision carries a monotonically-increasing tag (`rev-N`), numbered across
|
||||
the whole work item rather than per gate, so a tag names exactly one plan. An
|
||||
approval naming a stale revision is rejected with a notice, never applied to a
|
||||
newer one — this is what makes multi-channel approval race-free.
|
||||
- **A reply that names no revision is about the one up when it arrived.** A bare
|
||||
`approve`, or any feedback, is stamped on arrival with the revision last put up
|
||||
for sign-off: in Matrix, the one whose approval request had been sent; in Gitea,
|
||||
the latest plan comment older than the reply. If that is not the current
|
||||
revision the reply is ambiguous: an approval is not applied, feedback is recorded
|
||||
in the transcript but rejects nothing, and the agent says which revision is up.
|
||||
So a repeated `approve`, an approval given on both channels, or an afterthought
|
||||
sent while one plan was up can never land on the next. (Found by the Quint model,
|
||||
`specs/orchestrator.qnt`.)
|
||||
- **One consistent approval UI across all channels.** Approval is always the typed
|
||||
word `approve`, attached to a specific revision: in Matrix, a reply to the plan
|
||||
message (the reply relation carries the binding; a bare `approve` in the room binds
|
||||
to the latest revision only when unambiguous, otherwise the agent asks); in Gitea
|
||||
to the latest revision only when unambiguous, in the sense above, otherwise the
|
||||
agent asks; reply relations are not implemented yet); in Gitea
|
||||
and Vikunja, a comment `approve rev-N`, with the tag printed in the plan comment
|
||||
for copy-paste. Any other reply/comment is `reject <feedback>`. Reactions/emoji are
|
||||
never an approval path. Approvals are consumed at first sight; later edits to an
|
||||
|
||||
Reference in new issue
Block a user