Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
85 commits
Select commit Hold shift + click to select a range
6563262
Add the Foundry spec for this flight
ericmann Sep 24, 2026
77dcb0b
chore: pipeline friction (plan)
ericmann Sep 24, 2026
91bc3d3
plan: derive build plan from SPEC
ericmann Sep 24, 2026
b2c2864
chore: start implementation run
ericmann Sep 24, 2026
e1e7676
P1-01: Provision the throwaway install with a pinned WP-CLI
ericmann Sep 24, 2026
922cae0
progress: P1-01 done
ericmann Sep 24, 2026
c964e29
P1-02: Push phase 1 and record the manual check
ericmann Sep 24, 2026
5111dbe
progress: P1-02 done
ericmann Sep 24, 2026
d57c943
P2-01: Create smoke.sh with the TAP helpers and the has-command matrix
ericmann Sep 24, 2026
3b1f523
progress: P2-01 done
ericmann Sep 24, 2026
1fdce68
P2-02: Check the flag table exactly and pin the --version cause
ericmann Sep 24, 2026
d70f060
progress: P2-02 done
ericmann Sep 24, 2026
a874194
P2-03: Push phase 2 and record the manual check
ericmann Sep 24, 2026
7678c01
progress: P2-03 done
ericmann Sep 24, 2026
cb8145c
P3-01: Cover set and get, masking, stdin, porcelain, slots, and JSON
ericmann Sep 24, 2026
dfa32d0
progress: P3-01 done
ericmann Sep 24, 2026
fe509b7
P3-02: Cover list filters, retire, delete, absence, keys, health, dro…
ericmann Sep 24, 2026
d6748e1
progress: P3-02 done
ericmann Sep 24, 2026
7b1acc3
P3-03: Push phase 3 and record the manual check
ericmann Sep 24, 2026
2ce54a1
progress: P3-03 done
ericmann Sep 24, 2026
eeea787
P4-01: Rotate the site key end to end
ericmann Sep 24, 2026
f4bb964
progress: P4-01 done
ericmann Sep 24, 2026
d92a115
P4-02: Load drop-ins through the real loader, with cleanup on exit
ericmann Sep 24, 2026
3578368
progress: P4-02 done
ericmann Sep 24, 2026
5948fe9
P4-03: Push phase 4 and record the manual check
ericmann Sep 24, 2026
5a93331
progress: P4-03 done
ericmann Sep 24, 2026
bd38ff2
progress: P5-01 blocked
ericmann Sep 24, 2026
0227b71
chore: pipeline friction (implement)
ericmann Sep 24, 2026
860bd33
progress: skip P5-02, P5-03, P6-01, P6-02, P7-01, P7-02, P7-03, P7-04
ericmann Sep 24, 2026
0af5203
chore: handoff for review
ericmann Sep 24, 2026
3fdc23e
chore: round 0 implemented
ericmann Sep 24, 2026
8c773ce
chore: pipeline friction (review)
ericmann Sep 24, 2026
45afc11
chore: pipeline friction (review)
ericmann Sep 24, 2026
9857a02
review: round 1
ericmann Sep 24, 2026
a292a0b
chore: start review-fix round 1
ericmann Sep 24, 2026
fe08d2a
R1-01: Preserve the root key across multisite conversion
ericmann Sep 24, 2026
eb5f20f
progress: R1-01 done
ericmann Sep 24, 2026
ee77af8
chore: pipeline friction (implement)
ericmann Sep 24, 2026
42154c8
R1-02: Make the smoke list-value and rotation assertions able to fail
ericmann Sep 24, 2026
dca4425
progress: R1-02 done
ericmann Sep 24, 2026
1643f2e
R1-03: Widen the smoke diagnostic constraint to the variables the sui…
ericmann Sep 24, 2026
2cd46f2
progress: R1-03 done
ericmann Sep 24, 2026
2b76647
chore: handoff for review
ericmann Sep 24, 2026
23d65b9
chore: round 1 implemented
ericmann Sep 24, 2026
d659ce0
review: round 2
ericmann Sep 24, 2026
6fd1e75
chore: start review-fix round 2
ericmann Sep 24, 2026
53619ee
P5-01: Convert to multisite and run the network pass
ericmann Sep 24, 2026
804227e
progress: P5-01 done
ericmann Sep 24, 2026
9822d97
P5-02: Wire smoke into make ci, bin/ci-local.sh, and a smoke CI job
ericmann Sep 24, 2026
b9280df
progress: P5-02 done
ericmann Sep 24, 2026
e3879a6
P5-03: Push phase 5 and record the manual check
ericmann Sep 24, 2026
f4df960
progress: P5-03 done
ericmann Sep 24, 2026
31961a1
P6-01: Prove each historical bug fails the smoke test
ericmann Sep 24, 2026
01f0253
progress: P6-01 done
ericmann Sep 24, 2026
3b9e557
P6-02: Push phase 6 and record the manual check
ericmann Sep 24, 2026
4886a53
progress: P6-02 done
ericmann Sep 24, 2026
c2f259c
P7-01: Update the coverage gaps, the spec pages, and the detailed spe…
ericmann Sep 24, 2026
cce2bc3
progress: P7-01 done
ericmann Sep 24, 2026
f1697e9
P7-02: Document make smoke in the README and the CI reference
ericmann Sep 24, 2026
9e78e9e
progress: P7-02 done
ericmann Sep 24, 2026
b63f4e7
P7-03: Write the journal entry and link it from the index
ericmann Sep 24, 2026
ee4ad02
progress: P7-03 done
ericmann Sep 24, 2026
79f08c1
P7-04: Push phase 7 and record the final manual checks
ericmann Sep 24, 2026
106785a
progress: P7-04 done
ericmann Sep 24, 2026
504ff07
R2-01: Make the smoke diagnostic rule catch any key- or value-holding…
ericmann Sep 24, 2026
4ea429b
progress: R2-01 done
ericmann Sep 24, 2026
c2f4d62
chore: handoff for review
ericmann Sep 24, 2026
d7e5c06
chore: round 2 implemented
ericmann Sep 24, 2026
26b5ac3
review: round 3
ericmann Sep 25, 2026
9bc796f
chore: start review-fix round 3
ericmann Sep 25, 2026
f264c3e
R3-01: Correct bug 1's documented cause and other inaccurate claims
ericmann Sep 25, 2026
aec11a5
progress: R3-01 done
ericmann Sep 25, 2026
25dbcd1
chore: handoff for review
ericmann Sep 25, 2026
d1ca11c
chore: round 3 implemented
ericmann Sep 25, 2026
79410c8
review: round 4
ericmann Sep 25, 2026
67af400
chore: start review-fix round 4
ericmann Sep 25, 2026
37cff5f
R4-01: Correct journal's bug 1 account and case A's comment
ericmann Sep 25, 2026
ceeeb87
progress: R4-01 done
ericmann Sep 25, 2026
3a82ea3
chore: handoff for review
ericmann Sep 25, 2026
e936c6d
chore: round 4 implemented
ericmann Sep 25, 2026
9e61d13
review: round 5 approved
ericmann Sep 25, 2026
2b05b6d
chore: build summary
ericmann Sep 25, 2026
0d229e1
Remove the Foundry build bookkeeping
ericmann Sep 25, 2026
0e1a9b2
Date the journal tracking pages this branch changed
ericmann Sep 25, 2026
a354ba2
Merge main into build/cli-smoke
ericmann Sep 25, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
43 changes: 43 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -264,3 +264,46 @@ jobs:
exit 1

- run: make test-examples

# A dispatch-layer smoke test, not another correctness suite: three real
# dispatch bugs (--version=previous returning the current value,
# --format rejected for want of a description line, and migrate-legacy
# unregistered without @subcommand) reached a green PHPUnit suite because
# nothing exercised the real `wp` binary end to end. 7.4 is the floor
# this API has to run on; 8.3 is the newest PHP the rest of the matrix
# covers. No Composer step: the smoke test provisions its own WP-CLI phar
# and WordPress checkout and needs neither `vendor/` nor the WordPress
# test suite.
smoke:
name: "Smoke / PHP ${{ matrix.php }}"
needs: static
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix:
php: ['7.4', '8.3']
services:
mysql:
image: mysql:8.0
env:
MYSQL_ALLOW_EMPTY_PASSWORD: 'yes'
MYSQL_DATABASE: wordpress_smoke
ports:
- 3306:3306
options: >-
--health-cmd="mysqladmin ping"
--health-interval=10s
--health-timeout=5s
--health-retries=5
steps:
- name: Check out
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1

- name: Set up PHP ${{ matrix.php }}
uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # 2.37.2
with:
php-version: ${{ matrix.php }}
extensions: sodium, mysqli
coverage: none

- run: make smoke DB_HOST=127.0.0.1
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -24,3 +24,5 @@ site/.astro/
# Spacefast CLI link and state. Written wherever sf publish runs from; never commit it.
.spacefast/

# The WP-CLI smoke test's throwaway install; see bin/smoke-install.sh.
/.smoke/
9 changes: 7 additions & 2 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@ DB_NAME ?= wordpress_test
DB_USER ?= root
DB_PASS ?=
DB_HOST ?= 127.0.0.1
SMOKE_DB_NAME ?= wordpress_smoke

.DEFAULT_GOAL := help
.PHONY: help install lint lint-fix compat analyse test test-ms test-examples coverage reference reference-check ci clean
.PHONY: help install lint lint-fix compat analyse test test-ms test-examples coverage reference reference-check ci smoke clean

help: ## Show this help.
@grep -hE '^[a-zA-Z_-]+:.*?## ' $(MAKEFILE_LIST) \
Expand Down Expand Up @@ -54,7 +55,11 @@ reference: ## Regenerate docs/reference/ from source docblocks.
reference-check: ## Fail if docs/reference/ is stale relative to the source.
php bin/gen-reference.php --check

ci: lint compat analyse reference-check test test-ms ## Everything CI runs.
smoke: ## Provision the throwaway install and run the WP-CLI smoke test.
SMOKE_DB_NAME=$(SMOKE_DB_NAME) DB_USER=$(DB_USER) DB_PASS="$(DB_PASS)" DB_HOST=$(DB_HOST) WP_VERSION=$(WP_VERSION) bin/smoke-install.sh
tests/smoke/smoke.sh

ci: lint compat analyse reference-check test test-ms smoke ## Everything CI runs.

# Local Vault dev server for the vault-provider example (pinned digest):
# docker run -d --name secrets-api-vault -p 8201:8200 -e VAULT_DEV_ROOT_TOKEN_ID=dev-root --cap-add=IPC_LOCK hashicorp/vault@sha256:47f14a6acb98f48d798a07df7c83f23a6e636e1cf724c5f8ff165cb32667a1e2
Expand Down
9 changes: 6 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,8 @@ composer install
bin/ci-local.sh
```

Add `--keep` to leave the environment running between iterations.
Add `--keep` to leave the environment running between iterations. `bin/ci-local.sh` now also runs
the WP-CLI smoke test inside wp-env, against its own throwaway install.

If you already have a WordPress test suite and a database, skip wp-env entirely:

Expand All @@ -49,6 +50,7 @@ target list.
| `make test-examples` | phpunit against `examples/*/tests`, needs Moto running (see `examples/README.md`); not part of `make ci` |
| `make coverage` | phpunit with an HTML coverage report (see `docs/journal/test-coverage-gaps.md` re: wp-env) |
| `make reference` / `make reference-check` | regenerate `docs/reference/` from docblocks / fail if it is stale |
| `make smoke` | provision a throwaway WordPress in `.smoke/` and drive `wp secret` / `wp network-secret` end to end (needs MySQL and network access) |
| `make ci` | all of the above |

Runners without egress to wordpress.org can point the installer at a mirror with `WP_MIRROR_BASE`
Expand Down Expand Up @@ -167,8 +169,9 @@ stores credentials, and a flaw in it is a flaw in the thing protecting everythin

CI (`.github/workflows/ci.yml`) is a thin wrapper around the `make` targets above, running on
github.com's hosted runners: static analysis gates a PHP 7.4/8.0/8.3 × WordPress latest/trunk
matrix plus a multisite job, plus an `examples` job that runs the platform bindings against a
Vault service container. See [`docs/reference/ci.md`](docs/reference/ci.md).
matrix plus a multisite job, a PHP 7.4/8.3 smoke job driving a real `wp` binary, and an
`examples` job that runs the platform bindings against Moto and Vault service containers. See
[`docs/reference/ci.md`](docs/reference/ci.md).

## License

Expand Down
11 changes: 11 additions & 0 deletions bin/ci-local.sh
Original file line number Diff line number Diff line change
Expand Up @@ -71,5 +71,16 @@ echo "==> test (multisite)"
"${WP_ENV[@]}" run --env-cwd="$CONTAINER_CWD" tests-cli \
env WP_MULTISITE=1 vendor/bin/phpunit -c phpunit-multisite.xml.dist

# The smoke test provisions its own throwaway install under .smoke/, with its
# own database (wordpress_smoke), inside wp-env's *development* `cli`
# container -- never the `tests-cli` container or its wordpress_test
# database, which the PHPUnit suite above owns. The two scripts run directly,
# rather than through `make smoke`, because the `cli` container has no `make`.
echo "==> smoke (WP-CLI against a throwaway install)"
"${WP_ENV[@]}" run --env-cwd="$CONTAINER_CWD" cli \
env DB_HOST=mysql DB_USER=root DB_PASS=password bash bin/smoke-install.sh
"${WP_ENV[@]}" run --env-cwd="$CONTAINER_CWD" cli \
bash tests/smoke/smoke.sh

echo
echo "All green."
162 changes: 162 additions & 0 deletions bin/smoke-install.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,162 @@
#!/usr/bin/env bash
#
# Provision a throwaway single-site WordPress install for the WP-CLI smoke test.
#
# This script owns everything under .smoke/: a pinned wp-cli.phar, its download
# cache, and a full WordPress checkout with its own "wordpress_smoke" database.
# It never touches wp-env's own dev or test environment, because that one shares
# wp-content with PHPUnit -- and a drop-in the smoke test writes would sit in
# front of the PHPUnit suite. See tests/smoke/SPEC.md.
#
set -euo pipefail
cd "$(dirname "$0")/.."

# Pinned wp-cli release. Resolved from
# https://api.github.com/repos/wp-cli/wp-cli/releases/latest on 2026-09-24 and
# verified against that release's own published
# wp-cli-2.12.0.phar.sha256 before computing this digest -- the same discipline
# ci.yml applies to action SHAs.
WP_CLI_VERSION="2.12.0"
WP_CLI_SHA256="ce34ddd838f7351d6759068d09793f26755463b4a4610a5a5c0a97b68220d85c"
WP_CLI_URL="https://github.com/wp-cli/wp-cli/releases/download/v${WP_CLI_VERSION}/wp-cli-${WP_CLI_VERSION}.phar"

SMOKE_DIR="$PWD/.smoke"
SMOKE_DB_NAME="${SMOKE_DB_NAME:-wordpress_smoke}"
DB_USER="${DB_USER:-root}"
DB_PASS="${DB_PASS:-}"
DB_HOST="${DB_HOST:-127.0.0.1}"
WP_VERSION="${WP_VERSION:-latest}"
SMOKE_URL="${SMOKE_URL:-http://smoke.test}"

# The PHPUnit suite drops and recreates wordpress_test on every run; this
# script drops and recreates whatever SMOKE_DB_NAME names. Refusing when they
# collide protects the PHPUnit suite's database from the smoke test, and vice
# versa.
if [ "$SMOKE_DB_NAME" = "wordpress_test" ]; then
echo "SMOKE_DB_NAME must not be wordpress_test: that database belongs to the PHPUnit suite." >&2
exit 1
fi

export WP_CLI_CACHE_DIR="$SMOKE_DIR/cache"

WP=( php -d display_errors=stderr -d log_errors=0 "$SMOKE_DIR/wp-cli.phar" --path="$SMOKE_DIR/wordpress" --allow-root )

download() {
# $1 = URL, $2 = destination path.
if command -v curl >/dev/null 2>&1; then
curl -fsSL -o "$2" "$1"
elif command -v wget >/dev/null 2>&1; then
wget -nv -O "$2" "$1"
else
php -r 'copy($argv[1], $argv[2]);' -- "$1" "$2"
fi
}

sha256_of() {
if command -v sha256sum >/dev/null 2>&1; then
sha256sum "$1" | awk '{print $1}'
else
shasum -a 256 "$1" | awk '{print $1}'
fi
}

fetch_wp_cli() {
mkdir -p "$SMOKE_DIR" "$WP_CLI_CACHE_DIR"

if [ -f "$SMOKE_DIR/wp-cli.phar" ]; then
ACTUAL=$(sha256_of "$SMOKE_DIR/wp-cli.phar")
if [ "$ACTUAL" = "$WP_CLI_SHA256" ]; then
return
fi
fi

download "$WP_CLI_URL" "$SMOKE_DIR/wp-cli.phar"

ACTUAL=$(sha256_of "$SMOKE_DIR/wp-cli.phar")
if [ "$ACTUAL" != "$WP_CLI_SHA256" ]; then
rm -f "$SMOKE_DIR/wp-cli.phar"
echo "wp-cli.phar checksum mismatch." >&2
echo "expected: $WP_CLI_SHA256" >&2
echo "actual: $ACTUAL" >&2
exit 1
fi
}

fetch_wp_cli

if [ ! -f "$SMOKE_DIR/wordpress/wp-load.php" ]; then
mkdir -p "$SMOKE_DIR/wordpress"
"${WP[@]}" core download --version="$WP_VERSION"
fi

# Every run starts single-site with no drop-in: a leftover secrets.php or
# multisite wp-config would make this script's own idempotence lie about what
# state a fresh run leaves behind.
rm -f "$SMOKE_DIR/wordpress/wp-config.php" "$SMOKE_DIR/wordpress/wp-content/secrets.php"

"${WP[@]}" config create \
--dbname="$SMOKE_DB_NAME" \
--dbuser="$DB_USER" \
--dbpass="$DB_PASS" \
--dbhost="$DB_HOST" \
--skip-check \
--force

# Drop and recreate the database with mysqli directly, rather than a `wp db`
# subcommand or the `mysql` client binary, so this script stays dependency-free
# beyond wp and php. Host/port/socket parsing mirrors install_db() in
# bin/install-wp-tests.sh. Arguments are passed as $argv, never interpolated
# into the PHP source.
IFS=':' read -r DB_HOSTNAME DB_SOCK_OR_PORT <<< "$DB_HOST"
DB_PORT=""
DB_SOCKET=""
if [[ "$DB_SOCK_OR_PORT" =~ ^[0-9]+$ ]]; then
DB_PORT="$DB_SOCK_OR_PORT"
elif [ -n "${DB_SOCK_OR_PORT:-}" ]; then
DB_SOCKET="$DB_SOCK_OR_PORT"
fi

php -r '
list( $host, $user, $pass, $name, $port, $socket ) = array_slice( $argv, 1 );

$port = $port !== "" ? (int) $port : null;
$socket = $socket !== "" ? $socket : null;

mysqli_report( MYSQLI_REPORT_OFF );
$link = mysqli_init();

if ( ! $link || ! @mysqli_real_connect( $link, $host, $user, $pass, "", $port, $socket ) ) {
fwrite( STDERR, "mysqli connect error: " . mysqli_connect_error() . "\n" );
exit( 1 );
}

if ( ! mysqli_query( $link, "DROP DATABASE IF EXISTS `" . $name . "`" ) ) {
fwrite( STDERR, "mysqli error: " . mysqli_error( $link ) . "\n" );
exit( 1 );
}

if ( ! mysqli_query( $link, "CREATE DATABASE `" . $name . "`" ) ) {
fwrite( STDERR, "mysqli error: " . mysqli_error( $link ) . "\n" );
exit( 1 );
}
' -- "$DB_HOSTNAME" "$DB_USER" "$DB_PASS" "$SMOKE_DB_NAME" "$DB_PORT" "$DB_SOCKET"

"${WP[@]}" core install \
--url="$SMOKE_URL" \
--title="Secrets API smoke" \
--admin_user=smoke \
--admin_password="$(php -r 'echo bin2hex(random_bytes(16));')" \
--admin_email=smoke@example.com \
--skip-email

# Relative on purpose: the same link resolves on the host and inside the
# wp-env container.
ln -sfn ../../../.. "$SMOKE_DIR/wordpress/wp-content/plugins/secrets-api"
"${WP[@]}" plugin activate secrets-api

KEY="$("${WP[@]}" secret generate-key)"
# --quiet: `wp config set` otherwise echoes the constant's value in its
# "Success" line, which would put the generated key in plain sight.
"${WP[@]}" config set WP_SECRETS_KEY "$KEY" --type=constant --quiet

echo "Smoke install ready: $SMOKE_DIR/wordpress (database $SMOKE_DB_NAME)"
7 changes: 3 additions & 4 deletions cli/class-wp-cli-secret-command.php
Original file line number Diff line number Diff line change
Expand Up @@ -114,10 +114,9 @@ public function set( $args, $assoc_args ) {
* [--slot=<slot>]
* : Which stored version to read.
*
* Named --slot rather than --version because WP-CLI consumes `--version`
* itself before a subcommand ever sees it: passing --version=previous
* silently yielded the current value, since the flag was swallowed and the
* synopsis default filled in behind it.
* Named --slot rather than --version to avoid the value being dropped by
* wrappers such as `wp-env run`, which consume --version themselves (the
* original bug), and to avoid confusion with `wp --version`.
* ---
* default: current
* options:
Expand Down
1 change: 1 addition & 0 deletions docs/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,7 @@ directory holds everything longer than that.
- [`2026-09-04-0-1-0-is-public.md`](journal/2026-09-04-0-1-0-is-public.md) — devlog: what 0.1.0 shipped, what it left out, and the road to 7.2.
- [`2026-09-24-a-kms-keyring.md`](journal/2026-09-24-a-kms-keyring.md) — devlog: the first real `WP_Secrets_Keyring`, the root-key cache and `rotate --from` it drove, and what it found.
- [`2026-09-24-a-vault-provider.md`](journal/2026-09-24-a-vault-provider.md) — devlog: the Vault KV v2 example, the AWS site-scope bug it found, and what stayed open.
- [`2026-09-24-testing-the-cli-for-real.md`](journal/2026-09-24-testing-the-cli-for-real.md) — devlog: the WP-CLI smoke test, the root-key bug it found, and the last 🟡 coverage gap closing.
- [`open-questions.md`](journal/open-questions.md) — what is still deliberately undecided.
- [`proposal-questions.md`](journal/proposal-questions.md) — the five questions the proposal asked, and the answers so far.
- [`test-coverage-gaps.md`](journal/test-coverage-gaps.md) — paths the suite cannot reach and what was verified by hand.
Loading
Loading