Multi-Step Proposal Approval Verification & Application #27
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Summary
Implement the proposal approval verification and execution engine (
POST /v1/proposals/{id}/apply,POST /v1/proposals/{id}/reject, and CLIlife proposal apply), enforcing cryptographic approval digest verification, preflight live ETag checks, deterministic sequential multi-step execution across CalDAV resources, partial failure isolation without blind rollbacks (exit code 6 /needs_reconciliation), and strict non-replayability of partially applied proposals under new operation keys.Background & Technical Rationale
CalDAV and WebDAV architectures lack distributed transaction protocols: there is no two-phase commit (2PC) or native atomic batch mutation across multiple calendar resources. When a scheduling proposal comprises multiple steps (such as allocating three work blocks and updating two task deadlines), applying these steps presents severe consistency risks:
Life solves these challenges through a hardened multi-step execution protocol:
confirmed: true, the exactproposal_digest(SHA-256), and an ISO-8601reviewed_attimestamp. Mismatches immediately abort withapproval_required(HTTP 403, Exit 3) before any remote operations are initiated.parent_op:step_id) and its result is durably journaled to SQLite before the subsequent step is dispatched.failed, subsequent steps remainpending, the parent operation transitions toneeds_reconciliation, and the proposal transitions topartial. The CLI exits with code 6 (status: "partial"). No blind rollback is attempted. Repair requires generating and reviewing a fresh proposal.proposal_not_applicable(HTTP 409 / Exit 4).Exact Technical References & Specifications
1. CLI & API Approval & Execution Contract (
docs/technical/cli-api.md)Section:
## Idempotency and approval(lines 65–68):Section:
## Command-to-route table(lines 49–51):Section:
## Complete fictional examples(lines 163–174):Section:
## Error identifiers(lines 111–125):2. Timeline Consistency & Multi-Item Proposals (
docs/technical/timeline-consistency.md)Section:
## Multi-item proposals(lines 148–151):Section:
## Operation state machine(lines 121–128):Section:
## Database tables for operations and proposals(lines 15–75):3. Verification Scenarios R09, R14, R15 (
docs/technical/verification.md)Section:
## Recovery scenarios(lines 95, 100–101):What to build
1. Proposal Validator & Idempotency Check (
src/domain/proposal/validator.rs)operationstable byIdempotency-Key/operation_id:docs/technical/cli-api.md:63, 67).idempotency_conflict(Exit 4).proposal_id.404 Not Foundwith codenot_found.expiredornow() > expires_at, return400 Bad Requestwith codeproposal_expired(Exit 2).applied, return400 Bad Requestwith codeproposal_not_applicable.rejected, return400 Bad Requestwith codeproposal_not_applicable.partial:proposal_not_applicable(Scenario R15). Any repair requires a newly reviewed proposal (docs/technical/cli-api.md:67). Same-key replay reports the existing operation status without re-executing steps.approvalis missing orconfirmed != true: return403 Forbiddenwith codeapproval_required(Exit 3) (Scenario R14).proposal_digest != proposal.digest: return403 Forbiddenwith codeapproval_required(Exit 3) (Scenario R14).reviewed_atis missing or invalid: return400 Bad Requestwith codeinvalid_input.2. Live Preflight ETag Verifier (
src/domain/proposal/preflight.rs)steps_jsoninto ordered step structs.action == "create_event":etag_conflictbefore executing any writes.action == "update_event",action == "cancel_event", oraction == "update_task":item_id.ETag.ETagtostep.baseline_etag.etag_conflict(HTTP 412, Exit 4).3. Step-by-Step Proposal Executor (
src/domain/proposal/executor.rs)operationstable withstate: 'prepared'.proposals SET operation_id = ?, approval_json = ? WHERE proposal_id = ?.i:(parent_operation_id, step_id). If alreadyapplied, preserve existing result and proceed to stepi+1.state: 'prepared'.If-Match: baseline_etagorIf-None-Match: *).state: 'applied', record result inoperations.life_eventswithevidence_class: "confirmed_service".state: 'failed'(or'unknown'if network dropped mid-transfer).i+1 \dots Naspending.operationstostate: 'needs_reconciliation'.proposalstostate: 'partial'.state: 'applied'.state: 'applied'.status: "ok"and list of applied steps with new ETags.4. Proposal Rejection Handler (
src/api/proposal_reject.rs)POST /v1/proposals/{id}/reject:state: 'pending'.state = 'rejected', store optional rejection reason inproposals.5. CLI Presentation & Exit Code Mapping (
src/cli/proposal.rs)life proposal apply <ID> --input - [--operation-id KEY] [--json]:approval_required).etag_conflict).status: "partial",partial_apply).proposal_not_applicable).Acceptance Criteria
life proposal applywithout matchingproposal_digestfails withapproval_required(HTTP 403, Exit 3) with zero remote writes (Scenario R14).proposal_expired(Exit 2).state: "partial", parent operationneeds_reconciliation, and exits with code 6 without attempting rollback (verifying Scenario R09).proposal_not_applicable(verifying Scenario R15).docs/technical/cli-api.md:63, 67).life proposal rejectmarks the proposal rejected in SQLite and performs zero writes to Radicale.Notes
If-Matchis asserted equal to the reviewed baseline.[applied, failed, pending, ...]with the proposalpartial, parentneeds_reconciliation, zeroDELETEs, no rollback and no retry.pendingproposal past its window reports as expired.Blocked by
Issue #027: Multi-Step Proposal Approval Verification & Applicationto Multi-Step Proposal Approval Verification & Application