# Release process

1. Ensure the intended commit is clean and tagged.
2. Install the exact lockfile with `npm ci`.
3. Run `npm run release:artifact`.
4. Record the reported SHA-256, then run
   `npm run package:release -- --force` twice and verify both reported digests
   match it. Inspect an existing exact-version artifact before deliberately
   replacing it.
5. Inspect ZIP paths, manifest, legal documents and absence of maps/secrets.
6. Publish ZIP and `.sha256` on the matching Gitea release.
7. Use the public immutable URL and digest in Toolbox Portal.

The package script sorts paths without locale-dependent collation, rejects
symlinks and credential-like paths, normalizes file timestamps and modes,
verifies manifest/source identity, stages both outputs and rolls back the pair
on publication failure, replaces an existing exact version only with
`--force`, and produces:

```text
release/regex-tools-0.4.0.zip
release/regex-tools-0.4.0.zip.sha256
```

The release scan checks ASCII/UTF-8, UTF-16LE and UTF-16BE content in text,
JavaScript, WebAssembly and PDB-like assets for local Unix/macOS/Windows build
paths. Runtime virtual homes are allowed only for the named generated loaders.
Two byte-exact upstream prebuilts are hash-bound exceptions:
`engines/perl/emperl.data` retains WebPerl's historical build paths and
documentation examples, and `engines/ruby/ruby.wasm` retains ruby.wasm's
upstream build paths. Any byte or path change removes that exception and fails
the release scan; neither artifact is rewritten.

Before packaging, the engine verifier checks the closed standalone-PCRE2, PHP,
Perl, Python, Ruby, Java, C++, Go, Rust and .NET file sets in `dist/`,
including checksums, metadata, licences/notices, module and WebAssembly
identities and self-hosted relative asset references. Browser/runtime smoke
tests cover every selectable profile; Scala compatibility must resolve to the
verified TeaVM Java pack while reporting that no Scala runtime is present.
The Python gate additionally requires its complete `SOURCE-MANIFEST.json`,
every preferred-form archive/Git/patch identity and all 22 independently
anchored base-runtime legal files. The C++ gate requires its exact traced
component/source manifest and six independently pinned legal files. The release
packager names every one of those files explicitly.

The existing v0.1.0, v0.2.0 and v0.3.0 ZIPs and matching sidecars are
historical immutable artifacts. Creating v0.4.0 must not replace, rename or
remove them.
