Skip to content

Commit b845e68

Browse files
refactor(release): stage reproducible artifacts
1 parent 162acb3 commit b845e68

32 files changed

Lines changed: 1287 additions & 1765 deletions

.beads/issues.jsonl

Lines changed: 2 additions & 1 deletion
Large diffs are not rendered by default.

.github/workflows/release.yml

Lines changed: 5 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -197,29 +197,24 @@ jobs:
197197
"$haxe_bin" -version
198198
echo "HAXE_BIN=$haxe_bin" >> "$GITHUB_ENV"
199199
200-
- name: Configure git identity (for release commits)
201-
if: steps.release_context.outputs.should_skip != 'true'
202-
run: |
203-
git config user.name "github-actions[bot]"
204-
git config user.email "41898282+github-actions[bot]@users.noreply.github.com"
205-
206200
- name: Release
207201
id: semantic_release
208202
if: steps.release_context.outputs.should_skip != 'true'
209203
env:
210204
GITHUB_TOKEN: ${{ github.token }}
211205
GH_TOKEN: ${{ github.token }}
206+
RELEASE_SOURCE_SHA: ${{ steps.release_context.outputs.target_sha }}
212207
run: |
213208
set -euo pipefail
214-
before_version="$(node -e "process.stdout.write(require('./scripts/release/sync-versions').latestReachableVersion())")"
209+
before_tag="$(git describe --tags --abbrev=0 --match 'v[0-9]*.[0-9]*.[0-9]*' HEAD)"
215210
echo "[release] Publishing (semantic-release decides whether a new version is required)"
216211
npx semantic-release
217-
after_version="$(node -e "process.stdout.write(require('./scripts/release/sync-versions').latestReachableVersion())")"
212+
after_tag="$(git describe --tags --abbrev=0 --match 'v[0-9]*.[0-9]*.[0-9]*' HEAD)"
218213
published=false
219214
tag=""
220-
if [[ "$after_version" != "$before_version" ]]; then
215+
if [[ "$after_tag" != "$before_tag" ]]; then
221216
published=true
222-
tag="v$after_version"
217+
tag="$after_tag"
223218
fi
224219
{
225220
echo "published=$published"

AGENTS.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3558,6 +3558,10 @@ identify shipped versions; `release/manifest.json` owns only release-line policy
35583558
approval. Do not infer a shipped version or stable graduation from package metadata, generated
35593559
current-version prose, or roadmap completion labels.
35603560

3561+
Tracked npm, Haxelib, Mix, and scoped-HXML versions are development sentinels. Semantic-release
3562+
injects an exact version, tag, and source SHA only into temporary Reflaxe package staging; normal
3563+
publication must leave the tested commit unchanged and must not create a release commit.
3564+
35613565
## Test Status Summary
35623566
**See**: [`docs/03-compiler-development/testing-infrastructure.md`](docs/03-compiler-development/testing-infrastructure.md) - Complete test architecture and status
35633567

CHANGELOG.md

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -39,11 +39,12 @@
3939

4040
* **release:** replace mutable manifest-owned version/generation state with a tag-owned SemVer policy core; delegate Conventional Commit classification to the pinned official analyzer, validate versions with locked `semver`, keep `0.x` breaking changes minor until an independently approved graduation change, and require a durable approval for every stable major.
4141
* **release:** replace the original single graduation-evidence gate with independent, dated per-major approval records; approval remains non-releasing, and a subsequent new breaking commit is required to derive the authorized stable major.
42-
* **release:** keep the existing version metadata, scoped HXML, and current-posture generator as a compatibility bridge while release artifacts migrate to deterministic staging; release policy no longer contains its file inventory or mutable current version.
42+
* **release:** replace tracked version/changelog generation and release commits with development sentinels plus temporary Reflaxe package staging; tags now identify the unchanged tested source commit.
4343
* **release:** make no-op semantic-release runs skip published-package verification; successful publication passes the exact newly created tag to the verifier instead of inferring a prior release from tracked package metadata.
44-
* **release:** verify the prepared release commit and package before tag creation, verify the tag and tagged generated state before GitHub publication, and validate the downloaded GitHub Release asset afterward; document fail-closed partial-publication recovery.
44+
* **release:** build the complete Haxelib package twice with a pinned canonical ZIP writer, require byte-identical output, validate safe layout/modes and staged source identity, smoke the exact ZIP, and publish its SHA-256 sidecar.
4545
* **docs:** reconcile current-facing release language with the real pre-1.0 lineage; label old `v1.0.x`/`v1.1.x` milestones as unshipped historical plans and point entrypoints to the canonical generated posture.
46-
* **test:** derive synthetic release versions independently of release policy so generation and staged-verification contracts remain valid after each real publication.
46+
* **test:** add adversarial release-artifact coverage for metadata mismatch, missing/unexpected/unsafe entries, duplicate names, symlinks, unsafe modes, and environment-independent archive bytes.
47+
* **generator:** resolve scaffold package URLs from staged release metadata or the source checkout's nearest immutable tag, never from tracked development sentinels.
4748
* **stdlib:** classify `haxe.io.Mime` and `haxe.io.Scheme` as verified official Haxe fallback modules; add runtime, snapshot, and source-versus-package coverage without duplicating their String enum-abstract definitions in the target stdlib.
4849
* **compiler:** preserve omitted Haxe method defaults from Reflaxe's typed `ClassFuncData` instead of always passing `nil`, and keep multi-expression constructor blocks grouped when they are assigned as values.
4950
* **stdlib:** enable all upstream `haxe.io.ArrayBufferView` and typed-array runtime specs through the official Haxe fallback; preserve shared Bytes views while keeping source and built-package output identical.

README.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
# Reflaxe.Elixir (aka Haxir)
66

7-
[![Version](https://img.shields.io/badge/version-0.14.25-blue)](https://github.com/fullofcaffeine/reflaxe.elixir/releases)
7+
[![Release](https://img.shields.io/github/v/release/fullofcaffeine/reflaxe.elixir)](https://github.com/fullofcaffeine/reflaxe.elixir/releases)
88
[![License: GPL-3.0](https://img.shields.io/badge/License-GPL%20v3-blue.svg)](https://www.gnu.org/licenses/gpl-3.0)
99
[![CI](https://github.com/fullofcaffeine/reflaxe.elixir/actions/workflows/ci.yml/badge.svg)](https://github.com/fullofcaffeine/reflaxe.elixir/actions/workflows/ci.yml)
1010
[![Haxe](https://img.shields.io/badge/Haxe-4.3.7+-orange)](https://haxe.org)
@@ -13,12 +13,10 @@
1313
**[Haxe](https://haxe.org) -> [Elixir](https://elixir-lang.org) compiler for the BEAM ecosystem, with first-class [Phoenix](https://phoenixframework.org)/[LiveView](https://www.phoenixframework.org/liveview) support.**
1414
Write application code in Haxe and compile to conventional Elixir shapes for pure Elixir/OTP services and Phoenix applications.
1515

16-
<!-- BEGIN GENERATED: release-posture -->
1716
> [!WARNING]
18-
> **Stability**: v0.14.25 is on the pre-1.0 (`v0.x`) release line.
17+
> **Stability**: Reflaxe.Elixir is on the pre-1.0 (`v0.x`) release line.
1918
> Breaking changes to documented stable surfaces use minor releases until an explicitly reviewed stable graduation.
2019
> Some features remain experimental/opt-in; see [Known Limitations](docs/06-guides/KNOWN_LIMITATIONS.md) and [Versioning & Stability](docs/06-guides/VERSIONING_AND_STABILITY.md).
21-
<!-- END GENERATED: release-posture -->
2220
2321
Immutable Git tags identify released versions. The
2422
[release policy manifest](release/manifest.json) contains only release-line approvals; it is not a
@@ -110,6 +108,9 @@ The release zip is the normal consumer package. Reflaxe builds it from the check
110108
sources and includes the generated `.cross.hx` files required by a single `-lib reflaxe.elixir`.
111109
The `www.github.com` host is intentional: it lets Lix treat the file as a generic immutable HTTPS
112110
archive instead of misclassifying the release URL as a GitHub source-repository dependency.
111+
Each release also publishes a `.sha256` sidecar, and the ZIP embeds its exact version, tag, and source
112+
commit. The release job builds the complete package twice and requires byte-identical output before
113+
it tags that already-tested source commit.
113114

114115
### Working from a source checkout
115116

docs/06-guides/VERSIONING_AND_STABILITY.md

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,10 @@
33
Reflaxe.Elixir uses **semantic versioning** (`MAJOR.MINOR.PATCH`) and a **stability tier** model to
44
make it clear what is safe to depend on and what may change.
55

6-
<!-- BEGIN GENERATED: release-posture -->
7-
> Current version: **v0.14.25**<br>
86
> Current release line: **pre-1.0 (`v0.x`)**<br>
97
> Breaking stable-surface changes produce a **minor** release on this line.<br>
10-
> Stable graduation: **not approved**.
11-
<!-- END GENERATED: release-posture -->
8+
> Stable graduation: **not approved**.<br>
9+
> Exact released version: the latest immutable `vMAJOR.MINOR.PATCH` GitHub Release tag.
1210
1311
Experimental features remain opt-in and may evolve in minor releases. Breaking changes on the
1412
current line must still be documented clearly.
@@ -17,8 +15,8 @@ Reachable immutable `vMAJOR.MINOR.PATCH` Git tags are the source of truth for re
1715
[`release/manifest.json`](../../release/manifest.json) is intentionally version-independent: it
1816
contains only release-line policy and durable approval records. Semantic-release delegates
1917
Conventional Commit parsing to its official analyzer, then applies that small policy layer.
20-
Tracked package versions and current-version prose are compatibility mirrors during the release
21-
protocol migration; they do not decide the next version.
18+
Tracked package and HXML versions are development sentinels. They do not decide or mirror the
19+
current release; exact release metadata is injected only into temporary package staging.
2220

2321
The current policy shape is deliberately small:
2422

docs/10-contributing/RELEASING.md

Lines changed: 44 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ High level:
66

77
- Merge changes to `main` using **Conventional Commits** (`feat:`, `fix:`, etc.)
88
- When `CI` completes successfully on `main`, the separate **Release** workflow runs automatically
9-
- `semantic-release` determines the next version (if any), creates a `vX.Y.Z` tag, publishes a GitHub Release,
10-
and updates repo version strings + `CHANGELOG.md`
9+
- `semantic-release` determines the next version (if any), builds the package from that tested commit,
10+
creates a `vX.Y.Z` tag on the same commit, and publishes a GitHub Release
1111

1212
## What triggers a release?
1313

@@ -54,55 +54,59 @@ Use clear, scoped messages (examples):
5454

5555
3) **Let semantic-release do the rest**
5656

57-
Immutable reachable tags are the source of truth for released versions.
58-
`release/manifest.json` contains only release-line policy and per-major approvals; it does not own a
59-
current version or generated-file inventory. During the release-protocol migration,
60-
`scripts/release/sync-versions.js` remains a compatibility bridge that writes the derived tag version
61-
to these tracked mirrors:
57+
Immutable reachable tags are the source of truth for released versions. `release/manifest.json`
58+
contains only release-line policy and per-major approvals. Tracked `package.json`, `haxelib.json`,
59+
`mix.exs`, and scoped HXML versions are development sentinels, not release mirrors.
6260

63-
- `package.json` / `package-lock.json`
64-
- `haxelib.json`
65-
- `mix.exs`
66-
- `README.md` version badge
67-
- generated release-posture blocks in `README.md` and `VERSIONING_AND_STABILITY.md`
68-
- `CHANGELOG.md` (generated)
61+
The artifact plugin exports the exact tested Git commit to temporary storage, runs that commit's
62+
vendored Reflaxe builder, and injects the derived version, tag, and source SHA only into package
63+
staging. It builds the complete package twice under varied environment settings, requires identical
64+
bytes, validates the ZIP layout and metadata, runs installed-package parity against that exact ZIP,
65+
and emits `dist/reflaxe.elixir.zip.sha256`. Normal publication leaves tracked files unchanged and
66+
does not create a release commit. GitHub Release notes are the current release changelog.
6967

70-
Check for drift without modifying files:
71-
72-
```bash
73-
npm run guard:release-state
74-
```
75-
76-
The bridge rejects unsafe paths, missing or duplicate generated markers, and stable versions without
77-
an approval for that exact major. Semantic-release obtains its transitional release-commit asset
78-
list from the same module through `release.config.js`. The local policy plugin delegates commit
79-
classification to the official semantic-release analyzer and changes only the explicit pre-1.0
80-
breaking rule; its verification hook enforces stable-major approval.
68+
Project scaffolding also respects that split. An installed Haxelib release reads the exact version
69+
injected into its staged `haxelib.json`; Haxe and Mix scaffolds running from a repository checkout
70+
resolve the nearest reachable immutable release tag when they encounter a development sentinel.
71+
They fail clearly when neither identity exists instead of generating a fake `v0.0.0`,
72+
`v0.0.0-development`, or `vlatest` download URL.
8173

8274
## Staged release verification
8375

8476
Release verification runs at three boundaries:
8577

86-
1. After the official git plugin creates and pushes the release commit, but before semantic-release
87-
creates a tag, the prepared-state verifier checks committed generated assets, changelog version,
88-
clean tracked state, and package contents. Failure here prevents tag creation.
89-
2. After semantic-release creates the tag, but before the GitHub plugin publishes, the tag verifier
90-
checks that the tag targets the prepared commit and that tagged metadata/docs and package contents
91-
agree.
78+
1. Before tag creation, the artifact plugin proves two complete builds are byte-identical, validates
79+
canonical entries/modes/metadata, smokes the exact ZIP, records byte count and SHA-256, and checks
80+
that the tracked tree stayed clean. Failure here prevents tag creation.
81+
2. After semantic-release creates the tag, but before GitHub upload, the plugin re-validates the
82+
approved ZIP and checksum and confirms that tracked source was not modified.
9283
3. After semantic-release returns, the workflow downloads the GitHub Release asset and verifies the
93-
published release state, exact asset name, non-empty upload, tagged generated state, and package
94-
contents. This step receives the exact tag created by that run. If commit analysis produces no new
95-
version, the workflow records a no-op and does not re-audit an older release as though it had just
96-
been published.
84+
exact asset, checksum, staged package metadata, and source SHA. This step receives the exact tag
85+
created by that run. A no-op does not re-audit an older release as newly published.
9786

9887
### Partial-publication recovery
9988

10089
- **Failure before tag creation:** fix the cause and rerun the Release workflow. Do not create a tag
10190
manually; no public release identity exists yet.
10291
- **Tag exists but the GitHub Release or asset is missing:** do not move or recreate the tag. Check
103-
out that exact tag in a temporary clone, run `scripts/release/package-haxelib.sh`, verify the zip,
104-
then create the missing GitHub Release or upload the exact `reflaxe.elixir-X.Y.Z.zip` asset with
105-
`gh release create` / `gh release upload --clobber` as appropriate.
92+
out that exact tag in a temporary clone, then rebuild with the release identity explicitly:
93+
94+
```bash
95+
tag=vX.Y.Z
96+
version=${tag#v}
97+
source_sha=$(git rev-parse "${tag}^{commit}")
98+
scripts/release/package-haxelib.sh dist/reflaxe.elixir.zip "$version" "$tag" "$source_sha"
99+
archive="reflaxe.elixir-${version}.zip"
100+
mv dist/reflaxe.elixir.zip "dist/${archive}"
101+
node scripts/release/verify-release-artifact.js \
102+
--zip "dist/${archive}" --version "$version" --tag "$tag" --source-sha "$source_sha"
103+
hash=$(node -e 'const c=require("crypto"),f=require("fs");process.stdout.write(c.createHash("sha256").update(f.readFileSync(process.argv[1])).digest("hex"))' "dist/${archive}")
104+
printf '%s %s\n' "$hash" "$archive" > "dist/${archive}.sha256"
105+
```
106+
107+
Compare the rebuilt SHA-256 with the release job record before creating the missing GitHub Release
108+
or uploading the exact `reflaxe.elixir-X.Y.Z.zip` and checksum assets. Never rebuild with the
109+
script defaults for recovery: those intentionally produce development metadata.
106110
- **Published metadata is wrong:** treat the release as immutable until the discrepancy is understood.
107111
Prefer correcting GitHub Release notes/assets against the existing tag. Delete or move a public tag
108112
only as an explicitly reviewed release revocation.
@@ -113,10 +117,9 @@ Finish every recovery with:
113117
scripts/release/verify-published-package.sh vX.Y.Z
114118
```
115119

116-
Tags created before release policy schema v2, including tags with the earlier generated-state
117-
manifest, can be audited with `ALLOW_LEGACY_RELEASE=1`; this bypasses only tagged policy/generated-
118-
state comparison and is never set by the Release workflow. Package structure and hosted-asset checks
119-
still run.
120+
Tags created before package provenance/checksum metadata can be audited with
121+
`ALLOW_LEGACY_RELEASE=1`; this keeps legacy package-structure checks explicit and is never set by the
122+
Release workflow.
120123

121124
## Token / permissions notes
122125

examples/todo-app/README.md

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -518,7 +518,8 @@ CMD ["_build/prod/rel/todo_app/bin/todo_app", "start"]
518518

519519
## 🤝 Contributing
520520

521-
This example is part of Reflaxe.Elixir v1.1.x. Contributions welcome!
521+
This example tracks the current pre-1.0 Reflaxe.Elixir source tree and is part of the repository's
522+
compiler, Phoenix runtime, and browser QA.
522523

523524
1. Fork the repository
524525
2. Create your feature branch
@@ -531,7 +532,7 @@ GPL-3.0 - See LICENSE file in project root
531532

532533
---
533534

534-
**Built with Reflaxe.Elixir v1.1.x** - Write once in Haxe, run everywhere! 🚀
535+
**Built with Reflaxe.Elixir** - Haxe authoring with Phoenix and LiveView on the BEAM.
535536

536537
## Template Mode
537538

haxe_libraries/reflaxe.elixir.hxml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434
-lib reflaxe
3535

3636
# Define the library version
37-
-D reflaxe.elixir=0.14.25
37+
-D reflaxe.elixir=0.0.0-development
3838
# Match Reflaxe-generated target entrypoints: type the compiler package with
3939
# Haxe null-safety metadata before bootstrap/init macros run.
4040
--macro nullSafety("reflaxe.elixir")

haxelib.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@
1010
"macro"
1111
],
1212
"description": "A Haxe compilation target for Elixir/BEAM enabling gradual typing in Phoenix applications with compile-time type-safe Ecto queries and HXX→HEEx template transformation.",
13-
"version": "0.14.25",
13+
"version": "0.0.0",
1414
"classPath": "src",
15-
"releasenote": "v0.14.25: See CHANGELOG.md",
15+
"releasenote": "Development checkout; release metadata is injected during package staging",
1616
"contributors": [
1717
"fullofcaffeine"
1818
],

0 commit comments

Comments
 (0)