feat(releases): ship MSI and reuse exact-source CI proof (#40)
Stable1.0.2 and dev1.0.2-beta.1 EXEs/ZIPs are public and their actual downloaded signatures, timestamps, payloads, hashes, SBOM/provenance and updater feeds verify. Complete the owner's renewed MSI request by pinning standards1.2.0 at0ef3d319, enabling signed per-user MSI alongside EXE/ZIP, preparing1.0.3 channel links and next development prefix1.0.4. Stop duplicate release validation: a tag must be on main, have a verified source signature, match the entire genuinely merged PR Git tree, and reuse that head's latest successful required technical checks under their configured provider identities. Missing, failed, stale, pending or different-tree proof fails publication. All branch technical gates remain required; fresh release build/core coverage, signing, exact MSI payload/native lifecycle and distribution/SBOM/provenance checks remain required. Application behavior/dependencies/Azure trust are unchanged. Validation: all8 script tests pass (including10 release-proof boundary cases), live source proof verifies all17 genuine checks on the actual published18d8ae9 tree, five actual documents pass canonical lint, actionlint/zizmor and signed commit hooks pass. Exact-head full CI starts with this PR; production signed MSI lifecycle is still required before delivery. No claim of published MSI yet. Owner explicitly authorized release without further human-review requests. Record a truthful release-specific waiver; never fabricate personal-read acknowledgement; restore original rules immediately after the bounded merge. Substantial AI assistance: Codex GPT-6. Refs #33,#36,#38 and KofTwentyTwo/standards#47. --------- Signed-off-by: James Maes <james@kof22.com> Co-authored-by: Codex GPT-6 <noreply@openai.com>
This commit was merged in pull request #40.
This commit is contained in:
1 parent
18d8ae950c
commit
a44449b991
11 files changed
+154
-69
No files matched your search
@@ -0,0 +1,55 @@
|
||||
// Copyright (c) 2026 James Maes (KofTwentyTwo)
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
"use strict";
|
||||
|
||||
/** Requires genuine passing CI on the exact complete tree merged for this release. */
|
||||
async function run({ github, context, core })
|
||||
{
|
||||
const repository = context.repo;
|
||||
const { data: source } = await github.rest.repos.getCommit({ ...repository, ref: context.sha });
|
||||
if(!source.commit.verification.verified)
|
||||
{
|
||||
throw new Error("The release commit signature is not verified.");
|
||||
}
|
||||
const pulls = await github.paginate(github.rest.repos.listPullRequestsAssociatedWithCommit,
|
||||
{ ...repository, commit_sha: context.sha, per_page: 100 });
|
||||
const merged = pulls.filter(pull => pull.merged_at && pull.state === "closed"
|
||||
&& pull.base.ref === "main" && pull.merge_commit_sha === context.sha);
|
||||
if(merged.length !== 1)
|
||||
{
|
||||
throw new Error("Expected one merged main PR for the tagged release commit.");
|
||||
}
|
||||
const pull = merged[0];
|
||||
const { data: tested } = await github.rest.repos.getCommit({ ...repository, ref: pull.head.sha });
|
||||
if(tested.commit.tree.sha !== source.commit.tree.sha)
|
||||
{
|
||||
throw new Error("The release tree differs from the tested PR tree.");
|
||||
}
|
||||
const { data: rules } = await github.request("GET /repos/{owner}/{repo}/rules/branches/{branch}",
|
||||
{ ...repository, branch: "main" });
|
||||
// Personal acknowledgement is enforced at merge; technical validation is reused here.
|
||||
const required = rules.filter(rule => rule.type === "required_status_checks")
|
||||
.flatMap(rule => rule.parameters.required_status_checks)
|
||||
.filter(check => check.context !== "review / automated");
|
||||
if(required.length === 0)
|
||||
{
|
||||
throw new Error("No required technical checks were found.");
|
||||
}
|
||||
const checks = await github.paginate(github.rest.checks.listForRef,
|
||||
{ ...repository, ref: pull.head.sha, per_page: 100 });
|
||||
for(const requirement of required)
|
||||
{
|
||||
const records = checks.filter(check => check.name === requirement.context
|
||||
&& check.app?.id === (requirement.integration_id ?? 15368));
|
||||
records.sort((left, right) => right.id - left.id);
|
||||
if(records[0]?.status !== "completed" || records[0]?.conclusion !== "success")
|
||||
{
|
||||
throw new Error(`Required tested-source check is not successful: ${requirement.context}`);
|
||||
}
|
||||
}
|
||||
core.info(`Reused ${required.length} genuine passing checks from PR #${pull.number} at ${pull.head.sha}; exact release tree ${source.commit.tree.sha}.`);
|
||||
return { head: pull.head.sha, tree: source.commit.tree.sha, checks: required.length };
|
||||
}
|
||||
|
||||
module.exports = { run };
|
||||
@@ -0,0 +1,43 @@
|
||||
// Copyright (c) 2026 James Maes (KofTwentyTwo)
|
||||
// SPDX-License-Identifier: MIT
|
||||
|
||||
"use strict";
|
||||
|
||||
const assert = require("node:assert/strict");
|
||||
const test = require("node:test");
|
||||
const { run } = require("./assert-release-source.cjs");
|
||||
|
||||
/** Exercises release-tree, check-provider and latest-result boundaries. */
|
||||
test("reuses only verified identical-source passing CI", async () =>
|
||||
{
|
||||
for(const failure of [null, "signature", "tree", "merge", "provider", "pending", "failed", "missing", "latest", "rules"])
|
||||
{
|
||||
const check = { id: 1, name: "ci / ui-tests", app: { id: failure === "provider" ? 7 : 15368 },
|
||||
status: failure === "pending" ? "in_progress" : "completed",
|
||||
conclusion: failure === "failed" ? "failure" : "success" };
|
||||
const github = {
|
||||
rest: {
|
||||
repos: { getCommit: async ({ ref }) => ({ data: { commit: {
|
||||
verification: { verified: failure !== "signature" },
|
||||
tree: { sha: failure === "tree" && ref === "head" ? "other" : "tree" },
|
||||
} } }), listPullRequestsAssociatedWithCommit: "pulls" },
|
||||
checks: { listForRef: "checks" },
|
||||
},
|
||||
request: async () => ({ data: failure === "rules" ? [] : [{ type: "required_status_checks",
|
||||
parameters: { required_status_checks: [{ context: "ci / ui-tests", integration_id: 15368 }, { context: "review / automated" }] } }] }),
|
||||
paginate: async method => method === "pulls" ? (failure === "merge" ? [] : [{ number: 1, state: "closed",
|
||||
merged_at: "today", base: { ref: "main" }, merge_commit_sha: "release", head: { sha: "head" } }])
|
||||
: failure === "missing" ? [] : failure === "latest" ? [check, { ...check, id: 2, conclusion: "failure" }] : [check],
|
||||
};
|
||||
const operation = run({ github, context: { repo: { owner: "owner", repo: "repo" }, sha: "release" }, core: { info: () =>
|
||||
{} } });
|
||||
if(failure)
|
||||
{
|
||||
await assert.rejects(operation);
|
||||
}
|
||||
else
|
||||
{
|
||||
assert.deepEqual(await operation, { head: "head", tree: "tree", checks: 1 });
|
||||
}
|
||||
}
|
||||
});
|
||||
@@ -38,6 +38,8 @@ jobs:
|
||||
environment: release
|
||||
permissions:
|
||||
contents: read
|
||||
checks: read
|
||||
pull-requests: read
|
||||
steps:
|
||||
- name: Checkout main and tag history
|
||||
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
@@ -61,41 +63,16 @@ jobs:
|
||||
ARTIFACT_SIGNING_SUBJECT: ${{ vars.ARTIFACT_SIGNING_SUBJECT || vars.TRUSTED_SIGNING_SUBJECT }}
|
||||
run: ./build/Assert-ReleaseSigning.ps1
|
||||
|
||||
gates:
|
||||
needs: verify
|
||||
uses: ./.github/workflows/ci.yml
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
security:
|
||||
needs: verify
|
||||
uses: ./.github/workflows/security.yml
|
||||
permissions:
|
||||
contents: read
|
||||
security-events: write
|
||||
|
||||
codeql:
|
||||
needs: verify
|
||||
uses: ./.github/workflows/codeql.yml
|
||||
permissions:
|
||||
contents: read
|
||||
security-events: write
|
||||
|
||||
scripts:
|
||||
needs: verify
|
||||
uses: ./.github/workflows/scripts.yml
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
fuzzing:
|
||||
needs: verify
|
||||
uses: ./.github/workflows/fuzz.yml
|
||||
permissions:
|
||||
contents: read
|
||||
- name: Require passing CI for the exact merged release tree
|
||||
uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8
|
||||
with:
|
||||
script: |
|
||||
const { run } = require('./.github/scripts/assert-release-source.cjs');
|
||||
await run({ github, context, core });
|
||||
|
||||
release:
|
||||
needs: [verify, gates, security, codeql, scripts, fuzzing]
|
||||
uses: KofTwentyTwo/standards/.github/workflows/release-velopack.yml@083d90326a8f3b750d840b6180e00870ee1342f5 # v1.1.2
|
||||
needs: verify
|
||||
uses: KofTwentyTwo/standards/.github/workflows/release-velopack.yml@70b555bd5658273090769991bfc4c9b6681dde18 # v1.2.1
|
||||
permissions:
|
||||
contents: write # create and publish the GitHub release
|
||||
id-token: write # sign attestations; Azure OIDC when signing
|
||||
@@ -113,6 +90,7 @@ jobs:
|
||||
test-projects: |
|
||||
tests/MarkdownViewr.Windows.Core.Tests
|
||||
sign: true
|
||||
msi: true
|
||||
signing-files: |
|
||||
app/MarkdownViewr.Windows.exe
|
||||
app/MarkdownViewr.Windows.dll
|
||||
|
||||
@@ -54,7 +54,7 @@
|
||||
commit as "+<sha>" on AssemblyInformationalVersion, which the About dialog and the
|
||||
CLI's version output show as a short hash. -->
|
||||
<PropertyGroup>
|
||||
<VersionPrefix>1.0.3</VersionPrefix>
|
||||
<VersionPrefix>1.0.4</VersionPrefix>
|
||||
<VersionSuffix>dev</VersionSuffix>
|
||||
</PropertyGroup>
|
||||
|
||||
|
||||
@@ -31,8 +31,8 @@ Windows packages support **Windows 10 version 1809 or later and Windows 11, x64*
|
||||
|
||||
| Edition | Choose it for | Package |
|
||||
| --- | --- | --- |
|
||||
| Stable | Everyday reading | [Download EXE installer](https://github.com/KofTwentyTwo/MarkdownViewr.Windows/releases/latest/download/markdownviewr-windows-stable-Setup.exe) |
|
||||
| Development preview | Trying upcoming changes | [Download dev EXE installer](https://github.com/KofTwentyTwo/MarkdownViewr.Windows/releases/download/v1.0.2-beta.1/markdownviewr-windows-dev-dev-Setup.exe) |
|
||||
| Stable | Everyday reading | [Download EXE installer](https://github.com/KofTwentyTwo/MarkdownViewr.Windows/releases/latest/download/markdownviewr-windows-stable-Setup.exe) · [MSI installer](https://github.com/KofTwentyTwo/MarkdownViewr.Windows/releases/latest/download/markdownviewr-windows-stable.msi) |
|
||||
| Development preview | Trying upcoming changes | [Download dev EXE installer](https://github.com/KofTwentyTwo/MarkdownViewr.Windows/releases/download/v1.0.3-beta.1/markdownviewr-windows-dev-dev-Setup.exe) · [MSI installer](https://github.com/KofTwentyTwo/MarkdownViewr.Windows/releases/download/v1.0.3-beta.1/markdownviewr-windows-dev-dev.msi) |
|
||||
| Portable | Running without installation | [Download portable ZIP](https://github.com/KofTwentyTwo/MarkdownViewr.Windows/releases/latest/download/markdownviewr-windows-stable-Portable.zip) |
|
||||
|
||||
Download a package from [Releases](https://github.com/KofTwentyTwo/MarkdownViewr.Windows/releases). Run Setup for a per-user installation without administrator rights, or extract the portable ZIP before launching the app.
|
||||
|
||||
+21
-22
@@ -4,31 +4,30 @@ Updated: 2026-10-13
|
||||
|
||||
## Release source and verification
|
||||
|
||||
Application behavior remains the owner-reviewed PR37 source at main4c0ccc7.
|
||||
Actual focused30, native111, published111 and installed stable/dev110 reports
|
||||
have zero failures/skips. Unchanged-source main validation38325540268 attempt2
|
||||
also passes published111 and installed stable/dev110 each. Update/restart,
|
||||
channel coexistence, registration and preservation of defaults are verified.
|
||||
Stable1.0.2 and dev1.0.2-beta.1 EXE/ZIP releases are public and immutable at
|
||||
source18d8ae950cab445672d216d387a956b964d2c6c1, builder083d903. Actual downloaded
|
||||
signatures/publisher/trust/timestamps, package payloads, checksums, CycloneDX
|
||||
SBOM attestations, source/builder provenance and updater feeds verify.
|
||||
Production runs38392743207 and38392743312 succeeded. Failed older refs remain.
|
||||
|
||||
The signing caller pins standards1.1.2 at
|
||||
083d90326a8f3b750d840b6180e00870ee1342f5. Personally approved PR45 corrects the
|
||||
GitHub pwsh bootstrap wrapper; all hosted signing checks pass. Caller NuGet
|
||||
mapping, Azure trust scopes and strict publisher/signature gates are unchanged.
|
||||
No new Azure administration is required.
|
||||
The maintainer renewed the MSI request on2026-10-13; earlier MSI deferral is
|
||||
superseded. Prepare signed per-user MSI alongside EXE/ZIP for stable/dev1.0.3.
|
||||
MSI payload verification and focused native install/launch/uninstall must pass
|
||||
before publication. Source behaviors, dependencies and Azure trust are unchanged.
|
||||
|
||||
Preserve the failed1.0.0 and1.0.1 stable/dev tags. New candidates are
|
||||
1.0.2/stable and1.0.2-beta.1/dev. Production status and exact shipped-artifact
|
||||
proof belong in [release checklist](https://github.com/KofTwentyTwo/MarkdownViewr.Windows/issues/33)
|
||||
and [GitHub Releases](https://github.com/KofTwentyTwo/MarkdownViewr.Windows/releases).
|
||||
Require successful signing and verification of exact downloaded bytes before
|
||||
claiming a signed public release.
|
||||
Prevent duplicate release test passes by requiring the exact complete Git tree
|
||||
of a genuinely merged main PR whose latest required technical checks all passed
|
||||
under their required provider identities. Reject unverified source signatures,
|
||||
different trees, missing/failed/pending checks and API failures. Reuse real CI
|
||||
proof; do not post fabricated results or weaken branch protection. Fresh release
|
||||
build/core coverage, signing and installer/distribution checks remain required.
|
||||
|
||||
The maintainer authorized release without further human-review requests on
|
||||
2026-10-13. Record this as an explicit release-specific waiver, never as a claim
|
||||
that an unseen change was personally read. Automated review and technical CI
|
||||
remain required. The next development prefix is1.0.3. EXE/ZIP publication comes
|
||||
first; MSI is tracked for the following release in issue36. OpenSSF15417 remains
|
||||
70%, not passing. Preserve original owner previews/profiles and unrelated work.
|
||||
The owner explicitly authorized finishing releases without further human-review
|
||||
requests. Record the release-specific waiver truthfully; restore the original
|
||||
manual-review requirement immediately after each authorized merge. The next
|
||||
development prefix is1.0.4. OpenSSF15417 remains70%, not passing. Preserve owner
|
||||
previews/profiles and unrelated worktrees. Track actual publication/proof in
|
||||
[release checklist](https://github.com/KofTwentyTwo/MarkdownViewr.Windows/issues/33).
|
||||
|
||||
## Historical release snapshots
|
||||
|
||||
|
||||
+6
-10
@@ -2,16 +2,12 @@
|
||||
|
||||
## Release and follow-up work
|
||||
|
||||
- [x] Merge reviewed PR37 at4c0ccc7; focused30/native111/published111/stable110/dev110 pass with no failures/skips.
|
||||
- [x] Merge personally approved signing-wrapper PR45 and metadata-only standards1.1.2 proposal; pin083d903 without changing NuGet mapping or Azure trust scopes.
|
||||
- [x] Preserve failed1.0.0 and1.0.1 stable/dev tags and prepare new1.0.2 candidates.
|
||||
- [x] Prepare direct download links, portable-update guidance and developer verification links.
|
||||
- [x] Set next development VersionPrefix to1.0.3.
|
||||
- [ ] Verify published standards1.1.2 bundle hashes/provenance and exact builder bytes.
|
||||
- [ ] Pass final exact-head CI, record automated review and the maintainer's explicit release-specific review waiver, and merge normally.
|
||||
- [ ] Publish signed stable/dev EXE installers and portable ZIPs; verify exact shipped signatures, publisher, trust, timestamps, checksums, SBOMs and provenance (#33).
|
||||
- Deliver signed, installed-acceptance-tested MSI installers next release (#36).
|
||||
- Reduce repeated validation while preserving trust and complete acceptance (#38).
|
||||
- [x] Publish/verify signed stable1.0.2 and dev1.0.2-beta.1 EXE/ZIP releases (#33).
|
||||
- [x] Prepare exact-tree/provider-bound reuse of genuine source CI; boundary tests and real17-check source proof pass (#38).
|
||||
- [ ] Publish the native MSI builder, pin it and enable signed MSI in the caller (#36).
|
||||
- [ ] Pass exact-head CI, record the existing release waiver, merge and restore all original branch checks.
|
||||
- [ ] Publish1.0.3 stable/dev MSI+EXE+ZIP; verify actual MSI install/launch/uninstall and all final payload signatures, hashes, SBOMs and provenance.
|
||||
- [x] Set next development prefix to1.0.4 and prepare user download links.
|
||||
- Complete remaining OpenSSF evidence; saved70% is not a passing badge (#31).
|
||||
|
||||
## Prior release checklist
|
||||
|
||||
@@ -426,3 +426,7 @@ actual parsers. Independent properties detect unsafe acceptance as well as crash
|
||||
Synthetic inputs never execute scripts, start native processes or touch real profiles.
|
||||
The record in [assurance.json](assurance.json) does not claim coverage-guided
|
||||
instrumentation, Best Practices enrollment or verified production signatures.
|
||||
|
||||
## Release validation reuse
|
||||
|
||||
Release tags must be on main and have a verified commit signature. Reused source CI must belong to the genuinely merged main PR, match the entire tagged Git tree and have successful latest required technical checks from the configured provider identities. Missing, stale, failed or pending proof fails publication. This avoids repeating identical UI tests; it does not trust arbitrary PR artifacts or remove branch checks. Fresh build/coverage, Azure signing, MSI installation and exact distributed-payload verification remain release gates.
|
||||
@@ -75,3 +75,7 @@ Documents are rendered locally. Authored scripts and remote images are blocked;
|
||||
- **The app does not start:** confirm Microsoft Edge WebView2 Runtime is installed and inspect the activity logs if available.
|
||||
|
||||
For help, use [Discussions](https://github.com/KofTwentyTwo/MarkdownViewr.Windows/discussions). Report reproducible problems through [Issues](https://github.com/KofTwentyTwo/MarkdownViewr.Windows/issues/new/choose), including **Help → About** version, Windows version, and relevant sanitized logs. Report vulnerabilities privately using [SECURITY.md](../SECURITY.md).
|
||||
|
||||
## MSI installation
|
||||
|
||||
The MSI installs for the current Windows user, alongside the EXE and portable download options. Stable and dev have separate package identities. Use the same installer format for an existing edition; install the other channel separately to try previews. Uninstall an MSI installation through Windows Settings → Apps → Installed apps. The installer registers supported Markdown types and preserves your existing default choices.
|
||||
+1
-1
@@ -7,7 +7,7 @@
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "eslint .github/scripts eslint.config.mjs --max-warnings 0",
|
||||
"test": "node --test .github/scripts/review-gate.test.cjs"
|
||||
"test": "node --test .github/scripts/review-gate.test.cjs .github/scripts/assert-release-source.test.cjs"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@eslint/js": "10.0.1",
|
||||
|
||||
@@ -150,6 +150,12 @@ public partial class AppTests
|
||||
CloseSettings(session);
|
||||
InvokeDocumentMenu(session, sibling, "SourceToggle", menuName: "View");
|
||||
AssertSavedPrintingDefaults(session, options);
|
||||
session.WaitFor(() =>
|
||||
{
|
||||
string settings = session.ReadSettings();
|
||||
using var saved = JsonDocument.Parse(settings);
|
||||
return saved.RootElement.GetProperty("ShowSource").GetBoolean() ? sibling : null;
|
||||
}, "the sibling source toggle to finish saving before the print-cancel snapshot");
|
||||
string savedSettings = session.ReadSettings();
|
||||
InvokeDocumentMenu(session, primary, "PrintMenuItem");
|
||||
AssertPrintOptionControls(session, options);
|
||||
|
||||
Reference in new issue
Block a user