From f11673a7ac07df7b9ca5e0dfb2a5999209689963 Mon Sep 17 00:00:00 2001 From: Mike McDougall Date: Wed, 21 Oct 2026 07:57:06 -1000 Subject: [PATCH 1/2] docs(install): keep release getting-started on the published package Source-checkout fences in README and INSTALL replace the admitted package. Release qualification refuses that direct origin even when the version matches, so those fences stay for the development-source producer and are not release-install steps. The executed install path is the published index. --- INSTALL.md | 22 +++--- README.md | 34 ++++++---- .../test_published_getting_started_install.py | 68 +++++++++++++++++++ 3 files changed, 101 insertions(+), 23 deletions(-) create mode 100644 tests/test_published_getting_started_install.py diff --git a/INSTALL.md b/INSTALL.md index 0e0a9e8..270140d 100644 --- a/INSTALL.md +++ b/INSTALL.md @@ -27,15 +27,19 @@ pip install "honua-sdk[grpc,geopandas,raster]" pip install honua-admin ``` -For unreleased development work, install from a clone instead: - -This is an alternative to the PyPI path, not the next release-install step. The -commands below install the checkout's version into the current environment and -can replace published packages. An unqualified clone follows the current default -branch, which may contain a newer version than the release you are verifying. -Use the governed published package versions for release qualification; a -successful source build does not establish that those package artifacts work. - +The commands above are the release install. They use the configured package +index and version constraints and leave the admitted published package in place. + +For unreleased development work, install from a clone in a separate +environment instead. This is not the next release-install step. The commands +below install the checkout's version into the current environment and replace +published packages. An unqualified clone follows the current default branch, +which may contain a newer version than the release you are verifying. Release +qualification refuses every direct-origin install, including one whose version +string matches the admitted package. A successful source build does not +establish that those package artifacts work. + + ```bash git clone https://github.com/honua-io/honua-sdk-python.git cd honua-sdk-python diff --git a/README.md b/README.md index a8c6b1b..bf57138 100644 --- a/README.md +++ b/README.md @@ -50,16 +50,19 @@ pip install "honua-sdk[grpc,geopandas,raster]" # + optional extras pip install honua-admin # control-plane (admin) client ``` -For development, install from a source checkout instead: - -These commands install the checkout's version into the current Python environment -and can replace packages installed from PyPI above. Choose this path for source -development, rather than running it after the published install to verify a -release. A clone without a revision follows the repository's current default -branch; it does not select the source revision of this document or a released -package. Source installation still requires compatible dependencies and build -tooling. - +The commands above are the release install. They use the configured package +index and version constraints and leave the admitted published package in place. + +A source checkout is a separate development environment, not the next step +after that install. The commands below install the checkout's version into the +current environment and replace the published package. An unqualified clone +follows the default branch, not this document's revision and not the published +artifact. Release qualification refuses every direct-origin install, including +one whose version string matches the admitted package. A successful source +build does not establish published wheel identity. Use a separate environment +for that work; the contributor setup is also in [AGENTS.md](AGENTS.md). + + ```bash git clone https://github.com/honua-io/honua-sdk-python.git cd honua-sdk-python @@ -283,11 +286,14 @@ at [honua.io](https://honua.io) and ## Development -Run these from a clone of this repository (see [Install](#install)). Besides -Python 3.11+, the test suite needs Node.js with `node` on `PATH`: the `honua` -CLI forwards `honua admin ...` to the JavaScript CLI, and `tests/test_cli.py` -checks that forwarding. +Lint, type-check, and tests run from a clone. Besides Python 3.11+, the test +suite needs Node.js with `node` on `PATH`: the `honua` CLI forwards +`honua admin ...` to the JavaScript CLI, and `tests/test_cli.py` checks that +forwarding. The editable installs below replace the published package, so they +belong in a development environment, not in the environment used to verify a +release. The same contributor setup is in [AGENTS.md](AGENTS.md). + ```bash # Editable install of both packages with extras pip install -e "packages/honua-sdk[grpc,geopandas]" diff --git a/tests/test_published_getting_started_install.py b/tests/test_published_getting_started_install.py new file mode 100644 index 0000000..fefe260 --- /dev/null +++ b/tests/test_published_getting_started_install.py @@ -0,0 +1,68 @@ +"""Getting-started release installs stay on the admitted published package. + +Source-checkout fences remain in README and INSTALL so the development-source +producer can still capture them. The executable-docs gate must not run those +fences: a direct-origin install replaces the admitted package, and release +qualification refuses that origin even when the version string matches. +""" +from __future__ import annotations + +from pathlib import Path +import re + +from scripts import development_source_inputs as inputs +from scripts import python_document_inputs as documentation + + +ROOT = Path(__file__).resolve().parents[1] +DOCUMENTS = ("README.md", "INSTALL.md") +FENCE = re.compile(r"^```([^\n]*)\n(.*?)^```[ \t]*$", re.MULTILINE | re.DOTALL) +SKIP = re.compile( + r"\s*$", + re.MULTILINE, +) +DIRECT_ORIGIN = re.compile( + r"git\s+clone\b|pip\s+install\s+-e\b|pip\s+install\s+\.?/?packages/|pip\s+install\s+\"\./packages/", +) + + +def _release_fences(text: str) -> list[tuple[str, str, str]]: + """Return (info, preceding, code) for each fence.""" + found = [] + cursor = 0 + for match in FENCE.finditer(text): + found.append((match.group(1), text[cursor:match.start()], match.group(2))) + cursor = match.end() + return found + + +def test_release_install_fences_use_the_published_package() -> None: + for name in DOCUMENTS: + text = (ROOT / name).read_text(encoding="utf-8") + release_installs = [] + for info, preceding, code in _release_fences(text): + if info.strip().split()[0] not in {"bash", "sh", "shell"}: + continue + if SKIP.search(preceding): + continue + assert DIRECT_ORIGIN.search(code) is None, name + if "pip install" in code and "honua-sdk" in code: + release_installs.append(code) + assert release_installs, name + assert any(re.search(r"^pip install honua-sdk\b", line) or 'pip install "honua-sdk' in line + for code in release_installs for line in code.splitlines()), name + + +def test_source_checkout_fences_stay_captured_and_are_not_release_steps() -> None: + source = {"files": {name: {"gitBlob": "0" * 40} for name in DOCUMENTS}} + planned = documentation.plan(inputs, ROOT, source) + by_document: dict[str, int] = {} + for item in planned: + by_document[item["document"]] = by_document.get(item["document"], 0) + 1 + text = (ROOT / item["document"]).read_text(encoding="utf-8") + fences = [code for info, _preceding, code in _release_fences(text) if code == item["blockBytes"]] + assert fences, item["document"] + preceding = next(preceding for _info, preceding, code in _release_fences(text) if code == item["blockBytes"]) + assert SKIP.search(preceding), item["document"] + assert by_document["INSTALL.md"] >= 1 + assert by_document["README.md"] >= 2 From fce8f98baab407f5d9e5113bba48b28b8637080f Mon Sep 17 00:00:00 2001 From: Mike McDougall Date: Wed, 21 Oct 2026 08:09:47 -1000 Subject: [PATCH 2/2] fix(ci): pin certifi 2026.10.19 and compare fences in platform encoding security-audit rejects certifi 2026.7.22 (PYSEC-2026-4320). The Windows getting-started test compared utf-8 fence text with plan()'s platform decode. --- .github/requirements/ci-geopandas.lock | 6 +++--- .github/requirements/ci.lock | 6 +++--- tests/test_published_getting_started_install.py | 11 ++++++----- 3 files changed, 12 insertions(+), 11 deletions(-) diff --git a/.github/requirements/ci-geopandas.lock b/.github/requirements/ci-geopandas.lock index 62abfb9..0924faa 100644 --- a/.github/requirements/ci-geopandas.lock +++ b/.github/requirements/ci-geopandas.lock @@ -79,9 +79,9 @@ cachecontrol==0.14.4 \ --hash=sha256:b7ac014ff72ee199b5f8af1de29d60239954f223e948196fa3d84adaffc71d2b \ --hash=sha256:e6220afafa4c22a47dd0badb319f84475d79108100d04e26e8542ef7d3ab05a1 # via pip-audit -certifi==2026.7.22 \ - --hash=sha256:62f22742b58a1a33014a2b6b706588a8d7e2a88ae7bd1a6ebe8c992928483775 \ - --hash=sha256:741e2c3b351ddf169a738da9f2c048608ff7f2c5cc02f1ebc6b118bb090d5d55 +certifi==2026.10.19 \ + --hash=sha256:035c1e85572b141bf1c3a3eff0e33a8eb74917522f2559075ff8d2fea780a1ae \ + --hash=sha256:42d35340cc5b9ba78e818ddbff0c5b1c2b779de448a340fd1b989f5fa8670e1b # via # httpcore # httpx diff --git a/.github/requirements/ci.lock b/.github/requirements/ci.lock index ff648c4..ee0817e 100644 --- a/.github/requirements/ci.lock +++ b/.github/requirements/ci.lock @@ -79,9 +79,9 @@ cachecontrol==0.14.4 \ --hash=sha256:b7ac014ff72ee199b5f8af1de29d60239954f223e948196fa3d84adaffc71d2b \ --hash=sha256:e6220afafa4c22a47dd0badb319f84475d79108100d04e26e8542ef7d3ab05a1 # via pip-audit -certifi==2026.7.22 \ - --hash=sha256:62f22742b58a1a33014a2b6b706588a8d7e2a88ae7bd1a6ebe8c992928483775 \ - --hash=sha256:741e2c3b351ddf169a738da9f2c048608ff7f2c5cc02f1ebc6b118bb090d5d55 +certifi==2026.10.19 \ + --hash=sha256:035c1e85572b141bf1c3a3eff0e33a8eb74917522f2559075ff8d2fea780a1ae \ + --hash=sha256:42d35340cc5b9ba78e818ddbff0c5b1c2b779de448a340fd1b989f5fa8670e1b # via # httpcore # httpx diff --git a/tests/test_published_getting_started_install.py b/tests/test_published_getting_started_install.py index fefe260..0ff6956 100644 --- a/tests/test_published_getting_started_install.py +++ b/tests/test_published_getting_started_install.py @@ -55,14 +55,15 @@ def test_release_install_fences_use_the_published_package() -> None: def test_source_checkout_fences_stay_captured_and_are_not_release_steps() -> None: source = {"files": {name: {"gitBlob": "0" * 40} for name in DOCUMENTS}} + # plan() reads with the platform default encoding. Match that text so a + # Windows decode of the em dash does not look like a different fence. + texts = {name: (ROOT / name).read_text() for name in DOCUMENTS} planned = documentation.plan(inputs, ROOT, source) by_document: dict[str, int] = {} for item in planned: by_document[item["document"]] = by_document.get(item["document"], 0) + 1 - text = (ROOT / item["document"]).read_text(encoding="utf-8") - fences = [code for info, _preceding, code in _release_fences(text) if code == item["blockBytes"]] - assert fences, item["document"] - preceding = next(preceding for _info, preceding, code in _release_fences(text) if code == item["blockBytes"]) - assert SKIP.search(preceding), item["document"] + matched = [row for row in _release_fences(texts[item["document"]]) if row[2] == item["blockBytes"]] + assert matched, item["document"] + assert SKIP.search(matched[0][1]), item["document"] assert by_document["INSTALL.md"] >= 1 assert by_document["README.md"] >= 2