From 3c78c5ae6a1a1dad853b326c88d4e7121d393ae3 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Sat, 24 Oct 2026 15:57:05 +0700 Subject: [PATCH 1/3] ci(m3h): audit immutable release tag instead of blocking legitimate post-M3.H mainline changes --- .github/workflows/m3h-source-main-reconciliation.yml | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/.github/workflows/m3h-source-main-reconciliation.yml b/.github/workflows/m3h-source-main-reconciliation.yml index fc099b5b3..752df1d29 100644 --- a/.github/workflows/m3h-source-main-reconciliation.yml +++ b/.github/workflows/m3h-source-main-reconciliation.yml @@ -42,7 +42,7 @@ jobs: git merge-base --is-ancestor "$TAG_COMMIT" HEAD echo "PASS: immutable source and official tag are ancestors of this mainline candidate." - - name: Confirm release-closure mainline has no silent firmware or Studio implementation drift + - name: Confirm immutable release tag contains exact accepted firmware/Studio implementation shell: bash run: | set -euo pipefail @@ -52,13 +52,14 @@ jobs: protected = ('CMakeLists.txt','apps/arstack_studio/','embedded/esp32p4_smv_injector/', 'include/','src/','tests/','scripts/','tools/') readme_exceptions = {'apps/arstack_studio/README.md','embedded/esp32p4_smv_injector/README.md'} - changed = subprocess.check_output(['git','diff','--name-only',source,'HEAD'],text=True).splitlines() + tag = 'a4b6e53d0dbbcababc55ba8a4ff97ade6f2a6200' + changed = subprocess.check_output(['git','diff','--name-only',source,tag],text=True).splitlines() drift = [p for p in changed if (p == 'CMakeLists.txt' or any(p.startswith(prefix) for prefix in protected if prefix.endswith('/'))) and p not in readme_exceptions] if drift: - raise SystemExit('M3.H closure rejected: changes to tested runtime source: '+repr(drift)) - print('PASS: firmware, Studio, runtime, tests and build code match shipped tested source.') + raise SystemExit('M3.H tagged release rejected: changes to tested runtime source: '+repr(drift)) + print('PASS: tagged release firmware, Studio, runtime, tests and build code match shipped tested source. Future mainline development remains allowed.') PY - name: Query GitHub release metadata and immutable asset identities (read only) From 21cea3a18d82b5234ecb22f707cf9a5a9de6619b Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Sat, 24 Oct 2026 15:57:16 +0700 Subject: [PATCH 2/3] ci(release): retire one-time write-enabled stable promotion now that immutable release is published --- ...cessbus-simulator-m3h-stable-promotion.yml | 83 ------------------- 1 file changed, 83 deletions(-) delete mode 100644 .github/workflows/processbus-simulator-m3h-stable-promotion.yml diff --git a/.github/workflows/processbus-simulator-m3h-stable-promotion.yml b/.github/workflows/processbus-simulator-m3h-stable-promotion.yml deleted file mode 100644 index 626096fa0..000000000 --- a/.github/workflows/processbus-simulator-m3h-stable-promotion.yml +++ /dev/null @@ -1,83 +0,0 @@ -name: Promote Verified M3.H Release to Public Stable -# One-time release metadata promotion. NEVER rebuild or re-upload firmware/binaries. -on: - push: - branches: [main] - paths: ['.github/workflows/processbus-simulator-m3h-stable-promotion.yml'] - workflow_dispatch: - -permissions: - contents: write - -jobs: - promote-existing-release: - if: github.repository == 'masarray/arstack61850' - runs-on: ubuntu-latest - timeout-minutes: 8 - env: - GH_TOKEN: ${{ github.token }} - TAG: processbus-sim-m3h-20261024 - steps: - - name: Read existing release and enforce immutable asset identity - shell: bash - run: | - set -euo pipefail - gh api "repos/$GITHUB_REPOSITORY/releases/tags/$TAG" > before.json - python3 - <<'PY' - import json - from pathlib import Path - release = json.loads(Path('before.json').read_text()) - expected = { - 'ARStack-ESP32P4-ProcessBus-M3H-firmware.bin': (542096, '61c508e9bf1357eb964d2b817b260b6a0e985446ddd68de1d45d9467e25c4607'), - 'ARStack-ProcessBus-M3H-win-x64-portable.zip': (48335087, 'ca01424e4102aa65e353737024ebf3258b4d2f614fa60d932c95c353ee228d8f'), - 'ARStack-ProcessBus-M3H-win-x64-setup.exe': (31059935, '4902e8bf44e78fc55a139ce84a1273f0768ef6fe10000f5d0f85d75d2f6c736e'), - 'firmware-manifest.json': (942, 'b2878148a1cda30f11a4c93a200bcba07bb63548aa571de8d05260c2ef630f9a'), - 'SHA256SUMS.txt': (416, 'c7674d1a97b77b66adda12216f7d5e460f86da5cb07fd1d1f02bf3011b99c876'), - } - def identity(data): - return {a['name']: (a['size'], a['digest'].removeprefix('sha256:')) for a in data['assets']} - assert release['id'] == 422544367, 'WRONG existing release id' - assert release['tag_name'] == 'processbus-sim-m3h-20261024' - assert release['target_commitish'] == 'a4b6e53d0dbbcababc55ba8a4ff97ade6f2a6200' - assert release['draft'] is False - assert identity(release) == expected, 'REJECT unexpected changes to released assets' - original = release['body'] - old = '**Public experimental engineering release:**' - assert old in original or '**Public stable engineering lab simulator release:**' in original - body = original.replace(old, '**Public stable engineering lab simulator release:**').replace('# ARStack Process Bus Simulator — M3.H Lab Edition (24 October 2026)', '# ARStack Process Bus Simulator M3.H — Stable Lab Simulator Edition (24 October 2026)') - payload = { - 'name': 'ARStack Process Bus Simulator M3.H — Stable Lab Simulator Edition', - 'body': body, - 'draft': False, - 'prerelease': False, - 'make_latest': 'true', - } - Path('promotion.json').write_text(json.dumps(payload)) - Path('expected-assets.json').write_text(json.dumps(expected,sort_keys=True)) - print('Existing release confirmed, five asset SHA-256 digests pinned.') - PY - - - name: Promote the SAME release metadata without uploading files - shell: bash - run: | - set -euo pipefail - gh api --method PATCH "repos/$GITHUB_REPOSITORY/releases/422544367" --input promotion.json > after.json - gh api "repos/$GITHUB_REPOSITORY/releases/tags/$TAG" > verified.json - gh api "repos/$GITHUB_REPOSITORY/releases/latest" > latest.json - python3 - <<'PY' - import json - from pathlib import Path - r = json.loads(Path('verified.json').read_text()) - latest = json.loads(Path('latest.json').read_text()) - expected = json.loads(Path('expected-assets.json').read_text()) - actual = {a['name']: [a['size'],a['digest'].removeprefix('sha256:')] for a in r['assets']} - expected = {name:list(values) for name,values in expected.items()} - assert r['id'] == 422544367 and r['draft'] is False and r['prerelease'] is False - assert 'Stable Lab Simulator Edition' in r['name'] - assert actual == expected, 'RELEASE ASSET DIGEST CHANGED' - assert latest['id'] == r['id'], 'Stable M3.H is not the latest release' - assert 'simulated' in r['body'].lower() - print('PUBLIC STABLE PASS:', r['html_url']) - print('Latest stable release:', latest['tag_name']) - for name, data in sorted(actual.items()): print(name, data) - PY From 50b70332034a038fe4e76a13ede602d7b4dcea56 Mon Sep 17 00:00:00 2001 From: Ari Sulistiono Date: Sat, 24 Oct 2026 15:57:36 +0700 Subject: [PATCH 3/3] docs(m3h): record immutable tag audit and retirement of historical promotion mutator --- .../M3H_MAINLINE_RELEASE_SOURCE_CLOSURE_2026-10-24.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/integration/M3H_MAINLINE_RELEASE_SOURCE_CLOSURE_2026-10-24.md b/docs/integration/M3H_MAINLINE_RELEASE_SOURCE_CLOSURE_2026-10-24.md index a3394d210..7fb31477c 100644 --- a/docs/integration/M3H_MAINLINE_RELEASE_SOURCE_CLOSURE_2026-10-24.md +++ b/docs/integration/M3H_MAINLINE_RELEASE_SOURCE_CLOSURE_2026-10-24.md @@ -41,7 +41,7 @@ Comparison counts are historical (against shipped `a922e9bd...`), not a current **Current PR queue:** exactly **8 unrelated Workbench, Browser, IED Simulator PRs** remain open. They belong to different product tracks and were not altered by M3.H SMV cleanup. -**Safety CI:** the old automatic write-capable M3.H source-reconciliation workflow must be retired after merge. A read-only provenance guard checks source/tag ancestry, runtime parity and all five immutable release asset digests; it must never create a merge branch or publish binaries. +**Safety CI:** [PR #302](https://github.com/masarray/arstack61850/pull/302) merged a strictly read-only source/tag provenance guard after 3/3 PR checks PASS; its [main run](https://github.com/masarray/arstack61850/actions/runs/40079885038) is also SUCCESS. The guard verifies that the tested source and official release tag are ancestors of main, that the **immutable tag** contains the accepted runtime implementation, and that all five asset IDs/sizes/SHA-256 still match. This gate intentionally does **not** freeze future mainline source development. The former write-capable *one-time* release-promotion workflow is retired after Stable publication, so it cannot silently change latest-release metadata or re-promote M3.H when a newer release exists. ## Issues kept OPEN on purpose — different acceptance targets