From bb39e76f1c6d6028634f9faad13d4c92e842cb1d Mon Sep 17 00:00:00 2001 From: MobileMage Date: Sun, 4 Oct 2026 02:09:57 +0100 Subject: [PATCH 1/2] chore(repo): move to spec 0.3.0, which defines the postcode field The spec now holds the contract of the field and its message catalogue, which the next packages build on. The grammar and the client contract stay as they were, so every function of the package gives the same results and the package only reports the new spec version. The agents file names the new interface file, so an agent that needs a field symbol finds where the spec lists it. Signed-off-by: MobileMage --- .changes/unreleased/Changed-20261004-020848.yaml | 3 +++ AGENTS.md | 2 +- CONTEXT.md | 12 ++++++++++++ README.md | 2 +- docs/api-dump.md | 2 +- spec | 2 +- src/Postcode.php | 2 +- 7 files changed, 20 insertions(+), 5 deletions(-) create mode 100644 .changes/unreleased/Changed-20261004-020848.yaml diff --git a/.changes/unreleased/Changed-20261004-020848.yaml b/.changes/unreleased/Changed-20261004-020848.yaml new file mode 100644 index 0000000..59d4d14 --- /dev/null +++ b/.changes/unreleased/Changed-20261004-020848.yaml @@ -0,0 +1,3 @@ +kind: Changed +body: Implement spec 0.3.0. It adds the postcode field and its messages, and results stay the same. +time: 2026-10-04T02:08:48.403352+01:00 diff --git a/AGENTS.md b/AGENTS.md index bbb5e2e..df59905 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -11,7 +11,7 @@ This repo is part of Gatepost, unofficial open-source developer tools for Nigeri ## Rules that no config file shows -- If the task needs a public symbol that the Interface section of `spec/grammar.md` does not list, add nothing, not even an internal helper. Stop, and say that a spec change must come first (API-1, CS-8). +- If the task needs a public symbol that no Interface section of the spec lists, in `spec/grammar.md`, `spec/client.md` or `spec/field.md`, add nothing, not even an internal helper. Stop, and say that a spec change must come first (API-1, CS-8). - Call only the documented NIPOST gateway endpoints. - Commit only NIPOST's published test codes and synthetic values, such as `FC-01-Z99-ZZ-01`. - Sign off each commit with `git commit -s`. The person who opens the pull request is the only author. Leave out co-author lines for AI tools. diff --git a/CONTEXT.md b/CONTEXT.md index fcc8182..4b82f5f 100644 --- a/CONTEXT.md +++ b/CONTEXT.md @@ -169,6 +169,18 @@ _Avoid_: input, widget, picker To show the user what a postcode points to before the app stores it. _Avoid_: verify, validate (for this step) +**Check status**: +What the WooCommerce plugin learned about the postcode of an order: `valid`, `invalid`, `unchecked` or `error`. The order meta `_gatepost_postcode_check` holds it. +_Avoid_: verification status, validation result + +**Form value**: +The text that a field gives its form when the form is sent: the canonical form of a postcode, the 6 digits of a legacy postcode, or the text without white space at the start and the end. +_Avoid_: submitted value, output + +**Message catalogue**: +A data file that holds the text of a UI in one language, one message for each key. +_Avoid_: strings file, translations, locale file + **Tell**: A habit that makes code look machine-written, such as very long lines. _Avoid_: smell (smells are design problems), anti-pattern diff --git a/README.md b/README.md index f0a6d52..ae0d3ca 100644 --- a/README.md +++ b/README.md @@ -109,7 +109,7 @@ The example uses `FC-01-Z99-ZZ-01`, a synthetic postcode, and shows the answer o |---|---| | PHP | 8.1 or later | | HTTP, for the client | a PSR-18 client and a PSR-17 request factory | -| Gatepost spec | 0.2.0 | +| Gatepost spec | 0.3.0 | ## Docs diff --git a/docs/api-dump.md b/docs/api-dump.md index ce4859c..6a99b8f 100644 --- a/docs/api-dump.md +++ b/docs/api-dump.md @@ -125,7 +125,7 @@ final class ParseResult final class Postcode { - public const SPEC_VERSION = '0.2.0'; + public const SPEC_VERSION = '0.3.0'; public readonly string $compact; public readonly string $canonical; public readonly string $display; diff --git a/spec b/spec index 359db01..db359cd 160000 --- a/spec +++ b/spec @@ -1 +1 @@ -Subproject commit 359db01aa97f2d5b183db431d14dbccb793af9f3 +Subproject commit db359cde28a7c5c549f1a3069f20078f19f05a58 diff --git a/src/Postcode.php b/src/Postcode.php index eb45f64..7e6453d 100644 --- a/src/Postcode.php +++ b/src/Postcode.php @@ -24,7 +24,7 @@ final class Postcode /** * The version of the Gatepost spec that this package implements. */ - public const SPEC_VERSION = '0.2.0'; + public const SPEC_VERSION = '0.3.0'; // In UTF-8, a code point takes at most 4 bytes. Longer input is over the limit without a // count, so a 10 MB string costs no more than a short one. From 0133ba96708fadcabe8f503a0a1efe10766a9312 Mon Sep 17 00:00:00 2001 From: MobileMage Date: Sun, 4 Oct 2026 02:18:30 +0100 Subject: [PATCH 2/2] chore(repo): let api:check ignore the SPEC_VERSION value Postcode::SPEC_VERSION takes a new value with each spec version by design, so a change of that value is not a break of the public API. Since the first release tag, the BC check compared the constant with the tag and failed the move to spec 0.3.0. The script now drops that one finding, prints every other finding with its own count, and exits non-zero for any other break or any other failure of the tool. A test feeds the filter the finding alone and with a second break. The developing guide names the exception. Signed-off-by: MobileMage --- docs/developing.md | 2 ++ scripts/check-api.sh | 34 ++++++++++++++++++++++- tests/Unit/CheckApiTest.php | 55 ++++++++++++++++++++++++++++++++++++- 3 files changed, 89 insertions(+), 2 deletions(-) diff --git a/docs/developing.md b/docs/developing.md index 62ababe..38694a4 100644 --- a/docs/developing.md +++ b/docs/developing.md @@ -15,6 +15,8 @@ composer check The BC check tool lists ext-intl as a requirement. Its check runs without that extension, so the install line ignores the requirement. The line also works on a machine that has ext-intl. +`composer api:check` compares the public API with the newest release tag, so run `git fetch --tags` first. It ignores one finding: the value of `Postcode::SPEC_VERSION` changes with each spec version by design, so that change is not a break of the API. + `composer run` lists the other scripts. `composer test` runs the tests without coverage. `composer mutation` runs the mutation tests, and it needs Xdebug. ## Contract scenarios diff --git a/scripts/check-api.sh b/scripts/check-api.sh index 7f0e4af..bf00b23 100755 --- a/scripts/check-api.sh +++ b/scripts/check-api.sh @@ -3,6 +3,10 @@ # SPDX-License-Identifier: Apache-2.0 # Compares the public API with the newest release tag (GIT-7, API-13). Before the first tag, # no released API exists, so the script says so and stops. +# +# The value of Postcode::SPEC_VERSION changes with each spec version by design. That change is +# not a break of the API, so the script drops that one finding. It keeps every other finding, +# and it fails on every other failure of the tool. set -euo pipefail # Git sorts v0.1.0-alpha.0 above v0.1.0 unless the config names the suffix. Without the option, @@ -12,4 +16,32 @@ if [ -z "$tag" ]; then echo "No release tag yet, so there is no released API to compare." exit 0 fi -exec tools/bc-check/vendor/bin/roave-backward-compatibility-check --from="$tag" + +spec_version_change='[BC] CHANGED: Value of constant ' +spec_version_change+='Gatepost\Postcode\Postcode::SPEC_VERSION changed' +status=0 +tool=tools/bc-check/vendor/bin/roave-backward-compatibility-check +output="$("$tool" --from="$tag" 2>&1)" || status=$? + +dropped=0 +breaks=0 +while IFS= read -r line; do + if [[ $line == "$spec_version_change"* ]]; then + dropped=1 + elif [[ $line =~ ^[0-9]+\ backwards-incompatible\ changes\ detected$ ]]; then + : # The tool counts the dropped finding, so the script prints its own count. + else + echo "$line" + if [[ $line == '[BC]'* ]]; then breaks=$((breaks + 1)); fi + fi +done <<<"$output" + +if [ "$breaks" -gt 0 ]; then + echo "$breaks backwards-incompatible changes detected" + exit 3 +fi +# Code 3 is the code of the tool for a break. Another code, or code 3 with no finding to +# explain it, is a failure of the tool itself. +if [ "$status" -ne 0 ] && { [ "$status" -ne 3 ] || [ "$dropped" -eq 0 ]; }; then + exit "$status" +fi diff --git a/tests/Unit/CheckApiTest.php b/tests/Unit/CheckApiTest.php index e9fc5c6..43b5894 100644 --- a/tests/Unit/CheckApiTest.php +++ b/tests/Unit/CheckApiTest.php @@ -16,7 +16,7 @@ /** * scripts/check-api.sh starts the BC check from the newest release tag. These tests run the * script in a temporary repo with the tags of one case. A stub takes the place of the BC check - * tool and prints the tag that the script gave it. + * tool. It prints the tag that the script gave it, or the output of the case. */ final class CheckApiTest extends TestCase { @@ -83,6 +83,59 @@ public function stopsBeforeTheFirstTag(): void ); } + /** + * @return array + */ + public static function toolOutputs(): array + { + $constant = '[BC] CHANGED: Value of constant Gatepost\\Postcode\\Postcode::SPEC_VERSION ' + . "changed from '0.2.0' to '0.3.0'"; + $method = '[BC] REMOVED: Method Gatepost\\Postcode\\Postcode::check() was removed'; + $summary = static fn(int $count): string + => "{$count} backwards-incompatible changes detected"; + + return [ + 'the constant alone' => [ + "{$constant}\n{$summary(1)}", + 3, + 0, + '', + ], + 'the constant and another break' => [ + "{$constant}\n{$method}\n{$summary(2)}", + 3, + 3, + "{$method}\n{$summary(1)}", + ], + 'another break alone' => [ + "{$method}\n{$summary(1)}", + 3, + 3, + "{$method}\n{$summary(1)}", + ], + 'a failure of the tool' => ['Could not read the tag', 1, 1, 'Could not read the tag'], + 'a break code with no finding' => ['Odd output', 3, 3, 'Odd output'], + ]; + } + + #[Test] + #[DataProvider('toolOutputs')] + public function dropsOnlyTheChangeOfTheSpecVersion( + string $toolOutput, + int $toolCode, + int $exitCode, + string $output, + ): void { + $this->startRepo('v0.1.0'); + $stub = "{$this->repo}/tools/bc-check/vendor/bin/roave-backward-compatibility-check"; + \file_put_contents($stub, "#!/bin/sh\ncat <<'END'\n{$toolOutput}\nEND\nexit {$toolCode}\n"); + + $run = $this->checkApi(); + + self::assertSame($exitCode, $run->exitCode, $run->output); + self::assertSame($output, $run->output); + } + // The repo starts in each test, so that tearDown() removes it even when a git command fails. private function startRepo(string ...$tags): void {