Skip to content

Tracking — QUERY readiness: working order and issue tree #37

Open

Description

@moonmeister

Single entry point for the project. Everything else hangs off #1–#7; this issue holds the order and the reason for it.

Context and evidence live in scope.md; decisions in docs/decisions/; measurements in experiments/ and matrix/.


⚠️ The order below changed on 2026-08-19. ADR 0002's lean reversed from C (opt-in QUERYABLE) to D (READABLE gains QUERY) — see #10. D turns QUERY on across the entire read surface at once, which made #2 a hard precondition rather than a preference, and briefly made #18 a gate. #18 is now closed — ADR 0003 accepted option C, core emits nothing method-specific, so that gate is discharged with no core work. One new issue, #38, is the last input the decision is waiting on.

The prerequisite chain for proposing D — three items, one of them unstarted: #2/#14 (gap 3) → Trac#65905/#13136 → #38 (method-branching number) → #10 Accepted → #11 + #35 as one patch.

Suggested working order

1. #2 — gap 3, request body parsing ← start here

$accepts_body_data at class-wp-rest-request.php:377. One line.

Why first: it is the only gap that fails open. Measured 2026-08-16 on unmodified trunk — a QUERY carrying application/json already populates and schema-validates params correctly, but a form-encoded QUERY silently returns the full unfiltered collection with a 200. On a read endpoint that is a disclosure, not merely a wrong answer. Gaps 1 and 2 fail closed — a 404, a failed preflight — which is loud and harmless by comparison.

Every option under discussion in ADR 0002, including the QUERY → GET fallback, inherits that failure until this is fixed. Fixing it first takes the disclosure-shaped risk off the table before the contested decision is made.

Under D this is no longer just "first" — it is a precondition. D makes all 77 READABLE routes accept QUERY on upgrade, so shipping D before this would create the open failure on the whole read surface simultaneously. There is no ordering in which D lands first.

It is also the reason the lean moved. parse_body_params() already runs for QUERY (class-wp-rest-request.php:373-375); the parsed body sits in $this->params['POST'] and is merely left out of the lookup order. So this one line does not just fix QUERY bodies — it means every existing READABLE handler serves them correctly with no per-route work, which is the entire case for D.

Two things stay out of #14, both recorded in ADR 0001: a 415 for unrecognized Content-Type (pre-existing, method-general, a new core opinion — parse_body_params() returns early at class-wp-rest-request.php:762-766, so application/jsonpath still falls through to a 200), and Accept-Query (#20). Keep the diff to one array entry.

2. #1 — gap 2, method constants

The most contested call. Leaning D as of 2026-08-19, not yet Accepted.

Propose D and put "on by default vs. opt-in" on the table as the explicit question. That is the real decision and it belongs to the core team, not to this project. D and E produce the identical dispatch outcome; the only difference is who decides.

3. #3 — gap 1, CORS preflight

4. #4 — cache safety & Accept-Query

✅ Resolved 2026-08-19, and it cost no core work. This was put on the critical path that morning — under D, QUERY reaches every read route, so caching stopped being a per-route question. The survey answered it in the other direction: no shipping edge cache stores a QUERY response at all. Varnish synth(501)s it before the origin sees it, nginx's cache-method bitmask holds only GET/HEAD/POST, mod_cache/Squid/Fastly/Cloudflare allowlist, and RFC 9111 §3 already forbids storing a response whose method the cache has not understood. ADR 0003 accepted option C — emit nothing.

This was the project's fifth self-correction, and the first one found by reading default config files rather than by measuring. The ADR had reasoned that because the failure was untestable, the worst case should be assumed. Documentary evidence sits between measurement and assumption, and the ADR skipped past it.

What is left here is Accept-Query, plus the follow-through from the survey.

The one live vector is W3TC Pro with REST caching enabled, and it is confusion, not disclosure — every parameter expressible in a QUERY body is expressible in a GET query string. Note also that a Cache-Control header, the obvious mitigation, would not have reached it: the only layer at risk decides in PHP on the write path and honors DONOTCACHEPAGE, which core references zero times.

5. #5 — feature plugin

The strongest single artifact for Trac: it demonstrates the end-to-end path on stock core with no patch at all.

💡 #22 is cheap and out of order on purpose. It has no dependency on anything above and it is the highest evidence-per-hour item in the project. Worth doing early even though the plugin sits at position 5.

Under D it is worth more than that. The plugin's rest_request_parameter_order filter is the userland equivalent of the #14 one-liner, so running it demonstrates D's central claim on stock core with no patch: an existing READABLE handler, written years ago for GET, serving a form-encoded QUERY body correctly with zero per-route work. See the comment on #5 for what #22 should assert.


Parallel track — no dependency on any of the above

These land on their own merits whether or not the QUERY proposal ever ships. That is the point of keeping them separate: if the main effort stalls in review, these do not.


Done


In review upstream

Two core defects, both found by this project, both landable whether or not QUERY ships. Neither has a human review yet; CI is green on both.

PR Trac Issue Tests
#13136 — split comma-separated methods given in array form #65905 #36 +4; 3 fail on trunk
#13151 — stop clobbering Access-Control-Allow-Methods, add rest_allowed_cors_methods #46992 #16 10 / 46; 6 fail on trunk

Outstanding on both, blocked:human:

Two standing rules

Every claim gets verified before it is published. The project's entire pitch is arriving with evidence rather than assertions, and this repo has now had to correct itself five times — the ADR 0002 tripwire premise, the argument against option E, the framing of Trac#46992, the silent-body-discard argument against option D, and ADR 0003's cache-poisoning threat model. Each correction was found by measuring or reading source we had only reasoned about, and every one of them favoured the status quo before it was checked. Assume the next one exists, and weight the remaining unmeasured claim (#38) accordingly.

The fifth one added a rule of its own: "we cannot measure it" is not a licence to assume the worst. ADR 0003 said so explicitly and then did it. Reading six caches' default configs took an afternoon and reversed the conclusion.

State the weaknesses in our own position first. The BC objection to option A is weaker than we assumed; option D's failure count is inflated by an unrelated bug; the #46992 clobber can be worked around by a plugin hooking at priority 11+; and D's method-branching risk is still unmeasured. Saying so first is worth more than hoping nobody checks.

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

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions