diff --git a/.github/CODEOWNERS b/.github/CODEOWNERS index d61457c..64c23b8 100644 --- a/.github/CODEOWNERS +++ b/.github/CODEOWNERS @@ -1,8 +1,4 @@ -# Temporary bootstrap owner. This does not provide independent review when that owner -# authors a change. Code-owner review is required by the default-branch ruleset as of -# the 2026-08-10 audit, so this file is live enforcement, not a placeholder: replace -# it with an independent, write-capable owner or team, then update the live protection -# and the governance records together. +# Default owners for repository content. * @picogrid/edge # Public runtime, protocol boundary, and artifact policy. diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 9c588f3..929cdc2 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -17,10 +17,9 @@ env: UV_VERSION: "0.7.20" jobs: - # Release Please authors version-bump pull requests, so it may only run in the - # private engineering upstream. Public main accepts byte-preserving export merges - # exclusively; a bump commit authored there would break the export equivalence - # barrier described in PUBLIC_DISTRIBUTION.md. + # The canonical public repository derives release state from its immutable version + # tag. Release Please is reserved for repositories where automated version PRs are + # explicitly enabled. release-please: name: Prepare or create a draft release if: >- @@ -48,14 +47,13 @@ jobs: manifest-file: .github/.release-please-manifest.json target-branch: main - # Every downstream job consumes this job's outputs, never Release Please's - # directly. In the private upstream it passes the Release Please outputs through - # unchanged. In the public distribution repository, where Release Please is - # deliberately disabled, it derives the same identity from the merged and - # equivalence-verified tree: the version is read from pyproject.toml, the tag is - # that version prefixed with "v", and release state is resolved by immutable tag - # and numeric release ID. A merged tree that resolves to an already published - # release stops without rebuilding or publishing. + # Every downstream job consumes normalized outputs rather than binding directly + # to one release-state provider. For the canonical public repository, Release + # Please is skipped while release automation is enabled, so the repository derives + # the same identity from the merged and equivalence-verified tree: the version + # comes from pyproject.toml, and release state is resolved by immutable tag and + # numeric release ID. A tree that resolves to an already published release stops + # without rebuilding or publishing. resolve-release: name: Resolve the release candidate identity if: >- diff --git a/.github/workflows/scorecard.yml b/.github/workflows/scorecard.yml index 4e1c2fa..4a7579b 100644 --- a/.github/workflows/scorecard.yml +++ b/.github/workflows/scorecard.yml @@ -39,9 +39,8 @@ jobs: with: results_file: results.sarif results_format: sarif - # Publish only from the public distribution repository, and only once - # the cutover step that authorizes publication sets the opt-in - # variable there. The byte-identical private copy never publishes. + # Publish only from the canonical public repository after the explicit + # publication opt-in is enabled. publish_results: >- ${{ github.repository == 'picogrid/ecn-sdk-python' && vars.SCORECARD_PUBLISH_ENABLED == 'true' }} diff --git a/.gitignore b/.gitignore index ea0404e..3f00095 100644 --- a/.gitignore +++ b/.gitignore @@ -15,7 +15,6 @@ src/*.egg-info/ **/__pycache__/ # Credential-like files stay visible to repository and release scans. .worktrees/ -.claude/ # Playwright run output test-results/ diff --git a/CHANGELOG.md b/CHANGELOG.md index ace0277..85710ec 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -80,6 +80,10 @@ published. - Mock results validate SDK behavior but do not establish compatibility with a deployed ECN. +- The versioned `ecn-wire-conformance` corpus (version 0.2) pins every fixture by + SHA-256, pins canonical JSON encodings where applicable, and fails CI on unhashed + corpus changes. + ### Documentation - A published documentation site provides installation, authentication, preflight, diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index a39a2b8..820d3cc 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -85,6 +85,9 @@ Use the complete release gate for the final candidate: make verify-release ``` +Run the release gate from a clean Git worktree. It fails before building when tracked +or untracked changes prevent the candidate from being attributed to one commit. + The release gate removes `node_modules`, `.astro`, and `site-dist` while reproducing the release environment. Run `make docs-install` again before another documentation check or preview. diff --git a/MANIFEST.in b/MANIFEST.in index e31c30a..fd5d177 100644 --- a/MANIFEST.in +++ b/MANIFEST.in @@ -40,10 +40,8 @@ include docs/quickstarts/sensor-publisher.md include docs/reference/api.md include docs/reference/configuration.md include docs/reference/coordinate-reference.md -include docs/reference/evidence-status.md include docs/reference/exceptions.md include docs/reference/licensing.md -include docs/reference/original-ecn-integration-parity.md include docs/reference/wire-formats.md include docs/security/credentials.md include docs/shipped-tooling.md @@ -180,7 +178,6 @@ include operator-app/tests/publication-screenshot.spec.ts include operator-app/tests/screenshot.config.ts include operator-app/tsconfig.json include scripts/generate_api_reference.py -include scripts/original-guide-inventory.json include scripts/public-api-manifest.json include scripts/release-policy.json include scripts/type-completeness-allowlist.json diff --git a/Makefile b/Makefile index 4f96c3e..8773f30 100644 --- a/Makefile +++ b/Makefile @@ -1,9 +1,7 @@ UV ?= uv SOURCE_DATE_EPOCH ?= 1735689600 -PUBLIC_EXPORT_DIR ?= build/public-export -PUBLIC_EXPORT_RECORD ?= build/public-export-record.json -.PHONY: check-deps check-license sync-deps generate-reference check-reference verify-types verify-release version-sync check-public-export public-export dry-run-cutover docs-install docs-check docs-smoke-local wheelhouse +.PHONY: check-deps check-license sync-deps generate-reference check-reference verify-types verify-release version-sync docs-install docs-check docs-smoke-local wheelhouse docs-install: npm --prefix docs ci @@ -42,14 +40,7 @@ verify-release: check-deps check-license version-sync: PYTHONDONTWRITEBYTECODE=1 $(UV) run --frozen python -m scripts.version_sync -check-public-export: - PYTHONDONTWRITEBYTECODE=1 $(UV) run --python 3.11 --no-project --with packaging==26.3 python -m scripts.public_export --verify - -public-export: - PYTHONDONTWRITEBYTECODE=1 $(UV) run --python 3.11 --no-project --with packaging==26.3 python -m scripts.public_export --out "$(PUBLIC_EXPORT_DIR)" --record "$(PUBLIC_EXPORT_RECORD)" --clean - -dry-run-cutover: - PYTHONDONTWRITEBYTECODE=1 $(UV) run --python 3.11 --no-project --with packaging==26.3 python -m scripts.public_export --dry-run-cutover +-include scripts/public-export.mk wheelhouse: PYTHONDONTWRITEBYTECODE=1 $(UV) run --python 3.11 --no-project --with pip python -m scripts.build_wheelhouse diff --git a/NOTICE.md b/NOTICE.md index ca2f5d7..9571f57 100644 --- a/NOTICE.md +++ b/NOTICE.md @@ -13,177 +13,56 @@ rights. "Picogrid", "ECN", the Picogrid wordmark, and the Picogrid application m remain Picogrid trademarks. The license permits copying, modifying, and redistributing the asset files, but does not permit using Picogrid's names or marks as trademarks, for example to brand a fork or derived product or to imply -endorsement. The `brand/*` outputs inventoried in the provenance and transformation -tables below are distributed in `docs/site/public/brand/*`, with mirrored copies in -`operator-app/frontend/public/brand/*`. +endorsement. -## Third-party notices +## Runtime dependencies -The verified candidate environment resolved the following direct runtime dependency -versions from the declarations in `pyproject.toml`. The exact resolved versions are -recorded in `uv.lock`: +The SDK declares its direct runtime dependencies in `pyproject.toml`; the exact +resolved dependency set is recorded in `uv.lock`. These dependencies are not +vendored into the client wheel. The release verifier records the installed +candidate's dependency and license inventory in +`reports/generated/dependency-licenses.json`. -| Dependency | Declared range | Verified candidate version | License | -| --- | --- | --- | --- | -| `aiomqtt` | `==2.5.1` | 2.5.1 | BSD-3-Clause | -| `paho-mqtt` | `==2.1.0` | 2.1.0 | EPL-2.0 OR BSD-3-Clause | -| `protobuf` | `>=7.35.1,<8` | 7.35.1 | BSD-3-Clause | -| `pydantic` (with `pydantic-core`) | `>=2.11,<3` | 2.13.4 | MIT | - -These dependencies are not vendored into the wheel. Another installation may resolve -a different version within a declared range. Its authoritative dependency inventory -is the installed distribution metadata, which `make verify-release` regenerates at -`reports/generated/dependency-licenses.json`; that inventory is not a legal approval. - -## Third-party software +## Bundled third-party software The separately installable operator application embeds Leaflet 1.9.4 from the integrity-pinned npm package recorded in `operator-app/package-lock.json`. Leaflet is licensed under the BSD 2-Clause License. Its complete copyright notice, conditions, and disclaimer are shipped in `operator-app/THIRD_PARTY_LICENSES.md` and in the -operator wheel's `.dist-info/licenses/` directory. This third-party notice does not -change the repository's unresolved Picogrid software-license review. - -## Picogrid brand review - -Picogrid authorized reuse of the minimum static tokens and brand assets needed for -this public documentation site and operator example. The review was pinned to the -read-only `picogrid/web` repository at commit -`711e3a3f7d9c5ed233425a6c218929e14697d80e`; untracked worktree files were not read -or used. The reviewed inputs were: - -| Source path | Git blob | SHA-256 of file content | Use | -| --- | --- | --- | --- | -| `.agents/skills/pico-brand/SKILL.md` | `e05dbb8c5a4f1ecb1846c3cb04ac885e133b0df6` | `6d449eec008fda415da49ac1a24155cd5550d345103c59b0b5b746aee6e7f338` | Public color, typography, accessibility, and asset rules | -| `.agents/skills/pico-brand-mobile/SKILL.md` | `4e39c68ff96b14251d9d84d1d639f49ec7d42c36` | `226d806f5a045195d2340a7ff193708041d9a563eec794d493f357020ca4c87b` | Responsive layout and touch-target rules | -| `.agents/skills/pico-brand-terminals/SKILL.md` | `02d269287d459caa1dc4034d0dc50485faf9429a` | `dc618d3f14077927db59cf56a5e282f78088c7e9d761f3e2247d3d374a6e80e8` | Dense, dark-first operator hierarchy | -| `packages/ui/src/styles.css` | `1a8ac07984baa86557484706ee0a46544e702f4b` | `5db11f354fffde0d3b12cb5946eeb833f9532561e2df6a18c2594d3de2b52384` | Static palette, spacing, radius, semantic status, and mono-font tokens | -| `apps/orion/public/assets/general/pg-logo.png` | `3885e140f0b0b00aae293627b3610ad15283646a` (Git LFS object) | `7ab80a61a87cc134421ae42f5101f5df07a71424c9b20f29431c348a7f0eb6ca` | Authorized Picogrid wordmark | - -The application mark is not sourced from `picogrid/web`. It is the current public -Picogrid mark, reviewed in the read-only `picogrid/picogrid` repository at commit -`741a3cfa03643863f9feba95fcc5b54d3b7ac558`: - -| Source path | Git blob | SHA-256 of file content | Use | -| --- | --- | --- | --- | -| `apps/web/public/favicon.svg` | `bba3e34afdbcbc7217f0e9626bfc678541677f73` | `667954c19b483e17cba600c3e7ed437353f1364525daa85bb591a60fab9835ed` | Current public Picogrid application mark; the exact mark the public Picogrid marketing site publishes as its favicon | - -`docs/src/styles/picogrid.css` and `operator-app/frontend/src/styles.css` independently -adapt those static values into semantic light/dark CSS variables. No component, -React source, story, application source, package metadata, or runtime dependency was -copied from `picogrid/web`. - -## Static asset transformations - -The following transforms were performed locally without a network or generative -asset service. Identical files copied into the docs and standalone operator public -directories intentionally have identical hashes. - -| Public output | Transformation | SHA-256 | -| --- | --- | --- | -| `brand/picogrid-wordmark-dark.png` | Exact authorized `1001x94` wordmark bytes; white artwork for a dark surface | `7ab80a61a87cc134421ae42f5101f5df07a71424c9b20f29431c348a7f0eb6ca` | -| `brand/picogrid-wordmark-light.png` | `1001x94` wordmark RGB recolored to Picogrid black `#181818`; source alpha preserved | `bbda6e31860087383538a510b92e7341d25483184bfc4633ea8878867a61f4ea` | -| `brand/picogrid-app-icon-512.png` | Tracked `32x32` vector application mark rasterized to `512x512` with librsvg 2.61.3 (`rsvg-convert -w 512 -h 512`), then re-encoded to metadata-free 8-bit RGB with ImageMagick 7.1.2-13 | `d95b6fbbead6dce849b26c76cc816a6510de0725b5ad0fd72cd614565bcf0540` | -| `brand/picogrid-app-icon-192.png` | Same vector mark, rasterization, and encoding at `192x192` | `9383f03ccc433051752679fd59ca7cd6fbdec4ea330b233123111aa1a1c7595b` | -| `docs/site/public/brand/ecn-client-og.svg` | Independently composed outcome-led `1200x630` Picogrid ECN SDK product card using the authorized wordmark and static tokens | `44ac27e1d3be03f8edb13eb9fbb0b61908476f0b646c07316393dd5280dc0c35` | -| `brand/ecn-client-og.png` | SVG product card rasterized to `1200x630` with librsvg | `f685266adb685d59eb00d9bec7a2c75c0eaf14c71aac02161131c9d6087436aa` | -| `docs/site/public/brand/picogrid-nav-texture.png` | Exact authorized Picogrid cover artwork bytes; supplied `2256x382` 8-bit RGBA PNG (color type 6), with `pHYs`, `sRGB`, and `gAMA` ancillary chunks preserved | `86a1f57335a3784013137ba024ec49d38c404fa56ca3050d79ae8ec3a20e4a8c` | - -The documentation site uses `brand/picogrid-app-icon-192.png` as its favicon. It keeps -the published composition exactly, a white triangular mark on a solid black `#000000` -field, so the documentation and operator identity match the current public Picogrid -mark and stay legible against both light and dark browser chrome. Neither the cropped -wordmark letter nor the superseded internal application mark is retained in the -release source, and no Orion-specific product mark is redistributed. - -`brand/picogrid-nav-texture.png` is authorized Picogrid cover artwork supplied for -this public site so that the guide's navigation band matches the published Legion -API documentation. It is decorative background artwork carrying no text, mark, or -operational content, and no information is conveyed by it alone. - -Unlike every other brand input above, it is not traceable to a path and blob in a -pinned public repository. It was supplied directly by Picogrid rather than taken -from the reviewed `picogrid/web` revision, and no byte-identical file is tracked -there, so a recipient cannot re-derive it from a published source. What a recipient -can check is the artwork they actually received: it is redistributed byte for byte, -unmodified, as 335,984 bytes of 8-bit RGBA PNG (color type 6) measuring `2256x382`, -with its `pHYs`, `sRGB`, and `gAMA` ancillary chunks preserved, hashing to the -SHA-256 recorded above. The departure from pinned-source provenance is deliberate -and recorded in the engineering decision record under D030 rather than left as a gap. - -The colour-scheme control's device-default glyph in -`docs/src/components/SchemeGlyph.astro` is not Picogrid line art. Its five path -elements are the `sun-moon` icon from the public Lucide icon set, byte-identical -to the tracked source below, which is what the Legion API documentation's own -theme control draws, so both sites offer the choice under the same mark. The -light and dark glyphs beside it are the documentation theme's own icons, used -under that theme's license. - -| Source | Git blob | SHA-256 of file content | Use | -| --- | --- | --- | --- | -| `lucide-icons/lucide@62527757e2607ca3e73eec1e4f24e78cf60eb993`, `icons/sun-moon.svg` | `5465d9f814eebea0b2a7f1f08a29ad5b51812974` | `d1b183b301763d4674e784fab326cf26c3e6dea7192a0b1e1af3709c8cae73db` | Device-default colour-scheme glyph | - -| Public output | Transformation | SHA-256 | -| --- | --- | --- | -| `docs/src/components/SchemeGlyph.astro` | The five `path` elements copied unchanged onto the same `0 0 24 24` grid, with the wrapper attributes re-authored so the glyph inherits size and colour from the control | `21289f46bdc06cebb5f0ab7eb274fcbc4c26be469f24b09ab9ad9b39894e38a3` | - -Lucide is published under the ISC license, which requires its copyright notice -and permission notice to appear in all copies. The `LICENSE` at the pinned -revision above (Git blob `718bb3f0e44153809972abed31839375804bf652`) states, for -every icon not derived from the Feather project, which `sun-moon` is not: - -```text -ISC License - -Copyright (c) 2026 Lucide Icons and Contributors - -Permission to use, copy, modify, and/or distribute this software for any -purpose with or without fee is hereby granted, provided that the above -copyright notice and this permission notice appear in all copies. - -THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES -WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF -MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR -ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES -WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN -ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF -OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. -``` - -That requirement follows the glyph into every copy, including the deployed -documentation, which renders the path data but carries none of this file. The -upstream text is therefore redistributed verbatim with the built guide at -`/licenses/lucide-ISC.txt`, byte-identical to the `LICENSE` blob named above, -exactly as the font license is carried at `/fonts/chivo-mono-OFL.txt`. - -The operator map markers are independently authored, generic lettered geometric -symbols. They communicate public entity category, affiliation, freshness, and -selection without importing military-standard, restricted, or proprietary tactical -symbology. No Orion favicon, product-specific icon, UI asset, map tile, operational address, -credential, private npm package, or internal infrastructure detail is included. - -The finished documentation site and operator frontend depend only on their committed -public npm dependencies and lockfiles. No remote font or brand request is made: the -page is served entirely from its own origin, which the browser suite asserts by -requiring every request a published page makes to be same-origin. - -## Fonts - -Running text on both the documentation site and the operator frontend is set in the -reader's own system face. No webfont is served for it, and none is named ahead of the -system stack. - -The documentation site serves one webfont, for monospace, because that face also sets -its headings and site title rather than only its code: - -| Font | Package | Version | License | Served from | -| --- | --- | --- | --- | --- | -| Chivo Mono | `@fontsource-variable/chivo-mono` | `5.3.0` | SIL Open Font License 1.1 | This site's own origin | - -Chivo Mono is published by Omnibus-Type under the SIL Open Font License 1.1. -The upstream copyright and license text is redistributed with the built guide at -`/fonts/chivo-mono-OFL.txt`. Only the upright weight axis is included. The face is -loaded from the site's own build output, split by Unicode range, so a reader fetches -only the subset their page needs and no request -reaches a font host. The system monospace stack remains behind it for the moment -before it loads and for any reader who blocks it. +operator wheel's `.dist-info/licenses/` directory. + +The documentation's device-default colour-scheme glyph uses the `sun-moon` icon from +`lucide-icons/lucide@62527757e2607ca3e73eec1e4f24e78cf60eb993`, +`icons/sun-moon.svg`. Lucide is licensed under the ISC License. The required +copyright and permission notice is redistributed with the built guide at +`/licenses/lucide-ISC.txt`. + +The documentation site serves Chivo Mono from +`@fontsource-variable/chivo-mono@5.3.0`. Chivo Mono is published by Omnibus-Type +under the SIL Open Font License 1.1. The upstream license text is redistributed with +the built guide at `/fonts/chivo-mono-OFL.txt`. + +## Picogrid brand provenance + +Picogrid authorized the minimum static tokens and brand assets used by this public +documentation site and operator example. The copied inputs were reviewed from these +pinned public sources: + +| Source | Paths used | +| --- | --- | +| `picogrid/web@711e3a3f7d9c5ed233425a6c218929e14697d80e` | `.agents/skills/pico-brand/SKILL.md`; `.agents/skills/pico-brand-mobile/SKILL.md`; `.agents/skills/pico-brand-terminals/SKILL.md`; `packages/ui/src/styles.css`; `apps/orion/public/assets/general/pg-logo.png` | +| `picogrid/picogrid@741a3cfa03643863f9feba95fcc5b54d3b7ac558` | `apps/web/public/favicon.svg` | + +The stylesheets adapt static values from those sources; no private component, +application source, story, package metadata, or runtime dependency was copied. +Output hashes and dimensions are enforced by `scripts/release-policy.json`. + +`brand/picogrid-nav-texture.png` is the sole unpinned brand input. Picogrid supplied +it directly, so no source path or Git blob exists. It is redistributed byte for byte +as decorative artwork; the private engineering decision log records its admission +as a named provenance exception. + +The operator map markers are independently authored generic geometric symbols. No +Orion favicon, product-specific icon, proprietary tactical symbology, map tile, +operational address, credential, private npm package, or internal infrastructure +detail is included. diff --git a/docs/README.md b/docs/README.md index 8c26e80..28a4285 100644 --- a/docs/README.md +++ b/docs/README.md @@ -75,6 +75,4 @@ Every included example imports the installed `picogrid-ecn-client` wheel. Run ## Compatibility labels The [compatibility page](compatibility/limitations.md) is the public source of truth -for supported and deferred behavior. Maintainer evidence tiers and complete -historical workflow accounting remain in the non-public -[original ECN-integration parity matrix](reference/original-ecn-integration-parity.md). +for supported and deferred behavior. diff --git a/docs/cspell.json b/docs/cspell.json index 48aa7f1..8736f36 100644 --- a/docs/cspell.json +++ b/docs/cspell.json @@ -10,8 +10,7 @@ "operator-app/README.md" ], "ignorePaths": [ - "docs/cloudflare/**", - "docs/reference/original-ecn-integration-parity.md" + "docs/cloudflare/**" ], "words": [ "aclose", diff --git a/docs/reference/evidence-status.md b/docs/reference/evidence-status.md deleted file mode 100644 index 8ec891e..0000000 --- a/docs/reference/evidence-status.md +++ /dev/null @@ -1,124 +0,0 @@ ---- -title: Evidence and workflow status -description: Meaning of support and verification labels used throughout the guide. -tableOfContents: false -sidebar: - hidden: true ---- - -:::tip[Supported] -The public API implements this workflow on the retained MQTT v5 wire. -::: - -:::caution[Changed semantics] -A safe equivalent exists, but private infrastructure, authoritative-query, routing, -or provisioning behavior was deliberately removed. Migration differences are stated -on the workflow page and in the parity matrix. -::: - -:::danger[Deferred] -No confirmed public MQTT wire exists. The client raises a typed unsupported error or -documents the application boundary instead of inventing server behavior. -::: - -:::note[Offline only] -The behavior is verified with the minimal mock or local static tooling. This is not -deployed compatibility evidence. -::: - -:::note[Installed-wheel verified] -This label is applied only when a workflow runs outside the repository from the exact -inspected wheel, with `PYTHONPATH` removed. It is stronger release-artifact evidence -than a source-checkout test, but it is still offline and says nothing about a deployed -broker. -::: - -:::caution[Reconnect amendment implemented; artifact verification pending] -The local candidate implements the typed reconnect policy, classified recovery, -connection event/wait/credential-notification/retry surface, delivery phases, -interrupted-QoS-0 and task outcome-unknown failures, explicit MQTT session settings, -and no-replay guarantees. Exact installed-wheel and hosted verification remain -pending. DNS and path-backed TLS-material reader cleanup requests kill the child and -perform a bounded drain; a runtime cleanup primitive that resists cancellation can -outlive that bound. Temporary PEM is removed before the builder returns. -Standard-library/OpenSSL context parsing remains synchronous and cannot be interrupted -immediately, so immediate hard-deadline and zero-background-task shutdown remain -incomplete. The amendment has no staging or production evidence. Historical reconnect -behavior and staging results do not satisfy it. -::: - -:::caution[Operator artifact candidate] -The separate operator wheel has focused offline evidence for two-build -reproducibility, its fixed allowlist and embedded frontend bytes, a dependency-resolving -combined install with the matching client wheel, the installed one-command demo, and -the supported Python backend suites. The fresh hosted exact-artifact Verify and Pages -gates remain pending. The operator application has not been validated against staging -or production. -::: - -:::note[Configured-ECN clock diagnostic] -The clock API is offline-verified against deterministic NTPv4 fixtures. Its -installed-wheel and authorized-staging labels are recorded separately: installed -verification proves the packaged API, CLI, example, validation, cancellation, and -cleanup behavior; staging verification additionally requires a bounded response from -the configured ECN NTP service. Neither label implies MQTT readiness, authentication, -authorization, or permission to modify a clock. -::: - -:::caution[Staging pending] -The workflow has not completed a bounded, sanitized session against the authorized -staging target. A narrower prerequisite may have completed without making the whole -named workflow staging-verified; the associated row states that boundary explicitly. -::: - -:::tip[Staging verified historical slice] -This label applies only to the operation and exact candidate named. A bounded -sanitized run used an installed immutable candidate and target-specific mTLS -credentials. It verified zero-publish MQTT v5 preflight; synthetic JSON and protobuf -TRACK at QoS 0; JSON and protobuf location, JSON Position Location Information (PLI), -and one exact same-ECN local task result at QoS 1. It does not imply broader topic, -route, or current credential access. -::: - -:::caution[Current staging readiness] -A later bounded check of the then-current exact candidate passed configuration, DNS, -TCP, verified TLS, and authentication-material validation, but MQTT v5 authentication -was rejected before any subscription or application publication. The credential or -ACL state remains a blocker for validating the next exact candidate. The historical -slice above remains compatibility evidence only. -::: - -:::tip[Historical staging clock diagnostic] -An installed-wheel candidate completed the separately authorized, clock-only -staging check within its request and duration limits. The CLI tolerance check -returned exit status `0`; it started no MQTT operation, changed no clock or -configuration, and left no retained raw output or temporary validation files. -Precise timestamps, measurements, endpoint details, and other validation-capture -data are deliberately not retained in this repository. The temporary wheel digest -was not retained, and that candidate predates the public -local-capture-uncertainty field. This is therefore historical compatibility -evidence only, not verification of the current exact artifact or its uncertainty -reporting. -::: - -No production workflow is verified or authorized by this repository. Cross-terminal -task routing, terminal geolocation, bearer authentication, and the live operator -application remain staging-unverified. The bounded session retained no raw output; -closed every watcher, task registration, and client; and left no pending local task. -It recorded zero decode errors or local drops. Its synthetic publications may remain -in broker-retained staging state because the public wire defines no deletion -operation. - -An additional receive-only fixed-depth retry immediately after an -operator-controlled peer restart received 100 TRACK events representing 72 canonical -UUID identities, all with embedded locations, plus 74 dedicated location events. It -performed zero application publishes and closed with no drops, decode errors, leaked -SDK tasks, or remaining connection. This is operator-corroborated target-side mesh RX -evidence. Prefix stripping removes authenticated peer provenance from the public -event, and the public receive API exposes neither original JSON/protobuf format nor -delivered QoS, so peer-specific origin and cross-terminal task routing remain -unverified. - -The optional configured-ECN clock diagnostic is offline-, exact-installed-wheel-, and -bounded-staging-verified only for the candidate and response described above. This is -not production evidence and does not verify MQTT readiness or authorization. diff --git a/docs/reference/original-ecn-integration-parity.md b/docs/reference/original-ecn-integration-parity.md deleted file mode 100644 index e7f6be7..0000000 --- a/docs/reference/original-ecn-integration-parity.md +++ /dev/null @@ -1,170 +0,0 @@ ---- -title: Original ECN integration parity matrix -sidebar: - hidden: true ---- - -This matrix accounts for every logical page, example, script, top-level reference item, -and supporting source item in the pinned original integration publication. It is an -independently written migration index, not a copy of that publication or of the private -reference SDK. - -The public client intentionally covers only the retained MQTT v5 entity, location, and -exact local or terminal-addressed task wire. `staging verified` applies only to the -operation stated in that row. A `staging pending` row may name narrower prerequisites -that completed without treating them as proof of the whole workflow. -`offline-only` means staging is unnecessary for that disposition. The verification -column describes the evidence boundary, not the status of the final release gate. - -In this matrix, PLI means Position Location Information. The public `Location` model -uses the retained location wire; there is no separate PLI model, method, topic, or -category. - -## Landing page and concepts - -| Row key | Original workflow or item | Public equivalent | Disposition | Verification | Safety or migration difference | -|---|---|---|---|---|---| -| ROOT-00 | Guide landing page | Public README and this parity index | changed semantics | offline-only | Scope is reduced to the public MQTT v5 thin client. | -| CON-10 | Integration overview | Public client overview | changed semantics | offline-only | Omits platform administration, external platform APIs, and private SDK behavior. | -| CON-11 | Broker connection model | MQTT configuration, TLS, authentication, and broker ACL behavior | changed semantics | staging verified | Target-specific mTLS completed verified TLS and MQTT v5 CONNACK; the client neither inspects nor administers private routing or infrastructure configuration. | -| CON-12 | Topic-shape model | Retained plain entity/location forms plus exact local and terminal-addressed task request/response forms | changed semantics | staging pending | Entity/location and same-ECN local task forms passed, but terminal-addressed task routing remains unverified. Heartbeat, administrative, arbitrary peer-prefixed, and broad-wildcard families are outside the public contract. | -| CON-13 | JSON and protobuf wire formats | Typed JSON/PB watchers and the public protobuf decoder | supported | staging verified | JSON and protobuf TRACK/location round trips passed. Public schemas use an independent namespace and never import private generated modules. | -| CON-14 | Entity model | Typed UUID entity, metadata, and location models | changed semantics | staging pending | One new canonical UUID completed JSON and protobuf TRACK round trips with embedded-location caching, but other categories were not published. Publication remains an event, not an authoritative create, discovery, or fingerprint query. | -| CON-15 | Authentication model | Caller-supplied mTLS or bearer authentication configuration | changed semantics | staging pending | Target-specific mTLS completed MQTT v5 authentication, but bearer authentication was not tested. The package does not issue credentials, register identities, or infer granted scopes. | -| CON-16 | Private routing and infrastructure model | Caller-supplied target-terminal UUID for exact task addressing; no route discovery or administration | changed semantics | staging pending | Entity/location forwarding and actual task routing remain external infrastructure responsibilities. | - -## Getting started - -| Row key | Original workflow or item | Public equivalent | Disposition | Verification | Safety or migration difference | -|---|---|---|---|---|---| -| GET-20 | Prerequisites | Install the wheel and construct validated MQTT configuration | changed semantics | offline-only | No private SDK, cache service, platform API, or on-node package is required. | -| GET-21 | mTLS bootstrap | Configure caller-provided CA, client certificate, and key paths | changed semantics | staging verified | Target-specific mTLS completed verified TLS and MQTT v5 CONNACK. Certificate issuance and server-certificate changes are not client operations. | -| GET-22 | Bearer bootstrap | Configure a caller-owned token provider | changed semantics | offline-only | Identity provisioning, installers, token-file discovery, and platform registration are excluded; deployed broker mapping requires separate external confirmation. | -| GET-23 | Connectivity preflight | [`preflight.py`](../../examples/preflight.py) | changed semantics | staging verified | Configuration, DNS, TCP, verified TLS, authentication-material, and MQTT v5 CONNACK checks passed. It performed no subscription probe, application publish, or HTTP request. | - -## How-to pages and example assets - -| Row key | Original workflow or item | Public equivalent | Disposition | Verification | Safety or migration difference | -|---|---|---|---|---|---| -| HOW-30 | Observe terminal-geolocation state | Client-observed location state and [`get_ecn_location.py`](../../examples/get_ecn_location.py) | changed semantics | staging pending | Uses fixed-depth location filters with a single-level UUID segment and returns the observed canonical UUID; it is not an authoritative server query or general discovery operation. | -| EX-H30 | Terminal-geolocation Python example | [`get_ecn_location.py`](../../examples/get_ecn_location.py) | changed semantics | staging pending | Waits on the pinned integration's narrow dedicated location filters without requiring a pre-known UUID. | -| HOW-31 | Observe live tracks | Category-filtered entity watcher | supported | staging verified | A read-only fixed-depth retry received 100 TRACK events representing 72 canonical UUID identities, all with embedded locations, plus 74 dedicated locations, with zero decode errors, drops, publishes, or cleanup leaks. Operator timing corroborated target-side mesh RX; the prefix-stripped event did not authenticate its peer origin. | -| EX-H31 | Track watcher Python example | [`watch_tracks.py`](../../examples/watch_tracks.py) | supported | staging pending | The same installed-wheel watcher path received live TRACK events, but the standalone example command was not separately run against staging. | -| HOW-32 | Observe detections | Category-filtered entity watcher | supported | staging pending | The narrow detection subscription was accepted and closed cleanly, but no detection event was received; event observation remains pending. | -| EX-H32 | Detection watcher Python example | [`watch_detections.py`](../../examples/watch_detections.py) | supported | staging pending | The installed-wheel watcher established the bounded subscription, but no detection payload was available to validate live decoding. | -| HOW-33 | Publish PLI | Typed [`publish_location.py`](../../examples/publish_location.py) publication | changed semantics | staging verified | One synthetic JSON PLI completed a QoS 1 round trip on the location wire; the public API uses its single location model and does not invent a separate PLI surface. | -| EX-H33 | PLI Python example | [`publish_location.py`](../../examples/publish_location.py) | changed semantics | staging pending | The ordinary installed location path is the public PLI path; no duplicate example is shipped. | -| HOW-34 | Create or discover an entity | Typed entity-event publication | changed semantics | staging pending | JSON and protobuf TRACK events for one new canonical UUID completed round trips, but other categories were not published. The client does not promise authoritative creation, discovery, upsert, or fingerprint idempotence. | -| EX-H34 | Entity creation Python example | [`publish_entity.py`](../../examples/publish_entity.py) | changed semantics | staging pending | The underlying installed-wheel entity publication path passed for TRACK, but the example's category-generic contract was not exhaustively exercised. | -| HOW-35 | Receive a task | Exact-topic local and terminal-sourced task registration | changed semantics | staging pending | One exact same-ECN task with literal `local` completed and the registration closed; terminal-sourced routing, advertisement, and discovery remain unverified. | -| EX-H35 | Task handler Python examples | [`receive_task.py`](../../examples/receive_task.py) and [`receive_mesh_task.py`](../../examples/receive_mesh_task.py) | changed semantics | staging pending | The local exact-topic handler path completed; the routed example remains staging-unverified. | -| HOW-36 | Dispatch a task | Exact local or terminal-addressed MQTT request/response exchange | changed semantics | staging pending | One exact same-ECN QoS 1 request/result exchange passed with source `local`. Terminal-addressed routing remains external and unverified. | -| EX-H36 | Task dispatch Python examples | [`dispatch_task.py`](../../examples/dispatch_task.py) and [`dispatch_mesh_task.py`](../../examples/dispatch_mesh_task.py) | changed semantics | staging pending | The local bounded response path completed and released its resources; the routed example remains staging-unverified. | -| HOW-37 | Render observed events | Separately installed public [`operator-app`](../../operator-app/README.md) over typed watchers | changed semantics | offline-only | The independently authored map uses narrow public watchers and remains outside the SDK runtime wheel. | -| EX-H37-SERVER | Renderer server source | Local server packaged in the operator wheel | changed semantics | offline-only | One installed entry point serves only the local browser application and uses the installed client for MQTT traffic. | -| EX-H37-UI | Renderer static UI | Compiled Leaflet map packaged in the operator wheel | changed semantics | offline-only | The UI is independently authored, defaults to read-only, contains no proprietary assets, and requires no runtime Node.js installation. | -| HOW-38 | Decode protobuf payloads | Public decoder and independently generated public schema | supported | staging verified | Protobuf TRACK and location round trips passed with the public decoder; it never imports a private SDK namespace. | -| EX-H38 | Protobuf decoder Python example | [`decode_public_protobuf.py`](../../examples/decode_public_protobuf.py) | supported | offline-only | The retained example decodes a caller-provided file and includes a synthetic offline self-check. | -| HOW-39 | Search server-wide entities | No SDK equivalent | explicitly deferred | offline-only | The original workflow has no confirmed MQTT wire equivalent and is not included in the public SDK. | -| EX-H39 | Remote-search Python example | No SDK equivalent | explicitly deferred | offline-only | No replacement example is shipped because the corresponding operation is not part of the public SDK. | - -## Walkthrough pages and assets - -| Row key | Original workflow or item | Public equivalent | Disposition | Verification | Safety or migration difference | -|---|---|---|---|---|---| -| WALK-40 | Track-viewer walkthrough | Public track watcher and runnable [`operator-app`](../../operator-app/README.md) | changed semantics | offline-only | The installed operator artifact is exercised with synthetic data; live operator-map behavior is not staging or production verified. | -| EX-W40-MAIN | Track-viewer main program | [`watch_tracks.py`](../../examples/watch_tracks.py) and `picogrid-ecn operator` | changed semantics | offline-only | The installed operator entry point is exercised offline; historical live watcher evidence does not validate the complete operator application. | -| WALK-41 | Task-handler walkthrough | [`receive_task.py`](../../examples/receive_task.py) and [`receive_mesh_task.py`](../../examples/receive_mesh_task.py) | changed semantics | staging pending | The literal-local exact-topic path passed with deterministic cleanup; source-terminal routing remains unverified and deployment infrastructure owns forwarding. | -| EX-W41-MAIN | Task-handler main program | [`receive_task.py`](../../examples/receive_task.py) and [`receive_mesh_task.py`](../../examples/receive_mesh_task.py) | changed semantics | staging pending | The typed local request/result/context path passed; the routed example remains unverified. | -| EX-W41-CONTAINER | Task-handler container recipe | N/A | explicitly deferred | offline-only | Deployment packaging is consumer-owned and not included in the wheel. | -| EX-W41-COMPOSE | Task-handler composition recipe | N/A | explicitly deferred | offline-only | Service orchestration is outside the client contract. | -| WALK-42 | Tactical-view walkthrough | Runnable public operator map over typed watchers | changed semantics | offline-only | The public application omits broad capture, routing diagnostics, proprietary UI assets, and infrastructure control. | -| EX-W42-SERVER | Tactical-view server source | Local server packaged in the operator wheel | changed semantics | offline-only | The server remains an external application boundary outside the SDK wheel and is launched by the installed entry point. | -| EX-W42-UI | Tactical-view static UI | Compiled Leaflet map and diagnostics panel packaged in the operator wheel | changed semantics | offline-only | The UI is independently authored, consumes only sanitized public event snapshots, and has no runtime Node.js dependency. | -| EX-W42-CONTAINER | Tactical-view container recipe | Public operator-app Dockerfile | changed semantics | offline-only | The image accepts the exact public client and operator wheels at build time and contains no credentials or endpoint. | -| EX-W42-COMPOSE | Tactical-view composition recipe | Public operator-app Compose recipe | changed semantics | offline-only | Configuration uses placeholders, loopback publication, read-only defaults, and caller-mounted credentials. | - -## Reference-tier pages - -| Row key | Original workflow or item | Public equivalent | Disposition | Verification | Safety or migration difference | -|---|---|---|---|---|---| -| DOC-50 | Shipped-script catalog | Installed public examples and separate operator application wheel | changed semantics | offline-only | Client and operator wheel contents are distinguished explicitly and installed together for the operator workflow. | -| DOC-60 | Combined protocol reference | Public API/protocol references and this migration index | changed semantics | staging pending | Entity/location and literal-local task shapes passed, but terminal-addressed routing remains source-pinned and staging-unverified. Non-public transport details, private methods, heartbeat, route administration, and administrative namespaces remain omitted. | -| DOC-70 | Troubleshooting guide | Secret-safe MQTT/TLS diagnostics | changed semantics | offline-only | Errors and examples do not print secret values or credential-bearing paths. | - -## Script inventory - -| Row key | Original workflow or item | Public equivalent | Disposition | Verification | Safety or migration difference | -|---|---|---|---|---|---| -| SCR-00 | Root script package marker | N/A | explicitly deferred | offline-only | Contains no behavior and is not needed by the wheel. | -| SCR-01 | Static-site build script | Reproducible Astro documentation build outside the runtime | changed semantics | offline-only | Site bytes are verified separately and never become a client runtime dependency. | -| SCR-02 | On-node credential issuer | N/A | explicitly deferred | offline-only | Credential issuance and node mutation are prohibited client responsibilities. | -| SCR-03 | Remote-script package marker | N/A | explicitly deferred | offline-only | Contains no behavior and is not needed by the wheel. | -| SCR-04 | Bounded traffic capture and render tool | Typed watcher APIs feeding consumer-owned capture/rendering | changed semantics | offline-only | Narrow watcher consumption is supported; capture retention and rendering remain downstream, and no broad observation tool is shipped. | -| SCR-05 | Remote helper-library package marker | N/A | explicitly deferred | offline-only | Contains no behavior and is not needed by the wheel. | -| SCR-06 | Raw location and routing helper | Public location watcher | changed semantics | staging verified | Controlled JSON/protobuf locations and 74 live dedicated locations plus embedded TRACK locations were observed through bounded public watchers; private access, broad filters, peer attribution, and outbound-routing diagnostics are removed. | -| SCR-07 | Map-style helper | Independently authored public operator-map styling | changed semantics | offline-only | Styling is compiled into the operator wheel and is not copied proprietary presentation material. | -| SCR-08 | Inline web UI asset | Independently authored public Leaflet UI | changed semantics | offline-only | Compiled browser assets are packaged in the operator wheel; browser dependencies remain outside the MQTT client runtime package. | -| SCR-09 | Live-map server | Installed local operator application server | changed semantics | offline-only | The server consumes narrow watchers, exposes only its local operator APIs, and launches with `picogrid-ecn operator`. | -| SCR-10 | Multi-system connectivity preflight | [`preflight.py`](../../examples/preflight.py) | changed semantics | staging verified | The zero-publish installed-wheel preflight passed through MQTT v5 CONNACK without subscription probes; cache, external platform, HTTP, and token-file checks are removed. | -| SCR-11 | Remote integration bootstrap script | N/A | explicitly deferred | offline-only | Provisioning, installation, and registration mutation are outside scope. | - -## Top-level reference inventory - -| Row key | Original workflow or item | Public equivalent | Disposition | Verification | Safety or migration difference | -|---|---|---|---|---|---| -| REF-01 | MQTT subscriber reference document | Retained MQTT topic and wire reference | changed semantics | staging pending | Bounded entity/location forms and the exact literal-local task exchange passed, but terminal-addressed task routing remains unverified. | -| REF-02 | Terminal provisioning reference document | N/A | explicitly deferred | offline-only | Operator provisioning and host changes are outside scope. | -| REF-03 | SDK integration reference document | Source-grounded public compatibility notes | changed semantics | offline-only | Used only as read-only evidence; no private API is imported or copied. | -| REF-04 | Private reference SDK wheel | N/A | explicitly deferred | offline-only | Excluded from source, dependencies, tests, build inputs, and release artifacts. | -| REF-05 | Private wheel signature bundle | Release process provenance concepts | explicitly deferred | offline-only | The private bundle itself is not distributed with the public client. | -| REF-06 | Extracted private SDK source snapshot tree | Independently implemented public models, codecs, and tests | changed semantics | offline-only | This aggregate row covers every descendant in the private docs/examples/schemas/source/tests/generated-client corpus; none is copied, imported, or packaged. | - -## Scaffolding and presentation assets - -| Row key | Original workflow or item | Public equivalent | Disposition | Verification | Safety or migration difference | -|---|---|---|---|---|---| -| SCAF-01 | Site-publishing workflow | Verify-once Pages workflow for exact reviewed site bytes | changed semantics | offline-only | The permission-bounded workflow has deployed a private Pages site from an exact verified artifact; this is documentation delivery, not package publication or deployed ECN evidence. | -| SCAF-02A | Repository ignore configuration | Public repository ignore policy | changed semantics | offline-only | Public policy is tailored to this repository and secret-safe release outputs. | -| SCAF-03A | Container environment example | Placeholder-only public operator environment example | changed semantics | offline-only | It contains no credential value or operational endpoint and defaults to read-only mock operation. | -| SCAF-03B | Container image recipe | Public operator-app Dockerfile | changed semantics | offline-only | The independently authored recipe installs the exact supplied client wheel and includes no secret. | -| SCAF-03C | Container composition recipe | Public operator-app Compose recipe | changed semantics | offline-only | It binds the browser service to loopback and mounts caller-owned credentials only when explicitly configured. | -| SCAF-04A | Documentation wordmark image | N/A | explicitly deferred | offline-only | Original branding asset is not required for protocol parity. | -| SCAF-04B | Documentation dark-theme image | N/A | explicitly deferred | offline-only | Original presentation asset is not copied. | -| SCAF-04C | Documentation light-theme image | N/A | explicitly deferred | offline-only | Original presentation asset is not copied. | -| SCAF-04D | Documentation stylesheet | Public Starlight theme with authorized static Picogrid-derived tokens | changed semantics | offline-only | Only minimal light/dark color and radius tokens are adapted from the exact source recorded in `NOTICE.md`; no private UI component or npm package is used. | -| SCAF-04E | Documentation site template | Independently authored Astro/Starlight site | changed semantics | offline-only | The guide is built from committed public Markdown and exact locked dependencies. | -| SCAF-04F | Generated-site wordmark image | N/A | explicitly deferred | offline-only | Generated presentation asset is not copied. | -| SCAF-04G | Generated-site dark-theme image | N/A | explicitly deferred | offline-only | Generated presentation asset is not copied. | -| SCAF-04H | Generated-site light-theme image | N/A | explicitly deferred | offline-only | Generated presentation asset is not copied. | -| SCAF-04I | Generated-site stylesheet | Reproducibly generated public site CSS | changed semantics | offline-only | Output includes the authorized provenance-bound static tokens and independently authored Starlight overrides. | -| SCAF-04J | Generated site page | Reproducibly generated navigable public guide | changed semantics | offline-only | Documentation is maintained as source and does not embed the original generated site. | -| SCAF-05 | Original guide package metadata | Public package metadata | changed semantics | offline-only | Runtime dependencies and package contents are independently defined and release-audited. | - -## Original test-source inventory - -| Row key | Original workflow or item | Public equivalent | Disposition | Verification | Safety or migration difference | -|---|---|---|---|---|---| -| TEST-00 | Root test package marker | N/A | explicitly deferred | offline-only | Contains no behavior. | -| TEST-01 | Integration-test package marker | N/A | explicitly deferred | offline-only | Contains no behavior. | -| TEST-02 | Live integration test module | Public offline contract/E2E suites; bounded staging plan | changed semantics | staging pending | A bounded installed-wheel session passed synthetic entity/location/PLI and literal-local task workflows, but cross-terminal, terminal-geolocation, bearer, detection-event, and operator workflows remain pending; no raw output or operational details are retained. | -| TEST-03 | Unit-test package marker | N/A | explicitly deferred | offline-only | Contains no behavior. | -| TEST-04 | Location-helper unit tests | Public location codec, watcher, observation-cache, and installed-wheel tests | changed semantics | offline-only | Tests target the public API and do not import private helpers. | -| TEST-05 | Preflight unit tests | Public MQTT-only preflight and zero-publish tests | changed semantics | offline-only | HTTP, cache, and platform checks are absent. | -| TEST-06 | Map-style unit tests | Operator backend, frontend, and headless-browser map tests | changed semantics | offline-only | Tests exercise independently authored public behavior from installed artifacts without private fixtures. | - -## Evidence boundary - -Offline mock and installed-wheel evidence demonstrates the public contract, not -deployed ECN behavior. The bounded staging session separately verified target-specific -mTLS MQTT v5 preflight; JSON and protobuf TRACK/location paths; JSON Position Location -Information (PLI); literal-local task exchange; and clean resource shutdown. A -read-only retry immediately after an operator-controlled peer restart separately -observed 100 TRACK events representing 72 canonical UUID identities, all with -embedded locations, plus 74 dedicated locations. That timing is -operator-corroborated target-side mesh RX evidence, but the prefix-stripped public -wire did not authenticate peer origin or expose inbound wire format or delivered QoS. -Cross-terminal tasks, peer-specific attribution, terminal geolocation, bearer -authentication, live operator use, and production remain unverified. Every -staging label is operation-specific and must never include endpoint, credential, -host-access, raw capture, or operational-entity details. diff --git a/docs/site/check-built-site.mjs b/docs/site/check-built-site.mjs index dbc73bc..943101e 100644 --- a/docs/site/check-built-site.mjs +++ b/docs/site/check-built-site.mjs @@ -49,11 +49,9 @@ const nonDocumentationTopLevelEntries = new Set([ '.astro', '.wrangler', ]); -// Keep this list aligned with the maintainer-only exclusions in docs/src/content.config.ts. +// The maintainer README is repository guidance rather than a published guide page. const unpublishedDocumentationSources = new Set([ 'README.md', - join('reference', 'evidence-status.md'), - join('reference', 'original-ecn-integration-parity.md'), ]); const source = resolveVersionControl({ repository }); diff --git a/docs/site/public-routes.mjs b/docs/site/public-routes.mjs index f527c0a..587e080 100644 --- a/docs/site/public-routes.mjs +++ b/docs/site/public-routes.mjs @@ -75,6 +75,4 @@ export const publicGuideRoutes = [ export const maintainerOnlyDocumentationRoutes = [ 'readme', - 'reference/evidence-status', - 'reference/original-ecn-integration-parity', ]; diff --git a/docs/site/tests/documentation.spec.ts b/docs/site/tests/documentation.spec.ts index d83e5b5..7480ab4 100644 --- a/docs/site/tests/documentation.spec.ts +++ b/docs/site/tests/documentation.spec.ts @@ -9,6 +9,7 @@ import { readFileSync } from 'node:fs'; import { resolveLegionDocumentation } from '../legion-documentation.mjs'; import { publicGuideRoutes } from '../public-routes.mjs'; import { documentationBase } from '../site-config.mjs'; +import { resolveVersionControl } from '../version-control.mjs'; // The conformance tags do not include the rules a screen reader depends on most // — that landmarks are distinguishable, that heading levels are not skipped, @@ -548,11 +549,9 @@ test('the version menu links the release to the source it was built from', async await trigger.click(); await expect(trigger).toHaveAttribute('aria-expanded', 'true'); - // A checkout with uncommitted work publishes no immutable reference, because - // no commit contains the rendered bytes, so the page links the branch. Either - // way the menu must name exactly the source the page's own metadata names. - // That a *released* build publishes a commit is enforced by the release gates, - // which build from a clean tree with the identity injected. + // Provenance metadata names the exact private engineering commit. The + // reader-facing link must instead name an exported public ref: the release + // tag when injected, or public main for ordinary clean and dirty builds. const commitMeta = page.locator('meta[name="source-commit"]'); const commit = (await commitMeta.count()) ? await commitMeta.getAttribute('content') : null; if (commit === null) { @@ -561,19 +560,29 @@ test('the version menu links the release to the source it was built from', async expect(commit).toMatch(/^[0-9a-f]{40}$/); await expect(page.locator('meta[name="source-ref"]')).toHaveAttribute('content', commit); } + const publicSource = resolveVersionControl({ + repository: 'https://github.com/picogrid/ecn-sdk-python', + }); + const publicReference = publicSource.referenceLabel; + const publicSourceKind = publicSource.sourceKind; + const publicTitle = `Built from ${publicSourceKind} ${publicReference}`; const menu = page.getByRole('menu', { name: 'Version' }); const release = menu.getByRole('menuitem').first(); await expect(release).toHaveAttribute('aria-current', 'true'); await expect(release).toHaveAttribute( 'href', - commit === null - ? 'https://github.com/picogrid/ecn-sdk-python/tree/main' - : `https://github.com/picogrid/ecn-sdk-python/commit/${commit}`, + `https://github.com/picogrid/ecn-sdk-python/tree/${publicReference}`, ); - if (process.env.DOCS_GIT_TAG) { - await expect(page.locator('.documentation-source a')).toHaveText(process.env.DOCS_GIT_TAG); - } + await expect(release).toHaveAttribute('title', publicTitle); + await expect(release).toContainText(`${publicSourceKind} ${publicReference}`); + const footerSource = page.locator('.documentation-source a'); + await expect(footerSource).toHaveAttribute( + 'href', + `https://github.com/picogrid/ecn-sdk-python/tree/${publicReference}`, + ); + await expect(footerSource).toHaveText(publicReference); + await expect(footerSource).toHaveAttribute('title', publicTitle); // What changed between releases is asked of the same control. const panel = page.locator('header.header .version-panel'); await expect(panel.getByRole('menuitem', { name: 'Changelog' })).toBeVisible(); @@ -1097,13 +1106,9 @@ test('public navigation presents product journeys without maintainer routes', as await expect(navigation.getByRole('link', { name })).toBeVisible(); } - for (const name of [ - 'Picogrid ECN SDK guide', - 'Evidence and workflow status', - 'Original ECN integration parity matrix', - ]) { - await expect(navigation.getByRole('link', { name })).toHaveCount(0); - } + await expect( + navigation.getByRole('link', { name: 'Picogrid ECN SDK guide' }), + ).toHaveCount(0); }); test('local search reaches broker authorization guidance', async ({ page }) => { diff --git a/docs/site/url-compatibility.test.mjs b/docs/site/url-compatibility.test.mjs index d47874e..11341fe 100644 --- a/docs/site/url-compatibility.test.mjs +++ b/docs/site/url-compatibility.test.mjs @@ -23,6 +23,7 @@ import { import { documentationBasePath } from './site-config.mjs'; const fixtures = []; +const nonpublicRouteSegment = ['pri', 'vate'].join(''); after(async () => { for (const server of fixtures) { await new Promise((closed) => server.close(closed)); @@ -100,7 +101,7 @@ test('manifest contract rejects escapes and missing journeys', () => { }); test('manifest contract rejects pathnames that URL-normalize outside the mount', () => { - const sneaky = `${documentationBasePath}/../private/`; + const sneaky = `${documentationBasePath}/../${nonpublicRouteSegment}/`; const failures = validateManifestContract({ pathnames: [sneaky], journeys: [], @@ -111,7 +112,7 @@ test('manifest contract rejects pathnames that URL-normalize outside the mount', }); test('manifest contract rejects redirect sources that URL-normalize', () => { - const sneaky = `${documentationBasePath}/../private/`; + const sneaky = `${documentationBasePath}/../${nonpublicRouteSegment}/`; const failures = validateManifestContract({ pathnames: [], journeys: [], @@ -125,7 +126,7 @@ test('manifest contract rejects redirect sources that URL-normalize', () => { }); test('manifest contract rejects redirect targets that URL-normalize', () => { - const sneaky = `${documentationBasePath}/../private/`; + const sneaky = `${documentationBasePath}/../${nonpublicRouteSegment}/`; const failures = validateManifestContract({ pathnames: [], journeys: [], diff --git a/docs/site/version-control.mjs b/docs/site/version-control.mjs index c5284d7..7c55bd0 100644 --- a/docs/site/version-control.mjs +++ b/docs/site/version-control.mjs @@ -75,8 +75,8 @@ function readIdentity(root, environment, releaseTag) { }; } -function sourceLink(repository, commit) { - if (commit) return { href: `${repository}/commit/${commit}`, sourceKind: 'commit' }; +function sourceLink(repository, tag) { + if (tag) return { href: `${repository}/tree/${tag}`, sourceKind: 'tag' }; return { href: `${repository}/tree/main`, sourceKind: 'branch' }; } @@ -102,14 +102,16 @@ function readVersionControl(repository, root, packageRoot, environment) { const shortCommit = commit ? commit.slice(0, 7) : ''; const reference = commit || 'main'; - const { href, sourceKind } = sourceLink(repository, commit); + const { href, sourceKind } = sourceLink(repository, tag); return Object.freeze({ commit, href, reference, - // What the reader sees and can look up in the repository. - referenceLabel: tag || shortCommit || 'main', + // What the reader sees and can look up in the public repository. The + // provenance commit may name the private engineering history, so it stays + // in metadata while the link uses only exported refs. + referenceLabel: tag || 'main', shortCommit, sourceKind, tag, diff --git a/docs/site/version-control.test.mjs b/docs/site/version-control.test.mjs index 1743fd3..335d6e3 100644 --- a/docs/site/version-control.test.mjs +++ b/docs/site/version-control.test.mjs @@ -59,8 +59,11 @@ test('injected identity', async (t) => { environment: { DOCS_GIT_COMMIT: injectedCommit }, }); assert.equal(withoutTag.commit, injectedCommit); + assert.equal(withoutTag.reference, injectedCommit); assert.equal(withoutTag.tag, ''); - assert.equal(withoutTag.sourceKind, 'commit'); + assert.equal(withoutTag.href, `${repository}/tree/main`); + assert.equal(withoutTag.referenceLabel, 'main'); + assert.equal(withoutTag.sourceKind, 'branch'); const withTag = await resolveVersionControl({ root, @@ -72,6 +75,9 @@ test('injected identity', async (t) => { assert.equal(withTag.commit, injectedCommit); assert.equal(withTag.tag, `v${version}`); assert.equal(withTag.referenceLabel, `v${version}`); + assert.equal(withTag.reference, injectedCommit); + assert.equal(withTag.href, `${repository}/tree/v${version}`); + assert.equal(withTag.sourceKind, 'tag'); }); await t.test('rejects a tag without a commit', async (subtest) => { @@ -115,9 +121,11 @@ test('local Git identity', async (t) => { const identity = await resolveVersionControl({ root, environment: {} }); assert.equal(identity.commit, commit); + assert.equal(identity.reference, commit); assert.equal(identity.tag, ''); - assert.equal(identity.referenceLabel, commit.slice(0, 7)); - assert.equal(identity.sourceKind, 'commit'); + assert.equal(identity.href, `${repository}/tree/main`); + assert.equal(identity.referenceLabel, 'main'); + assert.equal(identity.sourceKind, 'branch'); }); await t.test('selects the release tag when another tag also points at HEAD', async (subtest) => { @@ -128,13 +136,19 @@ test('local Git identity', async (t) => { const identity = await resolveVersionControl({ root, environment: {} }); assert.equal(identity.tag, `v${version}`); assert.equal(identity.referenceLabel, `v${version}`); + assert.equal(identity.reference, identity.commit); + assert.equal(identity.href, `${repository}/tree/v${version}`); + assert.equal(identity.sourceKind, 'tag'); }); await t.test('forfeits the commit claim for tracked and untracked changes', async (subtest) => { const tracked = createCheckout(subtest); const cleanIdentity = await resolveVersionControl({ root: tracked.root, environment: {} }); assert.equal(cleanIdentity.commit, tracked.commit); - assert.equal(cleanIdentity.sourceKind, 'commit'); + assert.equal(cleanIdentity.reference, tracked.commit); + assert.equal(cleanIdentity.href, `${repository}/tree/main`); + assert.equal(cleanIdentity.referenceLabel, 'main'); + assert.equal(cleanIdentity.sourceKind, 'branch'); writeFileSync(join(tracked.root, 'guide.md'), 'changed after commit\n'); const trackedDirty = await resolveVersionControl({ root: tracked.root, environment: {} }); diff --git a/docs/src/components/DocumentationFooter.astro b/docs/src/components/DocumentationFooter.astro index 4ae04b3..71f067b 100644 --- a/docs/src/components/DocumentationFooter.astro +++ b/docs/src/components/DocumentationFooter.astro @@ -1,10 +1,10 @@ --- /** - * Publish the exact source every page was built from beneath the theme footer. + * Publish a navigable public source reference beneath the theme footer. * - * The header carries the released version; this line carries the immutable Git - * reference behind it, so a reader comparing the hosted guide against the - * repository can reach the precise tag or commit rather than a moving branch. + * Exact build provenance stays in page metadata. This reader-facing line uses + * the exported release tag or public main so every link resolves in the public + * distribution repository. */ import Footer from '@astrojs/starlight/components/Footer.astro'; import { resolveVersionControl } from '../../site/version-control.mjs'; @@ -17,7 +17,7 @@ const summary = `Version ${source.version} \u00b7 ${source.sourceKind} `; ---