Prevent RC regressions at release boundary

This commit is contained in:
rcourtman
2026-08-01 13:57:44 +01:00
parent 1f20ce5dd8
commit bc265ada2e
13 changed files with 425 additions and 74 deletions
+48 -2
View File
@@ -253,7 +253,7 @@ jobs:
needs: prepare
if: ${{ needs.prepare.outputs.historical_asset_backfill_only != 'true' }}
runs-on: ubuntu-24.04
timeout-minutes: 10
timeout-minutes: 20
steps:
- name: Checkout repository
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
@@ -277,6 +277,12 @@ jobs:
- name: Check frontend copy-paste duplication
run: npm --prefix frontend-modern run lint:cpd
- name: Type-check frontend
run: npm --prefix frontend-modern run type-check
- name: Test frontend
run: npm --prefix frontend-modern test
- name: Build verified frontend bundle
run: npm --prefix frontend-modern run build
@@ -290,6 +296,31 @@ jobs:
compression-level: 0
overwrite: true
windows_install_command_smoke:
name: Windows PowerShell 5.1 Install Command Smoke
needs: prepare
if: ${{ needs.prepare.outputs.historical_asset_backfill_only != 'true' }}
runs-on: windows-2025
timeout-minutes: 15
steps:
- name: Checkout repository
uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3
- name: Set up Node.js
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: 'frontend-modern/package-lock.json'
- name: Install frontend test dependencies
working-directory: frontend-modern
run: npm ci
- name: Execute generated command with Windows PowerShell 5.1
working-directory: frontend-modern
run: npm test -- --run src/utils/__tests__/agentInstallCommand.windows.test.ts
# Backend tests run in parallel with frontend checks
backend_tests:
needs:
@@ -764,11 +795,23 @@ jobs:
docker logs pulse-test-server || true
docker compose -f docker-compose.test.yml down -v || true
- name: Upload smoke diagnostics
if: failure()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: release-smoke-failures-${{ github.sha }}
path: |
tests/integration/test-results/
tests/integration/playwright-report/
if-no-files-found: ignore
retention-days: 14
create_release:
needs:
- prepare
- build_release_candidate
- frontend_checks
- windows_install_command_smoke
- backend_tests
- docker_build
- helm_smoke
@@ -776,7 +819,7 @@ jobs:
- release_smoke
# Run if integration_tests passed OR was skipped (prereleases). The
# release smoke has no skipped escape: it runs for prereleases too.
if: ${{ needs.prepare.outputs.historical_asset_backfill_only != 'true' && always() && needs.build_release_candidate.result == 'success' && needs.frontend_checks.result == 'success' && needs.backend_tests.result == 'success' && needs.docker_build.result == 'success' && needs.helm_smoke.result == 'success' && needs.release_smoke.result == 'success' && (needs.integration_tests.result == 'success' || needs.integration_tests.result == 'skipped') }}
if: ${{ needs.prepare.outputs.historical_asset_backfill_only != 'true' && always() && needs.build_release_candidate.result == 'success' && needs.frontend_checks.result == 'success' && needs.windows_install_command_smoke.result == 'success' && needs.backend_tests.result == 'success' && needs.docker_build.result == 'success' && needs.helm_smoke.result == 'success' && needs.release_smoke.result == 'success' && (needs.integration_tests.result == 'success' || needs.integration_tests.result == 'skipped') }}
runs-on: ubuntu-24.04
timeout-minutes: 30
permissions:
@@ -1463,6 +1506,7 @@ jobs:
needs:
- prepare
- release_smoke
- windows_install_command_smoke
- create_release
- publish_docker
- validate_release_assets
@@ -1481,6 +1525,7 @@ jobs:
IS_PRERELEASE: ${{ needs.prepare.outputs.is_prerelease }}
CREATE_RESULT: ${{ needs.create_release.result }}
SMOKE_RESULT: ${{ needs.release_smoke.result }}
WINDOWS_INSTALL_COMMAND_RESULT: ${{ needs.windows_install_command_smoke.result }}
DOCKER_RESULT: ${{ needs.publish_docker.result }}
VALIDATE_RESULT: ${{ needs.validate_release_assets.result }}
INSTALL_RESULT: ${{ needs.install_sh_smoke.result }}
@@ -1501,6 +1546,7 @@ jobs:
}
require_result "release smoke" "$SMOKE_RESULT" success
require_result "Windows install command smoke" "$WINDOWS_INSTALL_COMMAND_RESULT" success
require_result "release assembly" "$CREATE_RESULT" success
require_result "release asset validation" "$VALIDATE_RESULT" success
@@ -13,6 +13,8 @@ on:
- 'internal/kubernetesagent/**'
- 'internal/remoteconfig/**'
- 'pkg/agents/**'
- 'frontend-modern/src/utils/agentInstallCommand.ts'
- 'frontend-modern/src/utils/__tests__/agentInstallCommand.windows.test.ts'
- 'scripts/install.sh'
- 'scripts/install.ps1'
- 'scripts/installtests/**'
@@ -30,6 +32,8 @@ on:
- 'internal/kubernetesagent/**'
- 'internal/remoteconfig/**'
- 'pkg/agents/**'
- 'frontend-modern/src/utils/agentInstallCommand.ts'
- 'frontend-modern/src/utils/__tests__/agentInstallCommand.windows.test.ts'
- 'scripts/install.sh'
- 'scripts/install.ps1'
- 'scripts/installtests/**'
@@ -96,6 +100,24 @@ jobs:
if: ${{ !matrix.unix }}
run: go test ./scripts/installtests -run '^Test(InstallPS1|WindowsAgentLifecycle)'
- name: Set up Node.js for Windows command proof
if: ${{ !matrix.unix }}
uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: '20'
cache: 'npm'
cache-dependency-path: 'frontend-modern/package-lock.json'
- name: Install frontend test dependencies
if: ${{ !matrix.unix }}
working-directory: frontend-modern
run: npm ci
- name: Execute generated command with Windows PowerShell 5.1
if: ${{ !matrix.unix }}
working-directory: frontend-modern
run: npm test -- --run src/utils/__tests__/agentInstallCommand.windows.test.ts
- name: Build and execute native Unix agent
if: matrix.unix
shell: bash
+1 -15
View File
@@ -9227,21 +9227,7 @@
]
}
],
"work_claims": [
{
"id": "codex-release-v620rc5-release-gate-single-build-release-promotion-path",
"agent_id": "codex-release-v620rc5",
"summary": "Prepare and publish v6.2.0-rc.5 through the governed exact-SHA promotion workflow",
"target_id": "v6-product-lane-expansion",
"claimed_at": "2026-07-31T16:42:32Z",
"heartbeat_at": "2026-07-31T16:42:32Z",
"expires_at": "2026-08-01T00:42:32Z",
"work_item": {
"kind": "release-gate",
"id": "single-build-release-promotion-path"
}
}
],
"work_claims": [],
"open_decisions": [],
"source_of_truth_file": "docs/release-control/v6/internal/SOURCE_OF_TRUTH.md",
"resolved_decisions": [
@@ -4986,6 +4986,16 @@ clipboard transport: the rendered Linux/macOS/BSD and Windows install snippets
must already include the active token choice, custom-CA trust, insecure/plain-
HTTP handling, install-profile flags, and command-execution mode instead of
displaying one command and mutating it only during copy.
Windows first-hop TLS overrides must remain executable on the oldest supported
Windows PowerShell runtime. Generated install commands and inline
`install.ps1` bootstraps use a compiled .NET
`RemoteCertificateValidationCallback`, never a PowerShell scriptblock delegate
that can be invoked on a worker thread without a runspace. Their custom-CA
loader accepts PEM and DER without relying on `X509Certificate2.CreateFromPem`,
which Windows PowerShell 5.1 does not provide. Native Windows verification and
the release workflow both execute the generated insecure and PEM custom-CA
commands against a self-signed HTTPS installer fixture, including the full
preflight-to-install handoff.
For Unix-family host installs, that same seamless installer contract requires
the copied command to fetch the shared installer into an ephemeral directory,
run `install.sh --preflight-only` before privilege escalation, and fail before
@@ -6318,6 +6318,14 @@ custom-CA or insecure-TLS certificate handling before `install.ps1` is fetched,
not only after the installer starts executing. That bootstrap must accept the
same PEM/CRT/CER trust input that `scripts/install.ps1` itself accepts, so the
shared command contract does not narrow custom-CA behavior on the first fetch.
That first-fetch contract includes Windows PowerShell 5.1 execution semantics:
the generated API/UI command must install a compiled .NET certificate callback
rather than a PowerShell scriptblock delegate, and it must decode PEM without
calling runtime APIs absent from Windows PowerShell 5.1. Exact generated
commands for insecure TLS and PEM custom-CA trust are executed against a
self-signed HTTPS installer fixture in native Windows CI and again as a
release-gating Windows smoke; substring assertions alone are not sufficient
proof of this transport boundary.
That same shell transport contract also applies to the governed setup-completion
install handoff in `SetupCompletionPanel`: when the operator supplies a custom CA path
or opts into insecure/self-signed transport, the shared Unix install builder
@@ -1171,6 +1171,16 @@ collection; adds storage, alert-routing, and OpenShift coverage; and carries
the post-RC4 compatibility fixes. The exact `main` SHA must pass the integrated
release checks and immutable-candidate build before the single-build workflow
crosses its public mutation boundary.
Every release cut, including a prerelease, now gates that mutation boundary on
the complete frontend unit suite, frontend type-checking, and a deterministic
render smoke against the verified frontend bundle. The smoke must render
Proxmox nodes and workloads, Docker hosts and containers, Kubernetes clusters
and pods, and Alert thresholds with an omitted-zero disk payload; an error
boundary or uncaught browser error fails the cut. Failures retain Playwright
diagnostics. The same release workflow also executes the generated self-signed
and custom-CA Windows installer commands through Windows PowerShell 5.1 before
release assembly, so the first HTTPS fetch is release proof rather than a
string-shape assertion.
The `v6.2.0-rc.5` server cut is classified
`existing-mobile-build-compatible`. The synchronized Pulse Mobile 1.0.0 iOS
build 11 and Android versionCode 9 candidates, both using runtime version 2,
@@ -84,80 +84,79 @@ describe('buildPowerShellInstallScriptBootstrap — bootstrap script wiring', ()
expect(script).toContain('-or -not [string]::IsNullOrWhiteSpace($env:PULSE_CACERT))');
});
it('reads the custom CA bytes from $env:PULSE_CACERT when populated', () => {
it('loads the custom CA through the Windows PowerShell 5.1-compatible helper', () => {
const script = buildPowerShellInstallScriptBootstrap('https://pulse.example');
expect(script).toContain('if (-not [string]::IsNullOrWhiteSpace($env:PULSE_CACERT)) {');
expect(script).toContain(
'$pulseCustomCaBytes = [System.IO.File]::ReadAllBytes($env:PULSE_CACERT);',
'[PulseInstallerCertificateValidator]::LoadCertificate($env:PULSE_CACERT)',
);
});
it('routes a PEM-encoded CA through X509Certificate2::CreateFromPem', () => {
it('decodes PEM certificates without the newer X509Certificate2::CreateFromPem API', () => {
const script = buildPowerShellInstallScriptBootstrap('https://pulse.example');
expect(script).toContain('if ($pulseCustomCaText.Contains("-----BEGIN CERTIFICATE-----"))');
expect(script).toContain(
'$pulseCustomCa = [System.Security.Cryptography.X509Certificates.X509Certificate2]::CreateFromPem($pulseCustomCaText)',
);
expect(script).toContain('if (text.Contains("-----BEGIN CERTIFICATE-----"))');
expect(script).toContain('bytes = Convert.FromBase64String(base64);');
expect(script).not.toContain('CreateFromPem');
});
it('routes a DER-encoded CA through X509Certificate2::new (else arm)', () => {
it('passes PEM-decoded or raw DER bytes into X509Certificate2', () => {
const script = buildPowerShellInstallScriptBootstrap('https://pulse.example');
expect(script).toContain(
'$pulseCustomCa = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new($pulseCustomCaBytes)',
'Activator.CreateInstance(typeof(X509Certificate2), new object[] { bytes })',
);
});
it('installs the X509 chain-validation ServerCertificateValidationCallback', () => {
it('installs the compiled X509 chain-validation callback', () => {
const script = buildPowerShellInstallScriptBootstrap('https://pulse.example');
expect(script).toContain(
'$pulsePrev = [System.Net.ServicePointManager]::ServerCertificateValidationCallback;',
);
expect(script).toContain(
'[System.Net.ServicePointManager]::ServerCertificateValidationCallback = ({ param($sender, $certificate, $chain, $sslPolicyErrors)',
'[System.Net.ServicePointManager]::ServerCertificateValidationCallback = [PulseInstallerCertificateValidator]::ValidateCustomCaCallback',
);
expect(script).toContain(
'} finally { [System.Net.ServicePointManager]::ServerCertificateValidationCallback = $pulsePrev }',
'} finally { [System.Net.ServicePointManager]::ServerCertificateValidationCallback = $pulsePrev; [PulseInstallerCertificateValidator]::CustomCa = $null }',
);
});
it('short-circuits the callback to $true when PULSE_INSECURE_SKIP_VERIFY is "true"', () => {
it('uses the compiled accept-any callback when PULSE_INSECURE_SKIP_VERIFY is "true"', () => {
const script = buildPowerShellInstallScriptBootstrap('https://pulse.example');
expect(script).toContain(
'if ($env:PULSE_INSECURE_SKIP_VERIFY -eq "true") { [System.Net.ServicePointManager]::ServerCertificateValidationCallback = { param($sender, $certificate, $chain, $sslPolicyErrors) return $true }',
'if ($env:PULSE_INSECURE_SKIP_VERIFY -eq "true") { [System.Net.ServicePointManager]::ServerCertificateValidationCallback = [PulseInstallerCertificateValidator]::AcceptAnyCallback',
);
});
it('returns the raw sslPolicyErrors verdict when no custom CA was loaded', () => {
it('does not embed a PowerShell scriptblock callback', () => {
const script = buildPowerShellInstallScriptBootstrap('https://pulse.example');
expect(script).toContain(
'if ($null -eq $pulseCustomCa) { return $sslPolicyErrors -eq [System.Net.Security.SslPolicyErrors]::None };',
);
expect(script).not.toContain('ServerCertificateValidationCallback = { param(');
expect(script).not.toContain('GetNewClosure');
});
it('returns $false when the server supplied no certificate', () => {
it('fails closed when the server supplied no certificate or no custom CA', () => {
const script = buildPowerShellInstallScriptBootstrap('https://pulse.example');
expect(script).toContain('if ($null -eq $certificate) { return $false };');
expect(script).toContain('if (certificate == null || CustomCa == null ||');
expect(script).toContain('SslPolicyErrors.RemoteCertificateNameMismatch');
expect(script).toContain('SslPolicyErrors.RemoteCertificateNotAvailable');
expect(script).toContain('return false;');
});
it('builds an X509Chain with NoCheck revocation and the custom CA in ExtraStore', () => {
const script = buildPowerShellInstallScriptBootstrap('https://pulse.example');
expect(script).toContain('using (X509Chain candidateChain = new X509Chain())');
expect(script).toContain(
'$pulseChain = [System.Security.Cryptography.X509Certificates.X509Chain]::new();',
'candidateChain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;',
);
expect(script).toContain(
'$pulseChain.ChainPolicy.RevocationMode = [System.Security.Cryptography.X509Certificates.X509RevocationMode]::NoCheck;',
);
expect(script).toContain('$null = $pulseChain.ChainPolicy.ExtraStore.Add($pulseCustomCa);');
expect(script).toContain('$null = $pulseChain.Build($certificate);');
expect(script).toContain('candidateChain.ChainPolicy.ExtraStore.Add(CustomCa);');
expect(script).toContain('candidateChain.Build(new X509Certificate2(certificate));');
});
it('walks ChainElements and trusts the chain when the custom CA Thumbprint matches', () => {
const script = buildPowerShellInstallScriptBootstrap('https://pulse.example');
expect(script).toContain('foreach ($pulseElement in $pulseChain.ChainElements) {');
expect(script).toContain('foreach (X509ChainElement element in candidateChain.ChainElements)');
expect(script).toContain(
'if ($pulseElement.Certificate.Thumbprint -eq $pulseCustomCa.Thumbprint) { return $true }',
'String.Equals(element.Certificate.Thumbprint, CustomCa.Thumbprint, StringComparison.OrdinalIgnoreCase)',
);
expect(script).toContain('return $false }).GetNewClosure()');
expect(script).toContain('return true;');
});
it('fetches the script via `irm $pulseScriptUrl` inside both the custom-trust and the bare else arm', () => {
@@ -144,8 +144,10 @@ describe('agentInstallCommand', () => {
'Invoke-WebRequest -Uri $pulseScriptUrl -UseBasicParsing -OutFile $pulseInstallScript',
);
expect(command).toContain(
'ServerCertificateValidationCallback={ param($sender,$certificate,$chain,$sslPolicyErrors) return $true }',
'ServerCertificateValidationCallback=[PulseInstallerCertificateValidator]::AcceptAnyCallback',
);
expect(command).toContain('Add-Type -TypeDefinition');
expect(command).not.toContain('ServerCertificateValidationCallback={ param(');
expect(command).not.toContain('$pulseAllowInsecure');
expect(command).toContain(
'& $pulsePowerShell -NoProfile -ExecutionPolicy Bypass -File $pulseInstallScript -Url',
@@ -158,16 +160,21 @@ describe('agentInstallCommand', () => {
expect(command).not.toContain('$env:PULSE_TOKEN=');
});
it('captures a custom CA in the certificate callback when TLS verification stays enabled', () => {
it('uses a runspace-independent custom CA callback when TLS verification stays enabled', () => {
const command = buildWindowsAgentInstallCommand({
baseUrl: 'https://pulse.example',
token: null,
caCertPath: 'C:\\Pulse\\custom-ca.cer',
});
expect(command).toContain('ServerCertificateValidationCallback=({ param(');
expect(command).toContain('return $false }).GetNewClosure()');
expect(command).not.toContain('return $true }; if ($null -eq $pulseCustomCa)');
expect(command).toContain(
'ServerCertificateValidationCallback=[PulseInstallerCertificateValidator]::ValidateCustomCaCallback',
);
expect(command).toContain(
'[PulseInstallerCertificateValidator]::LoadCertificate($pulseCaCertPath)',
);
expect(command).not.toContain('CreateFromPem');
expect(command).not.toContain('GetNewClosure');
});
it('supports tokenless shared Windows install transport for optional auth', () => {
@@ -0,0 +1,135 @@
import { spawn } from 'node:child_process';
import { mkdtemp, readFile, rm, writeFile } from 'node:fs/promises';
import { createServer, type Server } from 'node:https';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it } from 'vitest';
import {
buildPowerShellInstallScriptBootstrap,
buildWindowsAgentInstallCommand,
} from '@/utils/agentInstallCommand';
const powerShellRuntime = process.platform === 'win32' ? 'powershell.exe' : undefined;
const installerScript = `
param([string]$Url, [string]$TokenFile)
$phase = if ($env:PULSE_PREFLIGHT_ONLY -eq "true") { "preflight" } else { "install" }
Add-Content -LiteralPath $env:PULSE_TEST_MARKER -Value $phase
`.trim();
const runPowerShell = (command: string, env: NodeJS.ProcessEnv) =>
new Promise<{ stdout: string; stderr: string }>((resolve, reject) => {
const child = spawn(
powerShellRuntime!,
[
'-NoLogo',
'-NoProfile',
'-NonInteractive',
'-ExecutionPolicy',
'Bypass',
'-Command',
command,
],
{ env: { ...process.env, ...env }, windowsHide: true },
);
let stdout = '';
let stderr = '';
child.stdout.setEncoding('utf8').on('data', (chunk) => (stdout += chunk));
child.stderr.setEncoding('utf8').on('data', (chunk) => (stderr += chunk));
child.on('error', reject);
child.on('close', (code) => {
if (code === 0) resolve({ stdout, stderr });
else reject(new Error(`PowerShell exited ${code}.\nstdout:\n${stdout}\nstderr:\n${stderr}`));
});
});
describe.runIf(Boolean(powerShellRuntime))('Windows install command TLS runtime', () => {
let server: Server | undefined;
let baseUrl: string;
let certificate: Buffer;
let suiteDirectory: string;
let testDirectory: string;
let markerPath: string;
let certificatePath: string;
beforeAll(async () => {
suiteDirectory = await mkdtemp(join(tmpdir(), 'pulse-windows-tls-suite-'));
const pfxPath = join(suiteDirectory, 'server.pfx');
const derPath = join(suiteDirectory, 'server.cer');
const quote = (value: string) => `'${value.replace(/'/g, "''")}'`;
await runPowerShell(
`$ErrorActionPreference="Stop"; ` +
`$certificate=New-SelfSignedCertificate -DnsName "localhost" -CertStoreLocation "Cert:\\CurrentUser\\My" -KeyExportPolicy Exportable; ` +
`try { ` +
`$password=ConvertTo-SecureString "pulse-test" -AsPlainText -Force; ` +
`Export-PfxCertificate -Cert $certificate -FilePath ${quote(pfxPath)} -Password $password | Out-Null; ` +
`Export-Certificate -Cert $certificate -FilePath ${quote(derPath)} -Type CERT | Out-Null ` +
`} finally { Remove-Item -LiteralPath ("Cert:\\CurrentUser\\My\\"+$certificate.Thumbprint) }`,
{},
);
const [pfx, der] = await Promise.all([readFile(pfxPath), readFile(derPath)]);
const lines = der.toString('base64').match(/.{1,64}/g) ?? [];
certificate = Buffer.from(
`-----BEGIN CERTIFICATE-----\n${lines.join('\n')}\n-----END CERTIFICATE-----\n`,
);
const testServer = createServer({ pfx, passphrase: 'pulse-test' }, (request, response) => {
if (request.url !== '/install.ps1') {
response.writeHead(404).end();
return;
}
response.writeHead(200, { 'content-type': 'text/plain' }).end(installerScript);
});
server = testServer;
await new Promise<void>((resolve) => testServer.listen(0, resolve));
const address = testServer.address();
if (!address || typeof address === 'string') throw new Error('HTTPS test server did not bind.');
baseUrl = `https://localhost:${address.port}`;
});
beforeEach(async () => {
testDirectory = await mkdtemp(join(tmpdir(), 'pulse-windows-tls-'));
markerPath = join(testDirectory, 'result.txt');
certificatePath = join(testDirectory, 'server.crt');
await writeFile(certificatePath, certificate);
});
afterEach(async () => {
if (testDirectory) await rm(testDirectory, { recursive: true, force: true });
});
afterAll(async () => {
if (server) {
await new Promise<void>((resolve, reject) =>
server!.close((error) => (error ? reject(error) : resolve())),
);
}
if (suiteDirectory) await rm(suiteDirectory, { recursive: true, force: true });
});
it('executes the inline bootstrap over self-signed HTTPS without a callback runspace', async () => {
await runPowerShell(buildPowerShellInstallScriptBootstrap(baseUrl), {
PULSE_INSECURE_SKIP_VERIFY: 'true',
PULSE_TEST_MARKER: markerPath,
});
await expect(readFile(markerPath, 'utf8')).resolves.toMatch(/install\r?\n/);
}, 30_000);
it('executes preflight and install through the insecure Windows command', async () => {
await runPowerShell(buildWindowsAgentInstallCommand({ baseUrl, insecure: true }), {
PULSE_TEST_MARKER: markerPath,
});
await expect(readFile(markerPath, 'utf8')).resolves.toMatch(/preflight\r?\ninstall\r?\n/);
}, 30_000);
it('executes preflight and install through the PEM custom-CA Windows command', async () => {
await runPowerShell(buildWindowsAgentInstallCommand({ baseUrl, caCertPath: certificatePath }), {
PULSE_TEST_MARKER: markerPath,
});
await expect(readFile(markerPath, 'utf8')).resolves.toMatch(/preflight\r?\ninstall\r?\n/);
}, 30_000);
});
@@ -2,6 +2,77 @@ const shellQuoteArg = (value: string) => `'${value.replace(/'/g, `'\"'\"'`)}'`;
export const powerShellQuote = (value: string) =>
value.replace(/`/g, '``').replace(/"/g, '`"').replace(/\$/g, '`$');
const powerShellSingleQuotedLiteral = (value: string) => `'${value.replace(/'/g, "''")}'`;
// ServicePointManager can invoke its validation callback on a worker thread
// that has no PowerShell runspace. Windows PowerShell 5.1 therefore cannot
// safely use a scriptblock delegate here. Keep the callback and PEM parsing in
// a compiled .NET type that is compatible with both Windows PowerShell 5.1 and
// modern PowerShell runtimes.
const powerShellTlsValidatorSource = `
using System;
using System.IO;
using System.Net.Security;
using System.Security.Cryptography.X509Certificates;
using System.Text.RegularExpressions;
public static class PulseInstallerCertificateValidator
{
public static X509Certificate2 CustomCa;
public static readonly RemoteCertificateValidationCallback AcceptAnyCallback = AcceptAny;
public static readonly RemoteCertificateValidationCallback ValidateCustomCaCallback = ValidateCustomCa;
public static X509Certificate2 LoadCertificate(string path)
{
byte[] bytes = File.ReadAllBytes(path);
string text = System.Text.Encoding.ASCII.GetString(bytes);
if (text.Contains("-----BEGIN CERTIFICATE-----"))
{
string base64 = Regex.Replace(text, "-----BEGIN CERTIFICATE-----|-----END CERTIFICATE-----|\\\\s", "");
bytes = Convert.FromBase64String(base64);
}
return (X509Certificate2)Activator.CreateInstance(typeof(X509Certificate2), new object[] { bytes });
}
private static bool AcceptAny(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors errors)
{
return true;
}
private static bool ValidateCustomCa(object sender, X509Certificate certificate, X509Chain chain, SslPolicyErrors errors)
{
if (certificate == null || CustomCa == null ||
(errors & (SslPolicyErrors.RemoteCertificateNameMismatch | SslPolicyErrors.RemoteCertificateNotAvailable)) != 0)
{
return false;
}
using (X509Chain candidateChain = new X509Chain())
{
candidateChain.ChainPolicy.RevocationMode = X509RevocationMode.NoCheck;
candidateChain.ChainPolicy.ExtraStore.Add(CustomCa);
candidateChain.Build(new X509Certificate2(certificate));
foreach (X509ChainElement element in candidateChain.ChainElements)
{
if (String.Equals(element.Certificate.Thumbprint, CustomCa.Thumbprint, StringComparison.OrdinalIgnoreCase))
{
return true;
}
}
}
return false;
}
}`.trim();
const powerShellTlsValidatorBootstrap =
`if ($null -eq ("PulseInstallerCertificateValidator" -as [type])) { ` +
`Add-Type -TypeDefinition ${powerShellSingleQuotedLiteral(powerShellTlsValidatorSource)} ` +
`}; `;
const powerShellLoadCustomCa = (pathExpression: string) =>
`$pulseCustomCa=[PulseInstallerCertificateValidator]::LoadCertificate(${pathExpression}); ` +
`[PulseInstallerCertificateValidator]::CustomCa=$pulseCustomCa; `;
export const normalizeInstallerBaseUrl = (baseUrl: string) => baseUrl.replace(/\/+$/, '');
export type AgentCommandPlatform = 'linux' | 'macos' | 'freebsd' | 'windows';
@@ -119,35 +190,20 @@ export const buildPowerShellInstallScriptBootstrap = (baseUrl: string) => {
return (
`& { $pulseScriptUrl="${scriptUrl}"; ` +
`if ($env:PULSE_INSECURE_SKIP_VERIFY -eq "true" -or -not [string]::IsNullOrWhiteSpace($env:PULSE_CACERT)) { ` +
`${powerShellTlsValidatorBootstrap}` +
`$pulseCustomCa = $null; ` +
`if (-not [string]::IsNullOrWhiteSpace($env:PULSE_CACERT)) { ` +
`$pulseCustomCaBytes = [System.IO.File]::ReadAllBytes($env:PULSE_CACERT); ` +
`$pulseCustomCaText = [System.Text.Encoding]::ASCII.GetString($pulseCustomCaBytes); ` +
`if ($pulseCustomCaText.Contains("-----BEGIN CERTIFICATE-----")) { ` +
`$pulseCustomCa = [System.Security.Cryptography.X509Certificates.X509Certificate2]::CreateFromPem($pulseCustomCaText) ` +
`} else { ` +
`$pulseCustomCa = [System.Security.Cryptography.X509Certificates.X509Certificate2]::new($pulseCustomCaBytes) ` +
`} ` +
`${powerShellLoadCustomCa('$env:PULSE_CACERT')}` +
`}; ` +
`$pulsePrev = [System.Net.ServicePointManager]::ServerCertificateValidationCallback; ` +
`try { ` +
`if ($env:PULSE_INSECURE_SKIP_VERIFY -eq "true") { ` +
`[System.Net.ServicePointManager]::ServerCertificateValidationCallback = { param($sender, $certificate, $chain, $sslPolicyErrors) return $true } ` +
`[System.Net.ServicePointManager]::ServerCertificateValidationCallback = [PulseInstallerCertificateValidator]::AcceptAnyCallback ` +
`} else { ` +
`[System.Net.ServicePointManager]::ServerCertificateValidationCallback = ({ param($sender, $certificate, $chain, $sslPolicyErrors) ` +
`if ($null -eq $pulseCustomCa) { return $sslPolicyErrors -eq [System.Net.Security.SslPolicyErrors]::None }; ` +
`if ($null -eq $certificate) { return $false }; ` +
`$pulseChain = [System.Security.Cryptography.X509Certificates.X509Chain]::new(); ` +
`$pulseChain.ChainPolicy.RevocationMode = [System.Security.Cryptography.X509Certificates.X509RevocationMode]::NoCheck; ` +
`$null = $pulseChain.ChainPolicy.ExtraStore.Add($pulseCustomCa); ` +
`$null = $pulseChain.Build($certificate); ` +
`foreach ($pulseElement in $pulseChain.ChainElements) { ` +
`if ($pulseElement.Certificate.Thumbprint -eq $pulseCustomCa.Thumbprint) { return $true } ` +
`}; ` +
`return $false }).GetNewClosure() ` +
`[System.Net.ServicePointManager]::ServerCertificateValidationCallback = [PulseInstallerCertificateValidator]::ValidateCustomCaCallback ` +
`}; ` +
`irm $pulseScriptUrl ` +
`} finally { [System.Net.ServicePointManager]::ServerCertificateValidationCallback = $pulsePrev } ` +
`} finally { [System.Net.ServicePointManager]::ServerCertificateValidationCallback = $pulsePrev; [PulseInstallerCertificateValidator]::CustomCa = $null } ` +
`} else { irm $pulseScriptUrl } } | iex`
);
};
@@ -176,10 +232,10 @@ export const buildWindowsAgentInstallCommand = ({
...(normalizedToken ? ['-TokenFile $pulseTokenFile'] : []),
];
const certificateValidationCallback = insecure
? `{ param($sender,$certificate,$chain,$sslPolicyErrors) return $true }`
: `({ param($sender,$certificate,$chain,$sslPolicyErrors) if ($null -eq $pulseCustomCa) { return $sslPolicyErrors -eq [System.Net.Security.SslPolicyErrors]::None }; if ($null -eq $certificate) { return $false }; $pulseChain=[System.Security.Cryptography.X509Certificates.X509Chain]::new(); $pulseChain.ChainPolicy.RevocationMode=[System.Security.Cryptography.X509Certificates.X509RevocationMode]::NoCheck; $null=$pulseChain.ChainPolicy.ExtraStore.Add($pulseCustomCa); $null=$pulseChain.Build($certificate); foreach ($pulseElement in $pulseChain.ChainElements) { if ($pulseElement.Certificate.Thumbprint -eq $pulseCustomCa.Thumbprint) { return $true } }; return $false }).GetNewClosure()`;
? `[PulseInstallerCertificateValidator]::AcceptAnyCallback`
: `[PulseInstallerCertificateValidator]::ValidateCustomCaCallback`;
const customTrustFetch = installerFetchRequiresCustomTrust
? `$pulseCustomCa=$null; if (-not [string]::IsNullOrWhiteSpace($pulseCaCertPath)) { $pulseCustomCaBytes=[System.IO.File]::ReadAllBytes($pulseCaCertPath); $pulseCustomCaText=[System.Text.Encoding]::ASCII.GetString($pulseCustomCaBytes); if ($pulseCustomCaText.Contains("-----BEGIN CERTIFICATE-----")) { $pulseCustomCa=[System.Security.Cryptography.X509Certificates.X509Certificate2]::CreateFromPem($pulseCustomCaText) } else { $pulseCustomCa=[System.Security.Cryptography.X509Certificates.X509Certificate2]::new($pulseCustomCaBytes) } }; $pulsePrev=[System.Net.ServicePointManager]::ServerCertificateValidationCallback; try { [System.Net.ServicePointManager]::ServerCertificateValidationCallback=${certificateValidationCallback}; Invoke-WebRequest -Uri $pulseScriptUrl -UseBasicParsing -OutFile $pulseInstallScript } finally { [System.Net.ServicePointManager]::ServerCertificateValidationCallback=$pulsePrev }`
? `${powerShellTlsValidatorBootstrap}$pulseCustomCa=$null; if (-not [string]::IsNullOrWhiteSpace($pulseCaCertPath)) { ${powerShellLoadCustomCa('$pulseCaCertPath')}}; $pulsePrev=[System.Net.ServicePointManager]::ServerCertificateValidationCallback; try { [System.Net.ServicePointManager]::ServerCertificateValidationCallback=${certificateValidationCallback}; Invoke-WebRequest -Uri $pulseScriptUrl -UseBasicParsing -OutFile $pulseInstallScript } finally { [System.Net.ServicePointManager]::ServerCertificateValidationCallback=$pulsePrev; [PulseInstallerCertificateValidator]::CustomCa=$null }`
: `Invoke-WebRequest -Uri $pulseScriptUrl -UseBasicParsing -OutFile $pulseInstallScript`;
const tokenBootstrap = normalizedToken
@@ -1842,6 +1842,52 @@ func TestReleasePipelinePromotesOneImmutableCandidate(t *testing.T) {
}
}
func TestReleaseCutGatesCriticalFrontendAndWindowsRuntimeProof(t *testing.T) {
content, err := os.ReadFile(repoFile(".github", "workflows", "create-release.yml"))
if err != nil {
t.Fatalf("read create-release.yml: %v", err)
}
workflow := string(content)
frontendJob := workflowJobBlock(t, workflow, "frontend_checks")
windowsJob := workflowJobBlock(t, workflow, "windows_install_command_smoke")
smokeJob := workflowJobBlock(t, workflow, "release_smoke")
createJob := workflowJobBlock(t, workflow, "create_release")
verdictJob := workflowJobBlock(t, workflow, "release_verdict")
for _, needle := range []string{
`npm --prefix frontend-modern run type-check`,
`npm --prefix frontend-modern test`,
} {
if !strings.Contains(frontendJob, needle) {
t.Fatalf("frontend release gate missing %s", needle)
}
}
for _, needle := range []string{
`runs-on: windows-2025`,
`agentInstallCommand.windows.test.ts`,
} {
if !strings.Contains(windowsJob, needle) {
t.Fatalf("Windows install-command release gate missing %s", needle)
}
}
for _, needle := range []string{
`tests/95-release-smoke.spec.ts`,
`release-smoke-failures-${{ github.sha }}`,
`tests/integration/test-results/`,
} {
if !strings.Contains(smokeJob, needle) {
t.Fatalf("release render smoke missing %s", needle)
}
}
if !strings.Contains(createJob, `needs.windows_install_command_smoke.result == 'success'`) {
t.Fatal("release assembly must fail closed on the Windows install-command smoke")
}
if !strings.Contains(verdictJob, `require_result "Windows install command smoke" "$WINDOWS_INSTALL_COMMAND_RESULT" success`) {
t.Fatal("definitive release verdict must report the Windows install-command smoke")
}
}
func TestCreateReleasePublishesPrivateProRuntime(t *testing.T) {
content, err := os.ReadFile(repoFile(".github", "workflows", "create-release.yml"))
if err != nil {
+18
View File
@@ -124,6 +124,24 @@ func TestNativeWindowsSelfTestDoesNotPreseedLifecycleState(t *testing.T) {
}
}
func TestNativeWindowsExecutesGeneratedInstallCommand(t *testing.T) {
content, err := os.ReadFile(repoFile(".github", "workflows", "unified-agent-native.yml"))
if err != nil {
t.Fatalf("read native agent workflow: %v", err)
}
workflow := string(content)
for _, needle := range []string{
`frontend-modern/src/utils/agentInstallCommand.ts`,
`Execute generated command with Windows PowerShell 5.1`,
`agentInstallCommand.windows.test.ts`,
} {
if !strings.Contains(workflow, needle) {
t.Fatalf("native Windows workflow missing generated install-command proof: %s", needle)
}
}
}
func TestInstallPS1OwnsWindowsServiceLoggingAndRecovery(t *testing.T) {
content, err := os.ReadFile(repoFile("scripts", "install.ps1"))
if err != nil {
@@ -2,6 +2,7 @@ import { expect, test, type Page } from "@playwright/test";
import { ensureAuthenticated } from "./helpers";
const DESKTOP_VIEWPORT = { width: 1440, height: 900 };
const pageErrors = new WeakMap<Page, Error[]>();
// Release smoke: the minimum bar every cut ships against, prereleases
// included. Each check asserts only that a primary surface renders real
@@ -20,6 +21,10 @@ async function expectNoErrorBoundary(page: Page) {
page.getByText("This page couldn't load"),
"an error boundary fired on a release-gating surface",
).toHaveCount(0);
expect(
pageErrors.get(page) ?? [],
"an uncaught browser error fired on a release-gating surface",
).toEqual([]);
}
test.describe("Release smoke", () => {
@@ -31,6 +36,9 @@ test.describe("Release smoke", () => {
"Release smoke gates on the desktop shell",
);
await page.setViewportSize(DESKTOP_VIEWPORT);
const errors: Error[] = [];
pageErrors.set(page, errors);
page.on("pageerror", (error) => errors.push(error));
await ensureAuthenticated(page);
});