diff --git a/README.md b/README.md index 8e1a4fa..a9a7094 100644 --- a/README.md +++ b/README.md @@ -1,29 +1,101 @@ -# Gatepost spec +

+ + + Gatepost + +

-The shared contract for every Gatepost SDK: the postcode grammar, the data files, the test vectors, the coding standards and the repo templates. +

Gatepost spec

+ +

The shared contract for every Gatepost SDK: the postcode grammar, the data files, the test vectors and the coding standards.

+ +

+ CI status + Licence: Apache-2.0 +

+ +

+ Docs + · + Playground + · + Grammar + · + Contributing + · + Discussions +

> Unofficial. Not made or endorsed by NIPOST. +This repo defines how every Gatepost SDK reads and writes Nigeria's digital postcodes, and how every Gatepost client calls NIPOST's gateway. An SDK is correct when it passes the shared vectors and the contract scenarios. If `grammar.md` and a vector disagree, `grammar.md` wins, and the disagreement is a bug. + +The current version is in [`VERSION`](VERSION). + +## A postcode in three forms + +| Form | Example | Use | +|---|---|---| +| Compact | `EK01A03FK01` | storage and comparison | +| Canonical | `EK-01-A03-FK-01` | APIs and logs | +| Display | `EK 01 A03 FK 01` | screens and print | + +A postcode has five segments, from large to small: state, LGA, district, area and unit. [`grammar.md`](grammar.md) has the rules. + +## One test vector + +This case is `contains-001` in [`vectors/contains.json`](vectors/contains.json). + +```json +{ + "id": "contains-001", + "description": "finds a full code in its district", + "input": { + "prefix": "EK-01-A03", + "code": "EK-01-A03-FK-01" + }, + "options": {}, + "expect": { + "value": true + } +} +``` + +Every SDK runs the cases in [`vectors/`](vectors). + ## Contents | Path | Holds | |---|---| -| `grammar.md` | how every SDK reads and writes postcodes, and the public interface | -| `client.md` | how every client calls NIPOST's gateway: results, errors, retries and the queue | -| `openapi/` | NIPOST's gateway, completed with the responses that its own file leaves out | -| `fixtures/` | synthetic gateway responses and keys for the mock server | -| `contract/` | shared client test cases that every client must pass against the mock server | -| `data/` | state codes, GPS accuracy limits and the postcode format | -| `vectors/` | shared test cases that every SDK must pass | -| `standards/` | the coding standards and one file for each language | -| `CONTEXT.md` | the glossary | -| `templates/` | files for new repos | -| `scripts/` | `check-tells`, the vector builder, a plain reading of the grammar that checks each vector, and the checks of the OpenAPI file, the fixtures and the scenarios | - -## Check - -Run `make check`. It needs Python 3.11 or later and `uv`. +| [`grammar.md`](grammar.md) | how every SDK reads and writes postcodes, and the public interface | +| [`client.md`](client.md) | how every client calls NIPOST's gateway: results, errors, retries and the queue | +| [`openapi/`](openapi) | NIPOST's gateway, completed with the responses that its own file leaves out | +| [`fixtures/`](fixtures) | synthetic gateway responses and keys for the mock server | +| [`contract/`](contract) | shared client test cases that every client must pass against the mock server | +| [`data/`](data) | state codes, GPS accuracy limits and the postcode format | +| [`vectors/`](vectors) | shared test cases that every SDK must pass | +| [`standards/`](standards) | the coding standards and one file for each language | +| [`CONTEXT.md`](CONTEXT.md) | the glossary | +| [`templates/`](templates) | files for new repos | +| [`scripts/`](scripts) | `check-tells`, the vector builder, a plain reading of the grammar that checks each vector, and the checks of the OpenAPI file, the fixtures and the scenarios | + +## Who uses it + +| Repo | Language | Implements | +|---|---|---| +| [js](https://github.com/gatepost-dev/js) | TypeScript | the grammar and the client | +| [php](https://github.com/gatepost-dev/php) | PHP | the grammar and the client | + +The [docs site](https://gatepost-dev.github.io/docs/) shows the same grammar and client contract as pages for readers. + +## Change the spec + +A change to the public interface of an SDK starts here. Open an issue first. Then read [`CONTRIBUTING.md`](https://github.com/gatepost-dev/.github/blob/main/CONTRIBUTING.md). Run `make check` before you open a pull request. It needs Python 3.11 or later and `uv`. + +## Support + +Ask questions in [GitHub Discussions](https://github.com/gatepost-dev/spec/discussions). Report bugs in [GitHub Issues](https://github.com/gatepost-dev/spec/issues). Report security problems through the [private form](https://github.com/gatepost-dev/spec/security/advisories/new). ## Licence -Apache-2.0. See `LICENSE` and `NOTICE`. +Apache-2.0. See [`LICENSE`](LICENSE) and [`NOTICE`](NOTICE). diff --git a/scripts/tests/test_readme.py b/scripts/tests/test_readme.py new file mode 100644 index 0000000..4b4871c --- /dev/null +++ b/scripts/tests/test_readme.py @@ -0,0 +1,29 @@ +# SPDX-FileCopyrightText: 2026 The Gatepost authors +# SPDX-License-Identifier: Apache-2.0 +"""Tests that the README example is a real vector, and that the README keeps its header.""" + +import json +import re +import unittest +from pathlib import Path + +ROOT = Path(__file__).resolve().parents[2] +NOTICE = "> Unofficial. Not made or endorsed by NIPOST." + + +class ReadmeTest(unittest.TestCase): + def test_example_vector_is_the_case_in_the_vectors_file(self) -> None: + readme = (ROOT / "README.md").read_text(encoding="utf-8") + block = re.search(r"```json\n(.*?)\n```", readme, re.DOTALL) + self.assertIsNotNone(block) + shown = json.loads(block.group(1) if block else "") + vectors = json.loads((ROOT / "vectors" / "contains.json").read_text(encoding="utf-8")) + case = next(item for item in vectors["cases"] if item["id"] == shown["id"]) + self.assertEqual(shown, case) + + def test_readme_and_template_keep_the_notice_after_the_header_block(self) -> None: + for name in ("README.md", "templates/README.md"): + with self.subTest(name=name): + text = (ROOT / name).read_text(encoding="utf-8") + self.assertIn(NOTICE, text) + self.assertLess(text.index('

'), text.index(NOTICE)) diff --git a/standards/CODING_STANDARDS.md b/standards/CODING_STANDARDS.md index dd933bd..882e753 100644 --- a/standards/CODING_STANDARDS.md +++ b/standards/CODING_STANDARDS.md @@ -1,6 +1,6 @@ # Gatepost coding standards -Version 1.1, 3 Oct 2026. +Version 1.2, 3 Oct 2026. These standards apply to every Gatepost repo, in every language. Each repo also follows its language file in `standards/languages/`. Together, the two files are the standard for that repo. @@ -169,8 +169,9 @@ The command exits with 0 when all files pass, with 1 when a rule is broken, and ## Documentation - **DOC-1 MUST.** Each public symbol has a doc comment. It gives a one-line summary, each parameter, each error and one example. -- **DOC-2 MUST.** Each README follows `templates/README.md`. -- **DOC-3 MUST.** Each code example in a README or on the docs site runs in CI. +- **DOC-2 MUST.** Each README follows `templates/README.md`, with the sections in its order. It starts with a centred header block: the logo in a `` element, the name, one tagline, the badges and a row of links. The line "Unofficial. Not made or endorsed by NIPOST." follows as a quote block. The header block is the only place for HTML. A package README has 100 to 130 lines, with an install command, two short examples, a table of the main functions, 4 to 8 feature bullets and the requirements. The docs site holds the rest. A monorepo root README holds a table of packages and little else. A badge shows a thing that exists today, so the version badge waits for the registry. Questions go to GitHub Discussions, bugs to GitHub Issues and security reports to the private form. Steps for maintainers do not belong in a README. Link the contributing guide. +- **DOC-2a MUST.** An install command is the real command. If the package is not on its registry, a plain sentence under the command says so. The first release removes that sentence. +- **DOC-3 MUST.** Each code example in a README or on the docs site runs in CI. The quickstart has 10 lines or fewer. - **DOC-4 MUST.** Prose uses plain English. Write sentences of 25 words or fewer, with one idea in each. Use the active voice, and name who does the action. Use the plainest word, and use one word for one meaning. Keep every article and connector. Write a verb, not a noun that is made from it, and do not put more than three nouns in a row. Do not use phrasal verbs, semicolons, stacked hedges, em dashes or marketing words such as "powerful" and "seamless". Do not open with filler or close with a summary. Use British spelling. - **DOC-5 MUST.** Each package has a changelog that change files generate. It follows the Keep a Changelog format, unless the release tool writes its own format. Changesets, the tool for npm packages, writes its own format. - **DOC-6 SHOULD.** Record a decision as an ADR when it is hard to reverse, surprising, and the result of a real trade-off. An ADR is a short file in `docs/adr/` that gives the context, the decision and the reason. diff --git a/templates/README.md b/templates/README.md index de22839..4e4f00e 100644 --- a/templates/README.md +++ b/templates/README.md @@ -1,26 +1,58 @@ -# {{PACKAGE_NAME}} - -{{ONE_SENTENCE_SUMMARY}} +

+ + + Gatepost + +

+ +

{{PACKAGE_NAME}}

+ +

{{ONE_SENTENCE_SUMMARY}}

+ +

+ CI status + Version + Licence: Apache-2.0 + OpenSSF Scorecard +

+ +

+ Docs + · + Playground + · + {{DOCS_PAGE_NAME}} + · + Contributing + · + Discussions +

> Unofficial. Not made or endorsed by NIPOST. -[![CI]({{CI_BADGE_URL}})]({{CI_URL}}) -[![Version]({{VERSION_BADGE_URL}})]({{REGISTRY_URL}}) -[![Licence](https://img.shields.io/badge/licence-Apache--2.0-blue)](LICENSE) +```{{LANGUAGE_ID}} +{{QUICKSTART_CODE}} +``` + +{{ONE_SENTENCE_ABOUT_THE_OUTPUT}} ## Install -```{{LANGUAGE_ID}} +```sh {{INSTALL_COMMAND}} ``` -## Quickstart +{{UNPUBLISHED_NOTE}} + +## Use ```{{LANGUAGE_ID}} -{{QUICKSTART_CODE}} +{{SECOND_EXAMPLE_CODE}} ``` -{{ONE_SENTENCE_ABOUT_THE_OUTPUT}} +| Function | What it does | +|---|---| +| `{{FUNCTION}}` | {{WHAT_IT_DOES}} | ## What it does @@ -39,21 +71,27 @@ The guide and the API reference are at {{DOCS_URL}}. ## Support -Ask questions in GitHub Discussions. Report bugs in GitHub Issues. Report security problems privately, as `SECURITY.md` describes. +Ask questions in [GitHub Discussions](https://github.com/gatepost-dev/{{REPO_NAME}}/discussions). Report bugs in [GitHub Issues](https://github.com/gatepost-dev/{{REPO_NAME}}/issues). Report security problems through the [private form](https://github.com/gatepost-dev/{{REPO_NAME}}/security/advisories/new). -## Contributing +## Develop -Read `CONTRIBUTING.md` before you open a pull request. +Read [`CONTRIBUTING.md`](https://github.com/gatepost-dev/.github/blob/main/CONTRIBUTING.md) before you open a pull request. {{REPO_STEPS_LINK}} ## Licence -Apache-2.0. See `LICENSE` and `NOTICE`. +Apache-2.0. See [`LICENSE`](LICENSE) and [`NOTICE`](NOTICE).