diff --git a/.github/workflows/pull-request.yml b/.github/workflows/pull-request.yml index 5cdf28f..65bb6d1 100644 --- a/.github/workflows/pull-request.yml +++ b/.github/workflows/pull-request.yml @@ -16,7 +16,7 @@ jobs: pull-requests: read steps: - name: Require one pre-existing issue - uses: actions/github-script@v7 + uses: actions/github-script@f28e40c7f34bde8b3046d885e986cb6290c5673b # v7 with: script: | const pull = context.payload.pull_request; @@ -49,9 +49,9 @@ jobs: name: Cargo check runs-on: ubuntu-latest steps: - - uses: actions/checkout@v4 - - uses: dtolnay/rust-toolchain@stable - - uses: swatinem/rust-cache@v2 + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 + - uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4 # stable + - uses: swatinem/rust-cache@42dc69e1aa15d09112580998cf2ef0119e2e91ae # v2 - name: Check formatting run: cargo fmt --all -- --check - name: Check compilation diff --git a/.github/workflows/release.yml b/.github/workflows/v-release.yml similarity index 88% rename from .github/workflows/release.yml rename to .github/workflows/v-release.yml index fab2f74..0c7d16a 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/v-release.yml @@ -42,7 +42,7 @@ on: pull_request: push: tags: - - '**[0-9]+.[0-9]+.[0-9]+*' + - 'v**[0-9]+.[0-9]+.[0-9]+*' jobs: # Run 'dist plan' (or host) to determine what tasks we need to do @@ -56,7 +56,7 @@ jobs: env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 with: persist-credentials: false submodules: recursive @@ -66,7 +66,7 @@ jobs: shell: bash run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.31.0/cargo-dist-installer.sh | sh" - name: Cache dist - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f with: name: cargo-dist-cache path: ~/.cargo/bin/dist @@ -82,7 +82,7 @@ jobs: cat plan-dist-manifest.json echo "manifest=$(jq -c "." plan-dist-manifest.json)" >> "$GITHUB_OUTPUT" - name: "Upload dist-manifest.json" - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f with: name: artifacts-plan-dist-manifest path: plan-dist-manifest.json @@ -116,7 +116,7 @@ jobs: - name: enable windows longpaths run: | git config --global core.longpaths true - - uses: actions/checkout@v6 + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 with: persist-credentials: false submodules: recursive @@ -131,7 +131,7 @@ jobs: run: ${{ matrix.install_dist.run }} # Get the dist-manifest - name: Fetch local artifacts - uses: actions/download-artifact@v7 + uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 with: pattern: artifacts-* path: target/distrib/ @@ -158,7 +158,7 @@ jobs: cp dist-manifest.json "$BUILD_MANIFEST_NAME" - name: "Upload artifacts" - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f with: name: artifacts-build-local-${{ join(matrix.targets, '_') }} path: | @@ -175,19 +175,19 @@ jobs: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} BUILD_MANIFEST_NAME: target/distrib/global-dist-manifest.json steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 with: persist-credentials: false submodules: recursive - name: Install cached dist - uses: actions/download-artifact@v7 + uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 with: name: cargo-dist-cache path: ~/.cargo/bin/ - run: chmod +x ~/.cargo/bin/dist # Get all the local artifacts for the global tasks to use (for e.g. checksums) - name: Fetch local artifacts - uses: actions/download-artifact@v7 + uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 with: pattern: artifacts-* path: target/distrib/ @@ -205,7 +205,7 @@ jobs: cp dist-manifest.json "$BUILD_MANIFEST_NAME" - name: "Upload artifacts" - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f with: name: artifacts-build-global path: | @@ -224,20 +224,24 @@ jobs: runs-on: "ubuntu-22.04" outputs: val: ${{ steps.host.outputs.manifest }} + permissions: + "attestations": "write" + "contents": "write" + "id-token": "write" steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 with: persist-credentials: false submodules: recursive - name: Install cached dist - uses: actions/download-artifact@v7 + uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 with: name: cargo-dist-cache path: ~/.cargo/bin/ - run: chmod +x ~/.cargo/bin/dist # Fetch artifacts from scratch-storage - name: Fetch artifacts - uses: actions/download-artifact@v7 + uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 with: pattern: artifacts-* path: target/distrib/ @@ -250,14 +254,14 @@ jobs: cat dist-manifest.json echo "manifest=$(jq -c "." dist-manifest.json)" >> "$GITHUB_OUTPUT" - name: "Upload dist-manifest.json" - uses: actions/upload-artifact@v6 + uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f with: # Overwrite the previous copy name: artifacts-dist-manifest path: dist-manifest.json # Create a GitHub Release while uploading all files to it - name: "Download GitHub Artifacts" - uses: actions/download-artifact@v7 + uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 with: pattern: artifacts-* path: artifacts @@ -266,6 +270,11 @@ jobs: run: | # Remove the granular manifests rm -f artifacts/*-dist-manifest.json + - name: Attest + uses: actions/attest-build-provenance@977bb373ede98d70efdf65b84cb5f73e068dcc2a + with: + subject-path: | + artifacts/* - name: Create GitHub Release env: PRERELEASE_FLAG: "${{ fromJson(steps.host.outputs.manifest).announcement_is_prerelease && '--prerelease' || '' }}" @@ -290,14 +299,14 @@ jobs: GITHUB_EMAIL: "admin+bot@axo.dev" if: ${{ !fromJson(needs.plan.outputs.val).announcement_is_prerelease || fromJson(needs.plan.outputs.val).publish_prereleases }} steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 with: persist-credentials: true repository: "shayyz-code/homebrew-tap" token: ${{ secrets.HOMEBREW_TAP_TOKEN }} # So we have access to the formula - name: Fetch homebrew formulae - uses: actions/download-artifact@v7 + uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 with: pattern: artifacts-* path: Formula/ @@ -335,12 +344,12 @@ jobs: if: ${{ !fromJson(needs.plan.outputs.val).announcement_is_prerelease || fromJson(needs.plan.outputs.val).publish_prereleases }} steps: - name: Fetch npm packages - uses: actions/download-artifact@v7 + uses: actions/download-artifact@37930b1c2abaa49bbe596cd826c3c89aef350131 with: pattern: artifacts-* path: npm/ merge-multiple: true - - uses: actions/setup-node@v6 + - uses: actions/setup-node@249970729cb0ef3589644e2896645e5dc5ba9c38 with: node-version: '20.x' registry-url: 'https://registry.npmjs.org' @@ -366,7 +375,7 @@ jobs: env: GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 with: persist-credentials: false submodules: recursive diff --git a/.github/workflows/web.yml b/.github/workflows/web.yml index 4886a64..fae04af 100644 --- a/.github/workflows/web.yml +++ b/.github/workflows/web.yml @@ -54,11 +54,11 @@ jobs: runs-on: ubuntu-latest steps: # Setup - - uses: actions/checkout@v4 + - uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4 with: fetch-depth: 0 - - uses: dtolnay/rust-toolchain@stable - - uses: swatinem/rust-cache@v2 + - uses: dtolnay/rust-toolchain@4cda84d5c5c54efe2404f9d843567869ab1699d4 # stable + - uses: swatinem/rust-cache@42dc69e1aa15d09112580998cf2ef0119e2e91ae # v2 # If you use any mdbook plugins, here's the place to install them! @@ -76,7 +76,7 @@ jobs: run: mkdir -p /tmp/link-check/poolang && cp -R public/* /tmp/link-check/poolang/ && cp CONTRIBUTING.md /tmp/link-check/poolang/ && cp LICENSE /tmp/link-check/poolang/ - name: Check HTML for broken internal links - uses: untitaker/hyperlink@0.1.29 + uses: untitaker/hyperlink@273ec52f34b7cbf039908d52985010c7f3f35160 # 0.1.29 with: args: /tmp/link-check/ @@ -88,7 +88,7 @@ jobs: # go into repo's settings > pages and set "deploy from branch: gh-pages". # The other defaults work fine. - name: Deploy to Github Pages - uses: JamesIves/github-pages-deploy-action@v4.4.1 + uses: JamesIves/github-pages-deploy-action@ba1486788b0490a235422264426c45848eac35c6 # v4.4.1 # ONLY if we're on main (so no PRs or feature branches allowed!) if: ${{ github.ref == 'refs/heads/main' }} with: diff --git a/AGENTS.md b/AGENTS.md index 8b7cfb5..047537a 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -15,6 +15,7 @@ Edit documentation sources under `docs/src/` and navigation in `docs/src/SUMMARY - `cargo fmt --all -- --check` checks formatting; `cargo fmt --all` applies it. - `cargo clippy --all-targets --all-features -- -D warnings` runs strict linting. - `mdbook build docs` is the canonical documentation build; `mdbook test docs` tests its Rust examples. +- `dist generate --check` verifies that the generated release workflow matches its configuration. All established formatting, compilation, test, and lint checks must pass before review. @@ -24,6 +25,10 @@ Use rustfmt defaults and idiomatic Rust names: `snake_case` for functions and mo Follow TDD for features and fixes. Add focused cases to `tests/language_specs.rs`, named `spec__`, and cover success plus relevant failure paths. +## Release Automation + +Treat `dist-workspace.toml` as the source of truth for releases. Never edit `.github/workflows/v-release.yml` directly; run `dist generate --mode=ci` after a configuration change and commit the result. Follow `RELEASING.md` for release checks, tag format, credential handling, and artifact verification. Do not create a release tag as part of an unrelated change. + ## Issue, Commit & Pull Request Rules Every change starts with an existing GitHub issue. Create a branch from `main` and keep the PR limited to one issue and one reviewable concern. The PR body must contain exactly one closing reference such as `Closes #123`, describe verification, and note documentation or screenshot impact. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 37a9445..2f8ad36 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -60,6 +60,10 @@ Pull requests are squash-merged only. Do not use merge commits or rebase merges. `mdbook build docs` is the canonical documentation build command. Before review, run all applicable commands above plus `cargo fmt --all -- --check` and `cargo clippy --all-targets --all-features -- -D warnings`; all established checks must pass. +## Release Changes + +Configure releases in `dist-workspace.toml`; `.github/workflows/v-release.yml` is generated and must not be edited by hand. Run `dist generate --mode=ci` after changing release configuration and `dist generate --check` before review. Maintainers must follow `RELEASING.md`; release tags are reserved for reviewed release commits on `main`. + ## Architectural Overview - **Lexer (`src/lexer.rs`)**: Converts source text into a stream of tokens. diff --git a/RELEASING.md b/RELEASING.md new file mode 100644 index 0000000..8b13805 --- /dev/null +++ b/RELEASING.md @@ -0,0 +1,29 @@ +# Releasing PooLang + +Releases are generated by cargo-dist 0.31.0 from `dist-workspace.toml`. Treat that file as the source of truth: never edit `.github/workflows/v-release.yml` by hand. After changing release configuration, run `dist generate --mode=ci` and commit the generated result. + +## Release Checklist + +Release version changes must first land on `main` through the normal issue and pull-request workflow. From a clean, synchronized checkout of that reviewed commit: + +1. Confirm `Cargo.toml` contains the intended version and that no tracked files are modified. +2. Run `dist generate --check` and `dist plan --output-format=json --no-local-paths`. +3. Run `cargo fmt --all -- --check`, `cargo check --all-targets`, `cargo clippy --all-targets --all-features -- -D warnings`, `cargo test`, `mdbook build docs`, `mdbook test docs`, and `oranda build`. +4. Create one annotated tag matching the package version: `git tag -a vX.Y.Z -m "Release vX.Y.Z"`. +5. Push only that tag: `git push origin vX.Y.Z`. +6. Monitor the **Release** workflow. Verify the GitHub release assets and SHA256 files, the npm package, the Homebrew formula, and the subsequent **Web** deployment. +7. Download an artifact and verify its provenance with `gh attestation verify -R shayyz-code/poolang`. + +Do not reuse, move, force-push, or delete a published version tag. + +## Supported Distribution + +Releases target Apple Silicon macOS, Intel macOS, Apple Silicon Linux, Intel Linux, and x86_64 Windows. cargo-dist produces shell, PowerShell, npm, Homebrew, and MSI installers; stable releases publish to `@shayyz-code/poo` and `shayyz-code/homebrew-tap`. + +## Credentials and Security + +- `NPM_TOKEN` must be a short-lived granular token with read/write access only to `@shayyz-code/poo`. Rotate it regularly. Migrate to npm trusted publishing for `v-release.yml`, then revoke and remove this token. +- `HOMEBREW_TAP_TOKEN` must be a fine-grained token limited to `shayyz-code/homebrew-tap` with Contents write access. Replace or rotate it when its scope or age cannot be confirmed. +- Never place secret values in repository files, issue text, pull-request text, command output, or workflow logs. + +All workflow actions are pinned to immutable commits. The generated workflow still declares top-level Contents write permission so cargo-dist can host releases; pull requests receive no repository secrets. Platform code signing is not yet configured, so binaries and MSI packages remain unsigned even though release artifacts receive GitHub provenance attestations. diff --git a/TODO.md b/TODO.md index a0bd712..dec76eb 100644 --- a/TODO.md +++ b/TODO.md @@ -18,6 +18,8 @@ Snapshot: 2026-07-29. - The crate exposes checked file/source execution APIs and typed I/O, parse, and runtime error categories. - Checked parsing reports malformed literals, comments, non-ASCII identifiers, and unexpected EOF without panicking or hanging. - Lexer, parser, interpreter, type-inference, examples, mdBook documentation, and cargo-dist release assets exist. +- cargo-dist 0.31.0 generates five platform targets and shell, PowerShell, npm, Homebrew, and MSI installers from reviewed configuration. +- Release actions use immutable commit pins, version tags are protected, and future release artifacts receive GitHub provenance attestations. ### Quality gaps @@ -78,8 +80,11 @@ Snapshot: 2026-07-29. - [x] Separate generated mdBook output from sources and define one reproducible documentation build command. - [x] Reconcile README commands, branch names, CI claims, supported features, and examples with executable behavior. - [x] Add focused lexer/parser error tests for malformed strings, comments, UTF-8 input, and unexpected EOF. -- [ ] Audit release workflow actions, permissions, secrets, installers, and generated cargo-dist configuration. +- [x] Audit release workflow actions, permissions, secrets, installers, and generated cargo-dist configuration. - [ ] Add dependency, license, and supply-chain checks appropriate to Rust and future native runtime dependencies. +- [ ] Migrate npm publishing to trusted publishing for `v-release.yml`, then revoke and remove `NPM_TOKEN`. +- [ ] Replace or rotate the Homebrew credential with a fine-grained, tap-only Contents-write token. +- [ ] Add platform code signing before the production v0.2 release. ## Phase 2 — Normative v0.2 Language Specification diff --git a/dist-workspace.toml b/dist-workspace.toml index 7e89f9d..758faee 100644 --- a/dist-workspace.toml +++ b/dist-workspace.toml @@ -7,6 +7,12 @@ members = ["cargo:."] cargo-dist-version = "0.31.0" # CI backends to support ci = "github" +# Only v-prefixed semantic-version tags trigger release automation +tag-namespace = "v" +# Produce GitHub artifact attestations after all artifacts are assembled +github-attestations = true +github-attestations-phase = "host" +github-release = "host" # The installers to generate for each app installers = ["shell", "powershell", "npm", "homebrew", "msi"] # A GitHub repo to push Homebrew formulas to @@ -21,3 +27,12 @@ install-path = "CARGO_HOME" publish-jobs = ["homebrew", "npm"] # Whether to install an updater program install-updater = false + +# Pin generated GitHub Actions to immutable commits. Update these through a +# reviewed cargo-dist configuration change, then regenerate the workflow. +[dist.github-action-commits] +"actions/checkout" = "d23441a48e516b6c34aea4fa41551a30e30af803" +"actions/upload-artifact" = "b7c566a772e6b6bfb58ed0dc250532a479d7789f" +"actions/download-artifact" = "37930b1c2abaa49bbe596cd826c3c89aef350131" +"actions/setup-node" = "249970729cb0ef3589644e2896645e5dc5ba9c38" +"actions/attest-build-provenance" = "977bb373ede98d70efdf65b84cb5f73e068dcc2a"