Skip to content

docs(wporg): answer round 2 where a re-scan reads it (#161) - #162

Merged
ifahimreza merged 1 commit into
mainfrom
docs/161-wporg-round-2-answers
Aug 26, 2026
Merged

docs(wporg): answer round 2 where a re-scan reads it (#161)#162
ifahimreza merged 1 commit into
mainfrom
docs/161-wporg-round-2-answers

Conversation

@ifahimreza

Copy link
Copy Markdown
Contributor

Refs #161

Uses Refs, not Closes#161 also covers rebuilding and re-verifying the .org
zip, which happens after this and #160 land.

What

Makes Saddle's read-authorization model legible at the line the WordPress.org
reviewer quoted, and writes both round-2 answers into WPORG-SUBMISSION.md in a form
that can be reused verbatim if a round 3 raises them again. Comments and docs only —
no behaviour change, no new strings, nothing that ships differently.

Why

The reviewer flagged saddle/get-media's permission_callback for checking only the
generic read capability. They were right, and #149 fixed it — six abilities wide,
not one. But that fix routes every id-taking read through
Saddle_Abilities::require_readable_post() on the execute path, while the reviewer
quoted the registration line. Nothing at that line says the object is authorized
elsewhere, so an automated re-scan quotes it again and reads it the same way.

The second finding — https://unsplash.com/api-terms answering 401 — is a false
positive that no change to this plugin can clear, so it needs a written answer rather
than a fix.

How

Code comments. A block note at the top of includes/abilities/core-content.php
setting out the two layers explicitly:

Layer Where Answers
Tool Saddle_Capabilities::permission() May this caller use this tool at all
Object require_readable_post() / collection() May this caller read this row

…plus a one-line pointer above each id-taking read (get-media, get-post,
get-page, get-blocks, list-post-revisions, lint-page, render-node,
verify-page) and each listing (list-posts, list-pages, list-media,
search-content).

Two things the note records that were previously only implicit:

  • Why the object check is on the execute path and not in the gate. Core does pass
    $input to WP_Ability::check_permissions() and accepts a WP_Error back, so it
    could move — but denial_reason() and is_callable_now(), which build tools/list
    and every refusal an agent reads, are input-free by construction, and a gate that says
    "no" without naming which layer said so is what puts an agent into a retry loop.
  • get-preview-url is deliberately stricter, not outside the funnel. A preview link
    renders unpublished content on the front end, so an unpublished post needs edit_post
    there rather than read_post.

WPORG-SUBMISSION.md. A round-2 history entry, plus:

  • §13 — the authorization model, what each control does (including the attachment
    inherit → post_parent resolution, the password-protected refusal and why it refuses
    rather than blanks, and the deliberate total-counts-dropped-rows divergence from
    core), and how it was verified on a live install.
  • §14 — the Unsplash 401, with the Anubis reproduction and a table showing that
    every unsplash.com path behaves identically, including the /privacy link the
    checker did not flag. That table is the argument: swapping the flagged URL fixes
    nothing, and there is no browser-reachable canonical Unsplash terms or privacy URL to
    swap to.
  • A draft reply, not sent, ending with an explicit offer to drop both unsplash.com
    links if the reviewer would rather the readme contain no URL their checker flags.

Testing

  • composer lint — 0 errors (3 pre-existing warnings, none in the changed files).
  • composer test — unchanged; the read-authorization suite still passes.
  • Unsplash reproduction re-run 2026-08-27, deterministic. unsplash.com/api-terms,
    /privacy, /terms and /license all 401 to a browser UA and 200 to curl;
    help.unsplash.com answers 200 to both.
  • No notices or warnings with WP_DEBUG on.

WPORG-SUBMISSION.md is excluded from both zips (root *.md in Gruntfile.js), so
none of the doc content ships.

Screenshots

None — no UI change.

The get-media finding is fixed (#149), but the fix is on the execute path
and the reviewer quoted the permission_callback line — so an automated
re-scan quotes the same line again and reads it the same way.

Makes the two-layer model legible at the registration site: a block note at
the top of core-content.php saying the permission_callback gates the TOOL
and require_readable_post()/collection() authorize the OBJECT, plus a
pointer on each id-taking read and each listing. Says why the object check
is on the execute path rather than in the gate — denial_reason() and
is_callable_now() are input-free by construction, and an unexplained "no"
is what puts an agent into a retry loop. get-preview-url's comment records
that it is deliberately stricter than the funnel, not outside it.

Comments only; no behaviour change.

WPORG-SUBMISSION.md gains the round-2 history entry and two reusable
answers: §13 the authorization model, what each control does, the
divergence from core in `total`, and how it was verified; §14 the Unsplash
401 with the Anubis reproduction and the table showing every unsplash.com
path behaves the same — including the /privacy link the checker did not
flag, which is why swapping the flagged URL fixes nothing. Draft reply
appended; not sent.

Refs #161
@ifahimreza ifahimreza linked an issue Aug 26, 2026 that may be closed by this pull request
4 tasks
@ifahimreza
ifahimreza merged commit 24eef2b into main Aug 26, 2026
1 of 7 checks passed
@ifahimreza
ifahimreza deleted the docs/161-wporg-round-2-answers branch August 26, 2026 20:08
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

WordPress.org review round 2: close out the reply and rebuild the .org zip

1 participant