Phase 3: DRY — shared integration engine, accessor resolver, gate registry - #68
Merged
Conversation
…llisions - Saddle_Approval::gate now logs a confirmed destructive execution even when the executor returns WP_Error — a partial mutation with no audit trail is worse than a noisy one. The entry carries a "FAILED after confirmation" marker plus the error message. - Saddle_Integrations::wrap surfaces genuine wrapper-name collisions via _doing_it_wrong instead of silently dropping the partner tool; its own wrappers are tracked so idempotent re-runs stay silent. Part of Phase 1 (correctness) of the 2026-07 architecture review (plugpressco/saddle-pro#39). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0133CZhoFPY6BBChDDGEa22Q
…it log caps, domain enforcement - Approval tokens are bound to the previewing user: with several agents on one site, agent A's preview can no longer be confirmed by agent B (saddle_token_user_mismatch). - Saddle-issued app passwords are recorded by UUID in user meta (saddle_issued_credentials); credential scoping, the XML-RPC block, and client revoke/rotate/list key on that immutable marker instead of the user-editable display name. Legacy prefix-named keys migrate on sight. - Saddle_Log GC caps denials (300) and executed mutations (1000) as separate filterable buckets, batch raised to 500, so denial noise can never evict change history. - Opt-in domain-drift enforcement (saddle_enforce_tier_domain, default off): write/admin abilities refuse on a migrated/cloned domain until the owner re-confirms the tier; exposed via GET/POST /settings. Phase 2 of the 2026-07 architecture review (#63). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0133CZhoFPY6BBChDDGEa22Q
…, small dedupes - New Saddle_Integration_Engine: the wrap/executor safety logic that existed as ~280 near-identical lines in free and Pro now lives once in free. Saddle_Integrations is catalog + filter names + context shape. The engine also lands the Phase-2 hardening deferred to it: full-args `bind` on wrapped destructive tools (a confirm can no longer change arguments after preview), merged target key list, and a force_destructive catalog override for partners that forget the destructive annotation. - New Saddle_Accessors: one build→filter→instanceof→409 resolution for lint-page / render-node / verify-page (was three hand-synced copies); verify's magic count(skipped)>=3 heuristic replaced with the explicit resolver verdict. Error codes and message strings unchanged. - Saddle_Capabilities records each ability's enforced (level, cap) at permission() build time; denial_reason() now mirrors the closure's exact order, gains the previously-missing capability reason, and reads the enforced tier instead of re-deriving from meta. - Saddle_Abilities::require_readable_post() replaces the copy-pasted read preamble; the two theme.json preset resolvers in the Gutenberg lint accessor merge into one preset_map(). Phase 3 of the 2026-07 architecture review (#64). record()/record_action() was reviewed and intentionally left split: record() is the sole entry carrying `type`, which denial logging needs. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0133CZhoFPY6BBChDDGEa22Q
Three weeks of main landed on top of this branch, and two changes collided head-on with it rather than beside it. **The wrapper bind.** Both this branch and #90 fixed the same hole — a destructive partner tool could be confirmed with different arguments than its preview showed — and they fixed it differently. This branch's extracted engine binds `substr( md5( json ), 0, 12 )` of the raw argument array. #90 binds a full sha256 over a recursively key-sorted copy. The sorting is the part that matters: without it, a client that serializes the same arguments in a different key order has a LEGITIMATE confirm refused. Kept this branch's structure (the engine owns the executor now) and ported #90's hash and its widened, filterable target-key list into it. **denial_reason().** This branch rewrote it to read the same recorded gate the closure enforces instead of re-deriving the tier from ability meta, which is strictly better and is what `$gates` is for — so `required_cap()` from #90 now reads that registry rather than keeping a second map that could drift. Two things were restored on top: the capability message names the capability (an agent told "you lack a permission" without being told WHICH cannot relay it), and the OAuth insufficient-scope branch, which distinguishes "this site does not allow that" from "this app was granted less than the site allows" — different screens, different fixes, and oauth-bearer-test.php pins it. One test changed meaning rather than breaking: it asserted that a disabled tool outranks a capability denial. permission() checks the capability FIRST, so that was simply wrong, and it only surfaced once denial_reason() started mirroring permission()'s real order. Rewritten to pin the ordering in both directions. Also fixed a docblock this branch shipped with, and one my own insertion split. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01GkZr73cqaSesHRDG89Yy8S
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #64. Stacked on #67 (Phase 2). Pro-side counterpart: saddle-pro PR from the same phase.
Saddle_Integration_Engine— one wrap/executor implementation for both catalogs, including the deferred Phase-2 items: full-args destructivebind, merged target keys,force_destructiveoverride.Saddle_Accessors— single accessor resolution for lint/render/verify (3 copies removed); verify'scount(skipped)>=3magic number replaced.denial_reason()now reads the exact (level, cap)permission()enforces, in the same order, with the missing capability reason added.require_readable_post()helper; merged theme.json preset resolvers.3 new engine tests (bind replay, object-cast schema normalization, force_destructive); suite green (368 tests). Pro suite green against the shared engine.
🤖 Generated with Claude Code
https://claude.ai/code/session_0133CZhoFPY6BBChDDGEa22Q