diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 6fbb238..f4062ac 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -57,11 +57,6 @@ jobs: if ($blockingAlerts.Count) { throw "Unresolved serious code-scanning findings on main: $($blockingAlerts -join ', ')" } gh run download $runs['verify.yml'] --name "AppSwitcher-$commit" --dir artifacts/release-upload ./scripts/check-release-artifacts.ps1 -Path artifacts/release-upload -Version $version -Commit $commit - if ($version -notmatch '-') { - foreach ($installer in Get-ChildItem artifacts/release-upload -Filter '*-setup.exe') { - if ((Get-AuthenticodeSignature -LiteralPath $installer.FullName).Status -ne 'Valid') { throw 'Stable installer lacks a valid Windows publisher signature.' } - } - } foreach ($asset in Get-ChildItem artifacts/release-upload -File) { gh attestation verify $asset.FullName --repo $env:GITHUB_REPOSITORY --signer-workflow "$env:GITHUB_REPOSITORY/.github/workflows/verify.yml" --source-ref refs/heads/main --source-digest $commit --deny-self-hosted-runners } diff --git a/CHANGELOG.md b/CHANGELOG.md index 7f5e7b8..c366d2d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Changelog +## 1.2.0 — 2026-11-03 + +- Support unsigned stable Windows releases under the owner's zero-cost distribution policy. Remove mandatory publisher signing from stable builds and acceptance records; retain workstation and offline-browser acceptance, exact-commit verification, security scans, checksums, manifests, SPDX inventories, attestations and immutable release assets. Optional installer signatures must still be valid. +- Update release and installation guidance to disclose unsigned stable distribution. The existing Mozilla-signed Firefox payload is unchanged. +- Scope acceptance to the intended workstations with no audio output: make actual Narrator speech testing optional while retaining keyboard navigation, visible focus/errors/status, high contrast, touch usability and automated accessibility checks. +- Record offline VM qualification of the published RC, including actual installer upgrade/rollback/uninstall, released browser-bridge recovery, and persistent Chrome/Edge installations with two unattended offline restarts each. Keep their manual folder-picker walkthrough and target-workstation acceptance open. Update installer wording for beta, RC and stable packages. +- Fix the browser test helper's disconnect handling so it consumes POST bodies before replying and clears stale connections before a reconnect check; add a regression that fails against the old helper. Released application and extension behavior is unchanged. + +This first **unsigned Windows x64 stable release** includes the RC's session-timeout, operator-scope, scheduler, lifecycle, window-restoration and audit-log fixes, plus explicit Save/discard behavior and streamlined application/browser setup. The Mozilla-signed offline Firefox bridge remains byte-for-byte 1.1.0, and configuration remains schema 12. Roll back with the previous binary and its pre-upgrade configuration backup. + +The owner authorized publication with [documented validation limits](https://github.com/Sparkz400/AppSwitcher/blob/main/docs/RELEASE_ACCEPTANCE_1.2.0.md): physical workstation/full-shift acceptance and the Chrome/Edge manual folder-picker walkthrough remain unperformed. Persistent browser installation and unattended offline restarts passed. Site deployment still requires its applicable acceptance checks. Windows publisher signing, browser-store publication and Narrator speech are not required for the agreed zero-cost, silent-workstation scope. Windows may show an unknown-publisher warning; verify the downloaded checksums and build/release attestations. + ## 1.2.0-rc.1 — 2026-11-03 - Repair session timeout transitions, scheduler retries, lifecycle cancellation, manual minimization and stale HTTP health results. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 692ce34..347364f 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -13,7 +13,7 @@ Install Git, PowerShell 7, the SDK specified by `global.json` and Node.js 24. Fr ./scripts/build-release.ps1 ``` -The first command restores locked packages, builds, runs .NET/extension tests, renders native UI, and exercises isolated process/recovery behavior. It opens synthetic windows and checks that the real saved configuration is unchanged. The second builds the self-contained Windows x64 package, validates bundled Firefox source/signature payload correspondence, checks installer install/upgrade/rollback/uninstall, and emits the ZIP, setup EXE, submission materials and checksums under `artifacts/releases`. Candidates are unsigned unless publisher signing is configured; stable builds require signing as described in [Maintenance](MAINTENANCE.md). +The first command restores locked packages, builds, runs .NET/extension tests, renders native UI, and exercises isolated process/recovery behavior. It opens synthetic windows and checks that the real saved configuration is unchanged. The second builds the self-contained Windows x64 package, validates bundled Firefox source/signature payload correspondence, checks installer install/upgrade/rollback/uninstall, and emits the ZIP, setup EXE, submission materials and checksums under `artifacts/releases`. Beta, RC and stable builds are unsigned unless optional publisher signing is configured; no paid signing identity is required. See [Maintenance](MAINTENANCE.md). A build with uncommitted changes records `dirty: true` in its release manifest and must not be published as a release. Output directories are revision-specific and existing release artifacts are never overwritten. Build a new revision or use a fresh checkout when repeating a release build. Build-time package/compiler downloads need internet access; deployment does not. diff --git a/Directory.Build.props b/Directory.Build.props index 5cce7b1..c54321e 100644 --- a/Directory.Build.props +++ b/Directory.Build.props @@ -1,6 +1,6 @@ - 1.2.0-rc.1 + 1.2.0 $(DefaultItemExcludes);**\artifacts\**;**\output\**;**\obj_*\** true win-x64 diff --git a/MAINTENANCE.md b/MAINTENANCE.md index af2ab4a..d2b0cd4 100644 --- a/MAINTENANCE.md +++ b/MAINTENANCE.md @@ -1,14 +1,14 @@ # Maintenance and release policy -AppSwitcher is a community-maintained release candidate. There is no promised release schedule, response time, service-level agreement or machine certification. The repository is available for inspection, modification and independent builds under MIT. Report reproducible bugs with synthetic data; production deployments remain the operator's responsibility. +AppSwitcher is a community-maintained Windows application. There is no promised release schedule, response time, service-level agreement or machine certification. The repository is available for inspection, modification and independent builds under MIT. Report reproducible bugs with synthetic data; production deployments remain the operator's responsibility. ## Supported line -The latest `1.2.0-rc.*` and `main` receive fixes. Older betas are retained for controlled rollback, not maintained in parallel. Version numbers use `major.minor.patch` with a beta or RC suffix while interfaces and configuration behavior are still being qualified. An incompatible configuration or bridge protocol change must be called out in the changelog with migration and rollback instructions. +The `1.2` line and `main` receive fixes. Older betas are retained for controlled rollback, not maintained in parallel. Version numbers use `major.minor.patch`, with a beta or RC suffix for prereleases. Preparing a stable-version package does not bypass its publication acceptance gates. An incompatible configuration or bridge protocol change must be called out in the changelog with migration and rollback instructions. The release target is Windows 11 x64 with a self-contained .NET runtime. The installer permits compatible Windows 10 build 19041 or later, but installation compatibility is not an operating-system support commitment. Use a Windows edition still supported under your organization's Microsoft servicing arrangement. ARM64 emulation, Windows Server, remote desktop, specific kiosk editions, unusual display drivers and physical touch panels require separate acceptance. The desktop app is not supported on macOS or Linux. -The extension manifests declare Chrome/Edge 120+ and Firefox 142+ compatibility. Deploy a browser with current vendor security support and test the exact version/profile/policy combination. Manifest minimums are API floors, not a promise to support obsolete browsers. Store approval and code signing are separate distribution steps; this beta is unsigned and uses offline extension installation. +The extension manifests declare Chrome/Edge 120+ and Firefox 142+ compatibility. Deploy a browser with current vendor security support and test the exact version/profile/policy combination. Manifest minimums are API floors, not a promise to support obsolete browsers. AppSwitcher uses offline extension installation and supports unsigned Windows releases, including stable. The project requires no paid signing identity or paid store distribution. ## Before each release @@ -16,7 +16,7 @@ The extension manifests declare Chrome/Edge 120+ and Firefox 142+ compatibility. 2. Restore locked packages; review vulnerability results, dependency licenses and asset/source provenance. Review new dependencies before updating the inventory. 3. Run full Windows CI at the exact source commit and retain native captures, test reports and installer evidence. Re-run real browser/restart checks when browser integration changes. 4. For a deployment candidate, record a sustained-run result and complete [workstation acceptance](docs/WORKSTATION_ACCEPTANCE.md) for the target site. No unattended update is enabled. -5. Build from a clean tagged source revision. Verify the archive manifest, setup/ZIP checksums, unsigned status and bundled Firefox payload. Publish release notes with exact evidence boundaries. +5. Build from a clean tagged source revision. Verify the archive manifest, setup/ZIP checksums, actual signing status and bundled Firefox payload. Unsigned releases are supported; any optional publisher signature must be valid. Publish release notes with exact evidence boundaries and disclose the signing status. 6. Keep the previous release and a pre-update configuration backup for rollback. Publish a correction or replacement version if a release is faulty; never silently replace a published asset. Dependency update proposals are checked weekly by automation. That cadence is a check, not a promise that updates will be merged weekly. Security reports and user-visible regressions take priority over feature work. @@ -25,7 +25,7 @@ Dependency update proposals are checked weekly by automation. That cadence is a 1. Update `Directory.Build.props` and the matching version section in `CHANGELOG.md` in a normal pull request. Review dependency notices and complete the checks above. 2. Merge through the protected branch. Wait for **Windows verification** and **CodeQL** to succeed on the exact resulting `main` commit, not just the pull-request merge preview. -3. Create and push the matching `vX.Y.Z-beta.N`, `vX.Y.Z-rc.N`, or qualified `vX.Y.Z` tag at that commit. The **Publish verified release** workflow checks the version and main ancestry, refuses open critical/high-security or error-level code-scanning findings on `main`, then downloads the eight assets from that commit's successful Windows run. It verifies package/source manifests, checksums, the SPDX inventory, and signed build/SBOM attestations from the exact main revision. It creates a draft, checks uploaded asset digests, then publishes an immutable prerelease with links to the verification runs. +3. Create and push the matching `vX.Y.Z-beta.N`, `vX.Y.Z-rc.N`, or authorized `vX.Y.Z` tag at that commit. The **Publish verified release** workflow checks the version and main ancestry, refuses open critical/high-security or error-level code-scanning findings on `main`, then downloads the eight assets from that commit's successful Windows run. It verifies package/source manifests, checksums, the SPDX inventory, and signed build/SBOM attestations from the exact main revision. It creates a draft, checks uploaded asset digests, then publishes an immutable release with links to the verification runs; beta/RC tags remain prereleases. The workflow publishes the tested artifact without rebuilding it. Artifacts are retained for 30 days; if the artifact expired, rerun Windows verification for the exact commit before tagging. A tag pushed before checks finish fails publication; after resolving the cause, rerun the failed release workflow without moving the tag. Existing releases are never overwritten. A correction to a published release needs a new version. @@ -35,14 +35,18 @@ GitHub generates the immutable release attestation after publication. Verificati Keep repository immutable releases enabled and the active `v*` tag ruleset in place, with updates/deletion denied and creation allowed. Existing beta.1/beta.2 assets remain unchanged and do not gain immutable status retroactively. Main builds produce provenance for all eight assets and an SBOM attestation for the desktop ZIP/installer only; pull requests do not produce publication attestations. See [download verification and inventory scope](docs/VERIFY_DOWNLOADS.md). -Beta/RC artifacts remain unsigned prereleases. Stable tags additionally require reviewed evidence in `docs/release-readiness.json` (workstation, Narrator, offline browsers, publisher signing), an ancestor qualification revision without subsequent code/build changes, and a valid Authenticode signature on the exact installer. Stable publication becomes the latest release; prereleases do not. Missing evidence blocks stable publication. No certificate or signing service is bundled. +Beta, RC and stable artifacts may be unsigned. Stable tags additionally require reviewed records in `docs/release-readiness.json` (workstation, offline browsers) and an ancestor reviewed revision without subsequent code/build changes. A record must be passed or explicitly deferred under versioned owner authorization, with a reason and evidence report. Deferred checks require an exact list of their scope and `deploymentAccepted: false`; missing/pending records still block publication. The [1.2.0 decision](docs/RELEASE_ACCEPTANCE_1.2.0.md) records the owner's instruction to publish with disclosed deployment limits. Stable publication becomes the latest release; prereleases do not. Windows publisher signing is optional; no certificate purchase or paid signing service is required. Checksums, manifests, SPDX inventory, security scans, exact-commit CI, build/release attestations and immutable assets remain required. An optional installer publisher signature must be valid; invalid or untrusted signatures are rejected. + +The owner selected zero-cost, unsigned Windows distribution on 3 November 2026. Release notes and installation guidance must disclose that Windows may show an unknown-publisher or reputation warning. GitHub attestations verify build/release provenance; they do not provide a Windows publisher signature. The bundled Mozilla-signed Firefox bridge is separate and remains unchanged. + +The intended workstations have no audio output. Actual Narrator speech testing is optional follow-up work, outside this deployment's stable-release acceptance scope; it is not recorded as passed. Keyboard navigation, visible focus, visible errors/status, high contrast and touch/glove usability remain part of workstation acceptance. Existing automated accessible-name and UI Automation checks remain enabled. A deployment that needs spoken screen-reader output requires separate qualification of that flow. Schema 12 preserves explicit empty operator restrictions and migrates the old 40px launcher/menu defaults. Roll back using the old binary **and its pre-upgrade configuration backup**; older versions cannot safely interpret the new empty-restricted scope. Do not feed schema-12 configuration to a beta launcher. Encryption remains tied to its Windows account. Retain browser ownership journals/profiles for rollback; never regenerate identities to bypass recovery. Normal release preparation never changes main protection or invents a reviewer. The sole-maintainer PR policy remains in effect; add required second-person review when an actual reviewer is available. -## Publisher signing preparation +## Optional publisher signing An authorized signing runner may set `APPSWITCHER_SIGNING_CERTIFICATE_THUMBPRINT` to a code-signing certificate already provisioned in its current-user certificate store, and `APPSWITCHER_SIGNING_TIMESTAMP_SERVER` to the publisher-approved timestamp service. No private key or certificate is committed, exported or installed by these scripts. Only the four AppSwitcher-owned app/host EXE/DLL files and the distributable installer are signed, before manifests, hashes and attestations are generated. Third-party binaries and the Mozilla XPI are preserved. Test installers use a separate product identity and remain unsigned. -The helper uses [Windows Authenticode signing](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.security/set-authenticodesignature), checks code-signing usage, certificate dates/private-key presence, signature validity and timestamp presence. Without configured signing, RC builds explicitly remain unsigned and stable builds stop. Hosted CI has no signing credential configured by this change; the real signing path must be qualified by the publisher before marking its gate passed. +The helper uses [Windows Authenticode signing](https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.security/set-authenticodesignature), checks code-signing usage, certificate dates/private-key presence, signature validity and timestamp presence. Without configured signing, beta, RC and stable builds explicitly remain unsigned. Hosted CI has no signing credential configured. If signing is introduced later, qualify that path and update the disclosed signing status before publishing signed artifacts; this is not required for the zero-cost release. diff --git a/README.md b/README.md index 00fa5de..eb6b6fd 100644 --- a/README.md +++ b/README.md @@ -2,11 +2,11 @@ [![Windows verification](https://github.com/Sparkz400/AppSwitcher/actions/workflows/verify.yml/badge.svg?branch=main)](https://github.com/Sparkz400/AppSwitcher/actions/workflows/verify.yml) [![CodeQL](https://github.com/Sparkz400/AppSwitcher/actions/workflows/codeql.yml/badge.svg?branch=main)](https://github.com/Sparkz400/AppSwitcher/actions/workflows/codeql.yml) -[![Latest prerelease](https://img.shields.io/github/v/release/Sparkz400/AppSwitcher?include_prereleases&label=prerelease)](https://github.com/Sparkz400/AppSwitcher/releases) +[![Latest release](https://img.shields.io/github/v/release/Sparkz400/AppSwitcher)](https://github.com/Sparkz400/AppSwitcher/releases/latest) A native Windows launcher for operator and maintenance workstations. Keep assigned applications within reach, return to the same browser tab, inspect software health and restore window layouts from one compact interface. -[Download the latest prerelease](https://github.com/Sparkz400/AppSwitcher/releases) · [Installation and rollback](docs/INSTALLATION.md) · [Examples](examples/README.md) · [Get help](SUPPORT.md) · [Roadmap](ROADMAP.md) · [Contribute](CONTRIBUTING.md) +[Download the latest release](https://github.com/Sparkz400/AppSwitcher/releases/latest) · [Installation and rollback](docs/INSTALLATION.md) · [Examples](examples/README.md) · [Get help](SUPPORT.md) · [Roadmap](ROADMAP.md) · [Contribute](CONTRIBUTING.md) [View the candidate UI](docs/images/maintenance-light.png) · [Demo walkthrough](docs/DEMO.md) · [Keyboard reference](docs/KEYBOARD.md) · [Troubleshooting](docs/TROUBLESHOOTING.md) @@ -21,11 +21,11 @@ A native Windows launcher for operator and maintenance workstations. Keep assign - A first-run checklist, live needs-attention filter, backup/import previews and authenticated recovery of damaged configuration. - Stable Windows display identities, English/German, light/dark themes, Windows high contrast and keyboard-accessible controls. -The current source targets **1.2.0-rc.1**. Stable release still requires the [recorded acceptance gates](docs/release-readiness.json). It is not a machine safety controller. PIN access protects application controls and does not replace Windows account security. Site-specific kiosk, power-loss, touch and display behavior needs [workstation acceptance](docs/WORKSTATION_ACCEPTANCE.md). +Version **1.2.0** uses unsigned Windows distribution. Its [publication decision and verification limits](docs/RELEASE_ACCEPTANCE_1.2.0.md) record the completed software checks and deferred deployment checks. AppSwitcher is not a machine safety controller. PIN access protects application controls and does not replace Windows account security. Site-specific kiosk, power-loss, touch and display behavior needs [workstation acceptance](docs/WORKSTATION_ACCEPTANCE.md). ## Start in a few minutes -1. Download the `win-x64-…-setup.exe` or complete portable ZIP and its adjacent checksum from [Releases](https://github.com/Sparkz400/AppSwitcher/releases). Beta/RC packages are unsigned; follow the [download verification instructions](docs/INSTALLATION.md). +1. Download the `win-x64-…-setup.exe` or complete portable ZIP and its adjacent checksum from [Releases](https://github.com/Sparkz400/AppSwitcher/releases). Windows packages are unsigned, including stable releases; follow the [download verification instructions](docs/INSTALLATION.md). 2. Install as the intended Windows user, or extract the complete ZIP and open `app\AppSwitcher.exe`. The .NET runtime is included; no SDK or internet connection is needed on the target PC for installation. 3. Choose an administrator PIN and follow **Settings → Setup checklist**. Add an application, use **Add from an open window**, or import a [safe starter example](examples/README.md). 4. For websites, follow [browser setup](docs/BROWSER_INTEGRATION.md) and select the connected profile. The offline package includes unpacked Chrome/Edge extensions and the Mozilla-signed Firefox 1.1.0 extension. Browser session restoration must also be enabled in the chosen profile. diff --git a/ROADMAP.md b/ROADMAP.md index 6b53267..62cacd7 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -1,25 +1,25 @@ # Public roadmap -AppSwitcher is a usable unsigned beta. This roadmap distinguishes software maintenance from work that requires publisher accounts or real workstation access. Priorities are intentions, not delivery dates. Bugs and private security reports can change the order. +AppSwitcher 1.2.0 uses unsigned stable Windows distribution with no paid signing requirement. Its [publication decision](docs/RELEASE_ACCEPTANCE_1.2.0.md) records completed software verification and remaining deployment limits. This roadmap distinguishes software maintenance from work that requires real workstation access or optional publisher accounts. Priorities are intentions, not delivery dates. Bugs and private security reports can change the order. ## Current foundation - Versioned offline ZIP, per-user installer, checksums, SPDX inventory, dependency notices and rollback guidance. - Windows application, browser, native UI, recovery and installer checks. - Protected pull requests, C#/JavaScript CodeQL, secret scanning and push protection. -- Weekly grouped dependency proposals, signed build records and automated publication of immutable verified betas. +- Weekly grouped dependency proposals, signed build records and automated publication of immutable verified releases. - [Contribution guidance](CONTRIBUTING.md), [support](SUPPORT.md) and [community expectations](CODE_OF_CONDUCT.md). ## Next priorities -The [3 November release audit](docs/RELEASE_AUDIT_2026-11-03.md) found additional runtime and settings defects. Follow the [first stable release TODO](docs/RELEASE_TODO.md) for reproductions, acceptance criteria, UI simplification and release gates before treating the beta as stable. +The [3 November release audit](docs/RELEASE_AUDIT_2026-11-03.md) led to the runtime and settings fixes delivered in the release candidate and included in 1.2.0. The [release checklist](docs/RELEASE_TODO.md) retains the completed fixes and remaining deployment acceptance work. | Priority | Work | Completion evidence / dependency | | --- | --- | --- | -| High | [Representative workstation acceptance #9](https://github.com/Sparkz400/AppSwitcher/issues/9) | Target displays, touch/gloves, kiosk recovery, reboot/power interruption and sustained workload; requires a workstation owner. | -| High | [Narrator and keyboard validation #10](https://github.com/Sparkz400/AppSwitcher/issues/10) | Actual speech/focus results in English and German; interactive Windows testing welcome. | -| High | [Windows publisher signing #11](https://github.com/Sparkz400/AppSwitcher/issues/11) | Owner provisions signing credentials, then downloaded signatures and installer behavior are verified. | -| Normal | [Browser-store distribution #12](https://github.com/Sparkz400/AppSwitcher/issues/12) | Publisher accounts, approvals and restart/pairing checks on the actual installed store packages. | +| High | [Representative workstation acceptance #9](https://github.com/Sparkz400/AppSwitcher/issues/9) | Target displays, keyboard/visible focus, visual feedback without sound, touch/gloves, kiosk recovery, reboot/power interruption and sustained workload; requires a workstation owner. | +| Optional | [Narrator speech validation #10](https://github.com/Sparkz400/AppSwitcher/issues/10) | Spoken English/German screen-reader results remain unverified. Final workstations have no audio output, so speech testing is outside this release scope; keyboard checks remain required under workstation acceptance. | +| Optional | [Windows publisher signing #11](https://github.com/Sparkz400/AppSwitcher/issues/11) | Deferred under the zero-cost release policy; unsigned stable releases are supported. No certificate purchase is required. | +| Optional | [Browser-store distribution #12](https://github.com/Sparkz400/AppSwitcher/issues/12) | Publisher accounts, approvals and restart/pairing checks on the actual installed store packages; paid store distribution is not required for release. | CI does not close hardware acceptance. Prepared store files do not mean store approval, and checksums do not mean publisher signing. Follow each issue for its current state; report a concrete defect separately instead of expanding a broad checklist indefinitely. diff --git a/docs/INSTALLATION.md b/docs/INSTALLATION.md index 01109d1..d245e5b 100644 --- a/docs/INSTALLATION.md +++ b/docs/INSTALLATION.md @@ -1,8 +1,8 @@ -# Unsigned beta: install, update and roll back +# Install, update and roll back Use a versioned release from the project's GitHub Releases page. The setup executable and portable ZIP contain the application, .NET Windows runtime, native browser bridge, offline extensions, guides and examples. The target PC needs no SDK, publisher account or internet connection for installation. A browser must already be installed; websites retain their own connectivity requirements. -This beta is **unsigned**, as selected for the first public release. Windows may display an unknown-publisher or reputation warning. Check the release source and compare the downloaded file with its adjacent SHA-256 file (`Get-FileHash -Algorithm SHA256 `). A matching checksum detects a changed download; it does not replace publisher authentication. Follow your organization's software approval policy. Beta.3 and later also provide [signed build/release records and a dependency inventory](VERIFY_DOWNLOADS.md). For common warnings, connection and display problems, see [Troubleshooting](TROUBLESHOOTING.md). +AppSwitcher Windows releases are **unsigned**, including stable releases, under the project's zero-cost distribution policy. Windows may display an unknown-publisher or reputation warning. Check the release source and compare the downloaded file with its adjacent SHA-256 file (`Get-FileHash -Algorithm SHA256 `). A matching checksum detects a changed download; it does not replace publisher authentication. Follow your organization's software approval policy. Beta.3 and later also provide [signed build/release records and a dependency inventory](VERIFY_DOWNLOADS.md). For common warnings, connection and display problems, see [Troubleshooting](TROUBLESHOOTING.md). ## Install diff --git a/docs/KEYBOARD.md b/docs/KEYBOARD.md index 0b573c8..47f0a14 100644 --- a/docs/KEYBOARD.md +++ b/docs/KEYBOARD.md @@ -18,4 +18,4 @@ The global shortcut is optional and off by default. Enable it in the settings se To close settings, use its **Close** button. Settings preview live. Use **Save changes** to persist them. Closing with pending edits asks whether to discard; cancelling keeps the editor open. Session expiry discards unsaved changes and closes privileged dialogs. Pinning/position updates made during editing join the preview and cannot save unrelated edits. Do not assume **Escape** dismisses every dialog or discards changes. A button marked as a dialog's default action may also accept **Enter**; use the explicit actions above when navigating the launcher or search. -The reference follows the application's key handlers and a visible English Windows walkthrough with synthetic apps. It is not a complete Narrator or workstation accessibility qualification; that remains tracked in [issue #10](https://github.com/Sparkz400/AppSwitcher/issues/10). Report a specific focus or keyboard problem through [Getting help](../SUPPORT.md), including the screen, focused control and exact key sequence. +The reference follows the application's key handlers and a visible English Windows walkthrough with synthetic apps. Full keyboard, visible-focus and visual-feedback qualification remains part of [workstation acceptance](WORKSTATION_ACCEPTANCE.md). The final workstations have no audio output; actual Narrator speech testing [issue #10](https://github.com/Sparkz400/AppSwitcher/issues/10) is optional follow-up work and remains unverified. Report a specific focus or keyboard problem through [Getting help](../SUPPORT.md), including the screen, focused control and exact key sequence. diff --git a/docs/OPERATIONS.md b/docs/OPERATIONS.md index 11c285b..ce9b5e0 100644 --- a/docs/OPERATIONS.md +++ b/docs/OPERATIONS.md @@ -56,7 +56,7 @@ AppSwitcher follows Windows high-contrast mode at startup and when the mode chan Use Tab/Shift+Tab to move through controls, Space/Enter for actions and checkboxes, and Escape to leave popups/dialogs. PIN input supports digits and Backspace without exposing the entered digits in its display. Settings search opens and focuses its target control. Native UI checks verify accessible names/control patterns, keyboard operations and 44-pixel action targets in both languages and compact layouts. -Before deploying to an operator panel, test with that user's Narrator configuration and required input hardware: launcher discovery, search and selection announcements, PIN errors, save/restore confirmation, session expiry, scrolling, and all kiosk exit routes. Repeat with the intended gloves and panel scaling. Automated UI Automation checks and scaled renders do not certify speech output, physical touch accuracy or machine operation. +Before deploying to an operator panel, test keyboard operation and visible feedback: launcher discovery, search and selection, PIN errors, save/restore confirmation, session expiry, scrolling, and all kiosk exit routes. Repeat with the intended gloves and panel scaling. The target workstations have no audio output; Narrator speech is optional follow-up work, not a release gate. Automated UI Automation checks and scaled renders do not certify physical touch accuracy or machine operation. ## Timed maintenance diff --git a/docs/RELEASE_ACCEPTANCE_1.2.0.md b/docs/RELEASE_ACCEPTANCE_1.2.0.md new file mode 100644 index 0000000..6871bb2 --- /dev/null +++ b/docs/RELEASE_ACCEPTANCE_1.2.0.md @@ -0,0 +1,26 @@ +# 1.2.0 publication decision and verification scope + +On 3 November 2026, the repository owner requested that the prepared unsigned 1.2.0 package be put fully on GitHub and published, after receiving the completed local verification results and the remaining acceptance limits. This records authorization to publish the open-source software with those limits disclosed. It is not approval to deploy an untested workstation, and unperformed checks are not recorded as passed. + +## Completed software evidence + +The clean local package at `9f604edb203771c1a05fef80b17670b6b8bac954` passed 273 application tests, 32 browser/helper tests, 10,089 native UI assertions with zero binding warnings, 15 bundled-setup assertions, process/recovery checks and 23 installer lifecycle checks. The dependency audit reported no vulnerable dependencies across the four projects. All eight release assets, all 894 payload files, source manifests, checksums and the SPDX inventory were verified. AppSwitcher-owned Windows binaries and the installer were unsigned; the Mozilla-signed Firefox 1.1.0 payload was unchanged. + +The exact local 1.2.0 installer also passed 19 offline VM checks as a standard user, upgrading the published RC to 1.2.0. All installed payload hashes matched; configuration bytes and three existing browser-host registrations/files were preserved. The VM network was restored and test processes were closed. Commands and test drivers were used, without Computer Use tools. + +The [earlier published-RC VM qualification](VALIDATION.md#offline-windows-vm-qualification--3-november-2026) also covers permanent signed Firefox installation/restart, Chrome/Edge persistent installation and two unattended offline restarts each, native reconnect, unrelated-tab preservation, copied-profile conflicts, installer rollback and a 30-minute synthetic sustained run. These records identify the revisions actually tested; they are not physical workstation acceptance or a full-shift result. + +The publication preparation changes release policy, tests and documentation only; application, native-host and extension implementation remains the tested 1.2.0 source. The public assets are built and checked again by GitHub Windows verification at the final main revision. The release notes link that exact run and CodeQL run. Local package hashes are not substituted for the GitHub-built asset hashes. + +## Deferred checks and deployment limits + +| Record | Status for publication | Remaining work | +| --- | --- | --- | +| Workstation | Deferred by the owner's publication instruction | Actual monitor/DPI/docking combinations, touch/gloves where used, keyboard and visible feedback, live contrast changes, kiosk escape, reboot/autostart and power-loss recovery, cold startup and representative full-shift workload on the intended workstation. | +| Offline browsers | Deferred only for the unperformed installation walkthrough and site-specific deployment | Chrome/Edge native folder-picker/manual installation walkthrough. Persistent unpacked installation state and unattended restarts passed through command-driven browser APIs. Qualify the exact deployed browser/profile/policy combination at the site. | + +The [workstation acceptance record](WORKSTATION_ACCEPTANCE.md) remains unaccepted until its applicable checks are actually performed and recorded. The final workstations have no audio output; Narrator speech is outside their acceptance scope. Windows publisher signing and browser-store publication are optional under the owner's zero-cost distribution decision. + +## Publication controls retained + +Publishing requires protected-branch integration, successful Windows verification and CodeQL at the exact main commit, no blocking serious scanner findings, verified source/package manifests and checksums, SPDX inventory, build/SBOM attestations, matching server asset digests and immutable release verification. The workflow publishes the already tested CI assets. Existing releases are not replaced. The versioned authorization in `release-readiness.json` permits only the explicitly listed deferred deployment checks; it cannot satisfy or skip these CI/security/integrity controls. diff --git a/docs/RELEASE_RESOLUTION_2026-11-03.md b/docs/RELEASE_RESOLUTION_2026-11-03.md index acc7ff0..4934c59 100644 --- a/docs/RELEASE_RESOLUTION_2026-11-03.md +++ b/docs/RELEASE_RESOLUTION_2026-11-03.md @@ -42,20 +42,20 @@ History retention: the current audit/crash CSV plus `.1`–`.5` archives are kep - Chrome and Edge each passed 20 minimize/activate cycles, preserved the document/unsaved value and unrelated same-URL tab, and recovered after host disconnect. Firefox 159 passed 20 cycles, session identity recovery and unrelated-tab preservation. Bundled Chromium 156 and Firefox Developer Edition 160 passed full close/reopen recovery. These use isolated test profiles/extension copies, not a fresh offline production-package installation. - The isolated real-app churn run passed **605.46 seconds**, **59 samples** and **one abrupt launcher restart**, preserving the synthetic managed process. Tested AppSwitcher assembly SHA-256: `44F6A757B16889C0014CA0AAB82B764C69DDE50C6EC23744BD31D17112222F77`. It includes test dependencies and predates final UI/build-script changes; current-commit CI validates the final source. This is not a full-shift or production-EXE startup measurement. - All isolated check registrations were removed; normal browser profiles/registrations were preserved. The user's encrypted saved configuration hash stayed unchanged. -- Stable artifact validation accepts the stable version format and still rejects 26 malformed inventories/packages. Readiness tests reject six incomplete/invalid qualifications. Signing adapter tests cover unsigned candidates, a synthetic configured signer and five rejected signing failures; they do not establish a real publisher signature. +- Stable artifact validation accepts unsigned or validly signed installers and rejects 28 malformed inventories/packages or invalid signatures. Readiness tests accept qualified unsigned silent-workstation evidence and reject eight incomplete/invalid qualifications. Signing adapter tests cover unsigned candidates, a synthetic configured signer and five rejected signing failures; they do not establish a real publisher signature. ## Release and repository contract Windows 11 x64 is the release target; the installer retains its existing compatible Windows 10 build 19041+ allowance, without asserting vendor support or site qualification. Chromium extension API minimum is 120; signed Firefox minimum is 142. Qualify the exact deployed browser versions, especially on offline machines. Desktop runtime is bundled. Chrome/Edge unpacked setup and Mozilla-signed Firefox 1.1.0 remain the offline distribution routes; store accounts are optional. -Beta, RC and stable tags use the same exact-main-CI artifact, manifest/checksum/SPDX, scanner and build-attestation gates. Stable additionally requires reviewed qualification records and a valid installer publisher signature. Signing is an optional configured build adapter; credentials are neither supplied nor installed by the repository. See [Maintenance](../MAINTENANCE.md). +Beta, RC and stable tags use the same exact-main-CI artifact, manifest/checksum/SPDX, scanner and build-attestation gates. Stable additionally requires reviewed qualification records. Following the owner's zero-cost distribution decision on 3 November 2026, unsigned stable releases are supported; Windows publisher signing is optional, not a release gate. Any optional installer signature must be valid. Credentials are neither supplied nor installed by the repository. See [Maintenance](../MAINTENANCE.md). Use `scripts/verify.ps1 -Fast` for the build/unit/browser/package-gate layer; the default command and required Windows CI retain the complete UI/process/recovery checks. The manual **Isolated sustained performance** workflow offers editor and 1–45 minute synthetic churn runs. Branch/tag protection is retained. No invented second reviewer or unstaffed approval rule is added. Legacy standalone host scripts stop safely and direct users to the maintained UI/installer paths. Rollback requires the prior binary **and pre-upgrade configuration backup**. Older binaries cannot represent an empty restricted scope safely. Preserve browser profiles/ownership journals; never reset them merely to force a new tab. -## Still required for stable +## Publication decision and remaining deployment acceptance -The tracked [readiness record](release-readiness.json) remains pending for actual target-workstation/offline deployment acceptance, real Narrator speech, distribution-specific Chrome/Edge/signed-Firefox restart tests, and publisher signing. Full-shift workload, touch/gloves, physical docking/mixed DPI, controlled reboot/power interruption and kiosk escape must be recorded on the intended spare/commissioning workstation. Automated tests and short synthetic runs do not close these gates. +The [offline Windows VM qualification](VALIDATION.md#offline-windows-vm-qualification--3-november-2026) covers published installer installation/upgrade/rollback/uninstall, installed-EXE startup, signed Firefox restart and released bridge recovery. Chrome/Edge persistent unpacked installations passed two unattended offline restarts each without extension-loading commands or unsafe extension flags, including published desktop-bridge restart and unrelated-tab preservation. Installation used browser APIs after a one-time test bootstrap; their native folder-picker/manual installation walkthrough remains untested. The owner subsequently [authorized public 1.2.0 publication with these deployment limits disclosed](RELEASE_ACCEPTANCE_1.2.0.md); the [readiness record](release-readiness.json) records explicit deferral without claiming passed site acceptance. Exact-main CI, scanners and artifact/attestation validation remain required. The final workstations have no audio output, so actual Narrator speech testing is optional and unverified. Windows publisher signing is optional under the zero-cost decision. Keyboard/visual feedback, high contrast, touch/gloves, physical docking/mixed DPI, reboot/power interruption, kiosk escape and representative full-shift operation still require the intended workstation's acceptance record. Useful follow-ups, not prerequisites to this candidate: a full workspace membership/monitor editor, stable-ID dependencies/scopes, transactional operational settings, portable configuration bundle/preview, richer commissioning and schedule preview. Current reference repair and capture/restore remain supported. Track these separately rather than adding a cloud account, updater, store requirement or Rust component to the offline switcher. diff --git a/docs/RELEASE_TODO.md b/docs/RELEASE_TODO.md index 22c44df..0df4c6e 100644 --- a/docs/RELEASE_TODO.md +++ b/docs/RELEASE_TODO.md @@ -1,6 +1,6 @@ # First stable release TODO -Based on the [3 November 2026 audit](RELEASE_AUDIT_2026-11-03.md) of `4519900a`. The implementation now targets `1.2.0-rc.1`, followed by `1.2.0` once deployment gates pass. +Based on the [3 November 2026 audit](RELEASE_AUDIT_2026-11-03.md) of `4519900a`. The published RC and unsigned 1.2.0 package completed the [recorded software/VM checks](VALIDATION.md). The owner [authorized public 1.2.0 publication with disclosed deployment limits](RELEASE_ACCEPTANCE_1.2.0.md). Unchecked deployment rows below remain unperformed; exact-main CI/security/integrity checks remain mandatory for publication. **Release recommendation: fix the confirmed behavior defects, simplify the existing UI, then qualify the exact offline package.** A major feature expansion or language rewrite should not delay these fixes. Implementation status is recorded below and in [the resolution report](RELEASE_RESOLUTION_2026-11-03.md). The historical audit describes the original revision, not the fixed candidate. IDs B01–B12 refer to reproducible findings in the audit. @@ -21,13 +21,13 @@ Based on the [3 November 2026 audit](RELEASE_AUDIT_2026-11-03.md) of `4519900a`. For each fix, add a regression test that fails on the audited revision and asserts the intended behavior. The local audit probes demonstrate current defects; do not turn their observed wrong results into passing expectations. -## Release gates +## Release records and remaining deployment acceptance -- [x] **Choose and document the stable contract.** Set the actual version, Windows editions/architectures, supported browser versions, upgrade/rollback path and support expectations. Decide whether any unsigned stable distribution is acceptable; publisher signing remains [#11](https://github.com/Sparkz400/AppSwitcher/issues/11), and GitHub attestations are not Windows publisher signing. +- [x] **Choose and document the stable contract.** Set the actual version, Windows editions/architectures, supported browser versions, upgrade/rollback path and support expectations. The owner selected zero-cost unsigned stable distribution on 3 November 2026; Windows publisher signing [#11](https://github.com/Sparkz400/AppSwitcher/issues/11) is optional and deferred, not a release gate. Disclose unsigned status; GitHub attestations are not Windows publisher signing. - [x] **Add stable publication to the release workflow.** Support the chosen stable tag/version and release notes without weakening exact-commit verification, scanner gates, checksums, SBOM/build attestations, draft inventory checks or immutable assets. Acceptance: a rehearsal rejects wrong commits, incomplete drafts and modified assets; stable publication is intentional. -- [ ] **Qualify actual offline browser packages.** On a disposable standalone/workgroup PC, install Chrome/Edge unpacked bridges and the signed Firefox XPI from the release. Test connection, minimize/restore, unrelated same-URL tabs, redirects, browser/AppSwitcher restart, disconnected host, copied-profile conflicts and explicit close/recreate. Acceptance: retain the owned tab when identity survives; otherwise fail clearly without choosing an unrelated tab or silently duplicating it. Keep session-restore setup distinct from a completed restart test. Store publication is optional. +- [ ] **Qualify actual offline browser packages.** On a disposable standalone/workgroup PC, install Chrome/Edge unpacked bridges and the signed Firefox XPI from the release. Test connection, minimize/restore, unrelated same-URL tabs, redirects, browser/AppSwitcher restart, disconnected host, copied-profile conflicts and explicit close/recreate. Acceptance: retain the owned tab when identity survives; otherwise fail clearly without choosing an unrelated tab or silently duplicating it. Keep session-restore setup distinct from a completed restart test. Store publication is optional. [Offline VM results](VALIDATION.md#offline-windows-vm-qualification--3-november-2026) now cover signed Firefox and all three released bridges' ownership/recovery behavior. Chrome/Edge persistent unpacked installations also passed two unattended offline restarts each without a test loader, including published desktop-bridge restart. Their native folder-picker/manual installation walkthrough remains open; command-driven installation is documented separately from that UI flow. - [ ] **Complete target-workstation acceptance [#9](https://github.com/Sparkz400/AppSwitcher/issues/9).** Use [WORKSTATION_ACCEPTANCE.md](WORKSTATION_ACCEPTANCE.md): offline install, real mixed-DPI docking, touch/gloves, controlled reboot/power interruption, kiosk escape and representative full-shift workload. Acceptance: signed-off results for the exact release and supported deployment scope; unperformed rows stay open. -- [ ] **Complete actual Narrator/keyboard acceptance [#10](https://github.com/Sparkz400/AppSwitcher/issues/10).** EN/DE, launcher/search, PIN failure, session expiry, recovery, custom scrollbars, high contrast and dialogs. Acceptance: useful spoken names/errors, predictable focus and a keyboard path for every action. +- [ ] **Complete keyboard and visual-feedback acceptance as part of workstation qualification.** EN/DE, launcher/search, PIN failure, session expiry, recovery, custom scrollbars, high contrast and dialogs. Acceptance: visible names/errors/status, predictable visible focus and a keyboard path for every action, all usable without sound. The final workstations have no audio output; actual Narrator speech testing [#10](https://github.com/Sparkz400/AppSwitcher/issues/10) is optional follow-up work, not a stable-release gate or a passed check. Keep automated accessible-name/UI Automation checks enabled. - [x] **Run final candidate verification after fixes.** RC `0472a982` passed locked restore/build, .NET/Node/WPF/process/recovery suites, signed Firefox package check, full installer upgrade/rollback/uninstall, release-manifest validation and exact-main CI. [Validation](VALIDATION.md) records its actual results. Repeat qualification for application/build changes before stable; deployment acceptance above remains open. ## UI improvements worth doing before stable @@ -45,7 +45,7 @@ For each fix, add a regression test that fails on the audited revision and asser - [x] **Fix audit-log scaling first (B12).** The audit's 100,000-row synthetic history allocated 42 MiB just to request 200 rows. Set a bounded-allocation/read-time test and avoid synchronous whole-log work on the UI thread. - [x] **Benchmark large editors before optimizing them.** Test 10/100/500 configured entries while typing, dragging sliders, filtering, reordering and opening/closing settings. Measure frame/input stalls, allocations and private-memory recovery. Replace repeated full snapshots with debounced/incremental dirty tracking only where it helps; preserve Undo and save-race correctness. -- [ ] **Measure the actual deployment package at startup and over a shift.** Record cold/warm startup separately, launcher-only and browser-host totals separately, and private memory separately from working set. Include disconnected browsers, unhealthy endpoints and recovery churn. Establish thresholds from the target panel rather than this development machine. +- [ ] **Measure the actual deployment package at startup and over a shift.** Record cold/warm startup separately, launcher-only and browser-host totals separately, and private memory separately from working set. Include disconnected browsers, unhealthy endpoints and recovery churn. Establish thresholds from the target panel rather than this development machine. [VM startup measurements](VALIDATION.md#offline-windows-vm-qualification--3-november-2026) cover three installed-EXE launches; cold-cache and representative full-shift acceptance remain open. - [x] **Bound long-lived data and reduce unnecessary work.** Review audit/crash/recovery retention, retired host versions, per-app gates/snapshots and event subscriptions. Cleanup must preserve active host registrations and rollback/recovery evidence. Add repeated editor/setup open-close and app-add-remove scenarios before declaring a leak fixed. Keep C#/WPF. No current measurement demonstrates that Rust would improve the user-facing result enough to justify another runtime/interop boundary. Reconsider a narrow native component only after profiling identifies a specific hotspot that cannot be addressed well in the current implementation. diff --git a/docs/VALIDATION.md b/docs/VALIDATION.md index 00dc0dd..2334dce 100644 --- a/docs/VALIDATION.md +++ b/docs/VALIDATION.md @@ -1,5 +1,13 @@ # Validation +## 1.2.0 unsigned package and publication decision — 3 November 2026 + +The clean local package at `9f604edb203771c1a05fef80b17670b6b8bac954` passed 273 application tests, 32 browser/helper tests, 10,089 native UI assertions with zero binding warnings, process/recovery checks, 15 bundled-setup assertions and 23 installer lifecycle checks. All 894 payload files, the eight release assets and the SPDX inventory passed validation. A four-project dependency audit reported no vulnerable dependencies. The exact unsigned installer passed 19 additional offline VM checks upgrading the published RC to 1.2.0 as a standard user, preserving configuration and all three existing browser-host registrations/files. The VM network was restored and no test processes remained. + +The local installer SHA-256 was `A6E36D1AE7FCDC41F598BBC8A8F82BDDC73AD399AF46EE513393B8EB3E54EA62`; the application assembly SHA-256 was `6085991B3D92D022E5730AC442643E0F7E059C1A0DE77000E99AD72FE9532960`. GitHub publication builds and verifies its own assets at the final main revision and publishes their matching hashes and attestations. These local hashes do not identify the later GitHub assets. + +The owner authorized public software release with the [remaining deployment limits disclosed](RELEASE_ACCEPTANCE_1.2.0.md). Those checks remain deferred, not passed. Exact-main Windows CI, CodeQL, security and asset/attestation verification remain publication requirements. This software release does not fill in an unperformed target-workstation acceptance record. + ## 1.2.0-rc.1 preparation — 3 November 2026 See [the release resolution and current evidence](RELEASE_RESOLUTION_2026-11-03.md) and [remaining gates](release-readiness.json). The records below are retained historical beta evidence, not substituted for validation of the changed candidate. @@ -8,6 +16,35 @@ The [published RC](https://github.com/Sparkz400/AppSwitcher/releases/tag/v1.2.0- The first publication run uploaded and published the immutable assets successfully, then failed because the newly generated release attestation was not available on its immediate verification attempt. A subsequent `gh release verify v1.2.0-rc.1` succeeded without modifying the tag or assets. Publication now has bounded absence retries and verification-only recovery; this is a workflow timing fix, not a replacement release. +## Offline Windows VM qualification — 3 November 2026 + +The published RC above was installed under a disposable standard Windows account on Windows 11 Pro x64 (build 26100.9457, two virtual processors, 12 GiB RAM). The VM's external network adapter was disconnected; its remaining simulation adapter had no default route. Tools and complete installers were staged through VMware shared folders. All checks used commands and test drivers, without desktop-control tools or audio testing. + +The installer SHA-256 is `103E85D30FFA783C7DE8E2471DB24461CC27BF6BB5AF4BD1E8F5BDB09543F10E`. The installed application assembly SHA-256 is `E995453D4FDE2AF9294D7A9EF11F5AF8C32EBAE29A548E193B273A657E21D9BF`. These identify the existing unsigned RC; they do not identify a new stable build. + +| Check | Observed result | Boundary | +| --- | --- | --- | +| Offline installation | Installer exited successfully; all 894 shipped payload files matched the release manifest; installed EXE launched | Standard account, external network disconnected; no target-panel hardware | +| Published installer lifecycle | 47 checks passed using published beta.3 and RC installers: running-app rejection, upgrade, rollback with the exact pre-upgrade configuration, uninstall and final reinstall | Actual EXEs migrated a synthetic German restricted scope from schema 11 to 12. Configuration bytes, user-added notes and three separately installed browser-host registrations survived uninstall. The original test-account configuration was restored. | +| Native interface and recovery | 10,084 native assertions, zero binding warnings; process switching, authenticated backup recovery and 15 bundled-setup checks passed. Repeated successfully after the sustained run. | RC-source test executable loading the exact published application assembly on the final pass; this count includes control enumeration and is not physical keyboard/touch acceptance | +| Installed EXE startup | Three visible-window times: 1,183.79 / 832.31 / 798.25 ms; median private memory approximately 23.0–23.1 MiB and working set approximately 84.4–84.5 MiB during each 15-second sample | Local/warm runs after installation, launcher only; not post-reboot cold-cache results, browser totals or a full-shift workload | +| Signed Firefox 157.0.1 | Released bridge 1.1.0 installed permanently with Mozilla signature state 2. Passed 20 minimize/activate cycles, native reconnect, browser restart, Firefox session-identity recovery and unrelated-tab preservation | Regular Firefox with an isolated profile and released native host; no signature-check bypass | +| Firefox against the released desktop bridge | Exact published application assembly passed redirects, 20 activations, abrupt bridge-driver process restart with ownership-journal recovery, permanent browser restart, copied-profile conflict rejection and explicit close/recreate | Exercises the actual `BrowserBridgeService` through a small test driver, not a launcher-button walkthrough or Windows reboot | +| Chrome 156.0.8078.80 and Edge 156.0.4314.58 | Both passed 20 minimize/activate cycles, document/unsaved-state preservation, unrelated same-URL tabs, redirects, native reconnect, restored tab identity and explicit close/recreate | This initial functional run loaded the released extension through DevTools each session; persistent restart was qualified separately below | +| Chrome/Edge against the released desktop bridge | Both also passed redirects, 20 activations, abrupt bridge-driver process restart with journal recovery, copied-profile conflict rejection without tab mutation, and explicit close/recreate preserving the unrelated tab | Same exact released application assembly as Firefox; the initial copied-profile checks used the session-only loader | +| Chrome/Edge persistent installation and offline restart | Both browsers saved the released bridge 1.1.0 as an enabled unpacked installation. Each passed a fresh start and two further complete browser restarts with automatic native connection, unchanged profile identity, activation of the existing managed tab, no duplicate and preservation of the unrelated same-URL tab. The second cycle also restarted the published desktop bridge and recovered its ownership journal. Explicit close/recreate passed. | All three acceptance launches per browser omitted extension-loading commands and unsafe extension flags. Session restoration was enabled through the normal browser preference. Isolated profiles, actual installed browser EXEs, external network disconnected; no Windows reboot or desktop interaction in this follow-up. | +| Sustained published-assembly host | Passed 1,802.67 seconds, 174 samples and five abrupt launcher-host restarts. The managed fixture retained one PID throughout. Sampled response p95 was 6.66 ms, maximum 499.19 ms; peak private memory was 136.54 MiB | Source-built test harness loading the exact published application assembly. Includes repeated settings open/close and concurrent browser checks early in the run. These are harness measurements, not standalone-EXE budgets, a leak verdict or full-shift acceptance. | +| Editor workload in the VM | At 10/100/500 entries, median edit+render was 80.55/82.02/97.45 ms and p95 was 82.73/96.21/236.73 ms. The 500-entry editor opened in 3.42 seconds. | Twenty combined name/size/search changes per size, actual WPF rendering with a harness render delay, exact published application assembly; no target-panel acceptance threshold asserted | +| Controlled Windows reboot | Fifteen checks passed: unchanged original configuration, all 894 payload hashes, three browser-host registrations/manifests/executables and removal of temporary runner registrations | External adapter remained disconnected. The signed-out test user's saved registry was read from a private copy. This does not establish post-login launcher/browser restoration, autostart, kiosk recovery or physical power-loss behavior. | + +The persistent Chrome/Edge installation used one initial `Extensions.loadUnpacked` call, followed by the browser's normal `developerPrivate.reload` API. The browser then saved the unpacked installation (location 4, creation flags 38), and was fully closed before acceptance began. Subsequent launches used only the isolated profile, local debugging port and first-run/default-browser prompt suppression; neither a loader nor `--restore-last-session` was used. The unmodified released extension files retained their hashes. Commands loaded the test user's Windows profile and ran the non-headless browser processes in session 0, so a desktop login or desktop-control tool was not required. **This qualifies persistent installation state and unattended browser restart; the native folder-picker/manual installation walkthrough remains untested.** It does not introduce a supported silent-install feature into AppSwitcher. + +Local raw reports and unsuccessful setup/driver attempts are retained under `artifacts/vm-qualification//results` and `artifacts/vm-browser-install/20261103`; they contain local account/profile details and are not publication artifacts. Only the completed passing scenarios above count as evidence. The synthetic browser broker's disconnect endpoint was corrected to consume POST bodies and clear stale connections; its regression fails against the old helper and passes with the fix. All 32 Node tests passed. This helper correction does not change the released application or extension. + +Cleanup completed: the VM's external adapter is back up, the temporary scheduled task/login runner and shared folder were removed, and no qualification application processes remain. The RC installation, test results and pre-test VM snapshot were retained. No release was published by this qualification run. + +These VM checks leave physical mixed-DPI/docking, touch/gloves, live contrast/keyboard acceptance, kiosk escape, power interruption and representative full-shift operation unperformed on the intended workstation. The deployment has no audio output, and Narrator speech is outside its required acceptance scope. The later [1.2.0 publication decision](RELEASE_ACCEPTANCE_1.2.0.md) explicitly defers the remaining deployment checks while retaining exact-main software verification and documenting the scope in the [release record](release-readiness.json). + # Historical beta software validation Recorded on 2026-11-02. [Public beta v1.2.0-beta.1](https://github.com/Sparkz400/AppSwitcher/releases/tag/v1.2.0-beta.1) was built at clean public commit `5ebac9b9dcc538349e3ec143375635cf031f316d`; its [Windows CI run](https://github.com/Sparkz400/AppSwitcher/actions/runs/41513508379) passed. The released artifacts remain tied to that tag; later documentation updates do not replace them. The evidence below covers the implemented beta features and distribution mechanism; it does not establish production workstation acceptance. @@ -41,4 +78,4 @@ These checks use isolated unpacked/temporary extension copies with a per-run ide ## Remaining workstation acceptance -Actual Narrator speech, intended touch panels/gloves, physical mixed-DPI hot-plug/docking, Windows kiosk policies/escape, reboot/power interruption, a disconnected target PC and a representative full-shift workload require the site record in [WORKSTATION_ACCEPTANCE.md](WORKSTATION_ACCEPTANCE.md). A procedure or simulated test is not recorded as a pass for those items. +Intended touch panels/gloves, physical mixed-DPI hot-plug/docking, Windows kiosk policies/escape, reboot/power interruption and a representative full-shift workload require the site record in [WORKSTATION_ACCEPTANCE.md](WORKSTATION_ACCEPTANCE.md). See the current VM results above for disconnected installation evidence. A procedure or simulated test is not recorded as target-workstation acceptance; Narrator speech is optional for the silent-workstation scope confirmed on 3 November 2026. diff --git a/docs/WORKSTATION_ACCEPTANCE.md b/docs/WORKSTATION_ACCEPTANCE.md index c731b7c..2c91db1 100644 --- a/docs/WORKSTATION_ACCEPTANCE.md +++ b/docs/WORKSTATION_ACCEPTANCE.md @@ -4,6 +4,8 @@ Complete this record on the intended spare/commissioning workstation before enab Record release filename/SHA-256/source commit, Windows edition/build, user/account policy, exact browser versions, display models/ports/scales, touch hardware/glove type, configured applications, tester/date and recovery owner. Use synthetic data until site authorization permits production inputs. +Deployment scope confirmed on 3 November 2026: the final workstations have no audio output. Narrator speech testing is outside this acceptance scope and remains optional, unverified follow-up work. Keyboard, visual feedback and touch checks below are still required; errors and recovery instructions must be usable without sound. Automated accessible-name/UI Automation checks remain enabled. + | Check | Procedure and required outcome | Result / evidence | | --- | --- | --- | | Install offline | Disconnect the test PC from external networks under site procedures; install the complete release and launch it without SDK/runtime downloads. Distinguish browser background traffic and website connectivity from application installation. | Not run | @@ -13,10 +15,12 @@ Record release filename/SHA-256/source commit, Windows edition/build, user/accou | Reboot/power loss | Use the site's approved controlled shutdown/reboot and power-interruption procedure on disposable test data. Check configuration/backups, browser journals, automatic launch and recovery. Process-kill automation is not evidence for this row. | Not run | | Browser restart/offline recovery | Pair each deployed browser/profile, manage a synthetic page, minimize/activate, restart with session restoration, temporarily disconnect the host/network, and reconnect. Retain the same owned tab; unrelated same-URL tabs must be untouched. Test the actual signed/offline or store package being deployed. | Not run | | Display change | Verify launcher, forced placement and saved layouts with the site's monitors, negative coordinates, changed primary display, mixed DPI, unplug/reconnect and docking. Missing displays must fall back safely without silently changing the saved identity. | Not run | -| Keyboard/screen reader | Navigate launcher/settings/PIN/recovery with keyboard only; inspect focus. Use actual Narrator speech to confirm names, states, errors and dialogs. Toggle Windows contrast themes live and restore the user's theme. | Not run | +| Keyboard and visual feedback | In English and German, navigate launcher/search/settings/PIN/recovery/dialogs and custom scrollbars with keyboard only; confirm a keyboard path for every action and predictable visible focus. Verify PIN errors, session expiry, states and recovery instructions are visible and usable without sound. Toggle Windows contrast themes live and restore the user's theme. | Not run | | Touch/gloves | Use the intended panel, scaling and gloves for launch, monitoring, PIN, scrolling, confirmation and escape. Record missed targets or accidental actions, and adjust supported sizing settings before retrying. | Not run | | Operator/admin boundaries | Test assigned app/group scopes, expired sessions, wrong-PIN delays, restore/import and privileged dialogs. Verify Windows user permissions separately from the application's PIN. | Not run | | Kiosk and independent escape | With a second administrator/recovery path available, verify the exact Windows edition's policies, reboot behavior, maintenance access and restoration of the normal shell. Do not enable restrictions on an active production PC during testing. | Not run | | Sustained operation | Run the representative site's workload for its agreed acceptance duration (a full shift at minimum is a useful starting point), including browser/application recovery. Record elapsed wall time, failures, responsiveness and memory/handles. A short synthetic run does not close this row. | Not run | -Decision: **not accepted until the responsible operator records the applicable results and approves the site deployment**. Track failures with a minimal reproduction and retest the corrected exact release. Code-signing and browser-store publication are outside the unsigned-beta/submission-files scope selected for this delivery. +The [offline VM results](VALIDATION.md#offline-windows-vm-qualification--3-november-2026) provide software evidence for the published RC. They do not fill in this site's unperformed hardware/workflow rows. + +Decision: **not accepted until the responsible operator records the applicable results and approves the site deployment**. Track failures with a minimal reproduction and retest the corrected exact release. Windows publisher signing and browser-store publication are optional under the zero-cost offline distribution policy, including stable releases. diff --git a/docs/release-readiness.json b/docs/release-readiness.json index d3c5480..bfcdddc 100644 --- a/docs/release-readiness.json +++ b/docs/release-readiness.json @@ -1,10 +1,26 @@ { "version": "1.2.0", - "qualifiedCommit": null, + "qualifiedCommit": "452f05a7725cbbf04c4953224d05500ba0b5ad38", + "publicationAuthorization": { + "status": "approved-with-limitations", + "version": "1.2.0", + "approvedBy": "Sparkz400, repository owner; explicit publication instruction on 2026-11-03", + "deploymentAccepted": false, + "deferredGates": ["workstation", "offlineBrowsers"], + "evidence": "docs/RELEASE_ACCEPTANCE_1.2.0.md" + }, "gates": { - "workstation": { "status": "pending", "reviewedBy": null, "evidence": null }, - "narrator": { "status": "pending", "reviewedBy": null, "evidence": null }, - "offlineBrowsers": { "status": "pending", "reviewedBy": null, "evidence": null }, - "publisherSigning": { "status": "pending", "reviewedBy": null, "evidence": null } + "workstation": { + "status": "deferred", + "reviewedBy": "Sparkz400, repository owner; publication with disclosed deployment limits", + "evidence": "docs/RELEASE_ACCEPTANCE_1.2.0.md", + "reason": "Target hardware, keyboard/visual feedback, kiosk, power interruption and representative full-shift acceptance remain unperformed; required before site deployment." + }, + "offlineBrowsers": { + "status": "deferred", + "reviewedBy": "Sparkz400, repository owner; publication with disclosed installation limits", + "evidence": "docs/RELEASE_ACCEPTANCE_1.2.0.md", + "reason": "Persistent offline installation and browser restart tests passed; Chrome/Edge native folder-picker/manual installation walkthrough and site-specific browser/profile/policy acceptance remain unperformed." + } } } diff --git a/installer/beta-installation.txt b/installer/beta-installation.txt index bffa8a5..19e212b 100644 --- a/installer/beta-installation.txt +++ b/installer/beta-installation.txt @@ -1,9 +1,9 @@ -AppSwitcher public beta +AppSwitcher installation -This Windows beta is unsigned. Obtain it from the project's official release, compare its published SHA-256 checksum, and follow your organization's software approval policy. A matching checksum checks file integrity; it does not establish publisher identity. +AppSwitcher supports unsigned Windows distribution. Check the signing status in the release notes; Windows may show an unknown-publisher warning for an unsigned package. Obtain it from the project's official release, compare its published SHA-256 checksum, and follow your organization's software approval policy. A matching checksum checks file integrity; it does not establish publisher identity. This installer works offline and installs only for the current Windows user. No separate .NET installation is required. Close AppSwitcher and its paired browser profiles before updating. Setup will not close managed applications, enable kiosk mode, install browser extensions or start the launcher automatically. Settings, logs and browser ownership data are kept separately under %LOCALAPPDATA%\AppSwitcher. They are preserved when upgrading or uninstalling. Before updating, export a configuration backup and retain the previous installer. For an offline rollback, close the launcher/browser profiles and run the previous installer into the same directory. If an earlier version cannot read your settings, restore a compatible configuration backup through the app's recovery flow. -The beta has automated Windows validation. Test your actual panel, display arrangement, browsers, touch/glove input and kiosk exit/recovery routes before deployment. AppSwitcher reports software status and is not a machine-safety system. +Release packages have automated Windows validation. Test your actual panel, display arrangement, browsers, touch/glove input and kiosk exit/recovery routes before deployment. AppSwitcher reports software status and is not a machine-safety system. diff --git a/scripts/browser-check-broker.mjs b/scripts/browser-check-broker.mjs index 74160ba..3ade5ac 100644 --- a/scripts/browser-check-broker.mjs +++ b/scripts/browser-check-broker.mjs @@ -35,10 +35,16 @@ const server = http.createServer(async (req, res) => { } if (req.headers.authorization !== token) { res.writeHead(403).end(); return; } try { - if (req.url === "/status") { res.end(JSON.stringify([...clients.values()].map(c => c.identity))); return; } - if (req.url === "/disconnect") { for (const c of clients.values()) c.socket.destroy(); res.end('{}'); return; } - if (req.url !== "/command") { res.writeHead(404).end(); return; } + if (!["/status", "/disconnect", "/command"].includes(req.url)) { res.writeHead(404).end(); return; } + // Drain POST bodies before replying so keep-alive clients do not see a reset. const chunks = []; for await (const chunk of req) chunks.push(chunk); + if (req.url === "/status") { res.end(JSON.stringify([...clients.values()].map(c => c.identity))); return; } + if (req.url === "/disconnect") { + const disconnected = [...clients.values()]; + clients.clear(); + res.end('{}', () => { for (const c of disconnected) c.socket.destroy(); }); + return; + } const { browser, request } = JSON.parse(Buffer.concat(chunks)); const client = clients.get(browser); if (!client) throw new Error("Browser not connected"); const id = randomUUID(); diff --git a/scripts/build-release.ps1 b/scripts/build-release.ps1 index 269cfb1..360c62f 100644 --- a/scripts/build-release.ps1 +++ b/scripts/build-release.ps1 @@ -7,7 +7,6 @@ $repoPath = Split-Path -Parent $PSScriptRoot Push-Location $repoPath try { $version = ([xml](Get-Content Directory.Build.props -Raw)).Project.PropertyGroup.Version - if ($version -notmatch '-' -and [string]::IsNullOrWhiteSpace($env:APPSWITCHER_SIGNING_CERTIFICATE_THUMBPRINT)) { throw 'Stable builds require configured Windows publisher signing.' } $commit = (git rev-parse HEAD).Trim() $dirty = [bool](git status --porcelain --untracked-files=normal) $name = "AppSwitcher-$version-win-x64-$($commit.Substring(0, 8))" diff --git a/scripts/check-release-artifacts.ps1 b/scripts/check-release-artifacts.ps1 index 9dc177d..8ccf0b5 100644 --- a/scripts/check-release-artifacts.ps1 +++ b/scripts/check-release-artifacts.ps1 @@ -18,6 +18,9 @@ foreach ($pair in @(@("$name.zip", "$name.sha256"), @("$name-setup.exe", "$name- $hash = (Get-FileHash -LiteralPath $file -Algorithm SHA256).Hash if ((Get-Item -LiteralPath $file).Length -eq 0 -or $checksum -cne "$hash $($pair[0])") { throw "Release checksum mismatch: $($pair[0])" } } +# Unsigned releases are supported; an optional publisher signature must still be valid. +$signatureStatus = (Get-AuthenticodeSignature -LiteralPath (Join-Path $Path "$name-setup.exe")).Status +if ($signatureStatus -notin @('NotSigned', 'Valid')) { throw "Installer has an invalid Windows publisher signature: $signatureStatus" } foreach ($package in @(@("$name.zip", 'release-manifest.json'), @("$name-store-submission.zip", 'submission-manifest.json'))) { $zip = [IO.Compression.ZipFile]::OpenRead((Join-Path $Path $package[0])) try { diff --git a/scripts/check-stable-readiness.ps1 b/scripts/check-stable-readiness.ps1 index c55926c..ca8c8f3 100644 --- a/scripts/check-stable-readiness.ps1 +++ b/scripts/check-stable-readiness.ps1 @@ -19,11 +19,32 @@ if ($LASTEXITCODE -ne 0) { throw 'Cannot read qualified build properties.' } $releaseProps = (git show "${Commit}:Directory.Build.props") -join "`n" if ($LASTEXITCODE -ne 0) { throw 'Cannot read release build properties.' } if (($qualifiedProps -replace '[^<]+', 'RELEASE') -cne ($releaseProps -replace '[^<]+', 'RELEASE')) { throw 'Build properties changed beyond the release version; repeat qualification.' } -foreach ($gate in @('workstation', 'narrator', 'offlineBrowsers', 'publisherSigning')) { - $record = $evidence.gates.$gate - if ($record.status -cne 'passed' -or [string]::IsNullOrWhiteSpace($record.reviewedBy) -or $record.evidence -notmatch '^docs/[a-zA-Z0-9_./-]+\.md$') { throw "Stable gate incomplete: $gate" } +function Assert-EvidenceReport([string]$Report, [string]$Label) { + if ($Report -notmatch '^docs/[a-zA-Z0-9_./-]+\.md$') { throw "Invalid acceptance report: $Label" } $repo = [IO.Path]::GetFullPath("$PSScriptRoot/..") - $file = [IO.Path]::GetFullPath((Join-Path $repo $record.evidence)) - if (-not $file.StartsWith((Join-Path $repo 'docs') + [IO.Path]::DirectorySeparatorChar, [StringComparison]::OrdinalIgnoreCase) -or -not (Test-Path -LiteralPath $file -PathType Leaf)) { throw "Missing acceptance report: $gate" } + $file = [IO.Path]::GetFullPath((Join-Path $repo $Report)) + if (-not $file.StartsWith((Join-Path $repo 'docs') + [IO.Path]::DirectorySeparatorChar, [StringComparison]::OrdinalIgnoreCase) -or -not (Test-Path -LiteralPath $file -PathType Leaf)) { throw "Missing acceptance report: $Label" } +} +$deferred = @() +foreach ($gate in @('workstation', 'offlineBrowsers')) { + $record = $evidence.gates.$gate + if ($record.status -cnotin @('passed', 'deferred') -or [string]::IsNullOrWhiteSpace($record.reviewedBy)) { throw "Stable gate incomplete: $gate" } + Assert-EvidenceReport $record.evidence $gate + if ($record.status -ceq 'deferred') { + if ([string]::IsNullOrWhiteSpace($record.reason)) { throw "Deferred gate lacks a disclosed limitation: $gate" } + $deferred += $gate + } +} +if ($deferred.Count) { + $authorization = $evidence.publicationAuthorization + if ($authorization.status -cne 'approved-with-limitations' -or $authorization.version -cne $Version -or + [string]::IsNullOrWhiteSpace($authorization.approvedBy) -or + $authorization.deploymentAccepted -isnot [bool] -or $authorization.deploymentAccepted -or + @($authorization.deferredGates).Count -ne $deferred.Count -or + @(Compare-Object $deferred @($authorization.deferredGates) -CaseSensitive).Count) { + throw 'Deferred checks require explicit versioned publication authorization with the exact disclosed scope; deployment must remain unaccepted.' + } + Assert-EvidenceReport $authorization.evidence 'publication authorization' + Write-Output "Owner-authorized publication with deferred checks: $($deferred -join ', '). Site deployment is not accepted." } -Write-Output "Stable acceptance records verified for $Version. Installer publisher signature is verified separately." +Write-Output "Stable release records verified for $Version. Windows publisher signing is optional." diff --git a/scripts/sign-release-files.ps1 b/scripts/sign-release-files.ps1 index f595826..dfea713 100644 --- a/scripts/sign-release-files.ps1 +++ b/scripts/sign-release-files.ps1 @@ -4,7 +4,7 @@ param([Parameter(Mandatory)][string[]]$Path) $ErrorActionPreference = 'Stop' $thumbprint = $env:APPSWITCHER_SIGNING_CERTIFICATE_THUMBPRINT if ([string]::IsNullOrWhiteSpace($thumbprint)) { - Write-Output 'Publisher signing not configured; candidate files remain unsigned.' + Write-Output 'Publisher signing not configured; release files remain unsigned (supported for beta, RC and stable).' return } if ($thumbprint -notmatch '^[A-Fa-f0-9]{40}$') { throw 'Invalid signing certificate selector.' } diff --git a/scripts/test-release-artifacts.ps1 b/scripts/test-release-artifacts.ps1 index f28f077..f9b30e4 100644 --- a/scripts/test-release-artifacts.ps1 +++ b/scripts/test-release-artifacts.ps1 @@ -5,6 +5,19 @@ $root = Join-Path (Split-Path -Parent $PSScriptRoot) ('artifacts/release-artifac $commit = '0123456789abcdef0123456789abcdef01234567' $name = "AppSwitcher-$version-win-x64-$($commit.Substring(0, 8))" +# Synthetic installers exercise signature policy without requiring a certificate. +function Get-AuthenticodeSignature { + param([string]$LiteralPath) + $scenario = Split-Path -Leaf (Split-Path -Parent $LiteralPath) + $status = switch ($scenario) { + 'valid-signed' { 'Valid' } + 'invalid-publisher-signature' { 'HashMismatch' } + 'untrusted-publisher-signature' { 'NotTrusted' } + default { 'NotSigned' } + } + [pscustomobject]@{ Status = $status } +} + function Write-Fixture([string]$Scenario) { $path = Join-Path $root $Scenario New-Item -ItemType Directory -Path $path | Out-Null @@ -74,8 +87,11 @@ function Write-Fixture([string]$Scenario) { try { $valid = Write-Fixture 'valid' & "$PSScriptRoot/check-release-artifacts.ps1" -Path $valid -Version $version -Commit $commit + $signed = Write-Fixture 'valid-signed' + & "$PSScriptRoot/check-release-artifacts.ps1" -Path $signed -Version $version -Commit $commit $scenarios = @('dirty', 'missing-dirty', 'wrong-source', 'wrong-version', 'tampered-payload', 'wrong-size', 'duplicate-manifest', 'dirty-store', 'submitted-store', 'extra-entry', 'unsafe-entry', 'duplicate-entry', 'missing-manifest', 'bad-checksum', 'extra-asset', 'missing-asset', 'sbom-schema', 'sbom-source', 'sbom-dependency', 'sbom-hash', 'sbom-duplicate', 'sbom-relationship', 'sbom-app-version') $scenarios += @('sbom-bridge-version', 'sbom-installer-version', 'sbom-license') + $scenarios += @('invalid-publisher-signature', 'untrusted-publisher-signature') foreach ($scenario in $scenarios) { $path = Write-Fixture $scenario $rejected = $false diff --git a/scripts/test-stable-readiness.ps1 b/scripts/test-stable-readiness.ps1 index 1137a12..583bcec 100644 --- a/scripts/test-stable-readiness.ps1 +++ b/scripts/test-stable-readiness.ps1 @@ -5,23 +5,44 @@ $commit = (git rev-parse HEAD).Trim() & "$PSScriptRoot/check-stable-readiness.ps1" -Version '1.2.0-beta.3' -Commit $commit $fixture = Join-Path ([IO.Path]::GetTempPath()) ('AppSwitcher-readiness-' + [Guid]::NewGuid().ToString('N') + '.json') try { - foreach ($scenario in @('pending', 'wrong-version', 'missing-reviewer', 'missing-report', 'traversal', 'unknown-commit')) { + $scenarios = @('valid-silent-unsigned', 'valid-authorized-deferral', 'pending', 'pending-browsers', 'missing-gate', 'wrong-version', 'missing-reviewer', 'missing-report', 'traversal', 'unknown-commit', 'deferred-without-authorization', 'deferred-without-reason', 'authorization-wrong-version', 'authorization-missing-owner', 'authorization-missing-report', 'authorization-traversal', 'authorization-wrong-scope', 'authorization-duplicate-scope', 'authorization-claims-deployment', 'authorization-missing-deployment-status', 'unknown-status') + foreach ($scenario in $scenarios) { $record = @{ version = '1.2.0'; qualifiedCommit = $commit; gates = @{} } - foreach ($gate in @('workstation', 'narrator', 'offlineBrowsers', 'publisherSigning')) { + foreach ($gate in @('workstation', 'offlineBrowsers')) { $record.gates[$gate] = @{status='passed'; reviewedBy='Synthetic test'; evidence='docs/WORKSTATION_ACCEPTANCE.md'} } + if ($scenario -eq 'valid-authorized-deferral' -or $scenario -like 'deferred-*' -or $scenario -like 'authorization-*') { + $record.gates.workstation.status = 'deferred' + $record.gates.workstation.reason = 'Synthetic publication decision; hardware acceptance remains unperformed.' + $record.publicationAuthorization = @{status='approved-with-limitations'; version='1.2.0'; approvedBy='Synthetic owner'; deploymentAccepted=$false; deferredGates=@('workstation'); evidence='docs/WORKSTATION_ACCEPTANCE.md'} + } switch ($scenario) { 'pending' { $record.gates.workstation.status = 'pending' } + 'pending-browsers' { $record.gates.offlineBrowsers.status = 'pending' } + 'missing-gate' { $record.gates.Remove('offlineBrowsers') } 'wrong-version' { $record.version = '0.0.0' } - 'missing-reviewer' { $record.gates.narrator.reviewedBy = '' } + 'missing-reviewer' { $record.gates.workstation.reviewedBy = '' } 'missing-report' { $record.gates.offlineBrowsers.evidence = 'docs/does-not-exist.md' } - 'traversal' { $record.gates.publisherSigning.evidence = 'docs/../README.md' } + 'traversal' { $record.gates.offlineBrowsers.evidence = 'docs/../README.md' } 'unknown-commit' { $record.qualifiedCommit = $null } + 'deferred-without-authorization' { $record.Remove('publicationAuthorization') } + 'deferred-without-reason' { $record.gates.workstation.Remove('reason') } + 'authorization-wrong-version' { $record.publicationAuthorization.version = '0.0.0' } + 'authorization-missing-owner' { $record.publicationAuthorization.approvedBy = ' ' } + 'authorization-missing-report' { $record.publicationAuthorization.evidence = 'docs/missing.md' } + 'authorization-traversal' { $record.publicationAuthorization.evidence = 'docs/../README.md' } + 'authorization-wrong-scope' { $record.publicationAuthorization.deferredGates = @('offlineBrowsers') } + 'authorization-duplicate-scope' { $record.publicationAuthorization.deferredGates = @('workstation', 'workstation') } + 'authorization-claims-deployment' { $record.publicationAuthorization.deploymentAccepted = $true } + 'authorization-missing-deployment-status' { $record.publicationAuthorization.Remove('deploymentAccepted') } + 'unknown-status' { $record.gates.workstation.status = 'ignored' } } $record | ConvertTo-Json -Depth 5 | Set-Content -LiteralPath $fixture $rejected = $false try { & "$PSScriptRoot/check-stable-readiness.ps1" -Version '1.2.0' -Commit $commit -EvidencePath $fixture } catch { $rejected = $true } - if (-not $rejected) { throw "Stable gate accepted $scenario." } + if ($scenario -like 'valid-*') { + if ($rejected) { throw "Stable gate rejected $scenario." } + } elseif (-not $rejected) { throw "Stable gate accepted $scenario." } } - Write-Output 'Stable readiness gate passed: beta/RC allowed; six invalid stable qualifications rejected.' + Write-Output 'Stable readiness gate passed: passed checks and explicit owner-authorized deferral accepted; 19 invalid qualifications rejected.' } finally { Remove-Item -LiteralPath $fixture -ErrorAction SilentlyContinue } diff --git a/scripts/tests/browser-check-broker.test.mjs b/scripts/tests/browser-check-broker.test.mjs index 0a25dbd..548aad9 100644 --- a/scripts/tests/browser-check-broker.test.mjs +++ b/scripts/tests/browser-check-broker.test.mjs @@ -2,6 +2,7 @@ import test from "node:test"; import assert from "node:assert/strict"; import { spawn } from "node:child_process"; import net from "node:net"; +import http from "node:http"; import { mkdtemp, readFile, writeFile, rm } from "node:fs/promises"; import { tmpdir } from "node:os"; import { dirname, join, resolve } from "node:path"; @@ -45,6 +46,32 @@ test("browser test broker rejects unrelated profiles without replacing the isola const later = await hello("Another normal profile"); await until(async () => later.destroyed); assert.equal((await status())[0].profileName, expected); + const body = JSON.stringify({ reason: "native host reconnect" }); + const disconnect = http.request(meta.url + "/disconnect", { + method: "POST", headers: { authorization: meta.token, "content-type": "application/json", "content-length": Buffer.byteLength(body) } + }); + const reply = new Promise((resolve, reject) => { + disconnect.once("error", reject); + disconnect.once("response", response => { + let text = ""; + response.on("data", chunk => { text += chunk; }); + response.once("end", () => resolve({ status: response.statusCode, text })); + response.once("error", reject); + }); + }); + try { + disconnect.write(body.slice(0, 5)); + assert.equal(await Promise.race([reply.then(() => "early response"), new Promise(resolve => setTimeout(() => resolve("waiting for body"), 200))]), + "waiting for body", "Drain a partial POST before replying; an early reply can reset native HTTP clients"); + disconnect.end(body.slice(5)); + assert.deepEqual(await reply, { status: 200, text: "{}" }); + } finally { disconnect.destroy(); } + assert.deepEqual(await status(), [], "A disconnected client must not satisfy the reconnect wait"); + await until(async () => clients.every(client => client.destroyed)); + const replacement = await hello(expected); + await until(async () => (await status()).length === 1); + assert.equal((await status())[0].profileName, expected); + assert.equal(replacement.destroyed, false); } finally { for (const client of clients) client.destroy(); if (process.exitCode === null) {