Skip to content

[blocked] Perimeter Guard and restricted Agent Jobs REST #299

Closed

Description

@MrDarkRoot

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.

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_Worker and Woo_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_Product and WC_Product_Variation save().
  • set_regular_price / set_sale_price outside the [v0.3.0] Freeze the price-write boundary as a tested contract #295 allowlist.
  • Direct _regular_price, _sale_price, and _price meta 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_post and the Woo product form).
  • Checkout stock reduction.
  • Cron and Action Scheduler callbacks, including writeleash_process_job with 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 enforced fails 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 unsupported or unverified path 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

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 enforced without 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 enforced and an explicit list of everything else.
  • No version bump in the same change unless the founder comment requested the release.

Activity

  1. MrDarkRoot commented on Oct 16, 2026

    @MrDarkRoot
    OwnerAuthor

    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

    Authorized scope after those gates pass

    • Restricted REST submission only under the v0.3.1 authority contract and the merged [blocked] Standing Grant: durable delegated authority #298 grant enforcement.
    • Authenticate the caller and treat Application Passwords as the WordPress user they authenticate; do not invent a separate agent principal.
    • Validate authentication, capability, grant scope/version, expiry/revocation, budget, idempotency, and payload schema before durable acceptance.
    • Accepted work must bind to an immutable plan with frozen concrete IDs and absolute targets. Apply/Resume must not reselect or recalculate.
    • Execute only through the existing Job_Worker and Woo price/Undo mutators. No second Woo save path.
    • Reject unauthorized, malformed, expired, revoked, out-of-scope, over-budget, and conflicting-idempotency requests before any product write; tests must assert zero unintended writes.
    • Use stable machine reason codes and operator-safe messages. Keep actor, issuer, approver/approval mode, and executor distinct in audit records.
    • Fail closed if grant/worker setup or evidence is unavailable. No fallback to unrestricted writes.

    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.

  2. MrDarkRoot commented on Oct 17, 2026

    @MrDarkRoot
    OwnerAuthor

    Pre-commit gate evidence — implementation stopped

    Recorded before any #299 implementation commit. Target ancestry checked: origin/main = 5adb135b56467f00bf1177fe154205e2d928c4f6 (PR #303 squash of standing grant). Local fix/298-grant 1fb006abe65adb1a5f0c322a756d7a5212abf437 is tree-identical to that squash (git diff empty). 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.

    Issue Merge SHA CI run CI head_sha Tree vs merge Required suites on that run
    #296 / PR #300 2be557604ae39996e32dd1e8da4b985aed686a01 38842493961 success cedb64a7658766eeb24e55444310f4549fcf211b (merge second parent) git diff empty PR_FAST, Admin, Journal, Jobs, Undo, CI_COVERAGE pass. Others skipped.
    #295 / PR #301 73e1bed002f594ba5b45580fc954200f8d8b90dd 38846944238 success c95012f0f9dd25595618783c76f4b8961fc1ca90 git diff empty PR_FAST, Admin, Jobs, Undo, CI_COVERAGE pass. Journal skipped on this run (not claimed by the #294 tracking comment).
    #297 / PR #302 ad85850933671689e4ffdd1395f6a001a787c452 38888114929 success 216641d2fb6303e814812461c9f622dbc3c14ee9 git diff empty PR_FAST, Admin, Journal, Jobs, Undo, CI_COVERAGE pass.

    GET /actions/runs?head_sha= for 2be5576, 73e1bed, ad85850, and 5adb135 returned total_count: 0. Suites passed against the merged trees, not against a workflow whose head_sha is 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-identical 1fb006a: 39020817790 — PR_FAST, jobs, journal, undo, admin, plan, foundation (mysql+mariadb) pass. acceptance skipped.

    Source on that tree enforces the ten decisions, including distinct actor/issuer/approver (approver_id=0 + approval_mode=grant), scope via Grant_Repository::assert_scope, expiry/revocation/disable, atomic budget_reserved CAS, (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-closed GRANT_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)

    enforced means a tested or source-proven deny-before-persistence on WriteLeash's own writers. Foreign Woo paths are unsupported: plugin PHP registers no save_post, Woo REST, checkout, stock, or trash interceptor. Do not read this as a database firewall.

    Path Status Evidence
    WriteLeash Apply price write enforced Woo_Price_Mutator::set_field / save() class-woo-price-mutator.php:36-41,200. Allowlist wordpress/tests/jobs/price-write-allowlist.php.
    WriteLeash Undo price write enforced Woo_Undo_Mutator::restore class-woo-undo-mutator.php:193-197.
    Variation parent sync enforced Price_Cache_Verifier::sync_variable_parent on the pinned connection; banner FREE-PRICE-APPLY-CONTRACT.md:8-9.
    Admin approve / resume / undo intentionally permitted class-free-admin.php:57-60 admin_post_*. Not a second save path.
    REST resume / undo intentionally permitted class-job-resume-rest.php, class-undo-rest.php. Resume does not create or approve a plan.
    writeleash_process_job / writeleash_process_undo intentionally permitted Scheduler hooks; no current-user capability substitute. Execution gate is job state, lease, runner option, mutator.
    Grant-bound item write enforced assert_grant_for_write before journal lock and before save(); denial throws before set_field. class-woo-price-mutator.php:106-120,199-200.
    WooCommerce REST product create/update/delete unsupported No woocommerce_rest_* registration under wordpress/writeleash/.
    Direct WC_Product / WC_Product_Variation::save() outside mutators unsupported Only two ->save() calls in plugin PHP: mutator line 200 and undo mutator line 197.
    set_regular_price / set_sale_price outside allowlist unsupported Only those two mutators.
    Direct _regular_price / _sale_price / _price meta writes unsupported No update_post_meta( price write in plugin PHP.
    wc_update_product_stock() and stock helpers unsupported Not in scope. No stock helper calls.
    Trash / delete unsupported No trash/delete interceptor.
    WooCommerce admin product edit (save_post / product form) unsupported No save_post / woocommerce_admin_process_product hook.
    Checkout stock reduction unsupported No checkout hook.
    Other plugins' hooks during save() unsupported Banner FREE-PRICE-APPLY-CONTRACT.md:12-14. Same-connection hook writes are not independently rolled back.
    Direct SQL / arbitrary PHP unsupported Not intercepted.

    No row is unverified in the sense of "claimed covered without proof." Foreign rows are unsupported, not covered.

    Gate D — regression baseline — BLOCKED

    Not run in this session. Do not treat the rows below as a new pass.

    Flow Result Evidence
    Preview / approve NOT re-run here. Prior CI pass on tree-identical 1fb006a admin job in 39020817790 success, ~1h12m. Pinned harness: WP 7.1.2, PHP 8.2, Woo 11.1.2, MySQL 8.0.44 and MariaDB 10.11.15 (wordpress/tests/jobs/docker-compose.yml).
    Resume same jobs job success, 10m19s, same run.
    Undo same undo job success, 20m13s, same run.
    Cron / Action Scheduler same jobs CI writeleash_process_job covered by the jobs harness, not by a separate cron-only log extracted here.
    Checkout NOT_TESTED No checkout / WC_Checkout / process_checkout test under wordpress/tests.
    WooCommerce product-admin edit NOT_TESTED No save_post_product / product-form regression. acceptance was 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-14 already 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 (2be5576 is an ancestor). FREE-GRANT-CONTRACT.md says 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.

  3. MrDarkRoot commented on Oct 17, 2026

    @MrDarkRoot
    OwnerAuthor

    Implementation is up for review, not merged: #305

    POST /wp-json/writeleash/v1/grants/{uuid}/jobs accepts only an absolute SET through Grant_Repository::accept_plan and Job_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.

  4. MrDarkRoot commented on Oct 17, 2026

    @MrDarkRoot
    OwnerAuthor

    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.php

    Engine WP Woo PHP DB Exit Result
    MySQL 7.1.2 11.1.2 8.2.34 8.0.44 0 PASS, 9 assertions
    MariaDB 7.1.2 11.1.2 8.2.34 10.11.15-MariaDB-ubu2204 0 PASS, 9 assertions

    The test uses WC_Admin_Meta_Boxes::save_meta_boxes (product edit form) and WC_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 stay unsupported for 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.sh for the next CI run.

    No merge and no release from this comment.

  5. MrDarkRoot commented on Oct 18, 2026

    @MrDarkRoot
    OwnerAuthor

    Implementation evidence — all gates resolved (PR #305 merged)

    Merged into main at 2a60ed7 (2026-10-18). Final CI green: run 39094368024.

    Order of gates (supersedes comment 6233354565 and its stop note)

    • Gates A (P0 ancestry + CI), B (Standing Grant), C (write-path matrix): recorded before implementation, comment 6233354565.
    • Gate D (checkout + product-admin regression) and Gate E (banner): resolved in 6235181759 plus the banner edits merged in PR feat(free): restricted grant job submission REST (#299) #305, before merge.
    • Review blockers (replay independent of mutable catalog; bounded delegated safety policy: ≲50% caps, 20% warning, zero target blocked) fixed in f630c8e, verified by review.

    Acceptance criteria mapping

    • Rejection matrix (unauthenticated/unauthorized/expired/revoked/out-of-scope/over-budget/malformed/same-key-different-payload), zero writes: wordpress/tests/jobs/agent-jobs-rest.php (87 assertions, both engines) + grant-integration.php (180).
    • Same-key/same-payload replay, no second plan: replay-stored path (class-agent-jobs-rest.php), regression includes catalog-mutated retry returning the original job with no second reservation.
    • Frozen IDs + absolute targets, Apply/Resume never reselect: WriteLeash Free: define WooCommerce bulk price contract and immutable Change Plan #107/[blocked] Standing Grant: durable delegated authority #298 already-enforced material, unchanged here.
    • Deny-before-persistence for enforced paths: unchanged mutators + allowlist; foreign paths named unsupported in both contract banners.
    • Checkout / product-admin / cron / preview / approve / resume / undo regressions on the supported matrix: foreign-path-regression.php (9 assertions, checkout + WC_Admin_Meta_Boxes::save_meta_boxes) and the jobs/admin/undo CI jobs on run 39094368024.
    • Every unsupported/unverified path named in the banner: yes (both files).
    • No fallback unrestricted write: fail-closed replay (GRANT_STORE_UNAVAILABLE on 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.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions