diff --git a/.github/workflows/m3h2-rc1-public-candidate.yml b/.github/workflows/m3h2-rc1-public-candidate.yml new file mode 100644 index 000000000..6c34c73e4 --- /dev/null +++ b/.github/workflows/m3h2-rc1-public-candidate.yml @@ -0,0 +1,157 @@ +name: Publish M3.H.2 RC1 Verified Windows Engineering Preview + +on: + pull_request: + paths: + - '.github/workflows/m3h2-rc1-public-candidate.yml' + - 'docs/releases/M3H2_RC1_PUBLIC_CANDIDATE_2026-10-25.md' + push: + branches: [main] + paths: + - '.github/workflows/m3h2-rc1-public-candidate.yml' + +permissions: + contents: read + actions: read + +concurrency: + group: publish-m3h2-rc1-once + cancel-in-progress: false + +env: + RC_TAG: processbus-sim-m3h2-rc1-20261025 + RC_HEAD: fb81c247fb24cd6b5ac6aad6b3fb27486d620b96 + RC_RUN: '40145602361' + RC_ARTIFACT: arstack-studio-native-preview-win-x64-328-fb81c247fb24cd6b5ac6aad6b3fb27486d620b96 + PREVIOUS_STABLE_ID: '422727082' + +jobs: + evidence: + name: Verify preview evidence, main ancestry and frozen stable release + runs-on: ubuntu-latest + timeout-minutes: 7 + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Check exact provenance; do not promote physical RX evidence + shell: bash + env: + GH_TOKEN: ${{ github.token }} + run: | + set -euo pipefail + git merge-base --is-ancestor "$RC_HEAD" HEAD + test "$(gh api "repos/$GITHUB_REPOSITORY/pulls/328" --jq '.merged')" = true + test "$(gh api "repos/$GITHUB_REPOSITORY/actions/runs/$RC_RUN" --jq '.head_sha')" = "$RC_HEAD" + test "$(gh api "repos/$GITHUB_REPOSITORY/actions/runs/$RC_RUN" --jq '.conclusion')" = success + test "$(gh api "repos/$GITHUB_REPOSITORY/releases/latest" --jq '.id')" = "$PREVIOUS_STABLE_ID" + test -s docs/releases/M3H2_RC1_PUBLIC_CANDIDATE_2026-10-25.md + grep -q 'prerelease, NOT Public Stable' docs/releases/M3H2_RC1_PUBLIC_CANDIDATE_2026-10-25.md + test "$(gh api "repos/$GITHUB_REPOSITORY/issues/324" --jq '.state')" = open + if gh release view "$RC_TAG" >/dev/null 2>&1; then + echo 'Refusing to overwrite or duplicate M3.H.2 RC1 release' >&2 + exit 1 + fi + echo 'PASS: exact PR-head Windows+firmware CI, ancestry and immutable stable release' + + publish: + name: Publish checksummed public prerelease only + needs: evidence + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + runs-on: ubuntu-latest + timeout-minutes: 25 + permissions: + contents: write + actions: read + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 0 + - name: Download exact successful Windows preview artifact + shell: bash + env: + GH_TOKEN: ${{ github.token }} + run: | + set -euo pipefail + mkdir -p candidate extracted dist + gh run download "$RC_RUN" --repo "$GITHUB_REPOSITORY" \ + --name "$RC_ARTIFACT" --dir candidate + test -s candidate/ARStack-Studio-Preview-win-x64-setup.exe + test -s candidate/ARStack-Studio-Preview-win-x64-portable.zip + test -s candidate/SHA256SUMS.txt + (cd candidate && sha256sum -c SHA256SUMS.txt) + unzip -t candidate/ARStack-Studio-Preview-win-x64-portable.zip | tail -n 1 + unzip -q candidate/ARStack-Studio-Preview-win-x64-portable.zip -d extracted + test -s extracted/PREVIEW-PROVENANCE.json + test -s extracted/firmware/firmware-manifest.json + python3 - <<'PY' + import hashlib, json, pathlib, os + root=pathlib.Path('extracted') + m=json.loads((root/'firmware/firmware-manifest.json').read_text(encoding='utf-8-sig')) + p=json.loads((root/'PREVIEW-PROVENANCE.json').read_text(encoding='utf-8-sig')) + sha=os.environ['RC_HEAD'] + assert p['guiCommit']==sha and p['firmwareSourceCommit']==sha + assert p['guiVersion']=='0.1.1-preview' + assert m['sourceCommit']==sha and m['chip']=='esp32p4' + assert m['phaseSyncPreview'] is True + assert m['globalGmTraceabilityPhysicallyProven'] is False + firmware=root/'firmware'/m['image'] + assert firmware.is_file() and firmware.stat().st_size>100000 + assert hashlib.sha256(firmware.read_bytes()).hexdigest()==m['sha256'] + assert len(m['image'])<128 and '/' not in m['image'] and '\\' not in m['image'] + assert (root/'ARStackStudio.exe').is_file() + print('PASS: preview GUI/source SHA equals bundled firmware SHA; firmware checksum valid') + PY + cp candidate/ARStack-Studio-Preview-win-x64-setup.exe dist/ + cp candidate/ARStack-Studio-Preview-win-x64-portable.zip dist/ + cp extracted/firmware/firmware-manifest.json dist/ + firmware_name="$(python3 -c 'import json; print(json.load(open("extracted/firmware/firmware-manifest.json"))["image"])')" + cp "extracted/firmware/$firmware_name" "dist/$firmware_name" + (cd dist && sha256sum ./*.exe ./*.zip ./*.bin ./firmware-manifest.json > SHA256SUMS.txt && sha256sum -c SHA256SUMS.txt) + + - name: Publish new prerelease without changing latest stable + env: + GH_TOKEN: ${{ github.token }} + shell: bash + run: | + set -euo pipefail + test "$GITHUB_REF_NAME" = main + git merge-base --is-ancestor "$RC_HEAD" HEAD + test "$(gh api "repos/$GITHUB_REPOSITORY/releases/latest" --jq '.id')" = "$PREVIOUS_STABLE_ID" + if gh release view "$RC_TAG" >/dev/null 2>&1; then + echo 'RC tag/release already exists; no --clobber permitted' >&2 + exit 1 + fi + gh release create "$RC_TAG" dist/* \ + --target "$GITHUB_SHA" \ + --title 'ARStack M3.H.2 RC1 — Windows Engineering Preview (not stable)' \ + --prerelease \ + --notes-file docs/releases/M3H2_RC1_PUBLIC_CANDIDATE_2026-10-25.md \ + --verify-tag=false + + - name: Verify published public preview and stable preservation + env: + GH_TOKEN: ${{ github.token }} + shell: bash + run: | + set -euo pipefail + gh api "repos/$GITHUB_REPOSITORY/releases/tags/$RC_TAG" > /tmp/rc.json + gh api "repos/$GITHUB_REPOSITORY/releases/latest" > /tmp/latest.json + python3 - <<'PY' + import json,pathlib,hashlib,os + rc=json.load(open('/tmp/rc.json')) + stable=json.load(open('/tmp/latest.json')) + assert rc['tag_name']==os.environ['RC_TAG'] + assert rc['prerelease'] is True and rc['draft'] is False + assert stable['id']==int(os.environ['PREVIOUS_STABLE_ID']), 'Stable latest was inadvertently replaced' + expected={f.name for f in pathlib.Path('dist').iterdir() if f.is_file()} + actual={a['name'] for a in rc['assets']} + assert actual==expected, f'Published asset list mismatch: {actual ^ expected}' + for asset in rc['assets']: + source=pathlib.Path('dist')/asset['name'] + assert source.stat().st_size==asset['size'] + digest=asset.get('digest') + if digest: + assert digest=='sha256:'+hashlib.sha256(source.read_bytes()).hexdigest() + print('PASS: M3.H.2 RC1 public prerelease, all exact files and immutable latest stable') + PY diff --git a/docs/releases/M3H2_RC1_PUBLIC_CANDIDATE_2026-10-25.md b/docs/releases/M3H2_RC1_PUBLIC_CANDIDATE_2026-10-25.md new file mode 100644 index 000000000..ded9170c2 --- /dev/null +++ b/docs/releases/M3H2_RC1_PUBLIC_CANDIDATE_2026-10-25.md @@ -0,0 +1,41 @@ +# ARStack Process Bus Simulator M3.H.2 RC1 — Public Engineering Preview + +**25 October 2026 — prerelease, NOT Public Stable.** This release candidate is intended for isolated, non-production IEC 61850 SV/PTP laboratory experiments. The latest tested stable M3.H.1 continues to be [processbus-sim-m3h1-20261024](https://github.com/masarray/arstack61850/releases/tag/processbus-sim-m3h1-20261024), without any changed assets or firmware. + +## What's improved + +- **Current-session SV STOP alarm:** board-retained `SVLIFE stop_cause=RUNTIME_FAULT` history no longer generates a frightening error on ordinary Studio startup. A red unexpected-STOP alarm requires an observed transition from verified RUNNING to STOPPED during the current session. New fault evidence remains diagnosable. +- **Compact hardware status LED:** DISCONNECTED (grey), CONNECTING (blue), PREPARING (amber), READY/RUNNING (green) and ERROR (red), sourced from the active serial/identity/supervisor state. +- **Canonical SV TX diagnostics:** first/last ESP-IDF Ethernet transmit errors, link evidence and contiguous-burst/stop history are retained across operator restart. The fail-closed **400 consecutive TX failure** threshold has **not** been bypassed. +- Exact Windows GUI and ESP32-P4 firmware pairing is verified in the source-head CI and installer lifecycle tests. + +## Exactly which binaries + +| Evidence | Immutable source | +| --- | --- | +| Studio + paired firmware source | PR [#328](https://github.com/masarray/arstack61850/pull/328) reviewed head `fb81c247fb24cd6b5ac6aad6b3fb27486d620b96` | +| Windows setup + portable build | [GitHub Actions run 40145602361](https://github.com/masarray/arstack61850/actions/runs/40145602361), artifact `arstack-studio-native-preview-win-x64-328-fb81c247fb24cd6b5ac6aad6b3fb27486d620b96` | +| Firmware built from same head | [Run 40145602361](https://github.com/masarray/arstack61850/actions/runs/40145602361), firmware `sourceCommit=fb81c247fb24cd6b5ac6aad6b3fb27486d620b96` | +| PR CI | 10 success, 1 conditional skip, 0 failures; Windows native, Qt headless/HiDPI, firmware, cleanup and installer migration | +| Merge to main | `c8d351f1fe80ca4cf8f3e58cf6727566bec7d1f6` (packaged runtime is PR head above; subsequent changes are release paperwork) | + +Download the Windows **Setup** or **Portable ZIP**, confirm the included `SHA256SUMS.txt`, and use the **firmware bundled with that package**. Do not mix preview and old stable firmware. The executable still reports the internal `0.1.1-preview` engineering version; **M3.H.2 RC1 is the package/release-candidate label, not a claim of a new stable semantic version or hardware-certified product**. + +The separate Linux pull-request AppImage artifact was a GUI/test build and did not include a cross-platform release-tag-matched firmware package. **It is not promoted as a firmware-paired Linux release in RC1.** Public Stable M3.H.1 remains the Linux choice. + +## Actual user-observed result + +Two operator screenshots on COM15 show: connected idle **READY**, active **RUNNING**, and a displayed snapshot of approximately **3,999 fps, MISSED 0, TX FAIL 0**. These prove useful operator-state UX and that a board claims RUNNING at that moment. They **do not** establish a 1-hour endurance pass, independent NIC capture, reception by DigSubAnalyzer/ARSAS, authenticated PTP grandmaster synchronization, or error-free long-term Ethernet transmission. + +## Stable promotion — blocked until these are completed + +- [ ] Physical **3600 s** retained run on this exact candidate, complete start/stop cycling and no missed frames/transport faults; attach test conditions and trustworthy raw logs. +- [ ] Recipient NIC unfiltered PCAPNG captures actual 0x88BA packets, VLAN/tag state, APPID, svID and sample-counter continuity; note NIC/offload/switch topology. +- [ ] **DigSubAnalyzer** receives and decodes the live stream with values and sample counter. +- [ ] **ARSAS** receives and decodes the same real stream independently, with filters/NIC identity recorded. +- [ ] Verify full package SHA-256, firmware manifest/source pairing, installer migration and Windows/Linux cross-platform distribution for the new stable release tag. Do not relabel these preview binaries stable. +- [ ] Activate and independently verify [main branch protection](https://github.com/masarray/arstack61850/issues/322) under authorized repository administration before calling governance complete. + +Field incident: [#324](https://github.com/masarray/arstack61850/issues/324). Release gate/runbook: [independent SV wire capture](../P0_SMV_WIRE_CAPTURE_ACCEPTANCE.md) and [RC2 physical acceptance](../SMV_STUDIO_RC2_ACCEPTANCE.md). + +This candidate is strictly **prerelease=true**. The workflow refuses to overwrite or downgrade the existing Public Stable release, and its artifacts are pinned and checksum-checked before publication.