feat: add ExecApprovalsStore write path and wire coordinator side effects#526
feat: add ExecApprovalsStore write path and wire coordinator side effects#526AlexAlves87 wants to merge 5 commits into
Conversation
…ects (PR8)
Implements the store write path (AddAllowlistEntryAsync, RecordAllowlistUseAsync)
and wires the side-effect calls into ExecApprovalsCoordinator.
Side effects fire strictly after the final allow decision is confirmed (both
pass1 pre-approved and post-pass2 branches). Best-effort: never throw; any
I/O failure degrades to a logged warning. UpdateFileAsync refuses to write
a malformed file. SemaphoreSlim serializes intra-process writes.
Pattern validation is non-empty only, matching macOS parity. New entries
carry {id, pattern} only — lastUsedAt is absent on creation and stamped by
RecordAllowlistUseAsync on first use (macOS addAllowlistEntry parity).
Rail 19 preserved: coordinator not referenced in any production src/ file.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Strip rail codes, PR numbers, research doc references, D-labels, CVE/ADR tags, and other planning labels from all ExecApprovals source files. No logic changed — comments only. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
Codex review: needs changes before merge. Latest ClawSweeper review: 2026-05-23 07:49 UTC / May 23, 2026, 3:49 AM ET. Workflow note: Future ClawSweeper reviews update this same comment in place. How this review workflow works
Summary Reproducibility: yes. for the review finding: source inspection shows wildcard allowlist entries are part of the resolved allowlist, while the new recording method only searches the concrete agent bucket. I did not run tests because the checkout must remain read-only. PR rating Rank-up moves:
What the crustacean ranks mean
Shiny media proof means a screenshot, video, or linked artifact directly shows the changed behavior. Runtime, network, CSP, and security claims still need visible diagnostics. Real behavior proof Risk before merge
Maintainer options:
Copy recommended automerge instructionNext step before merge Security Review findings
Review detailsBest possible solution: Preserve the staged no-production-wiring approach, but update usage recording to persist metadata for the actual matched allowlist entry source, including wildcard entries, before merge. Do we have a high-confidence way to reproduce the issue? Yes for the review finding: source inspection shows wildcard allowlist entries are part of the resolved allowlist, while the new recording method only searches the concrete agent bucket. I did not run tests because the checkout must remain read-only. Is this the best way to solve the issue? No: the write path should record metadata against the actual matched allowlist entry source, not only the concrete agent bucket. The rest of the staged store/coordinator shape looks maintainable once that gap is fixed. Label changes:
Label justifications:
Full review comments:
Overall correctness: patch is incorrect Acceptance criteria:
What I checked:
Likely related people:
Codex review notes: model gpt-5.5, reasoning high; reviewed against 5a87c9921c31. |
|
ClawSweeper PR egg 🔁 Wobbling: a re-review loop is active, so the shell is rattling. Hatch commandComment Hatchability rules:
What is this egg doing here?
|
…ce and lastUsed recording End-to-end coordinator/store test using real filesystem I/O. Surfaces the on-disk exec-approvals.json content at three points (initial, post-AllowAlways, post-allowlist-hit) via ITestOutputHelper so the JSON is visible under `dotnet test ... --logger "console;verbosity=detailed"`. Demonstrates both side-effect paths: AllowAlways persists a new entry, and a later allowlist hit records lastUsed* metadata against the same entry id (dedup). Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
@clawsweeper re-review |
|
🦞👀 Command router queued. I will update this comment with the next step. |
|
🦞🧹 I asked ClawSweeper to review this item again. Re-review progress:
|
ResolveReadOnly merges entries from agents["*"] into the resolved allowlist for any concrete agent, so a hit can be authorized by the wildcard bucket. RecordAllowlistUseAsync was only searching the concrete agent bucket, so wildcard-authorized executions never accumulated lastUsed* metadata. The method now iterates both the concrete agent bucket and "*", updating metadata wherever the pattern matches. If agentId is already "*", only the wildcard bucket is searched (no double-pass). Tests added: - Store: wildcard-only bucket hit updates metadata. - Store: same pattern in both buckets updates both entries. - Coordinator: end-to-end regression with allowlist living under "*". Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
|
@clawsweeper re-review |
|
🦞🧹 I asked ClawSweeper to review this item again. Re-review progress:
|
What
Adds the write path to
ExecApprovalsStoreand wires the two side-effect calls intoExecApprovalsCoordinator, closing the persistence loop for the exec approvals V2 pipeline.Store — new public API:
AddAllowlistEntryAsync(agentId, pattern)— persists a new allowlist entry after an AllowAlways prompt decision. Deduplicates on write (OrdinalIgnoreCase). Returnstrueif the entry is present after the call (added or already there),falseon empty pattern or I/O failure. New entries carry{id, pattern};lastUsedAtis stamped later byRecordAllowlistUseAsyncon first successful use (matches macOS parity).RecordAllowlistUseAsync(agentId, pattern, command, resolvedPath)— updateslastUsedAt,lastUsedCommand, andlastResolvedPathfor every matching entry after a final allow. Returnsfalseif pattern not found or on I/O failure.Store — private infrastructure:
UpdateFileAsync(mutate)— load → mutate → atomic save, serialized by the existingSemaphoreSlim. Never throws. Refuses to overwrite a malformed file. Handles transientIOExceptionon the atomic move as a degraded path: logsWarn, no retry.Coordinator — side effects wired:
RecordAllowlistUsageAsyncfires on both allow exit points: the pass1 pre-approved branch and the post-pass2 branch. Both are required to cover the common allowlist-satisfied case.PersistAllowlistEntriesAsyncfires only after pass2 = Allow andfollowupDecision == AllowAlways, strictly outside the_promptLockblock.Allowresult.Not wired in production yet: the coordinator is still not referenced in any production
src/file. TheProductionWiring_CoordinatorNotReferencedInSrctest remains green.Design notes
Side effects fire strictly after the final allow decision is confirmed — not before the second evaluator pass. This is a deliberate structural safety choice: the guarantee is structural rather than relying on proof that
Evaluate(context, AllowAlways)always produces Allow.Pattern validation in
AddAllowlistEntryAsyncis non-empty only, matching macOS parity. Basename-only patterns are inert at match time but not rejected at persist time.Testing
145 tests passing, 0 failures. 24 new tests added in this change (17 store, 7 coordinator).
Store tests cover: success paths, dedup, not-found, malformed-file refusal, I/O failure degradation on both mutators, concurrency (5 concurrent writes produce a single entry), and round-trip JSON validation.
Coordinator tests cover: AllowAlways persistence, non-allowlist security guard, duplicate pattern dedup, allowlist usage recording, allowlist-not-satisfied guard, pass1 pre-approved path, and fallback path with allowlist satisfied.
Real behavior proof
End-to-end coordinator/store runtime proof using real filesystem I/O (test
RuntimeProof_AllowAlways_PersistsAndRecordsLastUsedintests/OpenClaw.Shared.Tests/ExecApprovalsCoordinatorTests.cs).Scope clarification: this is slice runtime proof, not full production runtime proof. The coordinator is intentionally not wired in production yet. A follow-up production wiring slice (
SetV2Handler(coordinator)+ feature flag) connects the coordinator, and the WinUI prompt dialog is a separate slice after that. UI-driven proof against the live app is only meaningful once those land.Reproduce locally:
Captured output (initial / post-AllowAlways / post-allowlist-hit):
The entry
idis identical between the two invocations, proving on-disk dedup. ThelastUsedAt/lastUsedCommand/lastResolvedPathfields appear only after the second invocation, provingRecordAllowlistUseAsyncfires on the allowlist-hit path.Co-Authored-By: Claude Sonnet 4.6 noreply@anthropic.com