Repository navigation
[blocked] Perimeter Guard and restricted Agent Jobs REST #299
Description
Parent: #294
Blocked. Do not open an agent mutation route because a grant table exists or because P0 merged. #294 excludes an agent write path from v0.3.0. #271 is research-only and is not approval. Implementation starts only after the gates in this issue are checked on the issue itself, including a founder comment that names the release.
Depends on #296, #295, #297, and on the authority contract in #298 when this route delegates a write.
Problem
Resume and Undo already have REST routes. They cannot create or approve a plan. Those entries are admin_post_ only. An agent route that accepts a mutation without a mapped, tested deny path would widen the product past the boundary #294 allows.
There is no reviewed write-path coverage matrix in the repository for product price writes outside WriteLeash's own mutators. WooCommerce admin, WooCommerce REST, direct WC_Product::save(), postmeta writes, stock helpers, trash, checkout, and cron are outside the current plugin boundary. #295 documents that. It does not make those paths fail closed.
Prerequisites
All of these must be true before the first commit of this issue. Record each on this issue. A missing box means stop.
- [v0.3.0] Make apply-journal transitions conditional on the prior state #296, [v0.3.0] Freeze the price-write boundary as a tested contract #295, and [v0.3.0] Reconcile ambiguous apply commits and refresh Undo authorization #297 are merged, and the suites named in those issues were run on the merge commit. This issue does not claim they passed.
- The Standing Grant contract issue has an approving founder comment. If this route depends on a grant, that grant enforcement is merged. If the route is specified not to need a grant, the founder comment says so.
- A reviewed coverage matrix is attached for every in-scope asset and operation. A path is
enforced,intentionally permitted,unsupported, orunverified.enforcedrequires a test or a source proof cited by file and line.unverifiedcannot be shipped as covered. - Checkout, product admin edit, cron / Action Scheduler, and the existing v0.2.x preview / approve / resume / undo flows have regression results on a supported WordPress, WooCommerce, PHP, and database combination. Unsupported versions are not a pass.
- Unsupported paths and threat-model limits are written in the contract banner, not only in this issue.
- A founder comment names the release. v0.3.0 is not that release unless the comment revises [v0.3.0] Bounded Write Authority — architecture and delivery plan #294.
Scope
- A restricted REST API for submitting work under the approved authority contract.
- Authenticate the caller. Application Passwords authenticate as a WordPress user and inherit that user's capabilities. There is no separate agent principal in WordPress.
- Validate grant scope, idempotency, payload schema, and budget before inserting a job or plan.
- Bind accepted work to an immutable plan with frozen concrete ids and absolute targets. Apply and Resume must not rerun selectors or percentage calculations.
- Route supported mutations through
Job_WorkerandWoo_Price_Mutator/Woo_Undo_Mutator. Do not add a second save path. - Reject an unauthorized call before any Woo write. A denied request changes zero product rows.
- Use the same expiry, revocation, and failure behavior on the REST handler and in the worker.
- Return stable machine reason codes and operator-safe messages. Audit who requested, who issued, who approved, and who executed.
Write-path matrix to produce before coding
Inspect the installed WooCommerce version in the support range. Do not mark a path from memory. For each in-scope operation, record one status:
- WooCommerce REST product create, update, and delete.
- Direct
WC_ProductandWC_Product_Variationsave(). set_regular_price/set_sale_priceoutside the [v0.3.0] Freeze the price-write boundary as a tested contract #295 allowlist.- Direct
_regular_price,_sale_price, and_pricemeta writes. wc_update_product_stock()and other stock helpers, if and only if inventory is in the approved scope. It is not in scope today.- Trash and delete.
- Admin product edit (
save_postand the Woo product form). - Checkout stock reduction.
- Cron and Action Scheduler callbacks, including
writeleash_process_jobwith no current user. WC_Product_Variable::sync()and variation parent updates.- Other plugins' hooks during
save().
WriteLeash does not currently intercept those foreign paths. The matrix must say unsupported unless a test shows a deny before persistence. Do not label a path enforced without that test.
Proposed approach
Keep the route unable to approve as the caller unless the approved contract says that caller is the approver. Default: an agent submission creates a reviewable plan, or it is rejected. It does not set approver_id to the agent user.
If a grant is required, validate it before Change_Plan persistence and pin grant_id / grant_version only as #298's contract allows. Re-check at Apply inside the existing item transaction. A revoke after accept and before Apply fails closed.
Do not add a capability check on the Action Scheduler callback as a substitute for the mutator. #295 already records why a cron runner has no user. The execution gate remains job state, lease, runner option, and the mutator.
Acceptance criteria
- Every prerequisite box above is checked with evidence linked on this issue before implementation starts.
- Unauthenticated, unauthorized, expired, revoked, out-of-scope, over-budget, malformed, and same-key/different-payload requests are rejected. Zero product writes.
- A same-key/same-payload retry follows the approved idempotency contract and does not create a second plan.
- Accepted work stores frozen ids and absolute targets. Apply and Resume do not reselect.
- Each path the matrix marks
enforcedfails closed before persistence. The test asserts zero unintended writes. - Checkout, admin edit, cron, and existing preview / approve / resume / undo flows still pass on the supported matrix.
- Every
unsupportedorunverifiedpath is named in the contract banner. - The route has no fallback that runs the write when grant or worker setup fails.
Required tests
- REST authentication and capability matrix, including Application Password and cookie-nonce failure.
- Invalid, expired, revoked, and out-of-scope grant, if the contract uses a grant.
- Budget exhaustion and both idempotency cases.
- Direct CRUD, meta write, WooCommerce REST, and trash/delete probes for the declared scope. Assert zero writes on deny.
- A request accepted under one grant cannot be executed under another.
- Checkout, admin, cron, Action Scheduler, and the current admin UI regression on a supported configuration.
- Concurrent submission and a fault after accept before Apply.
- The support contract's WordPress, WooCommerce, PHP, and database range. One passing combination does not cover the range; list what was actually run.
Non-goals
- Do not add inventory mutation unless a separate founder comment adds it to the contract and the matrix.
- Do not promise a universal database firewall, a sandbox for other plugins, or protection from direct SQL or arbitrary PHP.
- Do not intercept WooCommerce admin or WooCommerce REST in this issue unless the matrix and the founder comment explicitly move that path to
enforced. - No public readme claim of "agent-safe" or "write authority" beyond the banner's tested boundary.
- No MCP adapter. [research][F16–F18] One certified MCP adapter, causal workflow allowance and managed-deployment feasibility #271 stays research.
Dependencies
- Parent: [v0.3.0] Bounded Write Authority — architecture and delivery plan #294. This issue does not authorize a v0.3.0 agent endpoint.
- P0: [v0.3.0] Make apply-journal transitions conditional on the prior state #296, [v0.3.0] Freeze the price-write boundary as a tested contract #295, [v0.3.0] Reconcile ambiguous apply commits and refresh Undo authorization #297.
- Standing Grant: the blocked grant issue. Required if the route delegates authority. Not required only if the founder comment says this route does not use a grant.
- [research][F16–F18] One certified MCP adapter, causal workflow allowance and managed-deployment feasibility #271 is not a dependency and not approval.
Risks and open questions
- WordPress has no agent principal. A route authenticated as a shop manager is that shop manager. The contract must say whether that is acceptable or whether the route is refused for every user.
save()on the pinned connection can still run third-party hooks that write other rows. [v0.3.0] Freeze the price-write boundary as a tested contract #295 already states that. This route must not claim those rows are rolled back.- A coverage matrix that marks foreign Woo paths
enforcedwithout a deny test is a false boundary. Reviewers should reject that matrix.
Definition of done
- The route is either still blocked, or it is implemented only after the gates above, with tests for every path marked
enforcedand an explicit list of everything else. - No version bump in the same change unless the founder comment requested the release.
Activity
MrDarkRoot commentedon Oct 16, 2026
Founder decision — GO for restricted Agent Jobs REST implementation (v0.3.1 only, gated)
GO in principle for coding agents to implement #299 for v0.3.1, subject to every prerequisite below being recorded with evidence on this issue before the first implementation commit. This explicitly supersedes the prior blanket blocked status only for the bounded scope described here. It does not authorize a v0.3.0 change, release publication, or merge without maintainer review.
Required pre-commit gates — do not start implementation until all are evidenced here
enforced,intentionally permitted,unsupported, orunverified. Eachenforcedrow needs a test or source proof with file and line. No unverified path may be described as covered.Authorized scope after those gates pass
Explicit exclusions
No inventory mutation, MCP adapter, universal database-firewall/sandbox claim, interception claim for foreign WooCommerce REST/admin/direct CRUD/direct SQL paths without a tested deny-before-persistence control, v0.3.0 scope change, version bump outside the v0.3.1 target, merge, or publication.
If any prerequisite cannot be met, keep implementation stopped and report the missing evidence instead of bypassing the gate. Research issue #271 is not approval.
MrDarkRoot commentedon Oct 17, 2026
Pre-commit gate evidence — implementation stopped
Recorded before any #299 implementation commit. Target ancestry checked:
origin/main=5adb135b56467f00bf1177fe154205e2d928c4f6(PR #303 squash of standing grant). Localfix/298-grant1fb006abe65adb1a5f0c322a756d7a5212abf437is tree-identical to that squash (git diffempty). No route, schema, or banner edit was made in this pass.Gate A — P0 ancestry and CI — PASS for merged trees; no workflow run on the merge SHAs
All three merge commits are ancestors of
origin/main.head_sha2be557604ae39996e32dd1e8da4b985aed686a01cedb64a7658766eeb24e55444310f4549fcf211b(merge second parent)git diffempty73e1bed002f594ba5b45580fc954200f8d8b90ddc95012f0f9dd25595618783c76f4b8961fc1ca90git diffemptyad85850933671689e4ffdd1395f6a001a787c452216641d2fb6303e814812461c9f622dbc3c14ee9git diffemptyGET /actions/runs?head_sha=for2be5576,73e1bed,ad85850, and5adb135returnedtotal_count: 0. Suites passed against the merged trees, not against a workflow whosehead_shais the merge commit.Gate B — Standing Grant — PASS on merged source
Founder contract: #298 (comment)
Merged: PR #303,
5adb135, 2026-10-17. CI on tree-identical1fb006a: 39020817790 — PR_FAST, jobs, journal, undo, admin, plan, foundation (mysql+mariadb) pass.acceptanceskipped.Source on that tree enforces the ten decisions, including distinct actor/issuer/approver (
approver_id=0+approval_mode=grant), scope viaGrant_Repository::assert_scope, expiry/revocation/disable, atomicbudget_reservedCAS,(grant, key)idempotency before mutable grant state, append-only audit in the same transaction, lock order runner/job then grant then journal then Woo (class-woo-price-mutator.php~94–220), and fail-closedGRANT_STORE_UNAVAILABLE/ zero Woo save on denial. No agent REST route in this merge.Gate C — write-path matrix (source review, current main, no new deny tests)
enforcedmeans a tested or source-proven deny-before-persistence on WriteLeash's own writers. Foreign Woo paths areunsupported: plugin PHP registers nosave_post, Woo REST, checkout, stock, or trash interceptor. Do not read this as a database firewall.Woo_Price_Mutator::set_field/save()class-woo-price-mutator.php:36-41,200. Allowlistwordpress/tests/jobs/price-write-allowlist.php.Woo_Undo_Mutator::restoreclass-woo-undo-mutator.php:193-197.Price_Cache_Verifier::sync_variable_parenton the pinned connection; bannerFREE-PRICE-APPLY-CONTRACT.md:8-9.class-free-admin.php:57-60admin_post_*. Not a second save path.class-job-resume-rest.php,class-undo-rest.php. Resume does not create or approve a plan.writeleash_process_job/writeleash_process_undoassert_grant_for_writebefore journal lock and beforesave(); denial throws beforeset_field.class-woo-price-mutator.php:106-120,199-200.woocommerce_rest_*registration underwordpress/writeleash/.WC_Product/WC_Product_Variation::save()outside mutators->save()calls in plugin PHP: mutator line 200 and undo mutator line 197.set_regular_price/set_sale_priceoutside allowlist_regular_price/_sale_price/_pricemeta writesupdate_post_meta(price write in plugin PHP.wc_update_product_stock()and stock helperssave_post/ product form)save_post/woocommerce_admin_process_producthook.save()FREE-PRICE-APPLY-CONTRACT.md:12-14. Same-connection hook writes are not independently rolled back.No row is
unverifiedin the sense of "claimed covered without proof." Foreign rows areunsupported, not covered.Gate D — regression baseline — BLOCKED
Not run in this session. Do not treat the rows below as a new pass.
1fb006awordpress/tests/jobs/docker-compose.yml).writeleash_process_jobcovered by the jobs harness, not by a separate cron-only log extracted here.WC_Checkout/process_checkouttest underwordpress/tests.save_post_product/ product-form regression.acceptancewas skipped on the #298 run.Local Docker is up, but there is no checkout or product-admin regression command to execute. An unrun probe is not a pass. One CI combination does not cover a support range.
Gate E — contract banner — NOT updated
FREE-PRICE-APPLY-CONTRACT.md:9-14already names Woo admin, Woo REST, WP-CLI, direct SQL, and other plugins as outside the boundary. Line 25 still says the #296 journal CAS "is a separate pending gate," which is false on current main (2be5576is an ancestor).FREE-GRANT-CONTRACT.mdsays there is no agent REST route. Banner was not edited, because implementation is stopped.Stop
#299 implementation did not start. No branch, commit, or PR. Missing evidence to unblock: a recorded checkout regression and a WooCommerce product-admin edit regression on a named WP / Woo / PHP / database combination, with command, exit code, and skipped tests. Banner line 25 should be corrected when that work is authorized, not as a silent route bypass.
MrDarkRoot commentedon Oct 17, 2026
Implementation is up for review, not merged: #305
POST /wp-json/writeleash/v1/grants/{uuid}/jobsaccepts only an absoluteSETthroughGrant_Repository::accept_planandJob_Worker::queue_job. No second price writer, no MCP adapter, no version bump.Local evidence: schema unit 21 PASS; MySQL 8.0.44 / WP 7.1.2 / Woo 11.1.2 / PHP 8.2 integration 65 PASS. MariaDB and Redis not re-run here. Checkout and Woo product-admin edit stay unsupported.
test(release): count the grant submission route in the v0.2 manifest (#…
fix(free): declare the grant submission route and move its read (#299)
MrDarkRoot commentedon Oct 17, 2026
Gate D update — supersedes the stop note in comment 6233354565
PR #305 exists: #305. The earlier note that implementation was stopped and no branch/PR existed is stale.
Checkout and product-admin edit regression
Command, inside the pinned jobs fixture (
wordpress/tests/jobs/docker-compose.yml, default cache):WL109_CACHE=default wp --path=$site eval-file /opt/tests/jobs/foreign-path-regression.phpThe test uses
WC_Admin_Meta_Boxes::save_meta_boxes(product edit form) andWC_Checkout::process_checkout(COD, stock 5 → 4). Both persist. Neither writes a WriteLeash journal row. That is a regression that the foreign paths still work, not a deny-before-persistence claim. They stayunsupportedfor interception.Skipped by this file: Redis object cache; cron/Action Scheduler; preview/approve/resume/undo. Those remain on the green jobs/admin/undo jobs in run 39034161676. This file is wired into
wordpress/tests/jobs/in-container.shfor the next CI run.No merge and no release from this comment.
test(jobs): regress checkout and product-admin edit (#299)
test(jobs): count only this checkout's order (#299)
fix(free): replay from stored acceptance before re-planning (#299)
Merge pull request #305 from MrDarkRoot/feat/299-agent-jobs-rest
MrDarkRoot commentedon Oct 18, 2026
Implementation evidence — all gates resolved (PR #305 merged)
Merged into
mainat2a60ed7(2026-10-18). Final CI green: run 39094368024.Order of gates (supersedes comment 6233354565 and its stop note)
f630c8e, verified by review.Acceptance criteria mapping
wordpress/tests/jobs/agent-jobs-rest.php(87 assertions, both engines) +grant-integration.php(180).class-agent-jobs-rest.php), regression includes catalog-mutated retry returning the original job with no second reservation.enforcedpaths: unchanged mutators + allowlist; foreign paths namedunsupportedin both contract banners.foreign-path-regression.php(9 assertions, checkout +WC_Admin_Meta_Boxes::save_meta_boxes) and the jobs/admin/undo CI jobs on run 39094368024.unsupported/unverifiedpath named in the banner: yes (both files).GRANT_STORE_UNAVAILABLEon missing/inconsistent stored job) and worker fail-closed retained.Combinations actually run
WordPress 7.1.2, WooCommerce 11.1.2, PHP 8.2.34, with MySQL 8.0.44 and MariaDB 10.11.15, each under default and Redis object cache (Redis Cache 2.7.0). That is the full pinned fixture in
wordpress/tests/jobs/docker-compose.yml; no untested version is claimed.Closing as complete. Parent tracking in #294 remains authoritative for its own scope.