wordpress-agent-kit 0.4.0 → 0.5.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.agents/skills/blueprint/SKILL.md +418 -0
- package/.agents/skills/wordpress-router/SKILL.md +52 -0
- package/.agents/skills/wordpress-router/references/decision-tree.md +55 -0
- package/.agents/skills/wp-abilities-api/SKILL.md +108 -0
- package/.agents/skills/wp-abilities-api/references/delegate-helper-pattern.md +241 -0
- package/.agents/skills/wp-abilities-api/references/domain-vs-projection.md +113 -0
- package/.agents/skills/wp-abilities-api/references/error-code-vocabulary.md +123 -0
- package/.agents/skills/wp-abilities-api/references/grouping-heuristic.md +89 -0
- package/.agents/skills/wp-abilities-api/references/input-schema-gotchas.md +265 -0
- package/.agents/skills/wp-abilities-api/references/php-registration.md +94 -0
- package/.agents/skills/wp-abilities-api/references/plugin-family-patterns.md +233 -0
- package/.agents/skills/wp-abilities-api/references/rest-api.md +13 -0
- package/.agents/skills/wp-abilities-api/references/shared-core-service.md +184 -0
- package/.agents/skills/wp-abilities-audit/SKILL.md +199 -0
- package/.agents/skills/wp-abilities-audit/references/audit-schema.md +300 -0
- package/.agents/skills/wp-abilities-audit/references/capability-gate-tracing.md +197 -0
- package/.agents/skills/wp-abilities-audit/references/controller-enumeration.md +116 -0
- package/.agents/skills/wp-abilities-verify/SKILL.md +215 -0
- package/.agents/skills/wp-abilities-verify/references/annotation-correctness.md +154 -0
- package/.agents/skills/wp-abilities-verify/references/audit-schema-validation.md +131 -0
- package/.agents/skills/wp-abilities-verify/references/permission-roundtrip.md +190 -0
- package/.agents/skills/wp-abilities-verify/references/runtime-harness.md +462 -0
- package/.agents/skills/wp-abilities-verify/references/schema-lints.md +118 -0
- package/.agents/skills/wp-abilities-verify/references/static-enumeration.md +126 -0
- package/.agents/skills/wp-block-development/SKILL.md +175 -0
- package/.agents/skills/wp-block-development/references/attributes-and-serialization.md +22 -0
- package/.agents/skills/wp-block-development/references/block-json.md +49 -0
- package/.agents/skills/wp-block-development/references/creating-new-blocks.md +46 -0
- package/.agents/skills/wp-block-development/references/debugging.md +36 -0
- package/.agents/skills/wp-block-development/references/deprecations.md +24 -0
- package/.agents/skills/wp-block-development/references/dynamic-rendering.md +23 -0
- package/.agents/skills/wp-block-development/references/inner-blocks.md +25 -0
- package/.agents/skills/wp-block-development/references/registration.md +30 -0
- package/.agents/skills/wp-block-development/references/supports-and-wrappers.md +18 -0
- package/.agents/skills/wp-block-development/references/tooling-and-testing.md +21 -0
- package/.agents/skills/wp-block-development/scripts/list_blocks.mjs +121 -0
- package/.agents/skills/wp-block-themes/SKILL.md +117 -0
- package/.agents/skills/wp-block-themes/references/creating-new-block-theme.md +37 -0
- package/.agents/skills/wp-block-themes/references/debugging.md +24 -0
- package/.agents/skills/wp-block-themes/references/patterns.md +18 -0
- package/.agents/skills/wp-block-themes/references/style-variations.md +14 -0
- package/.agents/skills/wp-block-themes/references/templates-and-parts.md +16 -0
- package/.agents/skills/wp-block-themes/references/theme-json.md +59 -0
- package/.agents/skills/wp-block-themes/scripts/detect_block_themes.mjs +117 -0
- package/.agents/skills/wp-interactivity-api/SKILL.md +180 -0
- package/.agents/skills/wp-interactivity-api/references/debugging.md +29 -0
- package/.agents/skills/wp-interactivity-api/references/directives-quickref.md +30 -0
- package/.agents/skills/wp-interactivity-api/references/server-side-rendering.md +310 -0
- package/.agents/skills/wp-performance/SKILL.md +147 -0
- package/.agents/skills/wp-performance/references/autoload-options.md +24 -0
- package/.agents/skills/wp-performance/references/cron.md +20 -0
- package/.agents/skills/wp-performance/references/database.md +20 -0
- package/.agents/skills/wp-performance/references/http-api.md +15 -0
- package/.agents/skills/wp-performance/references/measurement.md +21 -0
- package/.agents/skills/wp-performance/references/object-cache.md +24 -0
- package/.agents/skills/wp-performance/references/query-monitor-headless.md +38 -0
- package/.agents/skills/wp-performance/references/server-timing.md +22 -0
- package/.agents/skills/wp-performance/references/wp-cli-doctor.md +24 -0
- package/.agents/skills/wp-performance/references/wp-cli-profile.md +32 -0
- package/.agents/skills/wp-performance/scripts/perf_inspect.mjs +128 -0
- package/.agents/skills/wp-phpstan/SKILL.md +98 -0
- package/.agents/skills/wp-phpstan/references/configuration.md +52 -0
- package/.agents/skills/wp-phpstan/references/third-party-classes.md +76 -0
- package/.agents/skills/wp-phpstan/references/wordpress-annotations.md +124 -0
- package/.agents/skills/wp-phpstan/scripts/phpstan_inspect.mjs +263 -0
- package/.agents/skills/wp-playground/SKILL.md +233 -0
- package/.agents/skills/wp-playground/references/blueprints.md +36 -0
- package/.agents/skills/wp-playground/references/cli-commands.md +39 -0
- package/.agents/skills/wp-playground/references/debugging.md +16 -0
- package/.agents/skills/wp-playground/references/e2e-playwright.md +115 -0
- package/.agents/skills/wp-plugin-development/SKILL.md +113 -0
- package/.agents/skills/wp-plugin-development/references/data-and-cron.md +19 -0
- package/.agents/skills/wp-plugin-development/references/debugging.md +19 -0
- package/.agents/skills/wp-plugin-development/references/lifecycle.md +33 -0
- package/.agents/skills/wp-plugin-development/references/security.md +29 -0
- package/.agents/skills/wp-plugin-development/references/settings-api.md +22 -0
- package/.agents/skills/wp-plugin-development/references/structure.md +16 -0
- package/.agents/skills/wp-plugin-development/scripts/detect_plugins.mjs +122 -0
- package/.agents/skills/wp-plugin-directory-guidelines/SKILL.md +133 -0
- package/.agents/skills/wp-plugin-directory-guidelines/references/gpl-compliance.md +217 -0
- package/.agents/skills/wp-plugin-directory-guidelines/references/guideline-review-checklist.md +592 -0
- package/.agents/skills/wp-plugin-directory-guidelines/references/naming-rules.md +121 -0
- package/.agents/skills/wp-project-triage/SKILL.md +39 -0
- package/.agents/skills/wp-project-triage/references/triage.schema.json +143 -0
- package/.agents/skills/wp-project-triage/scripts/detect_wp_project.mjs +610 -0
- package/.agents/skills/wp-rest-api/SKILL.md +115 -0
- package/.agents/skills/wp-rest-api/references/authentication.md +18 -0
- package/.agents/skills/wp-rest-api/references/custom-content-types.md +20 -0
- package/.agents/skills/wp-rest-api/references/discovery-and-params.md +20 -0
- package/.agents/skills/wp-rest-api/references/responses-and-fields.md +30 -0
- package/.agents/skills/wp-rest-api/references/routes-and-endpoints.md +36 -0
- package/.agents/skills/wp-rest-api/references/schema.md +22 -0
- package/.agents/skills/wp-wpcli-and-ops/SKILL.md +126 -0
- package/.agents/skills/wp-wpcli-and-ops/references/automation.md +30 -0
- package/.agents/skills/wp-wpcli-and-ops/references/cron-and-cache.md +23 -0
- package/.agents/skills/wp-wpcli-and-ops/references/debugging.md +17 -0
- package/.agents/skills/wp-wpcli-and-ops/references/multisite.md +22 -0
- package/.agents/skills/wp-wpcli-and-ops/references/packages-and-updates.md +22 -0
- package/.agents/skills/wp-wpcli-and-ops/references/safety.md +30 -0
- package/.agents/skills/wp-wpcli-and-ops/references/search-replace.md +40 -0
- package/.agents/skills/wp-wpcli-and-ops/scripts/wpcli_inspect.mjs +90 -0
- package/.agents/skills/wp-wpengine/SKILL.md +398 -0
- package/.agents/skills/wp-wpengine/references/ci-gate.md +469 -0
- package/.agents/skills/wp-wpengine/references/github-actions-deploy.md +736 -0
- package/.agents/skills/wp-wpengine/scripts/ci-gate.sh +118 -0
- package/.agents/skills/wp-wpengine/scripts/wpe-check.sh +89 -0
- package/.agents/skills/wp-wpengine/scripts/wpe-preflight.sh +104 -0
- package/.agents/skills/wpds/SKILL.md +59 -0
- package/.github/agents/wp-architect.agent.md +1 -2
- package/.github/copilot-instructions.md +1 -1
- package/.github/instructions/wordpress-workflow.instructions.md +3 -3
- package/AGENTS.md +22 -10
- package/AGENTS.template.md +20 -10
- package/README.md +89 -85
- package/dist/cli.js +5 -1
- package/dist/commands/clean-skills.js +64 -0
- package/dist/commands/setup.js +6 -2
- package/dist/commands/sync-skills.js +3 -0
- package/dist/lib/api.js +164 -5
- package/dist/lib/installer.js +166 -2
- package/extensions/wp-agent-kit/index.ts +185 -10
- package/package.json +10 -14
- package/skills-custom/wp-wpengine/SKILL.md +299 -28
- package/skills-custom/wp-wpengine/references/ci-gate.md +469 -0
- package/skills-custom/wp-wpengine/references/github-actions-deploy.md +736 -0
- package/skills-custom/wp-wpengine/scripts/ci-gate.sh +118 -0
- package/skills-custom/wp-wpengine/scripts/wpe-check.sh +89 -0
- package/skills-custom/wp-wpengine/scripts/wpe-preflight.sh +104 -0
- package/.github/workflows/ci.yml +0 -44
- package/.husky/pre-commit +0 -7
- package/CLI_REVIEW.md +0 -250
- package/biome.json +0 -39
|
@@ -0,0 +1,215 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: wp-abilities-verify
|
|
3
|
+
description: "Verify a WordPress plugin's Abilities API registrations: enumerate abilities, check that callback behavior matches each annotation's claim (the adversarial readonly-but-writes detection), validate permissions and schemas, and validate audit documents produced by wp-abilities-audit."
|
|
4
|
+
license: GPL-2.0-or-later
|
|
5
|
+
compatibility: "Targets WordPress 6.9+ plugins (PHP 7.2.24+). Requires a runnable environment (wp-env, docker-based dev stack, or equivalent) for runtime mode; static mode runs entirely from the plugin checkout with no env. Filesystem-based agent with bash + node."
|
|
6
|
+
---
|
|
7
|
+
|
|
8
|
+
# WP Abilities Verify
|
|
9
|
+
|
|
10
|
+
Verify a WordPress plugin's Abilities API registrations. The
|
|
11
|
+
centerpiece is the **adversarial annotation correctness check**: a
|
|
12
|
+
`readonly: true` ability that actually writes (via `$wpdb->update`,
|
|
13
|
+
`update_option`, a non-GET delegate, etc.) is a security and UX
|
|
14
|
+
disaster because agents plan actions on the basis of the annotations
|
|
15
|
+
they introspect. This skill catches those lies by reading the callback
|
|
16
|
+
body and comparing what it does against what the annotation claims.
|
|
17
|
+
|
|
18
|
+
The skill also validates audit docs produced by `wp-abilities-audit`,
|
|
19
|
+
checks permission gates and schema hygiene, and optionally executes
|
|
20
|
+
each ability against a live environment.
|
|
21
|
+
|
|
22
|
+
## When to use
|
|
23
|
+
|
|
24
|
+
- After abilities have been registered in a plugin but before a PR
|
|
25
|
+
lands.
|
|
26
|
+
- As a health-check on an already-shipped plugin (catch regressions
|
|
27
|
+
where a refactor turned a readonly ability into a writing one).
|
|
28
|
+
- To validate an audit document before handing it to an implementer.
|
|
29
|
+
|
|
30
|
+
## Two modes
|
|
31
|
+
|
|
32
|
+
- **Static mode** — runs from the plugin checkout. No env. Enumerates
|
|
33
|
+
via source inspection, runs the adversarial correctness check, runs
|
|
34
|
+
schema and permission lints, and validates audit docs.
|
|
35
|
+
- **Runtime mode** — requires a running env. Does everything static
|
|
36
|
+
does PLUS: `wp_get_abilities()` for authoritative enumeration,
|
|
37
|
+
executes each ability with curated inputs, confirms permission
|
|
38
|
+
roundtrip against real users, and runs a twin-invocation heuristic
|
|
39
|
+
on `idempotent: true` abilities to flag candidates for review
|
|
40
|
+
(return-value equality is a signal, not a verdict — core defines
|
|
41
|
+
idempotent as "no additional effect on the environment").
|
|
42
|
+
|
|
43
|
+
Both modes produce the same structured report format.
|
|
44
|
+
|
|
45
|
+
A static-mode PASS means "no obvious-shape violations," not "verified
|
|
46
|
+
write-free." For high-stakes plugins, run runtime mode before landing
|
|
47
|
+
— it catches bootstrap-order, permission-roundtrip, and idempotency
|
|
48
|
+
issues that static can't. See `references/annotation-correctness.md`
|
|
49
|
+
for the static blind spots.
|
|
50
|
+
|
|
51
|
+
## Inputs required
|
|
52
|
+
|
|
53
|
+
1. **Plugin checkout path** — working tree to verify.
|
|
54
|
+
2. **Mode** — `static` or `runtime`. Default to static if unspecified.
|
|
55
|
+
3. **(Runtime only) Env-up command** — read the plugin's `AGENTS.md`.
|
|
56
|
+
Common patterns: `npm run wp-env start`, `npx wp-env start`, or a
|
|
57
|
+
composer-based bring-up. Plugin families with their own dev tooling
|
|
58
|
+
will document their own command. Do NOT assume `npm run wp-env`
|
|
59
|
+
works.
|
|
60
|
+
4. **(Optional) Audit doc path** — enables cross-checks between the
|
|
61
|
+
audit and the registered abilities, and validates the audit itself.
|
|
62
|
+
5. **Report output path** — explicit path, typically the user's vault.
|
|
63
|
+
|
|
64
|
+
## Prerequisites
|
|
65
|
+
|
|
66
|
+
- `wp-project-triage` has been run on the plugin.
|
|
67
|
+
- The plugin has at least one registered ability in source. Zero hits
|
|
68
|
+
on `wp_register_ability(` → return a clear "no abilities registered"
|
|
69
|
+
report, not an empty PASS.
|
|
70
|
+
|
|
71
|
+
## Procedure
|
|
72
|
+
|
|
73
|
+
### 1. (If audit provided) Validate the audit doc
|
|
74
|
+
|
|
75
|
+
Read `references/audit-schema-validation.md`. Validate the audit
|
|
76
|
+
against the canonical schema owned by `wp-abilities-audit`. Surface
|
|
77
|
+
missing required fields, multiple `reference_ability: true`, and
|
|
78
|
+
`backing: null` entries that aren't paired with a `surfaced_gaps`
|
|
79
|
+
entry. `backing: null` alone is WARN (intentional gap output), not
|
|
80
|
+
FAIL.
|
|
81
|
+
|
|
82
|
+
### 2. Enumerate abilities statically
|
|
83
|
+
|
|
84
|
+
Read `references/static-enumeration.md`. Find each
|
|
85
|
+
`wp_register_ability(` call, extract the name, the annotation block,
|
|
86
|
+
and the execute-callback location. Use a multi-line tool (`rg
|
|
87
|
+
--multiline --pcre2`) — the canonical formatting splits the call
|
|
88
|
+
across lines. Record each ability's source-file + line + annotations +
|
|
89
|
+
callback byte range.
|
|
90
|
+
|
|
91
|
+
### 3. (Runtime only) Enumerate via REST + wp-cli
|
|
92
|
+
|
|
93
|
+
Read `references/runtime-harness.md`. Bring the env up using the
|
|
94
|
+
command from `AGENTS.md`, then enumerate via `wp_get_abilities()` over
|
|
95
|
+
wp-cli and cross-check against the static inventory. Source-only →
|
|
96
|
+
FAIL (registration not firing). Runtime-only → WARN (dynamic
|
|
97
|
+
registration path).
|
|
98
|
+
|
|
99
|
+
### 4. Annotation correctness (the adversarial core)
|
|
100
|
+
|
|
101
|
+
Read `references/annotation-correctness.md`. Read each callback body
|
|
102
|
+
and verify it matches the annotation claim:
|
|
103
|
+
|
|
104
|
+
- `readonly: true` → callback must not write to the database, the
|
|
105
|
+
options table, post / user / term / comment data, the filesystem,
|
|
106
|
+
cron, or via non-GET HTTP / REST delegates.
|
|
107
|
+
- `destructive: false` → callback must not delete, refund, void,
|
|
108
|
+
cancel, or trash.
|
|
109
|
+
- `idempotent: true` → repeated calls with the same input have no
|
|
110
|
+
additional effect on the environment (per the `idempotent`
|
|
111
|
+
annotation's docblock in `class-wp-ability.php`). Static catches
|
|
112
|
+
counter writes and per-call cron schedules; runtime adds a
|
|
113
|
+
twin-invocation heuristic for visible state changes.
|
|
114
|
+
|
|
115
|
+
The reference lists common write patterns as a starting set, not a
|
|
116
|
+
checklist — plugin vocabularies vary, and the agent extends with verbs
|
|
117
|
+
specific to the plugin under verification.
|
|
118
|
+
|
|
119
|
+
False positives get suppressed via an inline `// verify-ignore:
|
|
120
|
+
<annotation> -- <reason>` comment.
|
|
121
|
+
|
|
122
|
+
### 5. Permission roundtrip
|
|
123
|
+
|
|
124
|
+
Read `references/permission-roundtrip.md`. Static: classify each
|
|
125
|
+
`permission_callback` against the six shapes (preferred Shape A
|
|
126
|
+
`current_user_can(...)`; FAIL on Shape B-bad `WP_REST_Request`
|
|
127
|
+
patterns or Shape E literal `true`). Runtime: anon and subscriber
|
|
128
|
+
denied; admin allowed (unless deliberately public). When an audit was
|
|
129
|
+
provided, cross-check the registered cap against the audit's declared
|
|
130
|
+
gate.
|
|
131
|
+
|
|
132
|
+
### 6. Schema lints
|
|
133
|
+
|
|
134
|
+
Read `references/schema-lints.md`. Six small principles applied to
|
|
135
|
+
each ability's `input_schema`: object schemas declare
|
|
136
|
+
`additionalProperties`; required fields have descriptions; enums
|
|
137
|
+
non-empty; no `$ref`; defaults are statically constant (including
|
|
138
|
+
`(object) array()`); reference abilities have no required inputs.
|
|
139
|
+
|
|
140
|
+
Cross-reference `../wp-abilities-api/references/input-schema-gotchas.md`
|
|
141
|
+
for the four runtime gotchas (defaults not injected on the
|
|
142
|
+
property-level path, pagination key drift, `empty()` on string IDs,
|
|
143
|
+
direct vs indirect invocation strictness).
|
|
144
|
+
|
|
145
|
+
### 7. Error-code vocabulary
|
|
146
|
+
|
|
147
|
+
Cross-reference `../wp-abilities-api/references/error-code-vocabulary.md`.
|
|
148
|
+
Inspect each callback's `WP_Error` returns; non-vocabulary codes →
|
|
149
|
+
WARN.
|
|
150
|
+
|
|
151
|
+
## Verification
|
|
152
|
+
|
|
153
|
+
The run produces a structured markdown report at the user-specified
|
|
154
|
+
path:
|
|
155
|
+
|
|
156
|
+
```
|
|
157
|
+
---
|
|
158
|
+
Last updated: <YYYY-MM-DD HH:MM>
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
# <Plugin> Abilities Verification — <Static|Runtime> Mode
|
|
162
|
+
|
|
163
|
+
## Status: <PASS|WARN|FAIL>
|
|
164
|
+
|
|
165
|
+
## Audit doc validation (if provided)
|
|
166
|
+
|
|
167
|
+
## Static inventory
|
|
168
|
+
|
|
169
|
+
## Annotation correctness
|
|
170
|
+
| Ability | Claim | Result | Evidence |
|
|
171
|
+
|---|---|---|---|
|
|
172
|
+
|
|
173
|
+
## Permission gates
|
|
174
|
+
|
|
175
|
+
## Schema lints
|
|
176
|
+
|
|
177
|
+
## Error-code vocabulary
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
Every ability is OK, WARN, or FAIL. A single FAIL → top-line FAIL;
|
|
181
|
+
WARNs without FAILs → WARN; otherwise PASS.
|
|
182
|
+
|
|
183
|
+
## Failure modes / debugging
|
|
184
|
+
|
|
185
|
+
- **Env not reachable (runtime)** — env-up failed or Docker isn't
|
|
186
|
+
running. Re-run `wp-project-triage`, then fix the env. Don't fall
|
|
187
|
+
back silently to static without noting it in the report.
|
|
188
|
+
- **No abilities in source** — return a clear "nothing to verify"
|
|
189
|
+
report.
|
|
190
|
+
- **Audit schema mismatch** — point at
|
|
191
|
+
`references/audit-schema-validation.md`; don't auto-fix the audit.
|
|
192
|
+
- **False positive on readonly-writes** — see the `// verify-ignore`
|
|
193
|
+
mechanism in `references/annotation-correctness.md`. Document why
|
|
194
|
+
each suppression is legitimate.
|
|
195
|
+
- **Runtime enumeration smaller than static** — registration hook
|
|
196
|
+
isn't firing. Check init hook timing, activation state, autoloader
|
|
197
|
+
order.
|
|
198
|
+
|
|
199
|
+
## Escalation
|
|
200
|
+
|
|
201
|
+
- Recurring legitimate pattern that trips the adversarial check across
|
|
202
|
+
multiple plugins → propose adding it to the suppression guidance in
|
|
203
|
+
`annotation-correctness.md`. Don't broaden the candidate-pattern
|
|
204
|
+
list speculatively.
|
|
205
|
+
- Audit-schema validator rejects a legitimate audit → the canonical
|
|
206
|
+
schema in `../wp-abilities-audit/references/audit-schema.md` has
|
|
207
|
+
evolved. Update `references/audit-schema-validation.md` to match.
|
|
208
|
+
|
|
209
|
+
## Out of scope
|
|
210
|
+
|
|
211
|
+
Token-budget measurement is a separate verification axis — an
|
|
212
|
+
annotation-clean, schema-clean, runtime-passing ability set can still
|
|
213
|
+
be unshippable if its `tools/list` form burns through an agent's
|
|
214
|
+
context budget. That axis is tracked separately. Do not aggregate
|
|
215
|
+
manual or external measurement into this skill's PASS / FAIL verdict.
|
|
@@ -0,0 +1,154 @@
|
|
|
1
|
+
# Annotation Correctness
|
|
2
|
+
|
|
3
|
+
The adversarial core of this skill: verify what the annotation claims
|
|
4
|
+
by reading the callback. A `readonly: true` ability that actually
|
|
5
|
+
writes is a security and UX disaster, and unit tests don't catch it
|
|
6
|
+
because the mock looks just like the real writer.
|
|
7
|
+
|
|
8
|
+
## Why this matters
|
|
9
|
+
|
|
10
|
+
Agents plan actions on the basis of the annotations they introspect.
|
|
11
|
+
If an ability is annotated `readonly: true`, an orchestrator will
|
|
12
|
+
confidently invoke it in a dry-run, speculative exploration, or
|
|
13
|
+
multi-agent fan-out without thinking twice — because `readonly` means
|
|
14
|
+
"can't break anything".
|
|
15
|
+
|
|
16
|
+
A `readonly: true` ability that actually writes is therefore:
|
|
17
|
+
|
|
18
|
+
1. **A security hazard** — agents will invoke it in contexts where
|
|
19
|
+
side effects are forbidden.
|
|
20
|
+
2. **A UX disaster** — the agent's mental model of what happened
|
|
21
|
+
diverges silently from reality.
|
|
22
|
+
3. **Undetectable at the annotation layer** — the annotation says
|
|
23
|
+
`readonly: true`; nothing in the registration forces it to be true.
|
|
24
|
+
|
|
25
|
+
Unit tests won't catch this class of bug because the mock the test
|
|
26
|
+
constructs looks just like the real writer. What catches it is reading
|
|
27
|
+
the execute callback body and comparing what it does against what the
|
|
28
|
+
annotation says it does.
|
|
29
|
+
|
|
30
|
+
## What each annotation promises
|
|
31
|
+
|
|
32
|
+
| Annotation | What it promises (from core) |
|
|
33
|
+
|---|---|
|
|
34
|
+
| `readonly: true` | No durable writes to user / business state. GET-style side-effect-free. |
|
|
35
|
+
| `destructive: false` | Won't irreversibly destroy data or forfeit money. |
|
|
36
|
+
| `idempotent: true` | Repeated calls with the same arguments produce no additional effect on the environment (per the `idempotent` annotation's docblock in `class-wp-ability.php`). |
|
|
37
|
+
|
|
38
|
+
`readonly: true` prohibits durable writes to user or business state.
|
|
39
|
+
Read-through cache writes (e.g. `set_transient`) and observability
|
|
40
|
+
timestamps (e.g. `last_read_at`) are acceptable when explicitly
|
|
41
|
+
annotated with `verify-ignore` — see the "Suppressing legitimate
|
|
42
|
+
exceptions" section below. The static check treats unannotated writes
|
|
43
|
+
as FAILs; annotated ones pass with the reason recorded as evidence.
|
|
44
|
+
|
|
45
|
+
These overlap but are not redundant: `readonly` is the strictest;
|
|
46
|
+
`destructive: false` is weaker (updates that don't destroy are OK);
|
|
47
|
+
`idempotent` is orthogonal (a POST that writes the same row twice is
|
|
48
|
+
both "writes" and "idempotent").
|
|
49
|
+
|
|
50
|
+
The Abilities REST run controller operationalizes annotations into
|
|
51
|
+
HTTP method routing (`readonly: true` → GET, `destructive && idempotent`
|
|
52
|
+
→ DELETE, otherwise POST — see
|
|
53
|
+
`WP_REST_Abilities_V1_Run_Controller::validate_request_method()`). That
|
|
54
|
+
mapping is the load-bearing semantic; verify checks that each
|
|
55
|
+
callback's behavior is consistent with how the routing will treat it.
|
|
56
|
+
|
|
57
|
+
## How to verify
|
|
58
|
+
|
|
59
|
+
For each ability, locate the `execute_callback` body (see
|
|
60
|
+
`static-enumeration.md` step 4), then:
|
|
61
|
+
|
|
62
|
+
1. **Read the callback end-to-end.** Form a model of what it actually
|
|
63
|
+
does. Don't rely on pattern-matching alone.
|
|
64
|
+
2. **Compare to the claim.** A `readonly: true` callback that writes
|
|
65
|
+
anywhere — the database via `$wpdb`, options / post / user / term /
|
|
66
|
+
comment writes, filesystem, cron schedules, or non-GET HTTP/REST
|
|
67
|
+
delegates — FAILs readonly. A `destructive: false` callback that
|
|
68
|
+
deletes, refunds, voids, cancels, or trashes FAILs destructive. An
|
|
69
|
+
`idempotent: true` callback whose environmental effect *accumulates*
|
|
70
|
+
per call (counters, append-only logs, per-call cron schedules) FAILs
|
|
71
|
+
idempotent.
|
|
72
|
+
3. **Record evidence.** Cite file + line of the offending pattern so a
|
|
73
|
+
reviewer can jump straight to it.
|
|
74
|
+
|
|
75
|
+
Use grep or ripgrep to surface *candidates*. Common writes worth
|
|
76
|
+
looking for:
|
|
77
|
+
|
|
78
|
+
```text
|
|
79
|
+
$wpdb->update / insert / delete / replace
|
|
80
|
+
update_option / add_option / delete_option
|
|
81
|
+
wp_insert_post / wp_update_post / wp_delete_post
|
|
82
|
+
update_post_meta / update_user_meta / update_term_meta
|
|
83
|
+
->save / ->delete / ->set_status / ->add_*
|
|
84
|
+
wp_remote_post / wp_remote_delete
|
|
85
|
+
file_put_contents / wp_upload_bits / unlink / rename
|
|
86
|
+
wp_schedule_event / wp_schedule_single_event
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Treat the list as a starting set, not a checklist. Plugin vocabularies
|
|
90
|
+
vary — domain-specific verbs (`->markAsPaid`, `->commit`, `->refund`)
|
|
91
|
+
and framework patterns (Doctrine `->persist`, queue `->dispatch`) won't
|
|
92
|
+
appear above. Once you've grepped for candidates, read the callback to
|
|
93
|
+
confirm whether each hit is actually a write and whether it
|
|
94
|
+
contradicts the annotation in context.
|
|
95
|
+
|
|
96
|
+
## Known blind spots
|
|
97
|
+
|
|
98
|
+
Static reading + grep can't reach every write. A static-mode PASS
|
|
99
|
+
means "no obvious-shape violations," not "verified write-free."
|
|
100
|
+
|
|
101
|
+
| Blind spot | Why static misses it | Mitigation |
|
|
102
|
+
|---|---|---|
|
|
103
|
+
| Indirected service writes — `$repo->persist()`, `$service->commit()`, custom verbs. | Any finite verb list drifts; domain vocabulary varies. | Inspect callbacks that touch custom services or repositories. |
|
|
104
|
+
| `do_action()` whose listeners write. | Provenance ambiguity: ability looks clean; system mutates state in a listener. | Audit listeners on the action. If any writes, downgrade or split. |
|
|
105
|
+
| Implicit core hooks fired by WP API calls — `wp_insert_post()` fires `save_post`; `update_option()` fires `updated_option`; `wp_create_user()` fires `user_register`; etc. | The WP API call IS the write; the hooks fire automatically as a side effect. Agents looking for `do_action()` won't see this. | Treat any WP write-API call as a write regardless of whether the callback also calls `do_action()`. |
|
|
106
|
+
| Action Scheduler / deferred writes — `as_schedule_single_action()`, `WC()->queue()->schedule_single()`, custom job dispatchers. | The callback returns cleanly with no immediately visible DB mutation; the durable write lands later in the AS tables. A static grep for `$wpdb->insert` won't catch it. | Treat scheduler dispatches as writes. The "no additional effect on the environment" promise of `idempotent: true` is violated by accumulating queued jobs even if the immediate return value is constant. |
|
|
107
|
+
| Variable-built HTTP methods on delegate helpers. | Static can't follow runtime values. | Treat callers of helpers whose default method isn't `GET` as suspect. |
|
|
108
|
+
| Tautological capability gates — `current_user_can('read')` on a "private" ability. | The cap looks valid; subscribers happen to hold it. | Cross-reference the permission roundtrip — subscribers should be denied. |
|
|
109
|
+
|
|
110
|
+
For high-stakes plugins, run runtime mode (see `runtime-harness.md`)
|
|
111
|
+
before landing — it catches some blind spots via twin-invocation diff
|
|
112
|
+
and live state inspection.
|
|
113
|
+
|
|
114
|
+
## Suppressing legitimate exceptions
|
|
115
|
+
|
|
116
|
+
When a pattern that looks like a write is semantically a read (e.g.
|
|
117
|
+
populating a read-through cache via `set_transient`, updating a
|
|
118
|
+
`last_read_at` timestamp for tracking, diagnostic logging), suppress
|
|
119
|
+
with an inline comment on the offending line:
|
|
120
|
+
|
|
121
|
+
```php
|
|
122
|
+
// verify-ignore: readonly -- writes to read-through cache; semantically a read.
|
|
123
|
+
set_transient( $cache_key, $data, HOUR_IN_SECONDS );
|
|
124
|
+
```
|
|
125
|
+
|
|
126
|
+
Format: `// verify-ignore: <annotation> -- <reason>`. Legal annotation
|
|
127
|
+
names: `readonly`, `destructive`, `idempotent`, `all`. Narrower is
|
|
128
|
+
better than `all`.
|
|
129
|
+
|
|
130
|
+
## Runtime check complement
|
|
131
|
+
|
|
132
|
+
For `idempotent: true` abilities, runtime mode adds a heuristic: invoke
|
|
133
|
+
twice with the same input and compare. See `runtime-harness.md`
|
|
134
|
+
Check 6. Differing returns are a *signal* to inspect, not a verdict —
|
|
135
|
+
under core's definition, the question is whether the *environment*
|
|
136
|
+
changed, not whether the *return value* matches. A response that
|
|
137
|
+
embeds a per-call timestamp / nonce / random ID is fine; a response
|
|
138
|
+
that reflects a counter that grew between calls is not.
|
|
139
|
+
|
|
140
|
+
## Report format
|
|
141
|
+
|
|
142
|
+
Each finding gets one row in the run's "Annotation correctness" table:
|
|
143
|
+
|
|
144
|
+
```markdown
|
|
145
|
+
| Ability | Claim | Result | Evidence |
|
|
146
|
+
|---|---|---|---|
|
|
147
|
+
| myplugin/get-things | readonly=true | OK | callback reads only |
|
|
148
|
+
| myplugin/get-things-with-counts | readonly=true | FAIL | `src/Abilities/Things.php:142`: `$wpdb->update( $table, ... )` |
|
|
149
|
+
| myplugin/submit-thing | destructive=false | OK | no destructive patterns |
|
|
150
|
+
| myplugin/submit-thing | idempotent=false | OK | check only applies when idempotent=true; false annotation acknowledged |
|
|
151
|
+
```
|
|
152
|
+
|
|
153
|
+
The evidence column MUST cite file + line so a reviewer can jump
|
|
154
|
+
straight to the issue.
|
|
@@ -0,0 +1,131 @@
|
|
|
1
|
+
# Audit Schema Validation
|
|
2
|
+
|
|
3
|
+
How `wp-abilities-verify` validates an audit document produced by
|
|
4
|
+
`wp-abilities-audit`. The canonical schema (field tables, types,
|
|
5
|
+
invariants, known limitations) lives in
|
|
6
|
+
`../../wp-abilities-audit/references/audit-schema.md` — this reference
|
|
7
|
+
covers only the validation procedure: how to extract the YAML, what
|
|
8
|
+
checks to run in what order, and how to report results.
|
|
9
|
+
|
|
10
|
+
If a field type or shape question is not answered here, look in the
|
|
11
|
+
canonical schema. Do NOT duplicate field tables in this file — the
|
|
12
|
+
canonical is the single source of truth.
|
|
13
|
+
|
|
14
|
+
## Why verify owns the validator
|
|
15
|
+
|
|
16
|
+
Verify fails fast on a malformed audit so the rest of its procedure can
|
|
17
|
+
assume well-formed input. Audit produces; verify validates the
|
|
18
|
+
production. Co-locating the validator with verify keeps the
|
|
19
|
+
"validate audit" step in the same procedure as "validate registered
|
|
20
|
+
abilities" and lets a single run produce one consolidated report.
|
|
21
|
+
|
|
22
|
+
## Step 1 — extract the YAML
|
|
23
|
+
|
|
24
|
+
The audit doc is a markdown file with a single fenced ` ```yaml ` block
|
|
25
|
+
containing the structured fields:
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
# Scan for the ```yaml fence and capture until the closing ``` fence.
|
|
29
|
+
awk '/^```yaml$/{f=1;next} /^```$/{f=0} f' <audit-doc.md> > /tmp/audit.yaml
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
If the audit has multiple YAML blocks (it shouldn't, but defensively),
|
|
33
|
+
take the first one with `proposed_abilities` as a top-level key.
|
|
34
|
+
|
|
35
|
+
Parse with any YAML library — `js-yaml` from Node, `yaml` (Python), or
|
|
36
|
+
`yq` from the command line. None of the canonical fields require
|
|
37
|
+
non-standard YAML features (no anchors, no aliases), so a plain
|
|
38
|
+
`yaml.load` is sufficient.
|
|
39
|
+
|
|
40
|
+
## Step 2 — validate against the canonical schema
|
|
41
|
+
|
|
42
|
+
Apply the field-shape rules defined in
|
|
43
|
+
`../../wp-abilities-audit/references/audit-schema.md`. Specifically:
|
|
44
|
+
|
|
45
|
+
1. Every required top-level field is present and non-empty (see
|
|
46
|
+
"Top-level fields" in the canonical).
|
|
47
|
+
2. `capability_gate` matches one of the legal shapes (single string,
|
|
48
|
+
`{read, write}` object, or — with WARN per the canonical's "Known
|
|
49
|
+
limitations" — the legacy slash-separated string).
|
|
50
|
+
3. Every entry in `proposed_abilities` has every required per-ability
|
|
51
|
+
field with the right type (see "`proposed_abilities`" in the
|
|
52
|
+
canonical).
|
|
53
|
+
4. Each ability's `annotations` block has all three booleans
|
|
54
|
+
(`readonly`, `destructive`, `idempotent`) as actual booleans —
|
|
55
|
+
string `"true"` / `"false"` is FAIL (indicates a quoting bug).
|
|
56
|
+
5. Each ability's `backing` is either an object with the canonical
|
|
57
|
+
fields or `null`; `null` is WARN, not FAIL (it's intentional gap
|
|
58
|
+
output).
|
|
59
|
+
|
|
60
|
+
Missing required field → FAIL. Wrong type → FAIL. Legacy
|
|
61
|
+
`capability_gate` slash-string → WARN.
|
|
62
|
+
|
|
63
|
+
## Step 3 — whole-audit invariants
|
|
64
|
+
|
|
65
|
+
Run these after per-field validation passes:
|
|
66
|
+
|
|
67
|
+
### Exactly 0 or 1 abilities with `reference_ability: true`
|
|
68
|
+
|
|
69
|
+
Count abilities where `reference_ability` is `true`. More than 1 → FAIL
|
|
70
|
+
(the schema permits at most one reference; multiple are ambiguous for
|
|
71
|
+
implementers picking a starting point).
|
|
72
|
+
|
|
73
|
+
```js
|
|
74
|
+
const refCount = audit.proposed_abilities.filter(a => a.reference_ability === true).length;
|
|
75
|
+
if (refCount > 1) fail("multiple abilities claim reference_ability: true");
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
### Every `backing: null` ability appears in `surfaced_gaps`
|
|
79
|
+
|
|
80
|
+
Per the canonical's "Known limitations": a `null` backing is intentional
|
|
81
|
+
gap output and MUST be paired with a matching `surfaced_gaps` entry.
|
|
82
|
+
|
|
83
|
+
```js
|
|
84
|
+
const gapNames = new Set((audit.surfaced_gaps || []).map(g => g.name));
|
|
85
|
+
for (const ability of audit.proposed_abilities) {
|
|
86
|
+
if (ability.backing === null && !gapNames.has(ability.name)) {
|
|
87
|
+
fail(`ability ${ability.name} has backing: null but is missing from surfaced_gaps`);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
### `excluded_from_mvp` and `surfaced_gaps` may be empty
|
|
93
|
+
|
|
94
|
+
Both are optional; empty arrays are legal. Missing entirely → WARN
|
|
95
|
+
(schema expects them, even if empty).
|
|
96
|
+
|
|
97
|
+
## Step 4 — emit the report section
|
|
98
|
+
|
|
99
|
+
Each check goes into the "Audit doc validation" section of the run's
|
|
100
|
+
final report:
|
|
101
|
+
|
|
102
|
+
```markdown
|
|
103
|
+
## Audit doc validation
|
|
104
|
+
|
|
105
|
+
| Check | Result | Detail |
|
|
106
|
+
|---|---|---|
|
|
107
|
+
| Top-level required fields | OK | All 7 required fields present |
|
|
108
|
+
| `capability_gate` shape | OK | string (single-cap) |
|
|
109
|
+
| Per-ability fields | WARN | 1 ability has `backing: null` (intentional) |
|
|
110
|
+
| `reference_ability` uniqueness | OK | 1 ability marked |
|
|
111
|
+
| `surfaced_gaps` consistency | OK | all `backing: null` entries present |
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
A single FAIL in this section makes the whole run FAIL; verify cannot
|
|
115
|
+
meaningfully continue without a trustworthy audit. WARN entries don't
|
|
116
|
+
block the rest of the procedure.
|
|
117
|
+
|
|
118
|
+
The procedure is manual-but-deterministic: follow the steps above in
|
|
119
|
+
order, emit the report section, and fail fast on any missing required
|
|
120
|
+
field. A future contribution may add a deterministic CLI helper that
|
|
121
|
+
extracts the YAML fence and applies the rules end-to-end; until that
|
|
122
|
+
exists, the steps above are the contract.
|
|
123
|
+
|
|
124
|
+
## Escalation
|
|
125
|
+
|
|
126
|
+
If the validator rejects an audit that's actually well-formed, the
|
|
127
|
+
canonical schema in
|
|
128
|
+
`../../wp-abilities-audit/references/audit-schema.md` has evolved.
|
|
129
|
+
Update this file's procedure to match (likely adding a new invariant
|
|
130
|
+
or relaxing a field rule). Don't loosen the validation in isolation —
|
|
131
|
+
the canonical schema is the contract; this file is the enforcer.
|