ProtectionAI v1.7.2
101 registered [WorkspaceView] modules, 30 AI copilot tools, 4 AI providers, 7 UI languages,
3,056 tests. Ribbon: 17 tabs, 85 groups, 144 controls. No counts move.
The repository now has no known intermittent tests, and not one of them turned out to be a test
problem. Chasing the last two documented flakes found four genuine defects — three in the
closed-loop engine, each of which would misreport a protection test on a machine that is merely
busy. That is the reason this release exists, and the reason the "it's just timing" instinct is now
written down as the wrong instinct.
Fixed
-
The closed-loop engine could return before its own deadline.
DelayUntilAsynccomputed the
time remaining againstDateTime.UtcNow— the clock every measurement in the result is reported
against — but slept on the runtime's monotonic timer. Those are different time bases and they
drift, so a wait could end before the measuring clock agreed the interval had passed. The
observable result was a breaker interrupting time shorter than the breaker's own mechanical
opening time: 37.63 ms against a modelled 40 ms, a physically impossible figure in a protection
report, produced with nothing wrong in the model. The wait now re-checks against the measuring
clock and tops itself up. The deadline is unchanged — it no longer returns early. -
A reclose command could be seen and then thrown away, and reported as a lockout.
closeWaiter.Arm()ran after the arc-extinction wait, the open-breaker outputs and the 52a/52b
feedback. A relay with a short dead time issues its close command inside that window, where the
waiter was not yet armed; the edge was dropped, the wait timed out, and the run recorded "no
reclose command within timeout" and declared lockout for a relay that had actually reclosed.
On a high-speed autoreclose scheme this is a false verdict on the exact behaviour under test.
ContactWaiternow records every rising edge whether armed or not, andArm(sinceUtc)counts
anything at or after the instant the sequence became interested — for a reclose, the trip that
caused it. -
A trip could be observed and still reported as no-trip.
ContactWaiter.WaitAsyncraced the
contact against the timeout, and when the host had been descheduled both came due together and
the race was a coin toss. Losing it returned "the relay did not assert binary input N" while the
object was holding that input's trip timestamp — a verdict contradicting the measurement in hand.
The contact task is now re-checked before any negative result is returned. The timeout still
expires exactly when asked; it no longer discards evidence already collected. The timer is also
cancelled once the race is settled rather than left running. -
Modelled breaker instants were stamped with software latency.
openAtMs = Now()recorded the
moment the code finished two IO round trips, not the modelled arc-extinction instant the engine
had just computed. Every interval derived from it inherited the scheduling jitter, and because a
late open instant shortens the open-to-close interval, a stall produced an autoreclose dead
time of 104.4 ms against the relay's own 120 ms setting. Breaker open and close instants are
now the model's own; relay contact times remain genuine measurements taken from the driver's
timestamps. The distinction is the point: measured quantities stay measured, modelled quantities
are reported at the instant the model gives them. -
The signal-clock slip test could not be right under load, for arithmetic reasons. Two
channels at 50.0 and 50.2 Hz slip at 72 deg/s, so consecutive observations more than
180 / 72 = 2.5 s apart are aliased — past half a turn, an advance is indistinguishable from a
small step backwards, and no unwrapping can recover it. A stall that long made the relative angle
appear to run in reverse with the simulator behaving perfectly. It also read its timestamp after
ReadMeasurementsAsyncreturned rather than at the instant the angles were evaluated inside it.
Channel phase is analytic (base + 360·f·t), so the fix is to choose the instants:
SimulatorDriver.SignalClockSecondsOverride(internal, null in production, unreachable outside
the test assembly) lets the test evaluate the rotation at exactt. The assertion moved from a
±15 % band to exact — 72.000000 deg/s, and every step exact to nine decimals — because the
band was only ever absorbing host jitter.
Verified
20 consecutive runs of the three affected test classes green with a dotnet publish and two
CPU-saturating jobs running alongside, individual runs stretching from 2 s to 12 s under the load.
Before the fixes the same loop failed 1–2 times in every 8–15 runs. Full suite 3,056 / 0 failed on
an idle machine.
Installer
ProtectionAI-Setup.msi is a Windows Installer (MSI) package. It installs ProtectionAI in Program Files, creates a Start Menu shortcut, registers Apps & Features uninstall support, and supports in-place major upgrades. ProtectionAI.App.exe is the same application as a self-contained single-file executable, for people who cannot run an installer.
This release is not Authenticode code signed. Windows SmartScreen will warn you when you run it, and that warning is expected. The checks below prove integrity (the bytes are the bytes we built) and source binding (the signed manifest came from this repository, tag and commit). They are not a Windows trust decision and do not stop SmartScreen warning.
Artifact digests (SHA-256)
4fe30cd8aaf032538ffc9fc76f1bca1dcaeaae85a8b153ec3102f3086dd64326 protectionai-1.7.2-cyclonedx.json
aaf30ee4fbb0a14021c858915c69bf28d5b9e917cfbe9d09783c50039e171e69 protectionai-1.7.2-cyclonedx.json.sha256
803fd26621426ec789deefbe096e4ff7b965b9d78b5acb1e975ad95fd4b51b84 ProtectionAI-Setup.msi
32f9247f29f051707a0a0109b4c11bbdd7ec963e704a2c33d776a9eebe06a1a0 ProtectionAI.App.exe
Published alongside the artifacts as SHA256SUMS, with a detached Sigstore signature (SHA256SUMS.sig), the ephemeral signing certificate (SHA256SUMS.pem) and a self-contained bundle (SHA256SUMS.cosign.bundle). The software bill of materials (protectionai-1.7.2-cyclonedx.json) and its checksum sidecar (protectionai-1.7.2-cyclonedx.json.sha256) are both attached and covered by the signed manifest.
1. Verify integrity — the files are what we built
PowerShell:
foreach ($line in Get-Content .\SHA256SUMS) {
$expected, $name = $line -split '\s+', 2
$actual = (Get-FileHash $name.Trim() -Algorithm SHA256).Hash.ToLower()
"{0} {1}" -f $(if ($actual -eq $expected) { "OK " } else { "FAILED" }), $name.Trim()
}POSIX shell:
sha256sum -c SHA256SUMS2. Verify authenticity — the digest list itself is genuine
Step 1 only proves the files match the list. This proves the list was produced by this release workflow. Install cosign and run:
cosign verify-blob SHA256SUMS \
--signature SHA256SUMS.sig \
--certificate SHA256SUMS.pem \
--certificate-identity https://github.com/Cwgtshome/ProtectionAI/.github/workflows/release.yml@refs/tags/v1.7.2 \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
--certificate-github-workflow-repository Cwgtshome/ProtectionAI \
--certificate-github-workflow-ref refs/tags/v1.7.2 \
--certificate-github-workflow-sha 442426b55951b9087beeafce6e01da695d4278a5Or with the bundle, which needs no separate certificate:
cosign verify-blob SHA256SUMS \
--bundle SHA256SUMS.cosign.bundle \
--certificate-identity https://github.com/Cwgtshome/ProtectionAI/.github/workflows/release.yml@refs/tags/v1.7.2 \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
--certificate-github-workflow-repository Cwgtshome/ProtectionAI \
--certificate-github-workflow-ref refs/tags/v1.7.2 \
--certificate-github-workflow-sha 442426b55951b9087beeafce6e01da695d4278a5Pin all five certificate constraints exactly as shown: identity, issuer, repository, ref and SHA. Together they reject a signature from another workflow, repository, tag or commit. There is no long-lived private key: the certificate above was issued to this workflow's OIDC identity, is valid for minutes, and the signing event is recorded in the public Rekor transparency log.
3. Build provenance — not available for this release
No GitHub-hosted SLSA build-provenance attestation was recorded, so gh attestation verify will not find one. GitHub's attestation store is not offered to user-owned private repositories on this plan, and the source repository is private.
What still holds: the signed SHA256SUMS manifest binds every shipped content file by digest, and the Sigstore certificate in step 2 binds that manifest to this repository, tag ref and exact source SHA against a public transparency log. What is missing is GitHub's separate SLSA predicate and attestation-store record; no SLSA level is claimed.
Built from Cwgtshome/ProtectionAI@442426b. See docs/RELEASE-INTEGRITY.md for what each file is and why it exists.