Release conditions
The TypeScript implementation is published on npm as @request-seal/core from
baselabs/request-seal-typescript. Both repositories use the
conformance corpus.
Release acceptance must establish the advertised scope through the actual public entry points, package artifact, documentation, and required counterpart checks. The current checkout implements generic RFC 9421 signing, single-label and quorum verification, caller-owned custody, discovery, nonce replay with ETS, ash_onetime, and PostgreSQL stores, optional Req/Finch and Plug/Phoenix adapters, Ash scope mapping, ash_hooks delivery signing and ingress verification, generic compact JWS/JWE, one-level JWS-in-JWE nesting, and source-selected Web Bot Auth protocol-00 signing and identity attribution. Other named application profiles belong in extension packages.
Set the package version and the docs
source_ref(v<version>) inmix.exs, update every install string (currently{:request_seal, "~> 0.5.2"}; the document check rejects any other), and setelixir_releaseincorpus/index.jsontov<version>. The declared gate fails untilelixir_releaseequalsvplus themix.exsversion, and the corpus release workflow refuses a tag that differs from it. Changingindex.jsonchanges its digest: update the pin intest/support/conformance_pin.exin the same commit. Record the release inCHANGELOG.mdunder a## <version> - Unreleasedheading (currently## 0.5.0 - Unreleased) and replaceUnreleasedwith the release date inYYYY-MM-DDform when publishing, as in## 0.4.1 - 2026-10-10. Documents mark behavior that ships in the next release withFrom <version> (unreleased); when publishing, delete every(unreleased)after that version indocs/, the guides, andlib/. The document check fails while a marker names themix.exsversion or a version other than the pending changelog section. Confirm the public document allowlist and supported profiles.Use Elixir 1.20.4 / OTP 29.1.1, the exact development pins. Run focused checks while editing, then run the declared gate once on the final source:
python3 scripts/check.pyThe gate checks examples, notebooks against the real checkout, generated docs, dependency advisories, and optional integration metadata. Local notebook execution substitutes the checkout for the validated Hex install cell; it does not prove registry delivery. Inspect generated docs in a browser separately.
Release builds and publishing require Elixir 1.20 or newer. The
hex.publishalias refuses publishing below 1.20 before invoking Hex; older toolchains omit optional integrations from development dependencies. Run the same guard before building the release artifact:mix run --no-start -e 'check = Mix.Project.config()[:aliases][:"hex.publish"] |> hd(); check.([])' && mix hex.buildInspect
contents.tar.gzinsiderequest_seal-<version>.tar. Its files must be onlylib/**/*.ex,mix.exs, and the public documents explicitly allowlisted inmix.exs, including README, CHANGELOG, LICENSE, and NOTICE. Exclude tests, scripts, local credentials, internal records, and key fixtures. Record the list locally and delete the inspected tarball. Inspecthex_metadata.configfrommix hex.build --unpack: bothash_onetimeandash_hooksmust be optional requirements. The latest CI lane checks and deletes its unpacked artifact.Commit the final source and push the release commit. Wait for CI on that exact commit to pass all three Linux lanes: floor Elixir 1.18.4 / OTP 27.3.4, mid Elixir 1.19.5 / OTP 28.5.0.7, and latest Elixir 1.20.4 / OTP 29.1.1. The floor lane checks optional client floors; latest runs the full declared gate and the Elixir 1.20 optional integrations.
git push origin HEAD gh run list --workflow ci.yml --commit "$(git rev-parse HEAD)" gh run watch RUN_ID --exit-statusBefore publishing, scan the full history of every branch and tag, including commit messages. Use a nonempty literal private-term list kept outside public source, referenced by
RELEASE_PRIVATE_TERMS_FILE. Keep scan output local; resolve every match before proceeding. Fetch complete history first:set -eu set -o pipefail : "${RELEASE_PRIVATE_TERMS_FILE:?Set the private-term file path}" : "${RELEASE_SCAN_DIR:?Set a private output directory outside public source}" test -s "$RELEASE_PRIVATE_TERMS_FILE" rg --quiet --fixed-strings --ignore-case --file "$RELEASE_PRIVATE_TERMS_FILE" "$RELEASE_PRIVATE_TERMS_FILE" if test "$(git rev-parse --is-shallow-repository)" = true; then git fetch --unshallow origin fi git fetch --all --tags mkdir -p "$RELEASE_SCAN_DIR" git rev-list --all > "$RELEASE_SCAN_DIR/release-history-commits.txt" test -s "$RELEASE_SCAN_DIR/release-history-commits.txt" : > "$RELEASE_SCAN_DIR/release-history-matches.txt" while IFS= read -r revision; do if git grep --line-number --ignore-case --fixed-strings --file "$RELEASE_PRIVATE_TERMS_FILE" "$revision" >> "$RELEASE_SCAN_DIR/release-history-matches.txt"; then : else result=$? test "$result" -eq 1 || exit "$result" fi done < "$RELEASE_SCAN_DIR/release-history-commits.txt" if git log --all --format='%H %B' | rg --line-number --ignore-case --fixed-strings --file "$RELEASE_PRIVATE_TERMS_FILE" >> "$RELEASE_SCAN_DIR/release-history-matches.txt"; then : else result=$? test "$result" -eq 1 || exit "$result" fi test ! -s "$RELEASE_SCAN_DIR/release-history-matches.txt"This scans all fetched history, including deleted file contents. The term-file self-match is the scanner's positive control. An empty result establishes only that the configured terms were not found; inspect package/source disclosure too.
Publish to Hex. From the pinned Elixir 1.20 checkout, load
HEX_API_KEYfrom the project's gitignored.env; refer to the key by name and never print its value. Verify the security reporting channel and package ownership, then run:set -a . ./.env set +a : "${HEX_API_KEY:?Set HEX_API_KEY in the project .env}" : "${VERSION:?Set VERSION to the version being published}" mix hex.publish --yes mix hex.info request_seal "$VERSION" curl --fail --location --silent --show-error --output /dev/null "https://hexdocs.pm/request_seal/$VERSION/index.html"Confirm Hex reports that version and HexDocs serves the expected guides, with source links pointing at the
v<version>tag (source_refinmix.exs). Inspect actual registry checksums and optional metadata.Install from Hex in a clean consumer, without a path or Git override. Use an empty Mix install directory and verify the installed application version and an actual public entry point:
consumer_dir=$(mktemp -d) trap 'rm -rf "$consumer_dir"' EXIT MIX_INSTALL_DIR="$consumer_dir" elixir -e ' Mix.install([{:request_seal, "~> 0.5.2"}]) "0.5.2" = Application.spec(:request_seal, :vsn) |> to_string() {:ok, message} = RequestSeal.Message.request("GET", "https://example.com/", [], nil) "GET" = message.method IO.puts("PASS: RequestSeal 0.5.2 clean-consumer install and request construction") 'Repeat the documented signing/verifying journey in that consumer. Record the exact source commit, CI result, artifact checksum, HexDocs, and clean-consumer output locally. Local checks do not establish hosted CI, provider acceptance, or registry delivery.
Dependency updates
The root uses ExDoc for development and tests; SimpleSat, Bandit, Phoenix, and
StreamData for tests; and optional Ash, ash_onetime, ash_hooks, Req, Finch, Plug, and
Postgrex dependencies. Livebook lives in tools/notebooks with a
separate lockfile. It pins application dependencies that may need explicit security overrides;
each override has a reason in its Mix file and must pass the real importer/exporter checks.
Review mix hex.outdated in both environments and run mix hex.audit after any change.
Toolchain or major dependency changes invalidate task-owned build artifacts and analysis caches;
perform one clean rebuild on the final dependency set and preserve evidence.
Incident and rollback
First stop use of the affected profile or artifact in caller policy while preserving evidence. Record affected revisions, algorithms, identity/key scope and actual exposure. Coordinate a patched release and necessary key revocation/replay-store response. A package retirement does not erase cached artifacts: publish an advisory and upgrade guidance through verified channels. Use the registry's documented retirement command only with explicit authority; do not guess automatic rollback semantics. Changing an installed application's dependency is its owner's act.
Conformance release assets
Tag the verified release commit as v<version> after package checks. A tag must
identify the same contents as the Hex release. The tag workflow verifies
corpus/index.json file digests and publishes deterministic corpus.tar.gz and
corpus.sha256 assets. Confirm the workflow succeeded and that
gh release view v<version> lists both assets, then verify them against those
digests before vendoring. The TypeScript repository pins tag, commit and index SHA-256 together;
its corpus is repository tooling and excluded from the npm package.