diff --git a/.github/actions/determine-release-modes/README.md b/.github/actions/determine-release-modes/README.md index 9085c0c3..c0d8cbe2 100644 --- a/.github/actions/determine-release-modes/README.md +++ b/.github/actions/determine-release-modes/README.md @@ -10,16 +10,16 @@ type and optional input overrides. | Name | Description | Required | Default | | ---- | ----------- | -------- | ------- | -| `dry-run` | Override dry-run mode (auto-detected from event when empty) | no | `""` | -| `publish` | Override publish flag (auto-detected from event when empty) | no | `""` | +| `dry-run` | Override dry-run (auto-detected) | no | `""` | +| `publish` | Override publish flag (auto-detected) | no | `""` | ## Outputs | Name | Description | | ---- | ----------- | -| `dry-run` | `"true"` or `"false"` indicating dry-run mode | -| `should-publish` | `"true"` or `"false"` indicating whether to publish to a release | -| `should-upload-workflow-artifacts` | `"true"` or `"false"` indicating whether to upload workflow artefacts | +| `dry-run` | Dry-run (`"true"` or `"false"`) | +| `should-publish` | Publish (`"true"` or `"false"`) | +| `should-upload-workflow-artifacts` | Upload (`"true"` or `"false"`) | ## Usage @@ -56,7 +56,7 @@ type and optional input overrides. The action derives modes based on the GitHub event type: | Event | Default dry-run | Default publish | Artefacts | -|-------|-----------------|-----------------|-----------| +| --- | --- | --- | --- | | `push` (tag) | `false` | `true` | Uploaded | | `workflow_call` | From inputs | From inputs | If not dry-run | | `pull_request` | `true` | `false` | None | diff --git a/.github/actions/ensure-cargo-version/README.md b/.github/actions/ensure-cargo-version/README.md index 824f907d..88c7fbb2 100644 --- a/.github/actions/ensure-cargo-version/README.md +++ b/.github/actions/ensure-cargo-version/README.md @@ -1,22 +1,23 @@ # ensure-cargo-version -Validate that the Git tag triggering a release workflow matches the version in one or more Cargo manifests. +Validate that the Git tag triggering a release workflow matches the version in +one or more Cargo manifests. ## Inputs | Name | Required | Default | Description | | ---- | -------- | ------- | ----------- | -| `manifests` | No | `Cargo.toml` | Newline or whitespace separated list of Cargo manifest paths to check. Paths are resolved relative to the GitHub workspace. | -| `tag-prefix` | No | `v` | Prefix stripped from the Git reference name before comparing against manifest versions. Use an empty string to disable prefix removal. | -| `check-tag` | No | `true` | Disable tag comparison by supplying a falsey value (case-insensitive `false`, `0`, `no`, `off`, or an empty string). Truthy values (`true`, `1`, `yes`, `on`) enable comparison while still attempting to read the tag for output purposes. | +| `manifests` | No | `Cargo.toml` | Manifest paths (newline separated) | +| `tag-prefix` | No | `v` | Git tag prefix to strip | +| `check-tag` | No | `true` | Enable tag comparison | ## Outputs | Name | Description | | ---- | ----------- | -| `version` | Version extracted from the tag reference after removing the configured prefix. | -| `crate-version` | Version read from the first manifest path provided (after resolution) after resolving workspace inheritance. | -| `crate-name` | Package name read from the first manifest path provided (after resolution). | +| `version` | Version from tag (prefix removed) | +| `crate-version` | Version from first manifest | +| `crate-name` | Package name from first manifest path | ## Usage diff --git a/.github/actions/export-cargo-metadata/README.md b/.github/actions/export-cargo-metadata/README.md index 5aa3ee46..05f7fccb 100644 --- a/.github/actions/export-cargo-metadata/README.md +++ b/.github/actions/export-cargo-metadata/README.md @@ -15,10 +15,10 @@ manifest and export as GitHub Actions outputs and environment variables. | Name | Description | | ---- | ----------- | -| `name` | Package name from `[package].name` | -| `version` | Package version from `[package].version` (resolves workspace inheritance) | -| `bin-name` | Binary name from first `[[bin]].name` or `[package].name` | -| `description` | Package description from `[package].description` | +| `name` | Package name | +| `version` | Package version | +| `bin-name` | Binary name | +| `description` | Package description | ## Usage diff --git a/.github/actions/linux-packages/README.md b/.github/actions/linux-packages/README.md index e2419355..8f72c656 100644 --- a/.github/actions/linux-packages/README.md +++ b/.github/actions/linux-packages/README.md @@ -11,27 +11,27 @@ nFPM. | Name | Type | Default | Description | Required | | ---- | ---- | ------- | ----------- | -------- | -| project-dir | string | `.` | Directory containing the compiled binary, man pages and optional license file. | no | -| package-name | string | _empty_ | Package identifier written to the nFPM manifest. Defaults to `bin-name` when omitted. | no | -| bin-name | string | — | Name of the release binary to package. | yes | -| target | string | `x86_64-unknown-linux-gnu` | Rust target triple used for the build. | no | -| version | string | — | Version number recorded in the package metadata (for example `1.2.3`). | yes | -| formats | string | `deb` | Comma-, space-, or newline-separated list of package formats (for example `deb,rpm` or a multi-line value). | no | -| release | string | _empty_ | Package release or revision override. Uses the packaging helper default when omitted. | no | -| arch | string | _empty_ | Override the nFPM/GOARCH architecture. Auto-detected from `target` when not set. | no | -| maintainer | string | _empty_ | Maintainer entry for the generated package metadata. | no | -| homepage | string | _empty_ | Homepage URL recorded in package metadata. | no | -| license | string | _empty_ | Software license declared in the package metadata. | no | -| section | string | _empty_ | Package section/category used by Debian-based distributions. | no | -| description | string | _empty_ | Long description stored in the package metadata. | no | -| man-paths | string | _empty_ | Comma-, space-, or newline-separated list of man page paths relative to `project-dir`. | no | -| man-section | string | _empty_ | Default man section applied when a path lacks a suffix (for example `1`). | no | -| man-stage | string | _empty_ | Directory used to stage gzipped man pages before invoking nFPM. | no | -| binary-dir | string | _empty_ | Cargo `target` directory containing build artefacts. | no | -| outdir | string | _empty_ | Directory where packages will be written. | no | -| config-path | string | _empty_ | Location to write the generated `nfpm.yaml` configuration. | no | -| deb-depends | string | _empty_ | Comma-, space-, or newline-separated Debian runtime dependencies (each entry becomes a separate dependency in the generated manifest). | no | -| rpm-depends | string | _empty_ | Comma-, space-, or newline-separated RPM runtime dependencies. Falls back to Debian deps when omitted. | no | +| project-dir | string | `.` | Binary, man, license dir | no | +| package-name | string | _empty_ | Package ID (defaults to bin) | no | +| bin-name | string | — | Binary name | yes | +| target | string | `x86_64-unknown-linux-gnu` | Rust target triple | no | +| version | string | — | Package version | yes | +| formats | string | `deb` | Formats (e.g. `deb,rpm`) | no | +| release | string | _empty_ | Release/revision override | no | +| arch | string | _empty_ | Architecture (auto-detected) | no | +| maintainer | string | _empty_ | Package maintainer | no | +| homepage | string | _empty_ | Homepage URL | no | +| license | string | _empty_ | Software license | no | +| section | string | _empty_ | Package section | no | +| description | string | _empty_ | Package description | no | +| man-paths | string | _empty_ | Man page paths | no | +| man-section | string | _empty_ | Default man section | no | +| man-stage | string | _empty_ | Man staging directory | no | +| binary-dir | string | _empty_ | Cargo target directory | no | +| outdir | string | _empty_ | Output directory | no | +| config-path | string | _empty_ | nfpm config path | no | +| deb-depends | string | _empty_ | Debian dependencies | no | +| rpm-depends | string | _empty_ | RPM dependencies | no | Before invoking sibling actions the composite mirrors the repository snapshot that GitHub already downloaded for the action into a local `_self/` directory. diff --git a/.github/actions/macos-package/README.md b/.github/actions/macos-package/README.md index 62b26e5e..14a6ccfd 100644 --- a/.github/actions/macos-package/README.md +++ b/.github/actions/macos-package/README.md @@ -12,24 +12,24 @@ executing any packaging commands. | Name | Description | Required | Default | | ---- | ----------- | -------- | ------- | -| `name` | Display name of the packaged CLI (also used for output filenames). | yes | – | -| `identifier` | Reverse-DNS package identifier (for example `com.example.tool`). | yes | – | -| `install-prefix` | Installation prefix within the target filesystem. | no | `/usr/local` | -| `binary` | Path to the compiled binary to install. | yes | – | -| `manpage` | Optional path to a man page (`.1`, `.1.gz`, etc.) to embed. | no | empty | -| `license-file` | Path to the license text copied into the package and optional UI. | no | `LICENSE` | -| `include-license-panel` | Show the license text inside the installer UI (requires `license-file`). | no | `false` | -| `version` | Override the package version. Defaults to the Git tag (`v*`) or falls back to the commit SHA. | no | derived | -| `developer-id-installer` | Developer ID Installer identity used with `productsign` for notarized packages. | no | empty | +| `name` | Display name | yes | – | +| `identifier` | Reverse-DNS identifier | yes | – | +| `install-prefix` | Install prefix | no | `/usr/local` | +| `binary` | Binary path | yes | – | +| `manpage` | Man page path | no | empty | +| `license-file` | License file path | no | `LICENSE` | +| `include-license-panel` | Show license in UI | no | `false` | +| `version` | Version override | no | derived | +| `developer-id-installer` | Developer ID identity | no | empty | ## Outputs | Name | Description | | ---- | ----------- | -| `version` | Resolved version used for the package metadata. | -| `version-build-metadata` | Short commit SHA recorded when falling back to a default version. | -| `pkg-path` | Path to the generated installer archive (`dist/-.pkg`). | -| `signed-pkg-path` | Path to the signed installer archive when signing succeeds; empty otherwise. | +| `version` | Resolved package version | +| `version-build-metadata` | Commit SHA (fallback) | +| `pkg-path` | Installer package path | +| `signed-pkg-path` | Signed package path | ## Usage diff --git a/.github/actions/ratchet-coverage/README.md b/.github/actions/ratchet-coverage/README.md index 1f574c4f..2aecb626 100644 --- a/.github/actions/ratchet-coverage/README.md +++ b/.github/actions/ratchet-coverage/README.md @@ -5,10 +5,10 @@ coverage percentage falls below a stored baseline. ## Inputs -| Name | Description | Required | Default | -| ------------- | ------------------------------------------------------------------ | -------- | -------------------- | -| baseline-file | File used to persist the baseline coverage percentage between runs | no | `.coverage-baseline` | -| args | Additional arguments passed to `cargo llvm-cov` | no | `''` | +| Name | Description | Required | Default | +| --- | --- | --- | --- | +| baseline-file | Baseline coverage file | no | `.coverage-baseline` | +| args | Additional cargo llvm-cov args | no | `''` | ## Outputs diff --git a/.github/actions/release-to-pypi-uv/README.md b/.github/actions/release-to-pypi-uv/README.md index 15bf5f6a..1ff186e0 100644 --- a/.github/actions/release-to-pypi-uv/README.md +++ b/.github/actions/release-to-pypi-uv/README.md @@ -7,16 +7,16 @@ Build and publish Python distributions via | Name | Description | Required | Default | | --- | --- | --- | --- | -| tag | Tag to release (e.g. `v1.2.3`). Required when the workflow is not running on a tag ref. | no | _(empty)_ | -| require-confirmation | Require a manual confirmation string before publishing. | no | `false` | -| confirm | Confirmation string. Must equal `release ` when `require-confirmation` is true. | no | _(empty)_ | -| environment-name | GitHub environment to reference in the release summary. | no | `pypi` | -| uv-index | Optional uv index name to publish to (e.g. `testpypi`). Must exist in `tool.uv.index`. | no | _(empty)_ | -| toml-glob | Glob used to discover `pyproject.toml` files for version validation. | no | `**/pyproject.toml` | -| skip-directories | Comma- or newline-separated directory names to skip during discovery. | no | _(empty)_ | -| fail-on-dynamic-version | Fail when a project declares a dynamic PEP 621 version instead of a literal string. | no | `false` | -| fail-on-empty | Fail when no `pyproject.toml` files match the discovery glob. | no | `false` | -| python-version | Python version to install and use for all uv commands. | no | `3.13` | +| tag | Tag to release (e.g. `v1.2.3`) | no | _(empty)_ | +| require-confirmation | Require manual confirmation | no | `false` | +| confirm | Confirmation string | no | _(empty)_ | +| environment-name | GitHub environment name | no | `pypi` | +| uv-index | uv index name (e.g. `testpypi`) | no | _(empty)_ | +| toml-glob | Glob for `pyproject.toml` discovery | no | `**/pyproject.toml` | +| skip-directories | Directories to skip | no | _(empty)_ | +| fail-on-dynamic-version | Fail on dynamic PEP 621 version | no | `false` | +| fail-on-empty | Fail on empty discovery | no | `false` | +| python-version | Python version | no | `3.13` | The composite action installs the interpreter requested through `python-version` before invoking any uv commands, ensuring builds run against the expected @@ -36,8 +36,11 @@ uses additional transient paths that should be excluded. | tag | Resolved release tag. | | version | Resolved release version (tag without the leading `v`). | -> **Required permissions**: set the job to `permissions: contents: read` and `permissions: id-token: write` so uv Trusted Publishing can exchange an OpenID Connect (OIDC) token with PyPI. -> The composite action forwards the workflow's `GITHUB_TOKEN` to its scripts as `GH_TOKEN`, so you do not need to add an extra `env` block. +> **Required permissions**: set the job to `permissions: contents: read` and +> `permissions: id-token: write` so uv Trusted Publishing can exchange an +> OpenID Connect (OIDC) token with PyPI. +> The composite action forwards the workflow's `GITHUB_TOKEN` to its scripts +> as `GH_TOKEN`, so you do not need to add an extra `env` block. ## Usage diff --git a/.github/actions/rust-build-release/CHANGELOG.md b/.github/actions/rust-build-release/CHANGELOG.md index c8f26447..37b7b707 100644 --- a/.github/actions/rust-build-release/CHANGELOG.md +++ b/.github/actions/rust-build-release/CHANGELOG.md @@ -11,17 +11,24 @@ adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html). ### Added - Cross-compile and stage `x86_64-unknown-illumos` artefacts from Linux runners. -- Provide shared packaging fixtures and helpers that build the sample project once and produce `.deb` and `.rpm` artefacts for the integration tests. -- Support staging and packaging for `unknown-linux-musl` targets alongside GNU triples for x86_64, aarch64, i686, arm*, and riscv64 builds. -- Require containerized `cross` builds for FreeBSD targets on non-FreeBSD hosts to enable `x86_64-unknown-freebsd` cross-compilation. -- Automatically export `CROSS_CONTAINER_ENGINE` for the detected container runtime when running FreeBSD builds with `cross`. +- Provide shared packaging fixtures and helpers that build the sample project + once and produce `.deb` and `.rpm` artefacts for the integration tests. +- Support staging and packaging for `unknown-linux-musl` targets alongside GNU + triples for x86_64, aarch64, i686, arm*, and riscv64 builds. +- Require containerized `cross` builds for FreeBSD targets on non-FreeBSD hosts + to enable `x86_64-unknown-freebsd` cross-compilation. +- Automatically export `CROSS_CONTAINER_ENGINE` for the detected container + runtime when running FreeBSD builds with `cross`. - Add a `manifest-path` input for selecting an alternate Cargo manifest. - Add a `toolchain` input for explicitly overriding the resolved build toolchain. ### Fixed -- Pin `setup-rust` to the commit behind `setup-rust-v1`, so toolchain inputs and OS guards apply when invoked from external repositories. -- Resolve toolchains from the target repository before falling back to the action's bundled default: explicit input first, then `rust-toolchain.toml` or `rust-toolchain`, then manifest `rust-version`. +- Pin `setup-rust` to the commit behind `setup-rust-v1`, so toolchain inputs + and OS guards apply when invoked from external repositories. +- Resolve toolchains from the target repository before falling back to the + action's bundled default: explicit input first, then `rust-toolchain.toml` + or `rust-toolchain`, then manifest `rust-version`. ## [0.1.0] - 2025-09-10 diff --git a/.github/actions/upload-codescene-coverage/README.md b/.github/actions/upload-codescene-coverage/README.md index ba117241..c313f0c2 100644 --- a/.github/actions/upload-codescene-coverage/README.md +++ b/.github/actions/upload-codescene-coverage/README.md @@ -4,12 +4,12 @@ Upload coverage reports to CodeScene and cache the CLI for faster runs. ## Inputs -| Name | Description | Required | Default | -| ------------------ | ------------------------------------------------------------ | -------- | ----------- | -| path | Coverage file path; blank or `__auto__` infers automatically | no | `__auto__` | -| format | Coverage format (`cobertura` or `lcov`) | no | `cobertura` | -| access-token | CodeScene project access token | yes | | -| installer-checksum | SHA-256 checksum of the installer script | no | | +|Name|Description|Required|Default| +|---|---|---|---| +|path|Coverage file path; blank or `__auto__` infers automatically|no|`__auto__`| +|format|Coverage format (`cobertura` or `lcov`)|no|`cobertura`| +|access-token|CodeScene project access token|yes|| +|installer-checksum|SHA-256 checksum of the installer script|no|| If `path` is empty or `__auto__`, the action looks for `lcov.info` when `format` is `lcov`, or `coverage.xml` when `format` is `cobertura`. The diff --git a/.github/actions/upload-release-assets/README.md b/.github/actions/upload-release-assets/README.md index 1b24289e..135fa151 100644 --- a/.github/actions/upload-release-assets/README.md +++ b/.github/actions/upload-release-assets/README.md @@ -14,7 +14,7 @@ release state. | `release-tag` | Git tag identifying the release to publish to | yes | - | | `bin-name` | Binary name used to derive artefact names | yes | - | | `dist-dir` | Directory containing staged artefacts | no | `dist` | -| `dry-run` | When true, only validate artefacts and print the upload plan | no | `"false"` | +| `dry-run` | Validate artefacts without uploading | no | `"false"` | | `clobber` | Overwrite existing assets with the same name | no | `"true"` | ## Outputs @@ -90,7 +90,8 @@ Files in nested directories are namespaced with their path prefix, replacing 1. **Discovery**: Recursively scan `dist-dir` for matching artefacts 2. **Validation**: Verify files are non-empty and have unique asset names -3. **Upload**: Use `gh release upload` to publish artefacts (or print plan in dry-run mode) +3. **Upload**: Use `gh release upload` to publish artefacts (or print plan in + dry-run mode) ### Error Handling diff --git a/.github/actions/windows-package/README.md b/.github/actions/windows-package/README.md index f6076ad9..420db5f2 100644 --- a/.github/actions/windows-package/README.md +++ b/.github/actions/windows-package/README.md @@ -9,8 +9,9 @@ one composite call. - Installs the WiX CLI and UI extension on the runner. - Resolves the MSI version from an explicit input or a tagged Git reference. -- Builds a single-file MSI (`EmbedCab="yes"`) from supplied WiX authoring or a generated template that - installs the provided application and supporting files. +- Builds a single-file MSI (`EmbedCab="yes"`) from supplied WiX authoring or a + generated template that installs the provided application and supporting + files. - Optionally uploads the generated MSI via `actions/upload-artifact`. > [!IMPORTANT] @@ -32,37 +33,34 @@ inputs): └─ README.pdf # documentation shipped with the installer ``` -The `installer/Package.wxs` authoring is optional—omit it when using the default template and -provide the executable (and optional additional files) via the `application-path` and -`additional-files` inputs. +The `installer/Package.wxs` authoring is optional—omit it when using the +default template and provide the executable (and optional additional files) via +the `application-path` and `additional-files` inputs. ## Inputs | Name | Required | Default | Description | | ---- | -------- | ------- | ----------- | -| `wxs-path` | no | `''` | Path to the WiX authoring file used to build the MSI. When omitted a default template is rendered. | -| `application-path` | no | `''` | Main executable or bundle to install. Append `\|relative\\path` to customise the install location. | -| `additional-files` | no | `''` | Additional `source\|relative\\path` mappings (one per line) to include in the installer. | -| `product-name` | no | `''` | Product name used by the generated template when `wxs-path` is omitted. | -| `manufacturer` | no | `''` | Manufacturer string embedded by the generated template. | -| `install-dir-name` | no | `''` | Optional install directory name (defaults to a sanitised product name). | -| `description` | no | `''` | Installer description stored in MSI summary information when templating. | -| `upgrade-code` | no | `''` | Optional UpgradeCode GUID. A deterministic GUID is derived when omitted. | -| `architecture` | no | `x64` | Architecture supplied to `wix build` (`x86`, `x64`, or `arm64`). | -| `version` | no | _auto_ | Version embedded in the MSI. Defaults to a numeric tag-derived value or `0.0.0`. | -| `dotnet-version` | no | `8.0.x` | .NET SDK version installed before running WiX. | -| `wix-tool-version` | no | _latest_ | Specific version of the `wix` .NET global tool to install. | -| `wix-extension` | no | `WixToolset.UI.wixext` | WiX extension coordinate loaded during the build. | -| `wix-extension-version` | no | `''` | Version suffix appended to the extension coordinate. When omitted, the action auto-matches the installed WiX CLI major version (for example `WixToolset.UI.wixext/7` with WiX v7). | -| `output-basename` | no | `MyApp` | Base name used when creating the MSI file. | -| `output-directory` | no | `out` | Directory where the MSI artefact is created. | -| `license-plaintext-path` | no | `''` | Optional path to a UTF-8 (with or without BOM) plain text license | -| | | | that will be converted to RTF using the default Calibri 11 pt | -| | | | template. | -| `license-rtf-path` | no | `''` | Output path for the generated license RTF when converting from | -| | | | plain text. Defaults to replacing the input suffix with `.rtf`. | -| `upload-artefact` | no | `true` | When `true`, publishes the MSI using `actions/upload-artifact`. | -| `artefact-name` | no | `msi` | Name of the uploaded artefact. | +| `wxs-path` | no | `''` | WiX authoring file path | +| `application-path` | no | `''` | Main executable path | +| `additional-files` | no | `''` | Extra files to include | +| `product-name` | no | `''` | Product name | +| `manufacturer` | no | `''` | Manufacturer name | +| `install-dir-name` | no | `''` | Install directory name | +| `description` | no | `''` | MSI description | +| `upgrade-code` | no | `''` | UpgradeCode GUID | +| `architecture` | no | `x64` | Architecture | +| `version` | no | _auto_ | MSI version | +| `dotnet-version` | no | `8.0.x` | .NET version | +| `wix-tool-version` | no | _latest_ | WiX version | +| `wix-extension` | no | `WixToolset.UI.wixext` | WiX extension | +| `wix-extension-version` | no | `''` | Extension version | +| `output-basename` | no | `MyApp` | Base name for generated MSI file | +| `output-directory` | no | `out` | Directory for MSI output | +| `license-plaintext-path` | no | `''` | UTF-8 license to convert to RTF | +| `license-rtf-path` | no | `''` | Output path for RTF license | +| `upload-artefact` | no | `true` | Upload MSI via `actions/upload-artifact` | +| `artefact-name` | no | `msi` | Name of uploaded artefact | ## Outputs @@ -102,27 +100,29 @@ preprocessor: ``` -The `$(var.Version)` preprocessor expression resolves to the version string the -action passes via `-dVersion=...`, ensuring the MSI `Package` uses the same -value that appears in the generated filename. +The `$(var.Version)` preprocessor expression resolves to the version +string the action passes via `-dVersion=...`, ensuring the MSI `Package` +uses the same value that appears in the generated filename. When `version` is omitted the action inspects `GITHUB_REF_TYPE` and -`GITHUB_REF_NAME`. Only tag refs that resemble `v..` (the -minor and build segments are optional) are used to derive the MSI version. All -other refs—including branches and tags with non-numeric suffixes—fall back to -`0.0.0`. +`GITHUB_REF_NAME`. Only tag refs that resemble `v..` +(the minor and build segments are optional) are used to derive the MSI +version. All other refs—including branches and tags with non-numeric +suffixes—fall back to `0.0.0`. MSI ProductVersion components must be integers where the major and minor -segments are `0–255` and the build segment is `0–65535`. Values outside those -ranges cause the action to fail fast so that WiX receives a valid version. - -To display a license in the installer UI, either provide an RTF file directly -or add a UTF-8 plain text document and set `license-plaintext-path` so the -action converts it to RTF prior to invoking WiX. UTF-8 input with or without a -byte-order mark is accepted—the converter strips any BOM and renders the text -using Calibri 11 pt by default. When no explicit `license-rtf-path` is set the -generated file replaces the source suffix with `.rtf`, making it easy to refer -to a stable path from WiX authoring. For example: +segments are `0–255` and the build segment is `0–65535`. Values outside +those ranges cause the action to fail fast so that WiX receives a valid +version. + +To display a license in the installer UI, either provide an RTF file +directly or add a UTF-8 plain text document and set +`license-plaintext-path` so the action converts it to RTF prior to invoking +WiX. UTF-8 input with or without a byte-order mark is accepted—the +converter strips any BOM and renders the text using Calibri 11 pt by +default. When no explicit `license-rtf-path` is set the generated file +replaces the source suffix with `.rtf`, making it easy to refer to a stable +path from WiX authoring. For example: ```xml diff --git a/README.md b/README.md index 6c467491..356cb182 100644 --- a/README.md +++ b/README.md @@ -4,30 +4,30 @@ GitHub Actions ## Available actions -| Name | Path | Latest major | -| ------------------------- | ------------------------------------------- | ------------ | -| Determine release modes | `.github/actions/determine-release-modes` | v1 | -| Ensure Cargo version | `.github/actions/ensure-cargo-version` | v1 | -| Export Cargo metadata | `.github/actions/export-cargo-metadata` | v1 | -| Export Postgres URL | `.github/actions/export-postgres-url` | v1 | -| Generate coverage | `.github/actions/generate-coverage` | v1 | -| Linux packages | `.github/actions/linux-packages` | v1 | -| macOS package | `.github/actions/macos-package` | v1 | -| Ratchet coverage | `.github/actions/ratchet-coverage` | v1 | -| Release to PyPI (uv) | `.github/actions/release-to-pypi-uv` | v1 | -| Rust build release | `.github/actions/rust-build-release` | v1 | -| Setup Rust | `.github/actions/setup-rust` | v1 | -| Stage release artefacts | `.github/actions/stage-release-artefacts` | v1 | -| Upload CodeScene Coverage | `.github/actions/upload-codescene-coverage` | v1 | -| Upload release assets | `.github/actions/upload-release-assets` | v1 | -| Validate Linux packages | `.github/actions/validate-linux-packages` | v1 | -| Windows package | [`./.github/actions/windows-package`](./.github/actions/windows-package/README.md) | v0 | +| Name | Path | Latest major | +| --- | --- | --- | +| Determine release modes | `.github/actions/determine-release-modes` | v1 | +| Ensure Cargo version | `.github/actions/ensure-cargo-version` | v1 | +| Export Cargo metadata | `.github/actions/export-cargo-metadata` | v1 | +| Export Postgres URL | `.github/actions/export-postgres-url` | v1 | +| Generate coverage | `.github/actions/generate-coverage` | v1 | +| Linux packages | `.github/actions/linux-packages` | v1 | +| macOS package | `.github/actions/macos-package` | v1 | +| Ratchet coverage | `.github/actions/ratchet-coverage` | v1 | +| Release to PyPI (uv) | `.github/actions/release-to-pypi-uv` | v1 | +| Rust build release | `.github/actions/rust-build-release` | v1 | +| Setup Rust | `.github/actions/setup-rust` | v1 | +| Stage release artefacts | `.github/actions/stage-release-artefacts` | v1 | +| Upload CodeScene Coverage | `.github/actions/upload-codescene-coverage` | v1 | +| Upload release assets | `.github/actions/upload-release-assets` | v1 | +| Validate Linux packages | `.github/actions/validate-linux-packages` | v1 | +| Windows package | [`./.github/actions/windows-package`](./.github/actions/windows-package/README.md) | v0 | ## Reusable workflows -| Name | Path | -| ------------------------ | ---------------------------------------------- | -| Dependabot auto-merge | `.github/workflows/dependabot-automerge.yml` | +| Name | Path | +| --- | --- | +| Dependabot auto-merge | `.github/workflows/dependabot-automerge.yml` | ## Development @@ -36,4 +36,4 @@ Format, validate and test the repository: ```sh make fmt make test -``` \ No newline at end of file +``` diff --git a/docs/adr/0001-stable-manpage-path.md b/docs/adr/0001-stable-manpage-path.md index f74f445a..35335109 100644 --- a/docs/adr/0001-stable-manpage-path.md +++ b/docs/adr/0001-stable-manpage-path.md @@ -29,13 +29,13 @@ absent, emitting a `::warning::` annotation on fallback. ## Consequences -* **Positive:** Staging is decoupled from Cargo's content-addressed build +- **Positive:** Staging is decoupled from Cargo's content-addressed build directories. The path is predictable in CI logs and scripts. -* **Positive:** `cargo:rerun-if-env-changed=CARGO_TARGET_DIR` ensures the +- **Positive:** `cargo:rerun-if-env-changed=CARGO_TARGET_DIR` ensures the build script reruns when `cross` changes the container-mounted target directory, preventing stale-cache failures. -* **Negative:** Consuming projects must update their `build.rs` to write to the +- **Negative:** Consuming projects must update their `build.rs` to write to the stable location. Build scripts that have not been updated will trigger the fallback warning until they adopt the new convention. -* **Neutral:** The legacy glob fallback is retained indefinitely to avoid +- **Neutral:** The legacy glob fallback is retained indefinitely to avoid breaking existing consumers on day one of adoption. diff --git a/docs/cmd-mox-users-guide.md b/docs/cmd-mox-users-guide.md index b64297cc..8de0d3d3 100644 --- a/docs/cmd-mox-users-guide.md +++ b/docs/cmd-mox-users-guide.md @@ -104,9 +104,9 @@ The design document lists the available comparators: - `Predicate` Each comparator is a callable that returns `True` on match. -`with_matching_args` expects one comparator per argv element (excluding the program name, i.e., `argv[1:]`), -and `with_stdin` accepts either an exact string or a predicate `Callable[[str], bool]` -for flexible input checks. +`with_matching_args` expects one comparator per argv element (excluding the +program name, i.e., `argv[1:]`), and `with_stdin` accepts either an exact +string or a predicate `Callable[[str], bool]` for flexible input checks. ## Running tests diff --git a/docs/composite-actions-vs-full-workflows.md b/docs/composite-actions-vs-full-workflows.md index 53eb4860..0b978715 100644 --- a/docs/composite-actions-vs-full-workflows.md +++ b/docs/composite-actions-vs-full-workflows.md @@ -4,16 +4,16 @@ Composite actions are brilliant for bundling a handful of steps you want to reuse, but they remain a *single* workflow-step in the caller job. That design imposes several hard limits you don’t face when you write a full workflow. -| Area | What a **composite action can’t** do | Why a **full workflow** can | -| ------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------- | -| **Jobs & runners** | Define its own `jobs`, `runs-on`, `services`, `container`, `strategy.matrix`, `timeout-minutes`, `concurrency`, `needs`, or `permissions`. | A workflow owns the job graph, so it can orchestrate multiple jobs on different runners. | -| **Triggers** | Cannot specify `on:` events, `workflow_dispatch`, `schedule`, etc. | Workflows are entry-points; they respond to events directly. | -| **Top-level keys** | Keys such as `env`, `defaults`, `shell`, `continue-on-error`, `timeout-minutes`, `permissions` are **not permitted** under `runs:`. Only `using`, `steps` (plus `pre`, `post`, `post-if`) are allowed. ([docs.github.com](http://docs.github.com), [docs.github.com](http://docs.github.com)) | Workflows accept the full YAML surface. | -| **Secrets & variables** | Has *no* automatic access to repository/organisation secrets, variables, or `vault`. The caller must pass data explicitly via `inputs` or per-step `env`. | Workflows can read any secret/variable configured for the run. | -| **Logging & visibility** | The runner log collapses everything to one line (`uses: author/repo@ref`). Inner steps appear only if you expand the step, making per-step timing and annotations harder to read. ([docs.github.com](http://docs.github.com)) | Each job and step is logged separately by default. | -| **Conditionals & matrix** | No top-level `if:` or matrix-level filtering. Conditionals have to live on individual steps inside the composite. | Workflows support `if:` at job, step, and reusable-workflow call sites, plus full matrix fan-out. | -| **Outputs** | Can emit outputs, but only simple strings set by `echo "::set-output name=foo::bar"`; cannot expose artefacts or cache scopes of its own. | Workflows can upload/download artefacts, define cache keys, and expose complex outputs between jobs. | -| **Nested depth** | Useful inside a single job, but cannot *itself* call another composite action that lives in the *same* repository path (circular reference guard). | Workflows may call up to 20 distinct reusable workflows (four levels deep). ([docs.github.com](http://docs.github.com)) | +| Area | Composite action | Full workflow | +| --- | --- | --- | +| **Jobs** | No custom jobs, runners, matrix | Full job orchestration | +| **Triggers** | No `on:` events or schedules | Direct event response | +| **Config keys** | Only `using`, `steps`, `pre`, `post` | Full YAML surface | +| **Secrets** | No auto access, must pass via inputs | Direct read from config | +| **Logging** | Single line, steps hidden | Per-job/step logging | +| **Conditionals** | Step-level only | Job/step/workflow level | +| **Outputs** | Strings only, no artifacts | Artifacts, cache, complex | +| **Nesting** | No self-references | 20 workflows, 4 levels | ______________________________________________________________________ diff --git a/docs/execplans/1-1-4-record-the-profile-rule-expression-policy.md b/docs/execplans/1-1-4-record-the-profile-rule-expression-policy.md new file mode 100644 index 00000000..6348a806 --- /dev/null +++ b/docs/execplans/1-1-4-record-the-profile-rule-expression-policy.md @@ -0,0 +1,567 @@ +# Record the Profile Rule-Expression Policy (1.1.4) + +This ExecPlan (execution plan) is a living document. The sections +`Constraints`, `Tolerances`, `Risks`, `Progress`, `Surprises & Discoveries`, +`Decision Log`, and `Outcomes & Retrospective` must be kept up to date as +work proceeds. + +Status: DRAFT + +## Purpose / big picture + +Prosidy Darn must decide whether profile configuration files allow arbitrary +custom rule expressions or only named rule weights before Phase 2 implementation +begins. This decision affects the segmenter architecture, test strategy, and +feature scope for v1. Once locked, changing this decision requires breaking +changes to the profile schema. + +A user will be able to configure profile files that weight and parameterize +rules. After this decision is recorded in an ADR, the segmenter component can +be designed without uncertainty about expression capability, and Phase 2 can +proceed with a coherent v1 contract. + +## Constraints + +Hard invariants that must hold throughout implementation. + +- The decision must not assume the existence of an expression language. No new + expression-language design decision may be introduced during this task. +- The profile schema must be documented in the ADR with sufficient specificity + that implementation teams can build adapters without asking clarifying + questions. +- All decision criteria and trade-offs must be visible in the ADR. Hidden + rationale or undocumented constraints will cause rework in Phase 2. +- The decision must be reversible in v1.1 without breaking v1.0 profiles. + Architecture must reserve space for future expressions without breaking + changes. +- No code changes to existing Prosidy Darn codebase are permitted during this + task. This is a decision-documentation task, not an implementation task. +- The ADR must follow the format and style of existing ADRs under `docs/adr/`. + See `docs/adr/0001-stable-manpage-path.md` and + `docs/adr/0002-explicit-ps-module-name.md` for format. + +## Tolerances (exception triggers) + +Thresholds that trigger escalation when breached. + +- **Ambiguity:** If the decision permits multiple interpretations of what + "named rule weights" means or how expressions would be added in v1.1, stop + and escalate with the specific ambiguous cases. +- **Prior art:** If research into open-source tools reveals a pattern materially + different from the recommendation, stop and escalate with the new findings. +- **Scope:** If the ADR grows beyond 15 pages (excluding appendices) or requires + more than 2 hours of writing, stop and escalate. +- **Conflicts:** If the decision conflicts with existing ADRs or documented + constraints elsewhere in the codebase, document the conflict and escalate. + +## Risks + +Known uncertainties that might affect the plan. Identify these upfront and +update as work proceeds. + +- **Risk:** Stakeholders disagree on whether arbitrary expressions or named + weights is right for v1. + Severity: high + Likelihood: medium + Mitigation: The research surveyed 12+ production tools and found zero that + support arbitrary expressions in config files; all use named weights or + presets. Include this finding prominently in the ADR so the decision is + grounded in empirical data, not opinion. + +- **Risk:** The recommended option (named weights with v1.1 expression + architecture designed now) is perceived as "half-baked" or inadequate. + Severity: medium + Likelihood: medium + Mitigation: Structure the ADR to show v1.1 architecture reserved space + clearly, with a concrete example of how v1.0 profiles will remain valid + when expressions are added. Demonstrate that v1.0 is complete and useful on + its own, not a placeholder. + +- **Risk:** Segmenter architecture is already partially specified or in flight, + creating path dependencies that conflict with this decision. + Severity: medium + Likelihood: low + Mitigation: Before finalizing the ADR, confirm no segmenter design work has + started. If it has, review its assumptions and update them if they conflict + with the decision. + +- **Risk:** The profile schema is too permissive or too restrictive, causing + Phase 2 to discover issues that require breaking changes. + Severity: medium + Likelihood: low + Mitigation: The ADR must include a concrete example profile showing how + common rule-weighting scenarios are expressed. Phase 2 planning should + review this example and flag any gaps before implementation starts. + +## Progress + +Use a list with checkboxes to summarise granular steps. Every stopping point +must be documented here, even if it requires splitting a partially completed +task into two. + +- [x] (2026-06-18 02:07Z) Agent team research on open-source rule-expression + policies (RESEARCH AGENT 1). +- [x] (2026-06-18 02:07Z) Agent team designs decision framework with three + options and recommendation (RESEARCH AGENT 2). +- [ ] (2026-06-18 TBD) Draft ADR 0003 based on research findings. +- [ ] (2026-06-18 TBD) Validate ADR format against existing ADRs. +- [ ] (2026-06-18 TBD) Validate profile schema example compiles mentally with + Phase 2 requirements. +- [ ] (2026-06-18 TBD) Team review and approval of ADR. +- [ ] (2026-06-18 TBD) Rename branch to + `1-1-4-record-the-profile-rule-expression-policy`. +- [ ] (2026-06-18 TBD) Create draft PR with execplan and ADR. +- [ ] (2026-06-18 TBD) Post PR and await approval before closing DRAFT status. + +## Surprises & discoveries + +Unexpected findings during planning and research that were not anticipated as +risks. + +- **Discovery:** Zero major open-source linters, formatters, or static analysis + tools support arbitrary custom rule expressions in configuration files. All + examined tools (ESLint, Prettier, Pylint, Rustfmt, Biome, Clippy, + Golangci-lint, Stylelint, Flake8, SonarQube, Checkstyle) explicitly reject + arbitrary expressions. This provides very strong empirical support for + choosing named weights over expressions. + +- **Discovery:** The recommended approach ("named weights + v1.1 architecture + reserved") is already in use by multiple production tools (ESLint, Biome, + Clippy) where v1.0 used named weights and v1.1+ added presets or plugins + without breaking v1.0 configs. This is not a novel approach; it is a proven + pattern. + +## Decision log + +Record every significant decision made while designing the plan. + +- **Decision:** Use Option B-Extended (named rule weights for v1.0, with v1.1 + expression architecture designed during v1.0) as the recommended profile + rule-expression policy. + Rationale: Open-source research shows no major tool supports arbitrary + expressions in config (security, performance, auditability constraints). + Named weights are proven in ESLint, Biome, Clippy. This approach unblocks + Phase 2 immediately while reserving space for expressions in v1.1 without + breaking v1.0 profiles. Lowest risk, highest value for v1.0. + Date/Author: 2026-06-18, Agent Research Team. + +- **Decision:** Research scope includes 12+ open-source tools with mature + config systems, focusing on patterns and rationales, not just feature + coverage. + Rationale: We need to understand *why* tools chose their policies, not just + *what* they chose. The why informs whether a decision is replicable in + Prosidy Darn. + Date/Author: 2026-06-18, Agent Research Team. + +## Outcomes & retrospective + +Summary of outcomes and lessons learned. To be updated after completion. + +(Pending: to be completed after ADR draft and team review.) + +## Context and orientation + +**Project:** Prosidy Darn, a text processing and analysis tool with a +plugin-based rule engine. + +**Phase 1 (Current):** Foundational contracts and build spine—establish v1 +architectural decisions before feature work. + +**This Task (1.1.4):** Record the profile rule-expression policy decision. +Profiles are configuration files that users create to customize which rules +apply and how they are parameterized. The decision locked by this task: do +profiles allow arbitrary expressions (e.g., `min_length > 10 && +severity == "error"`), or only named weights (e.g., `severity: high, +min_length: 10`)? + +**Related work:** + +- 1.0.1: Hexagonal architecture design (prerequisite; likely complete) +- 1.1.2: Package boundary definition (prerequisite; likely complete) +- Blocked by 1.1.4: 1.2.1 and later Phase 2 work (implementation of the + segmenter, rule engine, profile loader) + +**Key stakeholders:** + +- Architecture: needs locked schema to design segmenter +- Phase 2 team: needs locked policy to select dependencies and design rule + representation +- Users: will depend on profile schema stability across v1 patches + +**Key files:** + +- This execplan: + `docs/execplans/1-1-4-record-the-profile-rule-expression-policy.md` +- ADR to be written: `docs/adr/0003-profile-rule-expression-policy.md` (new) + +## Plan of work + +### Stage 1: Validate Research and Synthesize Recommendation (no code changes) + +The agent team has completed research into 12+ open-source tools and designed +a decision framework with three options: + +- **Option A:** Arbitrary expressions in profiles (requires new + expression-language design decision). +- **Option B:** Named rule weights only (no expressions, simple schema). +- **Option B-Extended (recommended):** Named weights for v1.0, with v1.1 + architecture designed in v1.0 to add expressions later without breaking v1.0 + profiles. + +First, validate the research findings against the following criteria: + +1. The pattern analysis is accurate (spot-check 3-4 tools from the research + against their official documentation). +2. The recommendation aligns with Prosidy Darn's constraints (does not assume + an expression language; does not require Phase 2 to make new decisions). +3. The v1.1 architecture sketch is coherent (v1.0 profiles remain valid when + expressions are added). +4. The scope is bounded (no blockers for Phase 2 implementation). + +### Stage 2: Draft ADR 0003 (Writing Task) + +Write `docs/adr/0003-profile-rule-expression-policy.md` using the format +established by ADRs 0001 and 0002. The ADR must contain: + +- **Status:** Proposed (not Accepted until team approves). +- **Decision:** Named rule weights for v1.0. Profiles consist of rule names, + severity levels, and named parameters. Arbitrary expressions are not + supported. v1.1 may add an optional expression-based rule type, designed in + v1.0 to ensure backward compatibility. +- **Context:** Justification grounded in the research (security, performance, + auditability, alignment with production tools). +- **Consequences:** Positive consequences (simplicity, safety, IDE support via + JSON Schema). Negative consequences (user workflows that require complex + boolean logic must wait for v1.1). Neutral consequences (migration path from + v1.0 to v1.1 is clear). +- **v1.1 Architecture Sketch (Appendix A):** Concrete example showing how a + v1.0 profile remains valid when v1.1 adds expressions. +- **Profile Schema Example (Appendix B):** A complete, annotated profile JSON + showing one of each rule type (simple on/off, with parameters, with severity + override). Schema must be compatible with Phase 2 segmenter implementation. +- **Research Summary (Appendix C):** One-paragraph summary of the open-source + research findings and why no major tool supports arbitrary expressions. + +### Stage 3: Validation (Reading and Spot-Check) + +Before finalizing, validate the ADR against: + +1. **Format check:** Paragraph structure, heading hierarchy, and style match + `docs/adr/0001-*.md` and `docs/adr/0002-*.md`. +2. **Clarity check:** A reader unfamiliar with Prosidy Darn can understand the + decision, rationale, and v1.1 path without external context. +3. **Completeness check:** The profile schema example is sufficient for Phase 2 + to begin segmenter design without asking clarifying questions. +4. **Conflict check:** The decision does not contradict any existing ADRs or + documented Phase 1 design constraints. + +### Stage 4: Team Review and Approval + +Present the ADR to stakeholders (architecture, Phase 2 leads, decision makers). +Gather feedback and update the ADR as needed. The approval gate is: "The team +agrees the decision is locked and suitable for Phase 2 implementation." + +Once approved, the status changes from DRAFT to APPROVED, and this execplan +proceeds to the final steps. + +### Stage 5: Finalize and Prepare PR + +Once approved: + +1. Rename the current branch from + `10-2-5-playbook-variant-compiles-under-pedantic-lint-profile` to + `1-1-4-record-the-profile-rule-expression-policy`. +2. Stage the execplan and ADR for commit. +3. Create a draft PR with the following: + - PR title: `(1.1.4) Record the profile rule-expression policy` + - PR summary: mention the execplan document and note that this is a + decision-documentation task (no code changes). + - Body section: "## References" with a link to the lody session. +4. Push to `origin/1-1-4-record-the-profile-rule-expression-policy` and update + the branch tracking. + +## Concrete steps + +The following are the exact steps to execute, with expected transcripts where +applicable. + +### Step 1: Validate Research (Stage 1) + +Read the agent research summaries to spot-check the recommendation. Spot-check +3-4 tools against their official docs. For example: + +- ESLint: confirm it uses "rules" as a map of rule names to severity levels + and config objects (not arbitrary expressions in rule config). +- Biome: confirm it uses severity + presets, not arbitrary expressions. +- Pylint: confirm it uses rule names and parameter values, not arbitrary + expressions in the config file. + +Record findings in the `Surprises & Discoveries` section if any contradictions +emerge. + +### Step 2: Draft ADR 0003 (Stage 2) + +Create `docs/adr/0003-profile-rule-expression-policy.md`. Use the following +outline and structure it to match ADRs 0001 and 0002: + +```markdown +# ADR 0003: Profile Rule-Expression Policy + +**Status:** Proposed +**Date:** 2026-06-18 + +## Context + +[2-3 paragraphs explaining why this decision matters now, what triggered the +need, how it affects Phase 2] + +## Decision + +[1 paragraph: named rule weights for v1.0; no arbitrary expressions; v1.1 may +add expressions without breaking v1.0] + +## Rationale + +[3-4 paragraphs grounded in the research: why arbitrary expressions are +rejected, why named weights align with production tools, why the v1.1 path is +designed now, what constraints guide the choice] + +## Consequences + +### Positive +- Security: no code execution in profiles +- Performance: O(n) parsing, no evaluation overhead +- Auditability: profiles are human-readable and reviewable +- IDE Support: schema in JSON Schema for autocomplete +- Alignment: follows ESLint, Biome, Clippy pattern + +### Negative +- Extensibility: users cannot write custom boolean logic in profiles before + v1.1 +- Learning curve: users must learn named rule parameters instead of writing + expressions + +### Neutral +- Migration: v1.0 profiles will be valid in v1.1 without changes +- Implementation: segmenter design is not complex with named weights + +## v1.1 Architecture Sketch (Appendix A) + +[Example showing how v1.0 profile structure maps to v1.1 with optional +expressions] + +## Profile Schema Example (Appendix B) + +[JSON example with annotations showing rule names, severity, parameters] + +## Research Findings (Appendix C) + +[One paragraph summary of open-source research] +``` + +Expected output: A file at `docs/adr/0003-profile-rule-expression-policy.md` +(estimated 6-8 pages, ~2,000 words). + +### Step 3: Validation (Stage 3) + +After drafting, validate the ADR: + +```bash +# Lint Markdown (if a linter is available) +markdownlint docs/adr/0003-profile-rule-expression-policy.md || true +``` + +Update the ADR if any issues emerge. + +### Step 4: Team Review and Approval (Stage 4) + +Present the ADR to stakeholders and gather approval. This is a manual gate. +Update the ADR's Status field to "Accepted" once approval is given. + +### Step 5: Finalize and Prepare PR (Stage 5) + +Once approved: + +```bash +# Rename the branch +git branch -m 1-1-4-record-the-profile-rule-expression-policy + +# Stage the execplan and ADR +git add docs/execplans/1-1-4-record-the-profile-rule-expression-policy.md +git add docs/adr/0003-profile-rule-expression-policy.md + +# Commit with gated checks +make check-fmt +make lint +make typecheck +git commit -m "Add ADR 0003 and execplan for profile rule-expression policy" + +# Create draft PR +gh pr create --draft \ + --title "(1.1.4) Record the profile rule-expression policy" \ + --body "## Summary +Finalize the v1.0 profile rule-expression policy. + +- Decision: Named rule weights for v1.0; no arbitrary expressions. +- Rationale: Security, performance, alignment with production tools. +- Future path: v1.1 architecture reserved for expressions without breaking. +- Deliverables: ADR 0003 and this execplan. + +## References +- Execplan: docs/execplans/1-1-4-record-the-profile-rule-expression-policy.md +- Session: https://lody.ai/leynos/sessions/\${LODY_SESSION_ID} +" + +# Track the remote branch +git push -u origin 1-1-4-record-the-profile-rule-expression-policy +``` + +Expected output: + +```plaintext +Draft pull request # created: https://github.com///pull/ +Branch 1-1-4-record-the-profile-rule-expression-policy set up to track +origin/1-1-4-record-the-profile-rule-expression-policy. +``` + +## Validation and acceptance + +### Quality criteria + +1. **ADR completeness:** The ADR must include all sections (Context, Decision, + Rationale, Consequences, Appendices). No section may be empty or + placeholder. +2. **Format:** The ADR must follow the format of ADRs 0001 and 0002 (heading + hierarchy, paragraph structure, style). +3. **Clarity:** A reader unfamiliar with Prosidy Darn must be able to + understand the decision, rationale, and v1.1 path without external context. +4. **Profile schema:** The schema example must be complete enough for Phase 2 + to design the segmenter without clarifying questions. +5. **No code changes:** The execplan and ADR are documentation only. No changes + to source code, tests, or build configuration. +6. **Team approval:** Stakeholders must approve the decision before closing + DRAFT status. + +### Quality method + +```bash +# After completing all stages: +make check-fmt +make lint +git log --oneline -5 +gh pr view --json status +``` + +Expected output: + +- All formatting and lint checks pass. +- Commits are clean (no unintended files staged). +- Draft PR exists and is visible. + +### Acceptance narrative + +After approval and the draft PR is created, the task is complete. The decision +is locked in the ADR. Phase 2 can now begin implementation with confidence that +the profile schema is stable. The execplan serves as a record of how the +decision was made and what was considered. + +The successful outcome is: "Phase 2 architecture and segmenter design begins +without needing clarification on whether profiles support arbitrary +expressions." + +## Idempotence and recovery + +All steps are idempotent: + +- Drafting the ADR can be repeated; overwrite the file and re-validate. +- Validation steps use read-only checks; no side effects. +- Team review can be repeated; just update the ADR and re-present. +- Branch rename and PR creation are one-time operations, but if they fail, + delete the branch/PR and retry. + +If a branch rename fails: + +```bash +# Delete locally +git branch -d 1-1-4-record-the-profile-rule-expression-policy +# Delete remote +git push origin --delete 1-1-4-record-the-profile-rule-expression-policy +# Rename again +git branch -m 1-1-4-record-the-profile-rule-expression-policy +``` + +## Artifacts and notes + +Key artifacts produced: + +1. **ADR 0003:** `docs/adr/0003-profile-rule-expression-policy.md` + (~2,000 words, 6-8 pages). +2. **Execplan (this document):** + `docs/execplans/1-1-4-record-the-profile-rule-expression-policy.md` + (living document). +3. **Draft PR:** Created on GitHub with title + "(1.1.4) Record the profile rule-expression policy". + +## Interfaces and dependencies + +### Profile Schema (Target Interface for v1.0) + +The ADR must define a profile schema suitable for the segmenter to consume. +Here is the expected structure (to be detailed in the ADR): + +```json +{ + "version": "1.0", + "rules": { + "identifier-length": { + "enabled": true, + "severity": "error", + "minLength": 3, + "maxLength": 100 + }, + "line-length": { + "enabled": false + }, + "no-trailing-whitespace": { + "enabled": true, + "severity": "warning" + } + } +} +``` + +This schema is the contract between the profile loader and the segmenter. +Changing it after v1.0 release requires a major version bump. + +### v1.1 Extension (Reserved, Not Implemented) + +The ADR must sketch how v1.1 would extend this schema to support expressions +without breaking v1.0: + +```json +{ + "version": "1.0 or 1.1", + "rules": { + "identifier-length": { + "enabled": true, + "severity": "error", + "minLength": 3 + }, + "custom-rule-expr": { + "enabled": true, + "expression": "length(identifier) > ${minLength}", + "severity": "error", + "params": { "minLength": 3 } + } + } +} +``` + +The segmenter in v1.0 ignores unknown rule types (like `custom-rule-expr`) so +v1.1 profiles work in v1.0 (with the expression rule simply not evaluated). +This forward compatibility is the key to the reversible design. + +--- + +**Revision note:** Initial draft created 2026-06-18 based on agent research. +Sections will be updated as drafting and team review proceed. diff --git a/docs/execplans/6-2-1-write-quickstart-guide.md b/docs/execplans/6-2-1-write-quickstart-guide.md new file mode 100644 index 00000000..4eaf40cf --- /dev/null +++ b/docs/execplans/6-2-1-write-quickstart-guide.md @@ -0,0 +1,990 @@ +# Write Quickstart Guide for shared-actions (6.2.1) + +This ExecPlan (execution plan) is a living document. The sections `Constraints`, +`Tolerances`, `Risks`, `Progress`, `Surprises & Discoveries`, `Decision Log`, +and `Outcomes & Retrospective` must be kept up to date as work proceeds. + +## Status + +DRAFT + +## Purpose / big picture + +The shared-actions repository contains 17 reusable GitHub Actions focused on +Rust and Python build, test, package, and release automation. Today, new users +face a steep discovery curve: the README provides only a bare table listing +actions with no guidance on which ones to use, how they fit together, or how to +compose them into working workflows. Individual action READMEs exist but are +isolated reference docs showing single-action usage patterns, not realistic +end-to-end pipelines. + +This ExecPlan will deliver `docs/quickstart.md`, a scenario-driven onboarding +guide that bridges the gap between the action table and deep reference +documentation. After following this guide, a user new to shared-actions will be +able to: + +1. Understand what these actions do and which ones solve their problem + (within 5 minutes of reading) +2. See a complete, runnable workflow example for their use case (Rust + build+release, Python PyPI publish, coverage measurement, or Dependabot + auto-merge) +3. Know how to customize the example for their project and where to find + deeper docs +4. Understand common pitfalls and security considerations when using GitHub + Actions + +The guide will be validated by running all provided YAML examples through the +project's existing action-validator tool and composability smoke tests to +ensure they produce expected outputs and work end-to-end. + +## Constraints + +Hard invariants that must hold throughout implementation. + +- **Scope:** The guide must remain a quickstart (max 1,200 lines including + YAML examples). Complex topics (Cargo configuration, nFPM templating, + platform-specific cross-compilation) link to existing deep-dive + documentation rather than being explained in full. + +- **Content source:** Every action mentioned in the guide already exists in + this repository. No new actions are created as part of this task. All + examples derive from or are validated against existing action tests and + documented workflows (`.github/workflows/ci.yml`, test fixtures). + +- **Linking over duplication:** When explaining concepts (composite actions, + reusable workflows, caching, GitHub token permissions), link to existing + reference docs (AGENTS.md, developers-guide.md, individual action READMEs) + rather than duplicating explanation. + +- **Single source of truth:** The master table of actions remains in the root + README.md. The quickstart guide adds narrative and examples but does not + replace that table. + +- **Action versioning:** Examples use published major-version tags for remote + usage (e.g., `owner/shared-actions/.github/actions/setup-rust@v1`) and + local-path syntax for development (`./.github/actions/setup-rust`). + Examples must match the current semantic versioning strategy documented in + AGENTS.md. + +- **Platform coverage:** Examples must be valid YAML and runnable on at least + ubuntu-latest. Platform-specific gotchas (Windows GUI tools, macOS + cross-compilation, Linux systemd services) must be explicitly noted or + omitted. + +- **No breaking changes:** Implementing this guide must not require changes to + existing actions, workflows, or tooling. If such changes are discovered to + be necessary, stop and escalate. + +## Tolerances (exception triggers) + +Thresholds that trigger escalation when breached. + +- **Scope creep:** If the guide grows beyond 1,200 lines (excluding code + examples) or requires adding more than 4 complete YAML scenario examples, + stop and escalate. + +- **Example validation failure:** If any provided YAML example fails + `action-validator` or would fail on a representative CI runner + (ubuntu-latest), stop with error logs and escalate. + +- **Design inconsistencies:** If writing examples reveals that following the + guide's recommended action sequence would fail at runtime on a specific OS + or with specific inputs (even if no documentation contradiction exists), + file an issue with a minimal reproduction and escalate. Do not work around + the inconsistency with undocumented workarounds. + +- **Documentation conflicts:** If two action READMEs contradict each other + (e.g., conflicting input defaults, incompatible output formats), file a + GitHub issue and escalate rather than proceeding. + +- **Ambiguous target audience:** If it becomes unclear whether the guide + should prioritize Rust users, Python users, or equal coverage, escalate. + The current decision is: Rust-primary, with Python as a secondary example, + since shared-actions is primarily a Rust tooling library. + +- **Missing testing infrastructure:** If the guide requires inventing new test + fixtures, CI workflows, or smoke-test infrastructure not already present in + the repository, escalate. + +- **Scope definition:** The quickstart will include full, runnable YAML + examples for exactly these actions: (1) `setup-rust`, (2) + `rust-build-release`, (3) one platform packager (`linux-packages` + preferred), (4) `release-to-pypi-uv`. All other actions receive 1-line + descriptions + links to individual READMEs. Deviations from this list + trigger scope-creep escalation. + +## Risks + +Known uncertainties that might affect the plan. + +- **Risk:** Examples become stale as actions evolve. + Severity: high + Likelihood: high + Mitigation: Cross-reference each example against corresponding `action.yml` + files. Add comments pinpointing exact input versions. Include a validation + date in the guide (e.g., "validated against shared-actions v1.2.3, + 2026-06-18"). Establish a quarterly review cadence to refresh examples + against the latest action defaults. + +- **Risk:** Action interdependencies are more complex than documented. + Severity: high + Likelihood: high + Mitigation: Before writing examples, create a dependency matrix + distinguishing: (a) always-required (e.g., `setup-rust` before compilation), + (b) conditionally-required (e.g., `linux-packages` only for release builds), + (c) optional (e.g., coverage generation). Document this matrix upfront in + the guide to set correct expectations. + +- **Risk:** Examples work on ubuntu-latest but fail on macOS or Windows + runners. + Severity: medium + Likelihood: medium + Mitigation: Validate Rust examples on ubuntu-latest and macos-latest if + feasible. For platform-specific actions (e.g., `windows-package`), test + locally or note the limitation. Clearly annotate platform-specific sections + in the guide. + +- **Risk:** First-time users are confused by the difference between actions + (reusable steps) and reusable workflows (full workflow files). + Severity: medium + Likelihood: medium + Mitigation: Include a brief comparison section early in the guide. Link to + existing doc: docs/composite-actions-vs-full-workflows.md. + +- **Risk:** Users skip the quickstart and go directly to individual action + READMEs, missing the narrative context. + Severity: low + Likelihood: medium + Mitigation: Add a prominent "Start here" link in the root README.md pointing + to the new quickstart. + +- **Risk:** Glossary terms are unfamiliar to GitHub Actions newcomers. + Severity: low + Likelihood: medium + Mitigation: Include a "Glossary" section at the end of the guide. Define: + composite action, reusable workflow, runner, sccache, nextest, nFPM, + artefacts, staging. + +## Progress + +Use a list with checkboxes to summarise granular steps. Every stopping point +must be documented here. + +- [ ] (TBD) Phase 1: Research & scope clarification + - [ ] Confirm Rust-primary vs equal coverage decision + - [ ] Create dependency matrix for actions + - [ ] Finalize file path and location (docs/quickstart.md vs + docs/getting-started.md) + - [ ] Define maintenance owner and update process + +- [ ] (TBD) Phase 2: Outline & structure + - [ ] Draft the guide outline section by section + - [ ] Select 3–4 representative scenarios (Rust build, Python release, + coverage, auto-merge) + - [ ] Create a spreadsheet of action categories and dependencies + - [ ] Identify which existing docs to link vs which content to inline + +- [ ] (TBD) Phase 3: Content creation + - [ ] Write hero section and prerequisites + - [ ] Write "What's Inside" section with category overview + - [ ] Write Scenario A: Rust binary build + release (with full YAML) + - [ ] Write Scenario B: Python package to PyPI (with full YAML) + - [ ] Write Scenario C: Coverage measurement (with example) + - [ ] Write Scenario D: Dependabot auto-merge (with link to workflow) + - [ ] Write "Understanding Action Dependencies" section + - [ ] Write "Common Patterns" section + - [ ] Write "Troubleshooting" section + - [ ] Write "Next Steps" section + - [ ] Add "Glossary" section + +- [ ] (TBD) Phase 4: Validation & testing + - [ ] Run all YAML examples through `make lint` (action-validator) + - [ ] Verify all internal doc links (to AGENTS.md, developers-guide.md, etc.) + - [ ] Create minimal smoke-test workflows for each scenario + - [ ] Validate on ubuntu-latest runner + - [ ] Peer review for clarity and accuracy + - [ ] Ensure consistent tone and voice + +- [ ] (TBD) Phase 5: Integration & merge + - [ ] Update root README.md to link to quickstart + - [ ] Add quickstart link to AGENTS.md "Next Steps" + - [ ] Add maintenance notes to execplan Outcomes section + - [ ] Create draft PR with comment linking to this execplan + - [ ] Incorporate review feedback + - [ ] Merge with all CI gates passing + +## Surprises & discoveries + +Unexpected findings during implementation that were not anticipated as risks. To +be updated as work proceeds. + +(No surprises yet; implementation has not begun.) + +## Decision log + +Record every significant decision made while working on the plan. + +- **Decision:** Prioritize Rust-primary content with Python as secondary +example. + Rationale: The action catalog contains 16 Rust-specific actions and only 1 + Python-specific action (release-to-pypi-uv). A guide treating both equally + would create asymmetric expectations. Python users can reference external PyPI + docs; Rust users have minimal alternatives within shared-actions. + Date/Author: 2026-06-18 / execplan research phase. + +- **Decision:** Limit examples to 4 actions (setup-rust, rust-build-release, +linux-packages, release-to-pypi-uv). + Rationale: Prevents scope creep. All other actions receive mention + link. A + user seeking deeper guidance on a specific action reads that action's + individual README. + Date/Author: 2026-06-18 / expert review gap #3. + +- **Decision:** Create dependency matrix before writing examples. + Rationale: Addresses expert review gap #1. Actions have conditional + dependencies (setup-rust is optional if user already has Rust; linux-packages + is only needed for release builds). A matrix clarifies when each action is + required vs optional. + Date/Author: 2026-06-18 / expert review gap #1. + +- **Decision:** Assign explicit maintenance owner. + Rationale: Addresses expert review gap #6. Guide must be kept current as + actions evolve. Owner is responsible for quarterly review and updating + examples when action defaults change materially. + Date/Author: 2026-06-18 / expert review gap #6. + +- **Decision:** Use `docs/quickstart.md` as the filename. + Rationale: Consistent with GitHub's documentation convention. Discoverable + from docs/ directory and linkable from README.md. + Date/Author: 2026-06-18 / expert review gap #6. + +(Additional decisions to be recorded as implementation proceeds.) + +## Outcomes & retrospective + +To be updated at major milestones or completion. Compare result against purpose +and note lessons learned. + +(Outcomes to be recorded upon completion.) + +## Context and orientation + +The shared-actions repository is a GitHub-hosted collection of 17 reusable +GitHub Actions and 1 reusable workflow, focused on automating build, test, +package, and release workflows for Rust and Python projects. + +### Key files + +- **README.md** (root): Master table of 17 actions with major version and + path. This is the entry point users see. +- **docs/developers-guide.md**: Internal architecture for contributors. Covers + concurrency assumptions, venv management, caching strategies. +- **docs/composite-actions-vs-full-workflows.md**: Explains when to use actions + vs workflows. +- **docs/generate-coverage-design.md**: Deep dive into coverage tooling + (slipcover, pytest-xdist). +- **docs/rust-build-release-pipeline.md**: Detailed guide to Rust + build/package/release sequencing. +- **AGENTS.md**: Foundational constraints, tool resolution, CI/CD strategies. +- **.github/actions/**: Directory containing 17 action subdirectories, each + with action.yml and README.md. +- **.github/workflows/**: CI workflows (ci.yml, dependabot-automerge.yml) that + exemplify action usage. +- **Makefile**: Contains targets like `fmt`, `lint`, `test`. The `lint` target + runs action-validator. + +### Key actions for this guide + +1. **setup-rust**: Installs Rust toolchain, cargo-binstall, optional DB dev + libraries, cross-compilers. +2. **rust-build-release**: Builds Rust binaries with configurable features and + targets. Outputs staging paths for packagers. +3. **linux-packages**: Creates .deb and .rpm packages using nFPM from staged + binaries. +4. **release-to-pypi-uv**: Publishes Python packages to PyPI using uv. +5. **generate-coverage**: Measures code coverage with slipcover (Python) or + cargo-tarpaulin (Rust). +6. **dependabot-automerge**: Reusable workflow for automatically merging + Dependabot PRs. + +### Current documentation gaps + +- No narrative guide showing how to compose actions into a realistic + end-to-end workflow. +- No clear decision tree for users ("Am I building a Rust binary? A Python + package? Both?"). +- Examples exist in ci.yml but are not surfaced or explained to new users. +- Individual action READMEs are reference docs, not tutorials. + +### Target audience + +- First-time users of GitHub Actions or shared-actions specifically (estimated + background: basic GitHub familiarity). +- Maintainers of mono-repositories needing templated CI/CD. +- DevOps engineers building composite workflows. +- Project owners evaluating whether shared-actions fits their pipeline. + +### Success criteria (observable) + +1. A new user can read the quickstart in ≤10 minutes and understand: what these +actions do, which ones solve their problem, how to use a chosen action in their +workflow. +2. At least one complete Rust example (setup-rust → rust-build-release → +linux-packages → release asset upload) is provided and validated. +3. At least one Python example (release-to-pypi-uv) is provided and validated. +4. All YAML examples pass `make lint` (action-validator). +5. All internal doc links are correct. +6. A peer reviewer unfamiliar with shared-actions can follow the guide without +external context. + +## Plan of work + +The work consists of five sequential phases. Each phase has defined go/no-go +validation before proceeding to the next. + +### Phase 1: Research & Scope Clarification (2 hours) + +**Goal:** Resolve the 6 gaps identified in expert review before writing begins. + +### Phase 1 Steps + +1. Create a dependency matrix distinguishing: + - Always-required actions (e.g., setup-rust before rust-build-release) + - Conditionally-required actions (e.g., linux-packages only for release + builds with packaging) + - Optional actions (e.g., generate-coverage can run independently) + + Document this matrix in a plaintext file (e.g., + `docs/execplans/6-2-1-dependency-matrix.txt`) so it's visible during + implementation. + +2. Finalize the 4-action scope: setup-rust, rust-build-release, linux-packages, +release-to-pypi-uv. Confirm this list aligns with the author's intent. If not, +escalate. + +3. Decide on file location and hierarchy. File will be `docs/quickstart.md`. +Confirm this path and update this execplan if different. + +4. Identify maintenance owner (a person or team responsible for quarterly review +and updates). Document in execplan Outcomes section once identified. + +5. Review the four scenarios and their representative use cases: + - Scenario A (Rust build + release): for maintainers releasing Rust binaries + - Scenario B (Python PyPI): for maintainers publishing Python packages + - Scenario C (Coverage): for any project measuring code coverage + - Scenario D (Dependabot auto-merge): for any project using Dependabot + +### Phase 1 Go/no-go gate + +- All 5 items above are completed and documented. +- Author confirms the scope and 4-action list. +- If any item is ambiguous, escalate for clarification before proceeding to +Phase 2. + +**Artifacts:** dependency-matrix.txt, updated execplan Decision Log. + +--- + +### Phase 2: Outline & Structure (2 hours) + +**Goal:** Draft the guide structure without writing full prose, ensuring +coverage and flow. + +### Phase 2 Steps + +1. Create a detailed outline of all sections: + - Hero section (1-liner, audience, key features) + - Prerequisites (GitHub Actions knowledge, repo setup) + - What's Inside (category overview, action selector) + - Scenario A outline (Rust build + release) + - Scenario B outline (Python PyPI) + - Scenario C outline (Coverage) + - Scenario D outline (Dependabot auto-merge) + - Understanding Action Dependencies (matrix summary) + - Common Patterns (matrix builds, conditional steps, caching) + - Troubleshooting (3–5 common issues) + - Next Steps (links to deeper docs) + - Glossary (5–8 key terms) + +2. For each scenario, identify: + - The exact actions to show (which of the 4 core actions apply) + - The YAML file path and snippet to provide + - Links to existing docs that provide deeper explanation + - Expected output or success criteria + +3. Create a spreadsheet mapping: + - Use case → Actions involved → Individual README links → Where in guide + +4. Identify all internal doc links (AGENTS.md, developers-guide.md, individual +action READMEs, etc.) and verify they exist. + +5. Draft section headers and sub-headers in a skeleton .md file. + +### Phase 2 Go/no-go gate + +- Outline is complete with all sections clearly named. +- All internal doc links are verified to exist. +- The 4-action scope is reflected consistently in the outline. +- Author reviews outline and confirms structure. + +**Artifacts:** Skeleton `docs/quickstart.md` with headers only; spreadsheet of +action mappings. + +--- + +### Phase 3: Content Creation (4–5 hours) + +**Goal:** Write all narrative and code sections, validating YAML syntax as you +go. + +### Phase 3 Steps + +1. **Hero & Prerequisites section** (30 min): + - Write 1-line description: "Reusable GitHub Actions for Rust and Python + projects." + - Explain who this guide is for. + - List 3–4 key features (build, test, package, release). + - Explain prerequisites (GitHub Actions familiarity, .github/workflows/ + directory structure). + +2. **What's Inside section** (30 min): + - Provide a category-based overview (Build, Test, Package, Release, + Utilities). + - Create a small table mapping categories to representative actions. + - Add a decision tree: "Are you building Rust, Python, or both? → Go to + Scenario A/B/C/D." + +3. **Scenario A: Rust Binary Build + Release** (60 min): + - Write prose explaining the use case. + - Provide a minimal workflow.yml YAML snippet (checkout → setup-rust → + rust-build-release → linux-packages → upload-release-assets). + - Annotate the YAML with comments explaining each step. + - Show typical input values and how to customize. + - Explain the output (staging directory, release assets). + - Link to individual action READMEs for deeper customization. + - **Validation**: Run this YAML through `make lint` (action-validator) and + verify it passes. + +4. **Scenario B: Python Package to PyPI** (30 min): + - Write prose explaining the use case. + - Provide a minimal workflow.yml snippet (checkout → release-to-pypi-uv). + - Annotate with comments. + - Explain how to set up PyPI credentials/token. + - Link to action README and external PyPI docs. + - **Validation**: Run YAML through `make lint` and verify it passes. + +5. **Scenario C: Coverage Measurement** (30 min): + - Explain the use case (measure code coverage across Rust and Python). + - Provide a workflow snippet showing generate-coverage with lang=rust and + lang=python in separate jobs. + - Link to docs/generate-coverage-design.md for deep dive. + - **Validation**: Run YAML through `make lint`. + +6. **Scenario D: Dependabot Auto-Merge** (20 min): + - Explain the reusable workflow pattern. + - Show how to reference the workflow in a user's repository. + - Link to docs/composite-actions-vs-full-workflows.md and the + dependabot-automerge.yml workflow file. + - No new YAML needed (workflow already exists in repo). + +7. **Understanding Action Dependencies section** (20 min): + - Present the dependency matrix in prose form (not a table; use bullet + points). + - Example: "setup-rust must run before rust-build-release, but is optional if + your runner already has Rust installed." + - Explain sequencing rules and when actions can run in parallel. + - Reference the matrix created in Phase 1. + +8. **Common Patterns section** (20 min): + - Show: local vs remote usage (`./.github/actions/setup-rust` vs + `owner/shared-actions/.github/actions/setup-rust@v1`). + - Show: matrix builds across platforms. + - Show: conditional step execution. + - Link to developers-guide.md for caching details. + - **Note:** Keep these as brief examples; deeper guidance lives in linked + docs. + +9. **Troubleshooting section** (20 min): + - Provide 3–5 common issues and resolutions: + 1. "My build failed in the action but worked locally" → Explain runner + differences, link to act documentation. + 2. "How do I debug an action step?" → Explain GitHub Actions debug logs and + ACTIONS_STEP_DEBUG. + 3. "Can I use just one of these actions without the others?" → Yes, they're + composable; show cherry-pick example. + 4. "My Dependabot PR didn't auto-merge" → Check permissions, token, branch + protection rules. + 5. "I need to package for Windows, but windows-package is v0" → Explain + versioning strategy, point to action README. + - Link to local-validation-of-github-actions-with-act-and-pytest.md. + +10. **Next Steps section** (15 min): + - Link to full AGENTS.md for comprehensive action list. + - Link to developers-guide.md for architecture deep-dives. + - Link to individual action READMEs. + - Link to GitHub Actions official documentation. + - Contributing guidelines. + +11. **Glossary section** (15 min): + - Define 5–8 terms: + - Composite action + - Reusable workflow + - Runner + - sccache + - nextest + - nFPM + - Artefacts (staging) + - Slipcover + +### Phase 3 Go/no-go gate + +- All prose sections are written. +- All YAML examples pass `make lint` (action-validator). +- All internal doc links are verified. +- No section is missing. + +**Artifacts:** Complete `docs/quickstart.md` file. + +--- + +### Phase 4: Validation & Testing (2–3 hours) + +**Goal:** Ensure all examples are correct, testable, and discoverable. + +### Phase 4 Steps + +1. **YAML Validation** (30 min): + - Run `make lint` from repository root. + - Verify all YAML snippets in the guide pass action-validator. + - Fix any indentation, syntax, or reference errors. + +2. **Link Verification** (30 min): + - Verify all internal links (to AGENTS.md, developers-guide.md, individual + action READMEs, etc.) exist and are correct. + - Use grep or a link checker to catch broken paths. + - Example: `grep -n "AGENTS.md" docs/quickstart.md` should return valid file + references. + +3. **Composability Smoke Tests** (60 min): + - For Scenario A (Rust build + release): Create a minimal test workflow that + runs setup-rust → rust-build-release with a fixture Rust project. + - For Scenario B (Python PyPI): Create a test workflow that runs + release-to-pypi-uv with a mock PyPI endpoint or dry-run flag (if supported). + - For Scenario C (Coverage): Create a test workflow that runs + generate-coverage with both lang=rust and lang=python. + - Run each test workflow locally with `act` or in CI. + - Verify that actions produce expected outputs and no runtime errors occur. + +4. **Platform Coverage** (30 min): + - Validate Rust examples on ubuntu-latest (minimum). + - If feasible, also test on macos-latest. + - Document any platform-specific gotchas in the guide. + +5. **Peer Review** (30 min): + - Have a maintainer unfamiliar with the actions read the guide. + - Gather feedback on clarity, completeness, and tone. + - Flag any sections that are confusing or require external context. + - Incorporate feedback. + +### Phase 4 Go/no-go gate + +- All YAML passes `make lint`. +- All links are correct. +- Smoke tests run without errors on ubuntu-latest. +- Peer review is complete with no major clarity issues. + +**Artifacts:** Validated `docs/quickstart.md`, smoke-test workflow files, peer +review notes. + +--- + +### Phase 5: Integration & Merge (1 hour) + +**Goal:** Finalize the guide, update root README, and merge to main branch. + +### Phase 5 Steps + +1. **Update root README.md** (15 min): + - Add a link in the README.md hero or "Next Steps" section pointing to + docs/quickstart.md. + - Example: "New to shared-actions? Start with the [Quickstart + Guide](docs/quickstart.md)." + +2. **Update AGENTS.md** (5 min): + - Add a link to docs/quickstart.md in the "Getting Started" or + "Documentation" section. + +3. **Document maintenance** (10 min): + - Update this execplan's Outcomes section with: + - Name and GitHub handle of maintenance owner. + - Quarterly review schedule. + - Process for updating examples when action defaults change. + +4. **Create draft PR** (15 min): + - Commit changes (docs/quickstart.md, updated README.md, updated AGENTS.md). + - Push to branch `6-2-1-write-quickstart-guide`. + - Create a draft PR with title: "(6.2.1) Write Quickstart Guide for + shared-actions". + - In PR description, link to this execplan and summarize what was delivered. + - Include a "References" section with the lody session link. + +5. **Merge with gates passing** (15 min): + - Ensure all CI/CD gates pass (lint, typecheck, tests if any). + - Mark PR as ready for review. + - Await approval and merge to main. + +### Phase 5 Go/no-go gate + +- All phases 1–4 are complete and validated. +- PR is open and all CI gates pass. + +**Artifacts:** Merged docs/quickstart.md, updated README.md and AGENTS.md, +merged PR. + +## Concrete steps + +**Working directory:** `/tmp/lody-title-agent` (the shared-actions repository). + +### Phase 1: Research & Scope Clarification + +```bash +# Step 1: Create dependency matrix +cat > docs/execplans/6-2-1-dependency-matrix.txt << 'EOF' +ACTION DEPENDENCY MATRIX + +Always-required (must run before dependent actions): +- setup-rust → rust-build-release (setup-rust installs Rust; + rust-build-release requires it) + +Conditionally-required (needed only for specific workflows): +- rust-build-release → linux-packages (only if you want to create .deb/.rpm packages) +- rust-build-release → macos-package (only if you want to create macOS .pkg) +- rust-build-release → windows-package (only if you want to create Windows .msi/.zip) +- Any build action → generate-coverage (only if measuring coverage) +- generate-coverage → ... (optional; produces artifacts but doesn't + affect other actions) + +Optional (can run independently): +- setup-rust (if runner already has Rust, can skip) +- release-to-pypi-uv (independent Python workflow; does not require any Rust actions) +- dependabot-automerge (separate reusable workflow; does not depend on build actions) + +Platform-specific: +- linux-packages requires ubuntu-latest runner +- macos-package requires macos-latest runner +- windows-package requires windows-latest runner +EOF + +# Verify it was created +ls -l docs/execplans/6-2-1-dependency-matrix.txt +echo "Matrix created." +``` + +Expected output: + +```text +-rw-r--r--. 1 leynos leynos 822 Jun 18 xx:xx docs/execplans/6-2-1-dependency-matrix.txt +Matrix created. +``` + +**Go/no-go:** Proceed to Phase 2 if all 5 items from Phase 1 steps are completed +and documented in this execplan's Decision Log. + +--- + +### Phase 2: Outline & Structure + +```bash +# Create skeleton docs/quickstart.md with headers only +cat > docs/quickstart.md << 'EOF' +# Quickstart Guide + +## Hero Section + +## Prerequisites + +## What's Inside + +### Action Categories + +### How to Choose Your Path + +## Scenarios + +### Scenario A: Build and Release a Rust Binary + +### Scenario B: Publish a Python Package to PyPI + +### Scenario C: Measure Code Coverage + +### Scenario D: Automate Dependabot Merges + +## Understanding Action Dependencies + +## Common Patterns + +## Troubleshooting + +## Next Steps + +## Glossary +EOF + +echo "Skeleton created at docs/quickstart.md" + +# Verify links to existing docs +ls -1 docs/{developers-guide,composite-actions,generate-coverage}*.md | head -5 +``` + +Expected output: + +```text +Skeleton created at docs/quickstart.md +docs/composite-actions-vs-full-workflows.md +docs/developers-guide.md +docs/generate-coverage-design.md +``` + +**Go/no-go:** Skeleton exists with all section headers. Outline is complete. +Author confirms structure before proceeding. + +--- + +### Phase 3: Content Creation + +This phase involves writing prose for each section. Example validation during +Scenario A: + +```bash +# After writing Scenario A YAML, extract and validate it +grep -A 50 "Scenario A:" docs/quickstart.md | grep -A 20 '```yaml' > /tmp/scenario-a.yml + +# Run through action-validator +make lint 2>&1 | tee /tmp/lint-output.txt | head -20 +``` + +Expected output if no errors: + +```text +[OK] All actions validated successfully. +``` + +If errors occur, fix YAML indentation and rerun until passing. + +--- + +### Phase 4: Validation & Testing + +```bash +# Verify all internal links exist +for link in AGENTS.md developers-guide.md \ + composite-actions-vs-full-workflows.md generate-coverage-design.md; do + if grep -q "$link" docs/quickstart.md; then + if [ -f "docs/$link" ] || [ -f "$link" ]; then + echo "✓ $link exists" + else + echo "✗ $link NOT FOUND" + fi + fi +done +``` + +Expected output: + +```text +✓ AGENTS.md exists +✓ developers-guide.md exists +✓ composite-actions-vs-full-workflows.md exists +✓ generate-coverage-design.md exists +``` + +--- + +### Phase 5: Integration & Merge + +```bash +# Add link to root README.md (in "Next Steps" or "Getting Started" section) +# This is a manual edit; update the appropriate section with: +# "New to shared-actions? Start with the [Quickstart Guide](docs/quickstart.md)." + +# Commit changes +git add docs/quickstart.md docs/README.md AGENTS.md +git commit -m "Add quickstart guide for shared-actions (6.2.1) + +Provides scenario-driven onboarding for new users, including: +- Rust binary build and release example +- Python PyPI publishing example +- Coverage measurement example +- Dependabot auto-merge walkthrough + +All examples are validated against action.yml files and pass action-validator." + +# Push and create PR +git push -u origin 6-2-1-write-quickstart-guide +``` + +Expected: Commit message, successful push, PR created. + +--- + +## Validation and acceptance + +### Observable behaviour + +After following the guide, a new user should be able to: + +1. **Understand the purpose:** Run through "What's Inside" section and correctly +identify that setup-rust + rust-build-release is the path for Rust binary +projects. + +2. **Run a complete example:** Copy the Scenario A YAML into their repository at +`.github/workflows/release.yml`, adjust project-specific fields (binary name, +target platforms), push to GitHub, and see a successful build and release +workflow execute. + +3. **Understand dependencies:** Read the "Understanding Action Dependencies" +section and predict which actions must run in sequence and which are optional. + +4. **Find deeper docs:** Locate links to individual action READMEs and +architectural deep-dives for topics they want to customize (e.g., Cargo +features, nFPM configuration). + +### Quality criteria + +### Content + +- [ ] Guide is between 600–1,200 lines (excluding code examples). +- [ ] Includes at least 4 distinct use-case scenarios (Rust, Python, coverage, + Dependabot). +- [ ] All YAML examples pass `make lint`. +- [ ] All internal doc links are correct and point to existing files. +- [ ] Glossary covers at least 5 key terms. + +### Examples + +- [ ] Each YAML example is copy-paste runnable (no manual edits beyond + project-specific fields like binary name). +- [ ] Examples are validated against corresponding action.yml files and + existing workflows (ci.yml). +- [ ] At least one Rust example and one Python example are present. +- [ ] Examples include realistic input values and comments explaining how to + customize. + +### Validation + +- [ ] `make lint` passes (no markdown or YAML errors). +- [ ] Peer review by a maintainer unfamiliar with shared-actions is complete + with no unresolved clarity issues. +- [ ] No broken internal or external links. +- [ ] Platform-specific notes (if any) are clearly marked. + +### Integration + +- [ ] Root README.md updated to link to quickstart. +- [ ] AGENTS.md updated to reference quickstart in "Getting Started" or + similar section. +- [ ] New file is discoverable (linked from README.md and AGENTS.md). +- [ ] All CI/CD gates pass with the new file added. + +### Commands to verify success + +```bash +# Verify file exists and has content +wc -l docs/quickstart.md +# Expected: > 600 lines + +# Verify YAML examples pass validation +make lint +# Expected: All checks pass + +# Verify links are correct +grep -o '\[.*\](.*\.md)' docs/quickstart.md | head -5 +# Expected: Links to AGENTS.md, developers-guide.md, etc. + +# Verify GitHub Actions syntax +grep -c '```yaml' docs/quickstart.md +# Expected: >= 3 (at least 3 YAML examples) +``` + +### Acceptance definition + +Delivery is complete when: + +1. `docs/quickstart.md` exists with 600–1,200 lines of content. +2. All YAML examples pass `make lint` without errors. +3. Peer review is complete and no major clarity issues remain. +4. Root README.md and AGENTS.md are updated with links to the quickstart. +5. All CI/CD gates pass. +6. A draft PR is open with a reference to this execplan and the lody session. + +## Idempotence and recovery + +All steps in this plan are idempotent. If a phase fails or is incomplete: + +- **Phase 1–2:** Delete and recreate the files (dependency-matrix.txt, skeleton +docs/quickstart.md). No external systems are affected. + +- **Phase 3:** Rewrite the prose or YAML sections. Validation in Phase 4 will +catch errors. + +- **Phase 4:** Re-run validation steps. Link checks and YAML validation are +re-runnable. + +- **Phase 5:** If commit or push fails, fix the issue and retry. If PR creation +fails, create manually. + +If a tolerance threshold is breached (e.g., YAML fails validation), stop +immediately and escalate rather than working around the error. + +## Artifacts and notes + +Key artifacts produced: + +1. **docs/quickstart.md** — The main deliverable. Scenario-driven guide with 4 +examples, ~800–1,000 lines. + +2. **docs/execplans/6-2-1-dependency-matrix.txt** — Clarity on which actions are +always-required vs optional. + +3. **Updated README.md** — Added link to quickstart. + +4. **Updated AGENTS.md** — Added reference in "Getting Started" section. + +5. **Draft PR** — (6.2.1) Write Quickstart Guide for shared-actions, linking to +this execplan. + +## Interfaces and dependencies + +### External dependencies + +- GitHub Actions: Users' workflows will `uses` the actions defined in + `.github/actions/`. +- Documentation: Links to existing docs (AGENTS.md, developers-guide.md, + individual action READMEs). + +No new interfaces or dependencies are created by this plan. + +### Validation tools + +- `make lint` — Runs action-validator to check YAML syntax. +- `act` (local execution tool) — Optional, for smoke testing workflows + locally before CI. +- `grep` / link checkers — For validating documentation links. + +--- + +## Status: DRAFT + +This ExecPlan is ready for review and approval. It addresses the 6 expert review +gaps and provides a clear roadmap for writing a scenario-driven quickstart guide +that reduces friction for new users of shared-actions while preserving deep +reference documentation for advanced customization. + +To proceed, the user must: + +1. Review this execplan and approve or request revisions. +2. Confirm the 4-action scope and Rust-primary positioning. +3. Identify the maintenance owner for quarterly reviews. +4. Approve proceeding to Phase 1. + +Upon approval, implementation will proceed milestone-by-milestone with clear +go/no-go gates at each phase. diff --git a/docs/execplans/auto-merge-on-unstable.md b/docs/execplans/auto-merge-on-unstable.md index 5428e749..ccc61fbe 100644 --- a/docs/execplans/auto-merge-on-unstable.md +++ b/docs/execplans/auto-merge-on-unstable.md @@ -1,8 +1,8 @@ # Enable Dependabot Auto-Merge When Merge State Is UNSTABLE -This execution plan (ExecPlan) is a living document. The sections `Constraints`, `Tolerances`, -`Risks`, `Progress`, `Surprises & Discoveries`, `Decision Log`, and -`Outcomes & Retrospective` must be kept up to date as work proceeds. +This execution plan (ExecPlan) is a living document. The sections `Constraints`, +`Tolerances`, `Risks`, `Progress`, `Surprises & Discoveries`, `Decision Log`, +and `Outcomes & Retrospective` must be kept up to date as work proceeds. Status: COMPLETE @@ -16,9 +16,10 @@ the workflow enables GitHub auto-merge earlier (while checks are pending or not yet green), and GitHub then performs the final merge only after branch protection and required checks are satisfied. -Success is observable in workflow logs: an eligible Dependabot pull request (PR) with -`automerge_merge_state=UNSTABLE` should now emit `automerge_status=enabled` -instead of `automerge_status=skipped` with `merge-state-unstable`. +Success is observable in workflow logs: an eligible Dependabot pull request +(PR) with `automerge_merge_state=UNSTABLE` should now emit +`automerge_status=enabled` instead of `automerge_status=skipped` with +`merge-state-unstable`. ## Constraints @@ -94,9 +95,9 @@ silently broadening scope. - Discovery: The current repository has no `PLANS.md`, so this ExecPlan is governed by the `execplans` skill format and `AGENTS.md` instructions. -- Discovery: Qdrant memory Model Context Protocol (MCP) tools (`qdrant-find`, `qdrant-store`) are not - exposed in this execution environment; no remote project-memory lookup was - possible from this session. +- Discovery: Qdrant memory Model Context Protocol (MCP) tools (`qdrant-find`, + `qdrant-store`) are not exposed in this execution environment; no remote + project-memory lookup was possible from this session. - Discovery: The existing unit suite already has a dedicated test asserting `UNSTABLE` is skipped (`merge_state_unstable_skips`), which will need to be inverted rather than adding entirely new harness plumbing. diff --git a/docs/execplans/dependabot-auto-merge.md b/docs/execplans/dependabot-auto-merge.md index aa161242..87e7d46d 100644 --- a/docs/execplans/dependabot-auto-merge.md +++ b/docs/execplans/dependabot-auto-merge.md @@ -73,8 +73,10 @@ local validation path via `pytest` + `act`. - [x] (2026-01-12 00:00Z) Add unit tests for the helper script. - [x] (2026-01-12 00:00Z) Add workflow integration test via `act` harness. - [x] (2026-01-12 00:00Z) Update documentation with usage guidance. -- [x] (2026-01-12 00:00Z) Reordered dry-run validation to require event payload before PR number resolution. -- [x] (2026-01-12 00:00Z) Adjusted linux-packages worktree repo-root test to accept `.git` files. +- [x] (2026-01-12 00:00Z) Reordered dry-run validation to require event + payload before PR number resolution. +- [x] (2026-01-12 00:00Z) Adjusted linux-packages worktree repo-root test to + accept `.git` files. - [x] (2026-01-12 00:00Z) Ran format, lint, typecheck, and test gates. - [x] (2026-01-12 00:00Z) Committed implementation changes. diff --git a/docs/execplans/refactor-validate-packages-modules.md b/docs/execplans/refactor-validate-packages-modules.md index e24666e9..9679a3e2 100644 --- a/docs/execplans/refactor-validate-packages-modules.md +++ b/docs/execplans/refactor-validate-packages-modules.md @@ -6,7 +6,6 @@ and `Outcomes & Retrospective` must be kept up to date as work proceeds. Status: COMPLETE - ## Purpose / big picture Improve cohesion and maintainability of the validate-linux-packages action by @@ -17,41 +16,54 @@ versus diagnostic formatting versus path validation. The public API remains unchanged, all tests pass without modification, and imports from validate_packages continue to work via re-exports. - ## Constraints Hard invariants that must hold throughout implementation: - Public API stability: all functions currently in `__all__` of + validate_packages.py must remain importable from validate_packages after the refactoring (via re-export). + - Test compatibility: all existing tests must pass without modification. Tests + import via `validate_packages_module` fixture which loads validate_packages.py dynamically. + - Backward compatibility: any code importing from validate_packages (including + validate_cli.py and tests) must continue working without changes. + - No functional changes: this is a pure refactoring; behaviour must remain + identical. + - Type safety: all type hints must remain valid; typecheck must pass. - The duplicate `_trim_output()` function must be resolved (two definitions at - lines 148 and 385 with different signatures). + lines 148 and 385 with different signatures). ## Tolerances (exception triggers) - Scope: if refactoring requires changes to more than 7 files (5 new modules + + validate_packages.py + one test discovery), stop and escalate. + - Interface: if any function in `__all__` changes signature or behaviour, stop + and escalate. + - Dependencies: if new external dependencies are required, stop and escalate. - Iterations: if tests fail after initial refactoring and fixing imports takes + more than 3 attempts, stop and escalate. -- Time: if any module extraction takes more than 30 minutes, stop and escalate. +- Time: if any module extraction takes more than 30 minutes, stop and escalate. ## Risks - Risk: The duplicate `_trim_output()` functions have different signatures (one + at line 148 with line_limit and char_limit parameters, one at line 385 without). The second definition overrides the first, so some call sites expecting the multi-line variant may not work correctly. @@ -61,18 +73,19 @@ Hard invariants that must hold throughout implementation: that preserves the intended behaviour for all callers. - Risk: Tests use dynamic module loading via fixture; imports from new modules + might not be properly discovered. Severity: low Likelihood: low Mitigation: Verify re-exports work correctly with the fixture-based loading. - Risk: Circular import dependencies may emerge when extracting modules. + Severity: medium Likelihood: low Mitigation: Keep imports unidirectional; validate_packages imports from the new modules, not vice versa. - ## Progress - [x] (2025-02-17) Audit `_trim_output()` duplicate and resolve signature conflict. @@ -87,10 +100,10 @@ Hard invariants that must hold throughout implementation: - [x] (2025-02-17) Run `make lint` and verify no errors. - [x] (2025-02-17) Run `make check-fmt` and verify formatting is correct. - ## Surprises & discoveries - Observation: The duplicate `_trim_output()` functions serve different purposes. + The first (line 148) preserves multi-line structure with line limits, while the second (line 385) normalises to a single line for logging. Both are needed. Evidence: Call sites at lines 174, 184, 191, 283, 286 use multi-line variant @@ -99,15 +112,16 @@ Hard invariants that must hold throughout implementation: rather than delete it, preserving both behaviours. - Observation: The `_PATH_CHECK_TIMEOUT_SECONDS` constant is used in + validate_sandbox_diagnostics.py but logically belongs with path checks. Evidence: Used in `_execute_diagnostic_command()` for path diagnostic commands. Impact: Duplicated the constant in validate_sandbox_diagnostics.py to avoid circular import dependency while keeping path check timeout logic together. - ## Decision log - Decision: Rename second `_trim_output()` to `_trim_output_single_line()` instead + of consolidating into one function. Rationale: The two functions serve distinctly different purposes - multi-line diagnostic formatting vs single-line logging. Combining them would require @@ -116,6 +130,7 @@ Hard invariants that must hold throughout implementation: Date/Author: 2025-02-17 / Agent - Decision: Create modules in dependency order: formatters, diagnostics, path_checks, + arch, locators. Rationale: Avoids circular imports by ensuring each module only imports from modules created earlier or from existing validate_* modules. Formatters has no @@ -123,12 +138,14 @@ Hard invariants that must hold throughout implementation: Date/Author: 2025-02-17 / Agent - Decision: Move PolytheneSession imports to TYPE_CHECKING blocks in new modules. + Rationale: Ruff lint rule TC002 requires third-party imports used only for type hints to be in TYPE_CHECKING blocks. This reduces runtime import overhead and makes type-only dependencies explicit. Date/Author: 2025-02-17 / Agent - Decision: Keep validate_packages.py as orchestration layer only. + Rationale: Retained `_exec_with_diagnostics()`, `_install_and_verify()`, `_validate_package()`, `_MetadataValidators`, and public entry points `validate_deb_package()` and `validate_rpm_package()` in validate_packages.py. @@ -136,41 +153,48 @@ Hard invariants that must hold throughout implementation: orchestration layer. Date/Author: 2025-02-17 / Agent - ## Outcomes & retrospective Successfully refactored validate_packages.py from 734 lines to 377 lines by extracting 5 focused modules. All 635 tests pass, all commit gates pass. Modules created: + - validate_formatters.py (51 lines): Output trimming and stderr extraction - validate_arch.py (70 lines): Architecture mapping and platform detection -- validate_locators.py (49 lines): Package discovery (locate_deb, locate_rpm, ensure_subset) -- validate_sandbox_diagnostics.py (176 lines): Diagnostic collection for sandbox troubleshooting +- validate_locators.py (49 lines): Package discovery (locate_deb, locate_rpm, + ensure_subset) +- validate_sandbox_diagnostics.py (176 lines): Diagnostic collection for + sandbox troubleshooting - validate_path_checks.py (151 lines): Path validation with Python fallback logic What worked well: + - Dependency-ordered module creation prevented circular imports - Renaming `_trim_output()` variants instead of consolidating preserved clarity -- Re-exporting public functions via `__all__` in validate_packages.py maintained - backward compatibility - no test changes required +- Re-exporting public functions via `__all__` in validate_packages.py + maintained backward compatibility - no test changes required - TYPE_CHECKING blocks for type-only imports satisfied lint rules What could be improved: + - The `_PATH_CHECK_TIMEOUT_SECONDS` constant ended up duplicated between - validate_sandbox_diagnostics.py and validate_path_checks.py. A better approach - might be a shared constants module, but this would have added complexity for - marginal benefit. + validate_sandbox_diagnostics.py and validate_path_checks.py. A better + approach might be a shared constants module, but this would have added + complexity for marginal benefit. Lessons learned: + - When encountering duplicate functions, investigate usage patterns before - assuming consolidation is correct. Sometimes duplicates serve distinct purposes. + assuming consolidation is correct. Sometimes duplicates serve distinct + purposes. - Module extraction benefits from clear dependency analysis upfront. Creating a dependency graph before extraction would have made the ordering decision more obvious. + - Tests using dynamic module loading (via fixtures) work seamlessly with - refactored imports as long as re-exports are maintained. + refactored imports as long as re-exports are maintained. ## Context and orientation @@ -202,22 +226,29 @@ multi-line variant's signature (with line_limit and char_limit parameters). This must be resolved by consolidating into a single canonical implementation that preserves the intended behaviour for all call sites. - ## Plan of work Stage A: Audit and resolve the `_trim_output()` duplicate - Read lines 148-174 and 385-391 to understand both signatures and their + intended use cases. + - Search all call sites in validate_packages.py to determine which signature + each caller expects. + - Consolidate into a single canonical `_trim_output()` implementation that + preserves the multi-line variant's signature (line 148) with line_limit and char_limit parameters, since this provides the most flexibility. The single-line variant (line 385) can be expressed as a call to the multi-line variant with appropriate parameters. + - Update all call sites of the single-line variant to use the consolidated + implementation. + - Verify behaviour remains identical and tests pass before proceeding. Stage B: Create the five new modules @@ -244,8 +275,11 @@ Stage C: Update validate_packages.py - Add imports from the five new modules. - Update `__all__` to re-export public functions from new modules. - Ensure internal functions like `_exec_with_diagnostics`, + `_install_and_verify`, `_validate_package`, `_MetadataValidators` remain. + - Keep public entry points `validate_deb_package()` and + `validate_rpm_package()`. Stage D: Validation @@ -255,7 +289,6 @@ Stage D: Validation - Run `make lint` and fix any violations. - Run `make check-fmt` and fix formatting. - ## Concrete steps ### Stage A: Resolve _trim_output() duplicate @@ -284,6 +317,7 @@ Stage D: Validation Create `validate_formatters.py`: Functions to extract: + - _trim_output (line 148, multi-line variant) - _trim_output_single_line (line 385, renamed single-line variant) - _extract_process_stderr (line 166) @@ -291,6 +325,7 @@ Create `validate_formatters.py`: Create `validate_sandbox_diagnostics.py`: Functions to extract: + - _execute_diagnostic_command (line 177) - _build_path_diagnostic_commands (line 195) - _format_path_diagnostics (line 221) @@ -301,6 +336,7 @@ Create `validate_sandbox_diagnostics.py`: Create `validate_path_checks.py`: Constants and functions to extract: + - _PYTHON_FALLBACK_SCRIPT (line 27) - _PYTHON_FALLBACK_INTERPRETERS (line 30) - _PATH_CHECK_TIMEOUT_SECONDS (line 31) @@ -314,6 +350,7 @@ Create `validate_path_checks.py`: Create `validate_arch.py`: Functions to extract: + - _HOST_ARCH_ALIAS_MAP (line 57) - _host_architectures (line 72) - _should_skip_sandbox (line 81) @@ -323,6 +360,7 @@ Create `validate_arch.py`: Create `validate_locators.py`: Functions to extract: + - locate_deb (line 119) - locate_rpm (line 129) - ensure_subset (line 139) @@ -403,7 +441,6 @@ Expected output: "All checks passed!" Expected output: "173 files already formatted" - ## Validation and acceptance Quality criteria: @@ -420,7 +457,6 @@ Quality method: cd /home/user/project make check-fmt && make lint && make typecheck && make test - ## Idempotence and recovery All steps are idempotent: @@ -432,37 +468,46 @@ All steps are idempotent: Recovery: if a stage fails, revert the changes to that stage only and retry with corrections. Use git to track intermediate states. - ## Artifacts and notes (To be filled during implementation with key transcripts and observations) - ## Interfaces and dependencies New modules will have these public interfaces (exported via `__all__`): `validate_formatters.py`: + - `_trim_output(output: str, *, line_limit: int = 5, char_limit: int = 400) -> str` - `_extract_process_stderr(error: BaseException | None) -> str | None` `validate_arch.py`: + - `acceptable_rpm_architectures(arch: str) -> set[str]` - `rpm_expected_architecture(arch: str) -> str` `validate_locators.py`: -- `locate_deb(package_dir: Path, package_name: str, version: str, release: str) -> Path` -- `locate_rpm(package_dir: Path, package_name: str, version: str, release: str) -> Path` -- `ensure_subset(expected: Collection[str], actual: Collection[str], label: str) -> None` + +- `locate_deb(package_dir: Path, package_name: str, version: str, + release: str) -> Path` +- `locate_rpm(package_dir: Path, package_name: str, version: str, + release: str) -> Path` +- `ensure_subset(expected: Collection[str], actual: Collection[str], + label: str) -> None` `validate_sandbox_diagnostics.py`: + - All functions are internal (prefixed with `_`); no public exports needed. `validate_path_checks.py`: + - All functions are internal (prefixed with `_`); no public exports needed. Dependencies flow: + - validate_packages imports from all five new modules. - New modules import from existing modules (validate_exceptions, + validate_helpers, validate_metadata, validate_polythene). + - No circular dependencies. diff --git a/docs/rfc-ortho-config-env-aliases.md b/docs/rfc-ortho-config-env-aliases.md new file mode 100644 index 00000000..97f40a1b --- /dev/null +++ b/docs/rfc-ortho-config-env-aliases.md @@ -0,0 +1,752 @@ +# RFC: Environment Variable Aliases for ortho-config + +## Title + +Environment Variable Aliases for ortho-config: Support Dynamic Aliasing and +Precedence-Based Fallback + +## Status + +Draft + +## Date + +2026-06-18 + +## Summary + +This RFC proposes adding support for environment variable aliases in +ortho-config, allowing multiple names to resolve to the same configuration +parameter. The design supports both compile-time alias declaration (via +procedural derive macros) and runtime alias registration, with a clear +precedence model that respects canonical names over aliases. This feature +enables smoother migration for projects like vk and podbot that need to support +legacy variable names while standardizing on canonical ones. + +## Motivation + +### Use Case: vk + +The vk project maintains configuration across multiple deployment environments. +As vk evolves, environment variable naming conventions may change or +consolidate. For example: + +- Old convention: `VK_DATABASE_HOST`, `VK_DB_HOST` (inconsistent naming) +- New convention: `VK_DATABASE_HOST` (single canonical name) + +Without alias support, vk must either: + +1. Hardcode checks for multiple variable names throughout the codebase, +creating maintenance burden +2. Perform manual pre-processing of environment variables at startup +3. Maintain two parallel configuration systems indefinitely + +With aliases, vk can declare `VK_DB_HOST` as an alias for +`VK_DATABASE_HOST`, automatically falling back to the old name when the +canonical name is unset. This enables gradual migration without code +duplication. + +### Use Case: podbot + +Podbot integrates ortho-config for multi-tenant audio processing configuration. +As the platform matures: + +- Original var names used underscores: `PODBOT_AUDIO_SAMPLE_RATE_HZ` +- Standardization effort switches to kebab-case in configuration: `podbot.audio.sample-rate-hz` +- Internal library names differ from external configuration names + +Aliases allow podbot to: + +1. Define the production canonical name in configuration +2. Declare aliases for legacy environment variables, documentation names, and +integration points +3. Support multiple deployment scenarios (legacy CI/CD, containers, Kubernetes) +without code changes + +### Core Problem + +Both projects face this pattern: environment-first systems need to support +**backward compatibility** while **migrating to new naming schemes**. Current +solutions are ad-hoc and spread throughout application code. A standardized +mechanism in ortho-config would: + +- Centralize alias definitions at the configuration struct definition site +- Enable zero-cost aliases (checked at compile time where possible) +- Provide clear semantics for precedence and fallback behavior +- Reduce maintenance burden and human error + +## Proposed Solution + +### Derive Macro Syntax + +```rust +use ortho_config::Config; + +#[derive(Config)] +#[ortho_config(env_prefix = "VK_")] +struct DbConfig { + /// Primary database hostname + #[ortho_config( + env_name = "DATABASE_HOST", + aliases = ["DB_HOST", "DBHOST"] + )] + database_host: String, + + /// Connection pool size; legacy var name still accepted + #[ortho_config( + env_name = "POOL_SIZE", + aliases = ["CONNECTION_POOL_SIZE"], + default = "10" + )] + pool_size: u32, +} +``` + +Expansion semantics: + +- `DATABASE_HOST` (canonical) is checked first +- If unset, `DB_HOST` is checked +- If unset, `DBHOST` is checked +- If still unset, the default is used + +### Runtime API + +For dynamic alias registration: + +```rust +use ortho_config::{Config, Aliases}; + +// Register aliases at startup +let aliases = Aliases::new() + .alias("PODBOT_AUDIO_SR", "PODBOT_AUDIO_SAMPLE_RATE_HZ")? + .alias("LEGACY_MODE", "PODBOT_MODE")?; + +// When creating config, provide aliases +let config = PodBotConfig::from_env_with_aliases(&aliases)?; + +// Or use a builder pattern +let config = PodBotConfig::builder() + .with_aliases(aliases) + .from_env()?; +``` + +### Precedence Rules + +Aliases follow a strict precedence hierarchy: + +1. **Canonical environment variable** (highest priority) +2. **Aliases in declaration order** (left to right as listed in `aliases = [...]`) +3. **Default value** (if provided) +4. **Error** (if no default and no env vars set) + +Example walkthrough: + +```rust +#[ortho_config(env_name = "FOO", aliases = ["BAR", "BAZ"])] +field: String, +``` + +Resolution with environment state: + +| State | Resolved Value | Source | +| -------------------------- | -------------- | --------------------- | +| `FOO=a, BAR=b, BAZ=c` | `a` | Canonical (highest) | +| `BAR=b, BAZ=c` | `b` | First alias | +| `BAZ=c` | `c` | Second alias | +| `(none)` + `default="d"` | `d` | Default value | +| `(none)` + no default | Error | No source found | + +## Detailed Design + +### Macro Levels + +#### Compile-Time Macro Expansion + +The `#[ortho_config(...)]` attribute macro generates code that: + +1. **Statically generates the resolution chain** during compilation +2. **Unrolls the precedence checks** into individual environment lookups +3. **Emits constants** listing all possible names for documentation/debugging + +Example generated code: + +```rust +// Original: +#[ortho_config(env_name = "DATABASE_HOST", aliases = ["DB_HOST"])] +database_host: String, + +// Generates approximately: +const DATABASE_HOST_NAMES: &[&str] = &["DATABASE_HOST", "DB_HOST"]; + +// Within from_env() expansion: +let database_host = env::var("VK_DATABASE_HOST") + .or_else(|_| env::var("VK_DB_HOST")) + .context("DATABASE_HOST configuration")?; +``` + +#### Runtime Registration + +For scenarios where aliases cannot be known at compile time, a +`#[ortho_config(runtime_aliases)]` mode allows registration: + +```rust +#[derive(Config)] +#[ortho_config(env_prefix = "APP_", runtime_aliases = true)] +struct Config { + #[ortho_config(env_name = "MODE")] + mode: String, +} + +// At startup: +let mut aliases = Aliases::builder(); +if uses_legacy_system() { + aliases.alias("LEGACY_MODE", "APP_MODE")?; +} +let config = Config::from_env_with_aliases(aliases.build())?; +``` + +### Runtime API Details + +#### Alias Builder + +```rust +pub struct AliasesBuilder { /* ... */ } + +impl AliasesBuilder { + /// Register that `alias_name` is an alias for `canonical_name` + /// Both names are tested; canonical is tried first + pub fn alias( + mut self, + alias_name: &str, + canonical_name: &str, + ) -> Result { + // Validation: neither can be empty + // Validation: alias != canonical + // Validation: no cycles (A->B, B->A) + Ok(self) + } + + /// Build the immutable Aliases struct + pub fn build(self) -> Aliases { + Aliases { /* ... */ } + } +} + +pub struct Aliases { + // Internal: map of alias -> canonical + map: HashMap, +} + +impl Aliases { + pub fn resolve(&self, name: &str) -> &str { + // If name is an alias, return canonical name + // Otherwise, return name unchanged + self.map.get(name).map(|s| s.as_str()).unwrap_or(name) + } + + pub fn all_names(&self, canonical: &str) -> Vec<&str> { + // Return canonical and all aliases pointing to it + } +} +``` + +#### Integration with Config Trait + +```rust +pub trait Config: Sized { + /// Standard from_env() - uses only compile-time aliases + fn from_env() -> Result; + + /// With runtime aliases + fn from_env_with_aliases(aliases: &Aliases) -> Result; + + /// Merge aliases into resolution chain + fn resolve_env_var(name: &str, aliases: &Aliases) -> Result; +} +``` + +### Detailed Resolution Algorithm + +The resolution engine applies aliases at multiple levels: + +1. **Field-level resolution** (within a struct field) +2. **Nested struct resolution** (for composite configs) +3. **Array/list resolution** (for repeated config blocks) + +For each field: + +```rust +fn resolve_field(field_name, field_spec, aliases): + // field_spec = {env_name, aliases, default, ...} + + canonical = env_prefix + field_spec.env_name + candidates = [canonical] + [env_prefix + a for a in field_spec.aliases] + + for candidate in candidates: + if runtime_aliases provided: + candidate = runtime_aliases.resolve(candidate) + if env_var_exists(candidate): + return parse(env_var(candidate)) + + if field_spec.default: + return field_spec.default + else: + return Error +``` + +### Testing Strategy + +#### Unit Tests + +```rust +#[cfg(test)] +mod tests { + use ortho_config::*; + + #[test] + fn test_canonical_takes_precedence() { + // Set both canonical and alias + // Verify canonical is used + } + + #[test] + fn test_alias_used_when_canonical_absent() { + // Set only first alias + // Verify it's used + } + + #[test] + fn test_alias_order_respected() { + // Set second alias but not first + // Verify second alias is used + } + + #[test] + fn test_default_used_when_all_absent() { + // Don't set any env vars + // Verify default is used + } + + #[test] + fn test_error_when_required_absent() { + // Don't set any env vars, no default + // Verify error is returned with helpful message + } + + #[test] + fn test_cycle_detection() { + // Try to register A->B, B->A + // Verify error during build + } + + #[test] + fn test_nested_struct_aliases() { + // Config with nested struct containing aliases + // Verify aliases work transitively + } + + #[test] + fn test_runtime_override_of_compile_time_aliases() { + // Register runtime alias that shadows compile-time + // Verify correct precedence + } +} +``` + +#### Integration Tests + +```rust +#[cfg(test)] +mod integration_tests { + #[test] + fn test_vk_migration_path() { + // Simulate vk migration from VK_DB_HOST to VK_DATABASE_HOST + // Verify old vars still work in backward-compat mode + // Verify new vars take precedence + } + + #[test] + fn test_podbot_multi_naming_scenario() { + // Simulate podbot with legacy, current, and future naming + // Verify all resolve correctly with right precedence + } +} +``` + +#### Documentation Generation + +Aliases are included in generated documentation: + +```rust +#[derive(Config)] +struct MyConfig { + #[ortho_config(env_name = "FOO", aliases = ["BAR", "BAZ"])] + /// Main configuration + field: String, +} + +// Auto-generates: +// - docs/generated/config-reference.md listing all names per field +// - config help message showing acceptable names +// - migration guide for deprecated aliases +``` + +### Error Messages + +When configuration fails, errors clearly indicate all names tried: + +```text +Error: failed to load configuration field 'database_host' + +Tried in order: + + 1. VK_DATABASE_HOST (canonical) + 2. VK_DB_HOST (alias) + 3. VK_DBHOST (alias) + +No value found and no default set. + +Suggestion: Set one of the above environment variables. + +Learn more: +[https://docs.example.com/vk/config#database_host](https://docs.example.com/vk/config#database_host) +``` + +## Migration Path + +### Phase 1: vk Migration (Month 1) + +1. **Tag canonical variables** in vk's current code + - Define `VK_DATABASE_HOST` as the canonical name + - List all legacy names used historically + +2. **Declare aliases** in config structs + + ```rust + #[ortho_config(env_name = "DATABASE_HOST", aliases = ["DB_HOST", "DBHOST"])] + database_host: String, + ``` + +3. **Test backward compatibility** + - Run with old env var names only + - Verify old scripts still work + +4. **Document migration** + - Release notes: "Both `VK_DB_HOST` and new `VK_DATABASE_HOST` work" + - Deprecation timeline: "Legacy names supported through vX.Y" + +### Phase 2: podbot Migration (Month 2) + +1. **Inventory naming inconsistencies** + - Old: `PODBOT_AUDIO_SAMPLE_RATE_HZ` + - New canonical: `PODBOT_AUDIO_SAMPLE_RATE_HZ` (standardize) + - External/legacy: `PODBOT_AUDIO_SR`, `LEGACY_MODE`, etc. + +2. **Implement runtime alias registration** + + ```rust + let mut aliases = Aliases::builder(); + + // Legacy external integrations + aliases.alias("PODBOT_AUDIO_SR", "PODBOT_AUDIO_SAMPLE_RATE_HZ")?; + aliases.alias("LEGACY_MODE", "PODBOT_MODE")?; + + // Container/Kubernetes secrets using different scheme + if is_container_env() { + aliases.alias("AUDIO_RATE", "PODBOT_AUDIO_SAMPLE_RATE_HZ")?; + } + + let config = PodBotConfig::from_env_with_aliases(aliases.build())?; + ``` + +3. **Gradual deployment** + - Stage 1: Support both old and new in staging + - Stage 2: Require new names in production, warn on old ones + - Stage 3: Remove old names after grace period + +### Phase 3: Community Adoption + +vk and podbot publish: + +- Migration guides +- Before/after comparisons +- Case studies showing maintenance reduction + +Other projects adopt aliases for their own configurations. + +## Alternatives Considered + +### Alternative 1: Raw Environment Scanning at Startup + +**Approach**: Projects scan for multiple variable names and rename them before +initialization. + +```rust +// Anti-pattern: scattered throughout codebase +fn load_config() -> Result { + // Manual renaming + if let Ok(v) = env::var("VK_DB_HOST") { + env::set_var("VK_DATABASE_HOST", v); + } + if let Ok(v) = env::var("DBHOST") { + env::set_var("VK_DATABASE_HOST", v); + } + Config::from_env() +} +``` + +**Pros**: + +- No framework changes needed + +**Cons**: + +- Duplicated across projects +- Error-prone (easy to miss names) +- Hidden logic outside config struct definition +- Hard to document +- Can't optimize (multiple env lookups) +- Imperative, not declarative + +### Alternative 2: Separate Configuration Layer + +**Approach**: Add a "pre-processing" layer that normalizes environment before +passing to ortho-config. + +```rust +struct LegacyEnvMapper { + mappings: HashMap<&str, &str>, +} + +impl LegacyEnvMapper { + fn apply(&self) { + for (old, new) in &self.mappings { + if let Ok(v) = env::var(old) { + env::set_var(new, v); + } + } + } +} +``` + +**Pros**: + +- Loosely coupled to ortho-config +- Could be reused in other contexts + +**Cons**: + +- Requires explicit invocation before `from_env()` +- Easy to forget or misconfigure +- Still duplicates logic across projects +- Less efficient (double environment reads) +- Doesn't provide documentation benefits + +### Alternative 3: Environment Variable Configuration File + +**Approach**: Use a dotenv-like file listing aliases. + +```env +# aliases.env +ALIAS VK_DB_HOST -> VK_DATABASE_HOST +ALIAS VK_DBHOST -> VK_DATABASE_HOST +ALIAS PODBOT_AUDIO_SR -> PODBOT_AUDIO_SAMPLE_RATE_HZ +``` + +**Pros**: + +- Centralized +- Can be version-controlled + +**Cons**: + +- Another file to manage and synchronize +- Breaks single source of truth (separate from config struct) +- Requires parsing at runtime for every lookup +- Harder to discover (hidden in file, not visible in code) +- No type safety or compile-time validation + +### Why This RFC is Better + +The proposed approach: + +1. **Colocates** alias definitions with field definitions (single source of +truth) +2. **Provides** compile-time validation and optimization +3. **Supports** both compile-time and runtime aliases (flexible) +4. **Generates** documentation automatically +5. **Ensures** type safety through Rust's type system +6. **Reduces** boilerplate and human error + +## Drawbacks + +### Compilation Time Impact + +The macro expansion adds non-trivial code to each field with aliases: + +```rust +// Small for one alias, ~1 line of generated code per alias +// For 50-field config with ~2 aliases per field, adds ~100 lines +// Negligible impact on compile time, but worth benchmarking +``` + +**Mitigation**: + +- Profile macro expansion +- Offer `lazy = true` option if needed (defer to runtime resolution) + +### Documentation Burden + +Projects must maintain alias lists in two places: + +1. Source code (as attributes) +2. User documentation (manually) + +**Mitigation**: + +- Auto-generate `config-reference.md` from macro attributes +- Include generated docs in CI validation + +### Potential for Misuse + +Developers might declare excessive aliases, creating confusion: + +```rust +#[ortho_config( + env_name = "FOO", + aliases = ["BAR", "BAZ", "QUX", "QUUX", "CORGE", "GRAULT"] // Too many! +)] +field: String, +``` + +**Mitigation**: + +- Lint warning when >3 aliases per field +- Documentation recommends max 2-3 +- Migration guide emphasizes retiring old names + +### Runtime Alias Registration Complexity + +Runtime aliases add complexity to initialization: + +```rust +// More verbose than simple from_env() +let config = Config::from_env_with_aliases( + Aliases::builder() + .alias("A", "B")? + .alias("C", "D")? + .build() +)?; +``` + +**Mitigation**: + +- Provide builder method: `Config::with_runtime_aliases(|a| a.alias(...)?)` +- Document common patterns +- Examples for typical scenarios + +## Unresolved Questions + +### 1. Case Sensitivity + +Should aliases be **case-sensitive**? + +- **Current proposal**: Yes, case-sensitive (standard for Unix env vars) +- **Alternative**: Case-insensitive matching option + +**Open**: Should we offer a `case_insensitive = true` attribute? + +### 2. Deprecation Tracking + +How should deprecated aliases be tracked over time? + +```rust +#[ortho_config( + env_name = "NEW_NAME", + aliases = [ + ("OLD_NAME", "1.0", "Use NEW_NAME instead"), + ] +)] +``` + +**Open**: Should we add deprecation metadata and emit warnings? + +### 3. Environment Variable Precedence vs Aliases + +When both runtime and compile-time aliases exist for the same field, what wins? + +- **Current proposal**: Compile-time aliases tried first, then runtime aliases +- **Alternative**: Runtime aliases override compile-time + +**Open**: Should precedence be customizable or fixed? + +### 4. Array/List Handling + +For repeated config elements (e.g., multiple database replicas): + +```rust +#[ortho_config( + env_name = "REPLICA_URLS", + aliases = ["REPLICA_HOSTS", "DB_REPLICAS"] +)] +replicas: Vec, +``` + +**Open**: Do aliases apply to the entire list, or individually? + +### 5. Nested Struct Aliases + +When a nested struct has its own aliases, and the parent also does: + +```rust +#[derive(Config)] +struct Parent { + #[ortho_config(aliases = ["OLD_DB"])] + db: DbConfig, // DbConfig itself has aliases on fields +} +``` + +**Open**: Should nested aliases be merged, stacked, or isolated? + +### 6. Performance Profiling + +Before stabilizing, we need to measure: + +- Macro expansion overhead +- Runtime lookup performance vs. simple `env::var()` +- Memory overhead of Aliases struct + +**Open**: What are acceptable performance targets? + +## Next Steps + +1. **Implementation phase**: + - Write macro expansion code with comprehensive tests + - Build Aliases runtime API + - Integrate into ortho-config crate + +2. **Review phase**: + - Get feedback from vk and podbot teams + - Performance profiling against targets + - Test with real migration scenarios + +3. **Documentation phase**: + - Write user guide with examples + - Create migration playbooks for vk and podbot + - Generate reference documentation + +4. **Stabilization**: + - Gather feedback from early adopters + - Resolve unresolved questions + - Version bump and release + +## References + +- [12-Factor App Config](https://12factor.net/config) - environment-first + principle +- [Rust RFC 2407 Anonymous struct fields](https://rust-lang.github.io/rfcs/2407-anonymous-fields.html) + - related feature design +- [dotenv-rs](https://github.com/dotenv-rs/dotenv-rs) - prior art in Rust env + handling +- [Kubernetes environment variables](https://kubernetes.io/docs/tasks/configure-pod-container/define-environment-variable-container/) + - real-world naming patterns diff --git a/docs/scripting-standards.md b/docs/scripting-standards.md index 73c0167c..40b34351 100644 --- a/docs/scripting-standards.md +++ b/docs/scripting-standards.md @@ -108,7 +108,9 @@ def main( # Lists (whitespace/newline separated by default) formats: list[str] | None = None, - man_paths: Annotated[list[Path] | None, Parameter(env_var="INPUT_MAN_PATHS")] = None, + man_paths: Annotated[ + list[Path] | None, Parameter(env_var="INPUT_MAN_PATHS") + ] = None, deb_depends: list[str] | None = None, rpm_depends: list[str] | None = None, ): @@ -158,7 +160,9 @@ Guidance: - Per‑parameter environment names can be pinned for backwards compatibility: ```python - config_out: Annotated[Optional[Path], Parameter(env_var="INPUT_CONFIG_PATH")] = None + config_out: Annotated[ + Optional[Path], Parameter(env_var="INPUT_CONFIG_PATH") + ] = None ``` ## plumbum: command calling and pipelines @@ -256,7 +260,9 @@ f.write_text("1.2.3\n", encoding="utf-8") version = f.read_text(encoding="utf-8").strip() # Atomic write pattern (tmp → replace) -with tempfile.NamedTemporaryFile("w", delete=False, dir=f.parent, encoding="utf-8") as tmp: +with tempfile.NamedTemporaryFile( + "w", delete=False, dir=f.parent, encoding="utf-8" +) as tmp: tmp.write("new-contents\n") tmp_path = Path(tmp.name) diff --git a/docs/windows-gnullvm-build.md b/docs/windows-gnullvm-build.md index dcc070dc..0c63fe96 100644 --- a/docs/windows-gnullvm-build.md +++ b/docs/windows-gnullvm-build.md @@ -1,15 +1,33 @@ # Building for Windows `*-pc-windows-gnullvm` -This document describes adapting the `rust-build-release` action to build for Windows `gnullvm` targets. The configuration produces MinGW-style artefacts on a Windows runner using an MSVC host toolchain while linking against LLVM's MinGW CRT provided by `llvm-mingw`, thereby removing any dependency on `libgcc`. Both `x86_64-pc-windows-gnullvm` and `aarch64-pc-windows-gnullvm` are supported. +This document describes adapting the `rust-build-release` action to build for +Windows `gnullvm` targets. The configuration produces MinGW-style artefacts on a +Windows runner using an MSVC host toolchain while linking against LLVM's MinGW +CRT provided by `llvm-mingw`, thereby removing any dependency on `libgcc`. Both +`x86_64-pc-windows-gnullvm` and `aarch64-pc-windows-gnullvm` are supported. -The process is orchestrated through Python scripts within the action, which replace the PowerShell examples from the original recommendation. +The process is orchestrated through Python scripts within the action, which +replace the PowerShell examples from the original recommendation. ## Strategy Overview -1. **Host vs. Target**: The build runs on a `windows-latest` GitHub Actions runner, which uses the `stable-x86_64-pc-windows-msvc` toolchain as the _host_. This ensures any build scripts (`build.rs`) compile with MSVC and do not introduce GCC runtime dependencies. The _target_ is one of the `*-pc-windows-gnullvm` triples (currently `x86_64` or `aarch64`), which instruct `rustc` to produce a MinGW-style binary. -2. **`cross` Local Backend**: Instead of using Docker (which is unavailable on Windows runners for Linux containers), `cross` is instructed to use its "local backend" via the `CROSS_NO_DOCKER=1` environment variable. This uses the host's toolchains directly. -3. **LLVM Linker**: The key is to tell `rustc` how to link the `gnullvm` target. `clang` and `lld` from the `llvm-mingw` project are used, configured via a dynamically generated `.cargo/config.toml` file. -4. **Python Orchestration**: All setup steps—downloading `llvm-mingw`, creating the Cargo config, and setting environment variables—are handled by a dedicated Python script within the action, ensuring cross-platform consistency and maintainability. +1. **Host vs. Target**: The build runs on a `windows-latest` GitHub Actions + runner, which uses the `stable-x86_64-pc-windows-msvc` toolchain as the + _host_. This ensures any build scripts (`build.rs`) compile with MSVC and do + not introduce GCC runtime dependencies. The _target_ is one of the + `*-pc-windows-gnullvm` triples (currently `x86_64` or `aarch64`), which + instruct `rustc` to produce a MinGW-style binary. +2. **`cross` Local Backend**: Instead of using Docker (which is unavailable on + Windows runners for Linux containers), `cross` is instructed to use its + "local backend" via the `CROSS_NO_DOCKER=1` environment variable. This uses + the host's toolchains directly. +3. **LLVM Linker**: The key is to tell `rustc` how to link the `gnullvm` + target. `clang` and `lld` from the `llvm-mingw` project are used, + configured via a dynamically generated `.cargo/config.toml` file. +4. **Python Orchestration**: All setup steps—downloading `llvm-mingw`, creating + the Cargo config, and setting environment variables—are handled by a + dedicated Python script within the action, ensuring cross-platform + consistency and maintainability. ## Implementation within `rust-build-release` @@ -17,7 +35,9 @@ The following changes adapt the recommendation for the existing action. ### 1. GitHub Actions Workflow (`action.yml`) -A conditional step is added to `rust-build-release/action.yml` to trigger the setup whenever a Windows `gnullvm` target is requested. The target triple is passed through to the setup script. +A conditional step is added to `rust-build-release/action.yml` to trigger the +setup whenever a Windows `gnullvm` target is requested. The target triple is +passed through to the setup script. ```yaml # .github/actions/rust-build-release/action.yml @@ -28,28 +48,54 @@ A conditional step is added to `rust-build-release/action.yml` to trigger the se working-directory: ${{ inputs.project-dir }} run: | set -euo pipefail - uv run --script "$GITHUB_ACTION_PATH/src/setup_gnullvm.py" --target "${{ inputs.target }}" + uv run --script "$GITHUB_ACTION_PATH/src/setup_gnullvm.py" \ + --target "${{ inputs.target }}" - name: Build release # ... ``` -The composite action installs `uv` earlier via the "Setup uv" step, so the snippet -does not need to install it explicitly. +The composite action installs `uv` earlier via the "Setup uv" step, so the +snippet does not need to install it explicitly. ### 2. Setup Script (`setup_gnullvm.py`) -The Python helper automates the setup process previously handled by PowerShell. It performs the following actions: - -- **Downloads `llvm-mingw`**: Fetches the specified release archive from GitHub, extracts it to a temporary runner directory, and adds its `bin` directory to the `GITHUB_PATH`. On GitHub’s Windows runners (`x86_64` hosts), the setup defaults to the `ucrt-x86_64` archive, which bundles cross-compilers for both the `x86_64` and `aarch64` Windows targets. The `RBR_LLVM_MINGW_VARIANT` environment variable can override this default. When the requested variant is unavailable or lacks a registered checksum for the chosen release, the helper falls back to `ucrt-x86_64` so the job can proceed on x86_64 runners. -- **Creates `.cargo/config.toml`**: Generates the necessary configuration to instruct Cargo how to link the requested `gnullvm` target using the matching `*-w64-mingw32-clang` frontend. -- **Sets Environment Variables**: Writes the `CROSS_NO_DOCKER=1` flag and the target-scoped `CC_*`, `CXX_*`, `LD_*`, `AR_*`, and `RANLIB_*` variables to `GITHUB_ENV` for the subsequent build step to use. -- **Supports Overrides**: Optional `RBR_LLVM_MINGW_VERSION`, `RBR_LLVM_MINGW_VARIANT`, and `RBR_LLVM_MINGW_SHA256` environment variables allow selecting alternative releases while keeping checksum verification enabled. - -The default archive variant is `ucrt-x86_64`, matching the upstream `llvm-mingw` distribution that links against the UCRT and provides cross-compilers for multiple targets. The setup script supports variants that share the upstream archive layout, including `ucrt-i686`, `msvcrt-x86_64`, and `msvcrt-i686`. When overriding `RBR_LLVM_MINGW_VARIANT`, set `RBR_LLVM_MINGW_SHA256` to the checksum for the chosen archive to keep checksum validation enabled; otherwise the script automatically falls back to the default archive when a checksum is unavailable. +The Python helper automates the setup process previously handled by PowerShell. +It performs the following actions: + +- **Downloads `llvm-mingw`**: Fetches the specified release archive from + GitHub, extracts it to a temporary runner directory, and adds its `bin` + directory to the `GITHUB_PATH`. On GitHub's Windows runners (`x86_64` hosts), + the setup defaults to the `ucrt-x86_64` archive, which bundles + cross-compilers for both the `x86_64` and `aarch64` Windows targets. The + `RBR_LLVM_MINGW_VARIANT` environment variable can override this default. + When the requested variant is unavailable or lacks a registered checksum for + the chosen release, the helper falls back to `ucrt-x86_64` so the job can + proceed on x86_64 runners. +- **Creates `.cargo/config.toml`**: Generates the necessary configuration to + instruct Cargo how to link the requested `gnullvm` target using the matching + `*-w64-mingw32-clang` frontend. +- **Sets Environment Variables**: Writes the `CROSS_NO_DOCKER=1` flag and the + target-scoped `CC_*`, `CXX_*`, `LD_*`, `AR_*`, and `RANLIB_*` variables to + `GITHUB_ENV` for the subsequent build step to use. +- **Supports Overrides**: Optional `RBR_LLVM_MINGW_VERSION`, + `RBR_LLVM_MINGW_VARIANT`, and `RBR_LLVM_MINGW_SHA256` environment variables + allow selecting alternative releases while keeping checksum verification + enabled. + +The default archive variant is `ucrt-x86_64`, matching the upstream +`llvm-mingw` distribution that links against the UCRT and provides +cross-compilers for multiple targets. The setup script supports variants that +share the upstream archive layout, including `ucrt-i686`, `msvcrt-x86_64`, and +`msvcrt-i686`. When overriding `RBR_LLVM_MINGW_VARIANT`, set +`RBR_LLVM_MINGW_SHA256` to the checksum for the chosen archive to keep +checksum validation enabled; otherwise the script automatically falls back to +the default archive when a checksum is unavailable. ### 3. Build Script (`main.py`) -The main build script, `src/main.py`, is modified to recognise when the `cross` local backend should be used, even when no container runtime (Docker/Podman) is detected. +The main build script, `src/main.py`, is modified to recognise when the `cross` +local backend should be used, even when no container runtime (Docker/Podman) is +detected. ```python # src/main.py (excerpt) @@ -61,11 +107,14 @@ The main build script, `src/main.py`, is modified to recognise when the `cross` # ... ``` -This ensures that `cross` is invoked correctly when the setup script has prepared the environment for a local `gnullvm` build. +This ensures that `cross` is invoked correctly when the setup script has +prepared the environment for a local `gnullvm` build. ## How to Use -To build for a Windows `gnullvm` target, set the `target` input in a workflow that uses the `rust-build-release` action on a `windows-latest` runner. The action performs the setup automatically. +To build for a Windows `gnullvm` target, set the `target` input in a workflow +that uses the `rust-build-release` action on a `windows-latest` runner. The +action performs the setup automatically. ```yaml # .github/workflows/example-workflow.yml @@ -85,5 +134,5 @@ jobs: target: ${{ matrix.target }} # ... other inputs like project-dir, version, etc. -The matrix workflow above validates that both Windows `gnullvm` targets are configured correctly. -``` +The matrix workflow above validates that both Windows `gnullvm` targets are +configured correctly. diff --git a/rust-toy-app/README.md b/rust-toy-app/README.md index 8cde6ea4..7fb397c2 100644 --- a/rust-toy-app/README.md +++ b/rust-toy-app/README.md @@ -6,15 +6,15 @@ ## Purpose -This simple Command Line Interface (CLI) application demonstrates various Rust testing patterns for +This simple CLI application demonstrates various Rust testing patterns for coverage collection validation: | Test Type | Framework | File(s) | Harness | -|-----------|-----------|---------|---------| -| Unit tests | [rstest](https://crates.io/crates/rstest) | `src/lib.rs` | Standard | -| Integration tests | rstest + assert_cmd | `tests/cli.rs` | Standard | -| Behaviour-Driven Development (BDD) (Gherkin) | [cucumber-rs](https://cucumber-rs.github.io/cucumber/current/) | `tests/cucumber.rs`, `tests/features/*.feature` | Custom (`harness = false`) | -| BDD (rstest-style) | rstest | `tests/bdd.rs` | Standard | +| --- | --- | --- | --- | +| Unit | [rstest](https://crates.io/crates/rstest) | `src/lib.rs` | Standard | +| Integration | rstest + assert_cmd | `tests/cli.rs` | Standard | +| BDD (Gherkin) | [cucumber-rs](https://cucumber-rs.github.io/cucumber/current/) | `tests/features/*.feature` | Custom | +| BDD (rstest) | rstest | `tests/bdd.rs` | Standard | The test suite validates that the `generate-coverage` action correctly: @@ -97,7 +97,7 @@ The test suite is designed to be invoked via the `generate-coverage` action: ### Action Inputs Used | Input | Value | Description | -|-------|-------|-------------| +| --- | --- | --- | | `with-cucumber-rs` | `true` | Enables cucumber-rs coverage collection | | `cucumber-rs-features` | `tests/features` | Path to Gherkin feature files |