Skip to content

feat: derive approval reasons at read time and add opt-in auto-approve rules - #32

Merged
lr00rl merged 1 commit into
mainfrom
feat/principal-ux-approvals
Aug 10, 2026
Merged

feat: derive approval reasons at read time and add opt-in auto-approve rules#32
lr00rl merged 1 commit into
mainfrom
feat/principal-ux-approvals

Conversation

@lr00rl

@lr00rl lr00rl commented Aug 10, 2026

Copy link
Copy Markdown
Contributor

What

From the 2026-08-10 review: 45 pending production approvals, every reason empty, and trusted system writers force a human click for additive fleet-wide plans.

  1. Reasons, derived at read timeapprovalDisplayReason renders a human sentence per plugin/action from the plan ("Node agent upgrade 0.3.0 -> 0.3.3 (edge-1)", "Line identity metadata sync (26 inbounds)", nft/dns/netpolicy/netguard/group-policy shapes covered, title-case fallback). Wired in toApprovalViews only — stored rows are never rewritten; existing approvals gain reasons with zero migration. A stored non-empty reason always wins.
  2. Opt-in auto-approve policyLATTICE_APPROVAL_AUTO_RULES / -approval-auto-rules JSON rules ({name, writer, plugin, action_prefix, queue, daily_cap}). Empty/absent (the default) is a pure pass-through — zero behavior change. All nine creation sites now funnel through submitApproval, which evaluates the first matching rule against fresh pending submissions only. The auto path calls the factored approveApprovalCore with the sha256 of the just-persisted plan, so the TOCTOU hash binding is byte-identical to the manual endpoint; the policy actor (policy:<name>) is stamped only after a successful decision, so failures never poison the daily cap. Audits: approval.auto_approve / approval.auto_skip (daily_cap), both attributable. Malformed config warns and starts with zero rules — startup never fails on it.

Verification

  • go build ./... ; go vet ./internal/server/ ./cmd/... ; gofmt clean
  • go test ./internal/server/ -run 'Approval|Policy|Reason' → ok
  • Full suite go test ./internal/server/ -count=1 → ok 150s, no regressions
  • 21-case reason table incl. malformed plans; policy tests cover default-off, first-match, cap (yesterday doesn't count), writer trust boundary, kill-switch failure path

Notes

…e rules

Pending approvals created without a stored reason now render a
human-readable sentence derived from the reviewed plan (agent upgrades,
linemeta metadata syncs, nft/netpolicy/selfdns/proxycore/tunnel/wireguard
applies). Stored rows are never migrated; derivation happens in the view
mapping so every reader sees a consistent reason.

Auto-approve is an operator opt-in via LATTICE_APPROVAL_AUTO_RULES
(default off = fully manual). Fresh pending submissions flow through the
new submitApproval chokepoint; the first matching rule approves (and
optionally queues) through the exact same plan-hash-bound decision path
as the manual endpoint, crediting policy:<rule> as the actor, with a
per-rule daily cap and approval.auto_approve / approval.auto_skip audit
events.
@lr00rl
lr00rl marked this pull request as ready for review August 10, 2026 12:46
@lr00rl
lr00rl merged commit d137dbb into main Aug 10, 2026
1 check passed
@lr00rl
lr00rl deleted the feat/principal-ux-approvals branch August 10, 2026 12:47
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