Skip to content

feat: commit-confirmed apply (optional apply-confirm integration)#11

Merged
raspbeguy merged 1 commit into
mainfrom
feat/commit-confirmed-apply
Jun 22, 2026
Merged

feat: commit-confirmed apply (optional apply-confirm integration)#11
raspbeguy merged 1 commit into
mainfrom
feat/commit-confirmed-apply

Conversation

@raspbeguy

Copy link
Copy Markdown
Member

Summary

Adds safe-apply / commit-confirmed rollback to uapi. A config write can arm a deadline via X-Uapi-Confirm: <seconds> (or ?confirm= fallback): uapi snapshots the affected uci packages, commits, and returns 202 + a token; unless the client acks via POST /confirm/<token> before the deadline, the pre-change snapshot is restored automatically — surviving reboot, process kill, and a dead management path. This protects against the case ordinary atomic writes can't: a change that reloads cleanly yet severs the only path to the box.

The rollback timer and durable state live in a separate package, apply-confirm (uapi invokes its CLI, runs no daemon of its own — preserving the zero-bloat / no-aux-process principle). The integration is optional and feature-detected: absent the binary, a confirmed write returns 501 confirm_unavailable and ordinary writes are byte-identical to before.

Design decisions (from the planning session): client-driven ack (uapi never auto-acks — auto-acking on local reload success would defeat the lockout case); optional dependency (no hard DEPENDS); uapi passes its own resource.reload set to stage --service so a rollback reloads exactly what the apply did; confirm vocabulary.

Wire surface (additive)

  • X-Uapi-Confirm: <1..3600> header (or ?confirm=) on any config write (resource POST/PUT/PATCH/DELETE, singleton PATCH, /batch). Arming needs no extra scope.
  • Success → 202 Accepted + confirm body block + X-Confirm-Token / X-Confirm-Deadline headers.
  • GET /confirm, GET|POST|DELETE /confirm/<token> → list / status / ack / rollback.
  • New scope uapi:confirm (:ro for status+list, :rw for ack+rollback).
  • New error codes: confirm_unavailable (501), already_armed (409), confirm_window_closed (409), confirm_stage_failed (503), rollback_reload_failed (500).

Implementation

  • src/lib/apply_confirm.uc (new): CLI wrapper; every interpolated value (package/service names, timeout, token) validated against an anchored regex before it reaches fs.popen (the injection control).
  • src/lib/transaction.uc: stage between validate-success and commit (apply-confirm snapshots the pre-change on-disk config); revert on stage failure; disarm the window if commit/reload throws after arming (so a stale rollback can't fire); _attach_confirm carries the token on success and disarms on in-band revert.
  • src/lib/handler.uc: translate_tx emits 202 + token; confirmed DELETE routes through translate_tx (not the bare-204 short-circuit) so the token isn't dropped.
  • src/main.uc: parse ctx.confirm; /confirm routing; thread confirm into /batch.
  • build/gen_openapi.uc: header param, ConfirmWindow schema, PendingConfirm 202 response on config writes, /confirm/* paths (all marked x-uapi-requires: apply-confirm).

Sequencing

The wire surface lands now, but the 2.3.0 tag is held until apply-confirm reaches a stable, feed-published release (it soaks RC-first as a safety primitive). No DEPENDS; installation.md documents apk add apply-confirm as the opt-in.

Tests / verification

  • make lint + make test (775 pass, +19) + make coverage (81.9% direct, 100% module) + make openapi-check all green.
  • Unit: apply_confirm guard logic (token/name/timeout validation, presence→501), confirm threading→501-degrade, translate_tx 202 shaping (incl. null-body delete).
  • Integration (45_apply_confirm_test.sh): graceful-degrade smoke (runs in current CI, apply-confirm absent) + full stage→ack and stage→timeout-rollback happy path (runs once apply-confirm is installable; same install-guard pattern as 40_unbound).
  • Adversarial review across 8 angles found and fixed two data-integrity bugs (confirmed-DELETE token drop; orphaned armed window on commit-throw); both only fire when apply-confirm is installed.

Companion

apply-confirm's docs/cli-contract.md is corrected separately (the "uapi 3.x" reference → 2.3.0, and the stale "uapi acks after its own reload" line → client-driven ack).

@raspbeguy raspbeguy force-pushed the feat/commit-confirmed-apply branch 3 times, most recently from 111a7aa to ad1a0f0 Compare June 22, 2026 07:29
@raspbeguy raspbeguy force-pushed the feat/commit-confirmed-apply branch from ad1a0f0 to d6471a0 Compare June 22, 2026 08:16
@raspbeguy raspbeguy merged commit a85a5cd into main Jun 22, 2026
5 checks passed
@raspbeguy raspbeguy deleted the feat/commit-confirmed-apply branch June 22, 2026 08:28
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.

1 participant