mirror of
https://github.com/deepseek-ai/deepseek-harness.git
synced 2026-10-26 16:55:08 +00:00
test(windows): compare signing concurrency with supervised probes
This commit is contained in:
1 parent
0f21742b25
commit
4db4345865
7 files changed
+692
-2
No files matched your search
@@ -0,0 +1,70 @@
|
||||
/** Bounded scheduling for the opt-in signing experiment; production signing does not use this helper. */
|
||||
|
||||
/** Operations owned by one experiment, with independently bounded completion and hardware work. */
|
||||
export interface SigningExperimentOptions<T> {
|
||||
readonly jobs: 1 | 2
|
||||
readonly hardwareConcurrency: 1 | 2
|
||||
readonly signal?: AbortSignal
|
||||
/** Complete one item, awaiting the supplied signing operation and all subsequent work before returning. */
|
||||
readonly complete: (item: T, sign: (path: string) => Promise<void>) => Promise<void>
|
||||
/** Perform one hardware request without retrying it. */
|
||||
readonly sign: (item: T, path: string) => Promise<void>
|
||||
}
|
||||
|
||||
/**
|
||||
* Schedule independent experiment items and stop dispatch on the first failure or cancellation.
|
||||
* @param items Ordered inputs owned by this batch.
|
||||
* @param options Completion workers, hardware limit, cancellation and experiment operations.
|
||||
* @returns Resolves after every item completes; otherwise drains issued work before rejecting with the first reason.
|
||||
* Cancellation prevents new items and hardware requests but does not terminate an issued native operation.
|
||||
*/
|
||||
export async function runSigningExperimentBatch<T>(items: readonly T[], options: SigningExperimentOptions<T>): Promise<void> {
|
||||
let failure: { reason: unknown } | undefined
|
||||
let next = 0
|
||||
let signing = 0
|
||||
const requests = new Set<Promise<void>>()
|
||||
const waiting = new Set<() => void>()
|
||||
const wake = (): void => {
|
||||
for (const resume of waiting) resume()
|
||||
waiting.clear()
|
||||
}
|
||||
const stop = (reason: unknown): void => {
|
||||
failure ??= { reason }
|
||||
wake()
|
||||
}
|
||||
const aborted = (): void => { stop(options.signal?.reason) }
|
||||
const signItem = async (item: T, path: string): Promise<void> => {
|
||||
while (failure === undefined && signing >= options.hardwareConcurrency) {
|
||||
await new Promise<void>((resume) => { waiting.add(resume) })
|
||||
}
|
||||
if (failure !== undefined) throw failure.reason
|
||||
signing++
|
||||
try { await options.sign(item, path) }
|
||||
catch (error) { stop(error); throw error }
|
||||
finally { signing--; wake() }
|
||||
}
|
||||
const worker = async (): Promise<void> => {
|
||||
while (failure === undefined && next < items.length) {
|
||||
const item = items[next++]!
|
||||
try {
|
||||
await options.complete(item, (path) => {
|
||||
const request = signItem(item, path)
|
||||
requests.add(request)
|
||||
void request.then(() => requests.delete(request), () => requests.delete(request))
|
||||
return request
|
||||
})
|
||||
}
|
||||
catch (error) { stop(error) }
|
||||
}
|
||||
}
|
||||
options.signal?.addEventListener('abort', aborted, { once: true })
|
||||
try {
|
||||
if (options.signal?.aborted) aborted()
|
||||
await Promise.all(Array.from({ length: Math.min(options.jobs, items.length) }, worker))
|
||||
// A completion failure can leave a previously issued request awaiting its native result.
|
||||
await Promise.allSettled(requests)
|
||||
if (failure !== undefined) throw failure.reason
|
||||
} finally {
|
||||
options.signal?.removeEventListener('abort', aborted)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,180 @@
|
||||
/** Operator-only comparison of serial signing, post-sign overlap and two concurrent hardware requests. */
|
||||
import { execFile } from 'node:child_process'
|
||||
import { createHash, X509Certificate } from 'node:crypto'
|
||||
import { existsSync } from 'node:fs'
|
||||
import { copyFile, mkdir, readFile, writeFile } from 'node:fs/promises'
|
||||
import { homedir, cpus, totalmem } from 'node:os'
|
||||
import { isAbsolute, join, resolve } from 'node:path'
|
||||
import { parseArgs, promisify } from 'node:util'
|
||||
import { fileURLToPath } from 'node:url'
|
||||
import { loadDesktopPackageEnvironment } from '../scripts/desktop-package-environment.mjs'
|
||||
import { createPackagingRun, recordPackagingEvent } from '../scripts/packaging-run.mjs'
|
||||
import { beginWindowsSigningAttempt } from '../scripts/windows-signing-state.mjs'
|
||||
import { withWindowsSigningStage } from '../scripts/windows-signing-stage.mjs'
|
||||
import { inspectWindowsTokenIdentity, windowsTokenIdentityFailure } from '../scripts/windows-token-identity.ts'
|
||||
import { buildWindowsSigningEnvironment, createWindowsTokenSigner, scrubWindowsSigningEnvironment } from '../scripts/windows-sign.mjs'
|
||||
import { inspectWindowsRuntimeSignature } from '../scripts/windows-runtime-signature.mjs'
|
||||
import { completeWindowsSignature, normalizeWindowsSignature } from '../scripts/windows-timestamp.mjs'
|
||||
import { runSigningExperimentBatch } from './helpers/windows-signing-experiment.ts'
|
||||
|
||||
const APP_ROOT = fileURLToPath(new URL('..', import.meta.url))
|
||||
const MODES = [
|
||||
{ name: 'serial', jobs: 1, hardwareConcurrency: 1 },
|
||||
{ name: 'pipeline', jobs: 2, hardwareConcurrency: 1 },
|
||||
{ name: 'hardware2', jobs: 2, hardwareConcurrency: 2 },
|
||||
] as const
|
||||
|
||||
/**
|
||||
* Run an explicitly opted-in hardware experiment, retaining all generated probes and audit records.
|
||||
* @param args CLI arguments; omission uses the current process arguments.
|
||||
* @returns Resolves after all requested batches verify; any failure retains the batch interlock and stops later modes.
|
||||
*/
|
||||
export async function runWindowsSigningConcurrencyExperiment(args = process.argv.slice(2)): Promise<void> {
|
||||
const { values } = parseArgs({ args, options: {
|
||||
execute: { type: 'boolean' }, 'config-root': { type: 'string' }, rounds: { type: 'string', default: '2' },
|
||||
} })
|
||||
if (!values.execute) {
|
||||
console.log('Operator hardware experiment: node apps/desktop/tests/windows-signing-concurrency.perf.ts --execute --config-root <absolute-machine-packaging-directory> [--rounds 1|2]')
|
||||
return
|
||||
}
|
||||
if (process.platform !== 'win32' || !values['config-root'] || !isAbsolute(values['config-root'])
|
||||
|| !['1', '2'].includes(values.rounds)) throw new Error('Requires Windows, an absolute config-root and rounds 1 or 2')
|
||||
const environment = loadDesktopPackageEnvironment('win32', {
|
||||
...process.env, DSH_PACKAGING_PRESET: 'test', DSH_PACKAGING_CONFIG_DIR: values['config-root'],
|
||||
})
|
||||
const certificateFile = environment.DSH_DESKTOP_WINDOWS_CER_FILE!
|
||||
const signTool = environment.DSH_DESKTOP_WINDOWS_SIGNTOOL!
|
||||
const tokenPin = environment.DSH_DESKTOP_WINDOWS_TOKEN_PIN!
|
||||
const keyContainer = environment.DSH_DESKTOP_WINDOWS_KEY_CONTAINER!.trim()
|
||||
// Construction validates the same signing inputs; the production serial hook is never invoked here.
|
||||
createWindowsTokenSigner({ certificateFile, signTool, tokenPin, keyContainer })
|
||||
const certificate = await readFile(certificateFile)
|
||||
const leaf = new X509Certificate(certificate)
|
||||
if (Date.now() < Date.parse(leaf.validFrom) || Date.now() > Date.parse(leaf.validTo)) {
|
||||
throw new Error('Signing experiment requires a currently valid public certificate')
|
||||
}
|
||||
const thumbprint = leaf.fingerprint.replaceAll(':', '')
|
||||
const safeEnvironment = scrubWindowsSigningEnvironment(environment)
|
||||
delete safeEnvironment.NODE_OPTIONS
|
||||
delete safeEnvironment.NODE_PATH
|
||||
const run = createPackagingRun(join(APP_ROOT, '.desktop-build', 'qualification', 'signing-concurrency'), {
|
||||
kind: 'operator-signing-concurrency', certificateSha256: createHash('sha256').update(certificate).digest('hex'),
|
||||
node: process.version, cpu: cpus()[0]?.model, logicalCpus: cpus().length, memoryBytes: totalmem(),
|
||||
rounds: Number(values.rounds), resourceBytes: [0, 1024 * 1024, 16 * 1024 * 1024], filesPerBatch: 6,
|
||||
cache: 'disabled', probeExecuted: false,
|
||||
}, { parallel: true, secrets: [tokenPin, keyContainer] })
|
||||
console.log(`SIGNING_EXPERIMENT_RECORD ${run.directory}`)
|
||||
const controller = new AbortController()
|
||||
const abort = (): void => { controller.abort(new Error('Operator interrupted the signing experiment')) }
|
||||
process.once('SIGINT', abort)
|
||||
process.once('SIGTERM', abort)
|
||||
const record = (event: object): void => { recordPackagingEvent(run.directory, event) }
|
||||
let success = false
|
||||
try {
|
||||
await withWindowsSigningStage({ stage: 'operator-signing-concurrency', signal: controller.signal, record }, async () => {
|
||||
if (existsSync(join(homedir(), '.dsh-desktop-signing', 'attempt.json'))) {
|
||||
throw new Error('Retained signing interlock requires operator inspection; experiment refused')
|
||||
}
|
||||
const identity = await inspectWindowsTokenIdentity({ certificate, container: keyContainer, environment: safeEnvironment })
|
||||
record({ type: 'experiment-public-identity', result: identity, pinSupplied: false })
|
||||
if (identity !== 'matched') throw new Error(windowsTokenIdentityFailure(identity))
|
||||
const templates: string[] = []
|
||||
const source = join(run.directory, 'probe.cs')
|
||||
await writeFile(source, 'internal static class SigningProbe { private static int Main() { return 0; } }\n', { flag: 'wx' })
|
||||
for (const size of [0, 1024 * 1024, 16 * 1024 * 1024]) {
|
||||
controller.signal.throwIfAborted()
|
||||
const resource = join(run.directory, `resource-${size}.bin`)
|
||||
if (size) await writeFile(resource, Buffer.alloc(size, 0x5a), { flag: 'wx' })
|
||||
const output = join(run.directory, `unsigned-${size}.exe`)
|
||||
await run.run(`compile-probe-${size}`, join(environment.SystemRoot ?? 'C:\\Windows', 'Microsoft.NET/Framework64/v4.0.30319/csc.exe'),
|
||||
['/nologo', '/target:exe', '/platform:x64', `/out:${output}`, ...(size ? [`/resource:${resource}`] : []), source],
|
||||
{ cwd: run.directory, env: safeEnvironment, timeoutMs: 30_000 })
|
||||
if ((await inspectWindowsRuntimeSignature(output)).status !== 'NotSigned') throw new Error('Probe must be unsigned')
|
||||
templates.push(output)
|
||||
}
|
||||
const results: { round: number; mode: string; elapsedMs: number; files: number }[] = []
|
||||
for (let round = 1; round <= Number(values.rounds); round++) {
|
||||
const order = round % 2 ? MODES : [...MODES].reverse()
|
||||
for (const mode of order) {
|
||||
controller.signal.throwIfAborted()
|
||||
const directory = join(run.directory, `round-${round}-${mode.name}`)
|
||||
await mkdir(directory)
|
||||
const files: string[] = []
|
||||
for (let index = 0; index < templates.length * 2; index++) {
|
||||
const file = join(directory, `probe-${index}.exe`)
|
||||
await copyFile(templates[Math.floor(index / 2)]!, file)
|
||||
files.push(file)
|
||||
}
|
||||
record({ type: 'experiment-batch-plan', round, ...mode, files })
|
||||
const attempt = beginWindowsSigningAttempt({ runDirectory: run.directory, target: directory })
|
||||
let commandIndex = 0
|
||||
const started = performance.now()
|
||||
try {
|
||||
await runSigningExperimentBatch(files, {
|
||||
...mode, signal: controller.signal,
|
||||
sign: async (_file, path) => {
|
||||
controller.signal.throwIfAborted()
|
||||
const stage = `experiment-sign-${round}-${mode.name}-${commandIndex++}`
|
||||
record({ type: 'experiment-hardware-start', stage, path })
|
||||
const interpreter = environment.ComSpec ?? join(environment.SystemRoot ?? 'C:\\Windows', 'System32', 'cmd.exe')
|
||||
await run.run(stage, interpreter,
|
||||
['/d', '/v:off', '/c', 'windows-sign.cmd'], {
|
||||
cwd: join(APP_ROOT, 'scripts'), timeoutMs: 60_000,
|
||||
env: buildWindowsSigningEnvironment(safeEnvironment, {
|
||||
certificateFile, signTool, tokenPin, keyContainer, path, isNest: false,
|
||||
}),
|
||||
})
|
||||
record({ type: 'experiment-hardware-exited', stage, path })
|
||||
const signature = await inspectWindowsRuntimeSignature(path)
|
||||
if (signature.status !== 'Valid' || signature.timestamped || signature.thumbprint?.toUpperCase() !== thumbprint.toUpperCase()) {
|
||||
throw new Error('Primary signature verification failed')
|
||||
}
|
||||
record({ type: 'experiment-hardware-verified', stage, path })
|
||||
},
|
||||
complete: async (path, sign) => {
|
||||
await completeWindowsSignature(path, {
|
||||
thumbprint, sign, inspect: inspectWindowsRuntimeSignature, evidenceDirectory: run.directory, record,
|
||||
normalize: file => normalizeWindowsSignature(file, signTool, safeEnvironment),
|
||||
timestamp: async (file) => {
|
||||
await promisify(execFile)(signTool, ['timestamp', '/v', '/tr', 'http://timestamp.digicert.com', '/td', 'sha256', file], {
|
||||
env: safeEnvironment, windowsHide: true, timeout: 60_000,
|
||||
})
|
||||
},
|
||||
})
|
||||
const signature = await inspectWindowsRuntimeSignature(path)
|
||||
if (signature.status !== 'Valid' || !signature.timestamped || signature.thumbprint?.toUpperCase() !== thumbprint.toUpperCase()) {
|
||||
throw new Error('Published probe signature verification failed')
|
||||
}
|
||||
record({ type: 'experiment-file-verified', path, ...signature,
|
||||
sha256: createHash('sha256').update(await readFile(path)).digest('hex') })
|
||||
},
|
||||
})
|
||||
controller.signal.throwIfAborted()
|
||||
const result = { round, mode: mode.name, elapsedMs: performance.now() - started, files: files.length }
|
||||
results.push(result)
|
||||
record({ type: 'experiment-batch-complete', ...result })
|
||||
await writeFile(join(run.directory, 'measurements.json'), `${JSON.stringify(results, null, 2)}\n`, { flush: true })
|
||||
attempt.success()
|
||||
console.log(`SIGNING_EXPERIMENT_SAMPLE ${JSON.stringify(result)}`)
|
||||
} catch (error) {
|
||||
attempt.failure(null, 'Signing concurrency experiment failed; inspect retained records before recovery')
|
||||
throw error
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
success = true
|
||||
} finally {
|
||||
process.removeListener('SIGINT', abort)
|
||||
process.removeListener('SIGTERM', abort)
|
||||
run.finish(success)
|
||||
}
|
||||
}
|
||||
|
||||
if (process.argv[1] !== undefined && resolve(process.argv[1]) === fileURLToPath(import.meta.url)) {
|
||||
runWindowsSigningConcurrencyExperiment().catch(() => {
|
||||
// Child-process errors can embed arguments; the retained supervisor output is already redacted.
|
||||
console.error('Signing experiment stopped; inspect its retained record. No automatic hardware retry or interlock recovery.')
|
||||
process.exitCode = 1
|
||||
})
|
||||
}
|
||||
@@ -0,0 +1,178 @@
|
||||
/** Driver ownership tests replace every native signing and device boundary. */
|
||||
import { existsSync, mkdirSync, readFileSync, unlinkSync, writeFileSync } from 'node:fs'
|
||||
import { mkdtemp, rm, writeFile } from 'node:fs/promises'
|
||||
import { tmpdir } from 'node:os'
|
||||
import { dirname, join } from 'node:path'
|
||||
import { afterEach, beforeEach, expect, it, vi } from 'vitest'
|
||||
import { runWindowsSigningConcurrencyExperiment } from './windows-signing-concurrency.perf.ts'
|
||||
import { loadDesktopPackageEnvironment } from '../scripts/desktop-package-environment.mjs'
|
||||
import { createPackagingRun } from '../scripts/packaging-run.mjs'
|
||||
import { beginWindowsSigningAttempt } from '../scripts/windows-signing-state.mjs'
|
||||
import { inspectWindowsTokenIdentity } from '../scripts/windows-token-identity.ts'
|
||||
|
||||
const state = vi.hoisted(() => ({
|
||||
root: '', environment: {} as NodeJS.ProcessEnv,
|
||||
identity: 'matched', failure: '' as '' | 'primary' | 'final' | 'audit',
|
||||
signed: new Set<string>(), completed: new Set<string>(), stages: [] as string[],
|
||||
events: [] as object[], successes: 0, failures: 0,
|
||||
}))
|
||||
|
||||
vi.mock('node:child_process', () => ({
|
||||
execFile: vi.fn(() => { throw new Error('Native subprocesses are forbidden in driver tests') }),
|
||||
}))
|
||||
vi.mock('node:os', async original => ({ ...await original<typeof import('node:os')>(), homedir: () => state.root }))
|
||||
vi.mock('node:crypto', async original => ({
|
||||
...await original<typeof import('node:crypto')>(),
|
||||
X509Certificate: class {
|
||||
fingerprint = 'A'.repeat(40)
|
||||
validFrom = 'Jan 1 2000 GMT'
|
||||
validTo = 'Jan 1 2100 GMT'
|
||||
},
|
||||
}))
|
||||
vi.mock('../scripts/desktop-package-environment.mjs', () => ({
|
||||
loadDesktopPackageEnvironment: vi.fn(() => state.environment),
|
||||
}))
|
||||
vi.mock('../scripts/windows-sign.mjs', async original => ({
|
||||
...await original<typeof import('../scripts/windows-sign.mjs')>(),
|
||||
createWindowsTokenSigner: vi.fn(() => { throw new Error('unconfigured constructor') }),
|
||||
}))
|
||||
vi.mock('../scripts/windows-token-identity.ts', () => ({
|
||||
inspectWindowsTokenIdentity: vi.fn(async () => state.identity),
|
||||
windowsTokenIdentityFailure: (result: string) => `Identity refused: ${result}`,
|
||||
}))
|
||||
vi.mock('../scripts/windows-signing-stage.mjs', () => ({
|
||||
withWindowsSigningStage: vi.fn(async (_options: object, operation: () => Promise<void>) => operation()),
|
||||
}))
|
||||
vi.mock('../scripts/packaging-run.mjs', () => ({
|
||||
createPackagingRun: vi.fn(() => ({
|
||||
directory: join(state.root, 'run'),
|
||||
finish: vi.fn(),
|
||||
run: vi.fn(async (stage: string, _executable: string, args: readonly string[], options: { env: NodeJS.ProcessEnv }) => {
|
||||
state.stages.push(stage)
|
||||
if (stage.startsWith('compile-')) {
|
||||
await writeFile(args.find(arg => arg.startsWith('/out:'))!.slice(5), 'inert probe')
|
||||
} else {
|
||||
state.signed.add(options.env.DSH_DESKTOP_WINDOWS_SIGN_TARGET!)
|
||||
}
|
||||
}),
|
||||
})),
|
||||
recordPackagingEvent: (_directory: string, event: object) => {
|
||||
if (state.failure === 'audit' && 'type' in event && event.type === 'experiment-file-verified') throw new Error('Audit failed')
|
||||
state.events.push(event)
|
||||
},
|
||||
}))
|
||||
vi.mock('../scripts/windows-signing-state.mjs', () => ({
|
||||
beginWindowsSigningAttempt: vi.fn((options: { runDirectory: string; target: string; stateDirectory?: string }) => {
|
||||
expect(options.stateDirectory).toBeUndefined()
|
||||
const lock = join(state.root, '.dsh-desktop-signing', 'attempt.json')
|
||||
mkdirSync(dirname(lock), { recursive: true })
|
||||
writeFileSync(lock, 'batch attempt', { flag: 'wx' })
|
||||
return {
|
||||
success: () => {
|
||||
const verified = state.events.filter(event => 'type' in event && event.type === 'experiment-file-verified'
|
||||
&& 'path' in event && typeof event.path === 'string' && dirname(event.path) === options.target)
|
||||
expect(verified).toHaveLength(6)
|
||||
expect(readFileSync(join(options.runDirectory, 'measurements.json'), 'utf8')).toContain('"files": 6')
|
||||
unlinkSync(lock)
|
||||
state.successes++
|
||||
},
|
||||
failure: () => { state.failures++ },
|
||||
}
|
||||
}),
|
||||
}))
|
||||
vi.mock('../scripts/windows-runtime-signature.mjs', () => ({
|
||||
inspectWindowsRuntimeSignature: vi.fn(async (path: string) => {
|
||||
if (!state.signed.has(path)) return { status: 'NotSigned', timestamped: false, thumbprint: null }
|
||||
const timestamped = state.completed.has(path)
|
||||
const failed = state.failure === (timestamped ? 'final' : 'primary')
|
||||
return { status: failed ? 'HashMismatch' : 'Valid', timestamped, thumbprint: 'A'.repeat(40) }
|
||||
}),
|
||||
}))
|
||||
vi.mock('../scripts/windows-timestamp.mjs', () => ({
|
||||
completeWindowsSignature: vi.fn(async (path: string, options: { sign: (path: string) => Promise<void> }) => {
|
||||
await options.sign(path)
|
||||
state.completed.add(path)
|
||||
}),
|
||||
normalizeWindowsSignature: vi.fn(() => { throw new Error('Native normalization is forbidden in driver tests') }),
|
||||
}))
|
||||
|
||||
const platform = Object.getOwnPropertyDescriptor(process, 'platform')!
|
||||
beforeEach(async () => {
|
||||
vi.clearAllMocks()
|
||||
state.root = await mkdtemp(join(tmpdir(), 'signing-driver-'))
|
||||
mkdirSync(join(state.root, 'run'))
|
||||
state.identity = 'matched'; state.failure = ''; state.successes = 0; state.failures = 0
|
||||
state.signed.clear(); state.completed.clear(); state.stages = []; state.events = []
|
||||
state.environment = {
|
||||
DSH_DESKTOP_WINDOWS_CER_FILE: join(state.root, 'certificate.cer'),
|
||||
DSH_DESKTOP_WINDOWS_SIGNTOOL: 'forbidden-signer', DSH_DESKTOP_WINDOWS_TOKEN_PIN: 'fixture-pin',
|
||||
DSH_DESKTOP_WINDOWS_KEY_CONTAINER: ' fixture-container ', API_KEY: 'fixture-secret', NODE_OPTIONS: 'forbidden-option',
|
||||
}
|
||||
await writeFile(state.environment.DSH_DESKTOP_WINDOWS_CER_FILE!, 'inert certificate')
|
||||
Object.defineProperty(process, 'platform', { value: 'win32', configurable: true })
|
||||
const { createWindowsTokenSigner } = await import('../scripts/windows-sign.mjs')
|
||||
vi.mocked(createWindowsTokenSigner).mockReturnValue(vi.fn(async () => { throw new Error('Production signer is forbidden') }))
|
||||
vi.spyOn(console, 'log').mockImplementation(() => {})
|
||||
})
|
||||
afterEach(async () => {
|
||||
Object.defineProperty(process, 'platform', platform)
|
||||
vi.useRealTimers()
|
||||
vi.restoreAllMocks()
|
||||
await rm(state.root, { recursive: true, force: true, maxRetries: 3 })
|
||||
})
|
||||
|
||||
const execute = () => runWindowsSigningConcurrencyExperiment(['--execute', '--config-root', state.root, '--rounds', '1'])
|
||||
const lock = () => join(state.root, '.dsh-desktop-signing', 'attempt.json')
|
||||
|
||||
it('requires opt-in before reading configuration or querying a device', async () => {
|
||||
await runWindowsSigningConcurrencyExperiment([])
|
||||
expect(loadDesktopPackageEnvironment).not.toHaveBeenCalled()
|
||||
expect(inspectWindowsTokenIdentity).not.toHaveBeenCalled()
|
||||
expect(createPackagingRun).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it.each(['1999-01-01', '2101-01-01'])('rejects certificate validity at %s before querying or signing', async (date) => {
|
||||
vi.useFakeTimers({ toFake: ['Date'] }); vi.setSystemTime(new Date(date))
|
||||
await expect(execute()).rejects.toThrow('currently valid')
|
||||
expect(inspectWindowsTokenIdentity).not.toHaveBeenCalled()
|
||||
expect(beginWindowsSigningAttempt).not.toHaveBeenCalled()
|
||||
expect(state.stages).toEqual([])
|
||||
})
|
||||
|
||||
it.each(['certificate-mismatch', 'provider-unavailable', 'container-unavailable', 'unknown'])('refuses public identity %s before any signing', async (identity) => {
|
||||
state.identity = identity
|
||||
await expect(execute()).rejects.toThrow('Identity refused')
|
||||
expect(state.stages).toEqual([])
|
||||
expect(beginWindowsSigningAttempt).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('retains an existing default-account interlock without accessing the device', async () => {
|
||||
mkdirSync(dirname(lock()), { recursive: true }); writeFileSync(lock(), 'prior failure')
|
||||
await expect(execute()).rejects.toThrow('Retained signing interlock')
|
||||
expect(inspectWindowsTokenIdentity).not.toHaveBeenCalled()
|
||||
expect(readFileSync(lock(), 'utf8')).toBe('prior failure')
|
||||
})
|
||||
|
||||
it('completes all three modes and releases each default-account batch only after recording every verification', async () => {
|
||||
await execute()
|
||||
expect(state.successes).toBe(3)
|
||||
expect(state.failures).toBe(0)
|
||||
expect(state.signed.size).toBe(18)
|
||||
expect(existsSync(lock())).toBe(false)
|
||||
expect(beginWindowsSigningAttempt).toHaveBeenCalledTimes(3)
|
||||
expect(inspectWindowsTokenIdentity).toHaveBeenCalledWith(expect.objectContaining({
|
||||
container: 'fixture-container', environment: {},
|
||||
}))
|
||||
expect(JSON.stringify(state.events)).not.toMatch(/fixture-pin|fixture-secret|fixture-container/u)
|
||||
})
|
||||
|
||||
it.each(['primary', 'final', 'audit'] as const)('retains the batch interlock and stops later modes after %s failure', async (failure) => {
|
||||
state.failure = failure
|
||||
await expect(execute()).rejects.toThrow(failure === 'audit' ? 'Audit failed' : 'signature verification failed')
|
||||
expect(state.successes).toBe(0)
|
||||
expect(state.failures).toBe(1)
|
||||
expect(beginWindowsSigningAttempt).toHaveBeenCalledOnce()
|
||||
expect(state.signed.size).toBe(1)
|
||||
expect(existsSync(lock())).toBe(true)
|
||||
expect(state.stages.some(stage => stage.includes('pipeline') || stage.includes('hardware2'))).toBe(false)
|
||||
})
|
||||
@@ -0,0 +1,258 @@
|
||||
import { expect, it, vi } from 'vitest'
|
||||
import { runSigningExperimentBatch } from './helpers/windows-signing-experiment.ts'
|
||||
|
||||
function barrier() {
|
||||
const pending = Promise.withResolvers<undefined>()
|
||||
return { promise: pending.promise, resolve: () => { pending.resolve(undefined) } }
|
||||
}
|
||||
const observe = (operation: Promise<void>) => operation.then(
|
||||
() => ({ ok: true as const }), (error: unknown) => ({ ok: false as const, error }),
|
||||
)
|
||||
|
||||
it('finishes each item before beginning the next in serial mode', async () => {
|
||||
const events: string[] = []
|
||||
await runSigningExperimentBatch([1, 2], {
|
||||
jobs: 1, hardwareConcurrency: 1,
|
||||
sign: async (item, path) => { expect(path).toBe(`staged-${item}`); events.push(`sign ${item}`) },
|
||||
complete: async (item, sign) => {
|
||||
events.push(`start ${item}`)
|
||||
await sign(`staged-${item}`)
|
||||
events.push(`complete ${item}`)
|
||||
},
|
||||
})
|
||||
expect(events).toEqual(['start 1', 'sign 1', 'complete 1', 'start 2', 'sign 2', 'complete 2'])
|
||||
})
|
||||
|
||||
it('overlaps completion with the next signature while keeping hardware concurrency at one', async () => {
|
||||
const firstSigning = barrier()
|
||||
const releaseFirstSignature = barrier()
|
||||
const secondStarted = barrier()
|
||||
const firstPost = barrier()
|
||||
const secondSigned = barrier()
|
||||
const releaseCompletion = barrier()
|
||||
const started: number[] = []
|
||||
let active = 0
|
||||
let peak = 0
|
||||
const outcome = observe(runSigningExperimentBatch([1, 2, 3], {
|
||||
jobs: 2, hardwareConcurrency: 1,
|
||||
sign: async (item) => {
|
||||
active++
|
||||
peak = Math.max(peak, active)
|
||||
try {
|
||||
if (item === 1) { firstSigning.resolve(); await releaseFirstSignature.promise }
|
||||
if (item === 2) secondSigned.resolve()
|
||||
} finally { active-- }
|
||||
},
|
||||
complete: async (item, sign) => {
|
||||
started.push(item)
|
||||
if (item === 2) secondStarted.resolve()
|
||||
await sign(`staged-${item}`)
|
||||
if (item === 1) firstPost.resolve()
|
||||
await releaseCompletion.promise
|
||||
},
|
||||
}))
|
||||
try {
|
||||
await Promise.all([firstSigning.promise, secondStarted.promise])
|
||||
expect(active).toBe(1)
|
||||
releaseFirstSignature.resolve()
|
||||
await Promise.all([firstPost.promise, secondSigned.promise])
|
||||
expect(started).toEqual([1, 2])
|
||||
expect(peak).toBe(1)
|
||||
} finally {
|
||||
releaseFirstSignature.resolve()
|
||||
releaseCompletion.resolve()
|
||||
expect(await outcome).toEqual({ ok: true })
|
||||
}
|
||||
expect(started).toEqual([1, 2, 3])
|
||||
})
|
||||
|
||||
it('allows two hardware requests but does not start a third item while both jobs are occupied', async () => {
|
||||
const bothSigning = barrier()
|
||||
const release = barrier()
|
||||
const started: number[] = []
|
||||
let active = 0
|
||||
let peak = 0
|
||||
const outcome = observe(runSigningExperimentBatch([1, 2, 3], {
|
||||
jobs: 2, hardwareConcurrency: 2,
|
||||
sign: async () => {
|
||||
active++
|
||||
peak = Math.max(peak, active)
|
||||
if (active === 2) bothSigning.resolve()
|
||||
try { await release.promise } finally { active-- }
|
||||
},
|
||||
complete: async (item, sign) => { started.push(item); await sign(`staged-${item}`) },
|
||||
}))
|
||||
try {
|
||||
await bothSigning.promise
|
||||
expect(started).toEqual([1, 2])
|
||||
expect(active).toBe(2)
|
||||
} finally { release.resolve(); expect(await outcome).toEqual({ ok: true }) }
|
||||
expect(peak).toBe(2)
|
||||
expect(active).toBe(0)
|
||||
})
|
||||
|
||||
it.each([new Error('hardware failed'), undefined])('stops queued signatures and new items after a hardware rejection (%s)', async (failure) => {
|
||||
const secondStarted = barrier()
|
||||
const sign = vi.fn(async () => { await secondStarted.promise; throw failure })
|
||||
const started: number[] = []
|
||||
const result = await observe(runSigningExperimentBatch([1, 2, 3], {
|
||||
jobs: 2, hardwareConcurrency: 1, sign,
|
||||
complete: async (item, request) => {
|
||||
started.push(item)
|
||||
if (item === 2) secondStarted.resolve()
|
||||
await request(`staged-${item}`)
|
||||
},
|
||||
}))
|
||||
expect(result).toEqual({ ok: false, error: failure })
|
||||
expect(sign).toHaveBeenCalledTimes(1)
|
||||
expect(started).toEqual([1, 2])
|
||||
})
|
||||
|
||||
it('drains concurrent hardware after the first failure and retains that failure', async () => {
|
||||
const secondSigning = barrier()
|
||||
const firstFailed = barrier()
|
||||
const releaseSecond = barrier()
|
||||
const failure = new Error('first hardware failure')
|
||||
const calls: number[] = []
|
||||
let active = 0
|
||||
let settled = false
|
||||
const outcome = observe(runSigningExperimentBatch([1, 2, 3], {
|
||||
jobs: 2, hardwareConcurrency: 2,
|
||||
sign: async (item) => {
|
||||
calls.push(item)
|
||||
active++
|
||||
try {
|
||||
if (item === 1) { await secondSigning.promise; throw failure }
|
||||
secondSigning.resolve()
|
||||
await releaseSecond.promise
|
||||
throw new Error('later hardware failure')
|
||||
} finally { active-- }
|
||||
},
|
||||
complete: async (item, sign) => {
|
||||
try { await sign(`staged-${item}`) }
|
||||
catch (error) { if (item === 1) firstFailed.resolve(); throw error }
|
||||
},
|
||||
})).then((result) => { settled = true; return result })
|
||||
try {
|
||||
await firstFailed.promise
|
||||
expect(settled).toBe(false)
|
||||
expect(active).toBe(1)
|
||||
expect(calls).toEqual([1, 2])
|
||||
} finally { releaseSecond.resolve(); expect(await outcome).toEqual({ ok: false, error: failure }) }
|
||||
expect(active).toBe(0)
|
||||
expect(calls).toEqual([1, 2])
|
||||
})
|
||||
|
||||
it('preserves an undefined completion failure and starts no further work', async () => {
|
||||
const sign = vi.fn(async () => {})
|
||||
const complete = vi.fn(async () => { throw undefined })
|
||||
expect(await observe(runSigningExperimentBatch([1, 2], {
|
||||
jobs: 1, hardwareConcurrency: 1, complete, sign,
|
||||
}))).toEqual({ ok: false, error: undefined })
|
||||
expect(complete).toHaveBeenCalledTimes(1)
|
||||
expect(sign).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('prevents a waiting signature when completion fails and drains the issued hardware request', async () => {
|
||||
const secondSigning = barrier()
|
||||
const releaseSecond = barrier()
|
||||
const failureObserved = barrier()
|
||||
const failure = new Error('timestamp failed')
|
||||
const calls: number[] = []
|
||||
let pending: ReturnType<typeof observe> | undefined
|
||||
let settled = false
|
||||
const outcome = observe(runSigningExperimentBatch([1, 2, 3], {
|
||||
jobs: 2, hardwareConcurrency: 1,
|
||||
sign: async (item) => {
|
||||
calls.push(item)
|
||||
if (item === 2) { secondSigning.resolve(); await releaseSecond.promise }
|
||||
},
|
||||
complete: async (item, sign) => {
|
||||
await sign(`staged-${item}`)
|
||||
if (item === 1) {
|
||||
await secondSigning.promise
|
||||
// Failure can interrupt a completion that already owns a queued request.
|
||||
const waiting = observe(sign(`staged-retry-${item}`)).then((result) => { failureObserved.resolve(); return result })
|
||||
pending = waiting
|
||||
throw failure
|
||||
}
|
||||
},
|
||||
})).then((result) => { settled = true; return result })
|
||||
try {
|
||||
await failureObserved.promise
|
||||
expect(settled).toBe(false)
|
||||
expect(calls).toEqual([1, 2])
|
||||
} finally { releaseSecond.resolve(); expect(await outcome).toEqual({ ok: false, error: failure }) }
|
||||
expect(calls).toEqual([1, 2])
|
||||
expect(await pending).toEqual({ ok: false, error: failure })
|
||||
})
|
||||
|
||||
it('drains both completion workers after one fails without starting more items', async () => {
|
||||
const secondPost = barrier()
|
||||
const releaseSecondPost = barrier()
|
||||
const failed = barrier()
|
||||
const failure = new Error('verification failed')
|
||||
const started: number[] = []
|
||||
let settled = false
|
||||
const outcome = observe(runSigningExperimentBatch([1, 2, 3], {
|
||||
jobs: 2, hardwareConcurrency: 2, sign: async () => {},
|
||||
complete: async (item, sign) => {
|
||||
started.push(item)
|
||||
await sign(`staged-${item}`)
|
||||
if (item === 1) { await secondPost.promise; failed.resolve(); throw failure }
|
||||
secondPost.resolve()
|
||||
await releaseSecondPost.promise
|
||||
},
|
||||
})).then((result) => { settled = true; return result })
|
||||
try {
|
||||
await failed.promise
|
||||
expect(settled).toBe(false)
|
||||
expect(started).toEqual([1, 2])
|
||||
} finally { releaseSecondPost.resolve(); expect(await outcome).toEqual({ ok: false, error: failure }) }
|
||||
expect(started).toEqual([1, 2])
|
||||
})
|
||||
|
||||
it('cancels waiting signatures and new items while waiting for issued hardware to finish', async () => {
|
||||
const controller = new AbortController()
|
||||
const secondStarted = barrier()
|
||||
const release = barrier()
|
||||
const waitingRejected = barrier()
|
||||
const reason = new Error('operator cancelled')
|
||||
const calls: number[] = []
|
||||
let active = false
|
||||
let settled = false
|
||||
const outcome = observe(runSigningExperimentBatch([1, 2, 3], {
|
||||
jobs: 2, hardwareConcurrency: 1, signal: controller.signal,
|
||||
sign: async (item) => {
|
||||
calls.push(item)
|
||||
active = true
|
||||
try { await release.promise } finally { active = false }
|
||||
},
|
||||
complete: async (item, sign) => {
|
||||
if (item === 2) secondStarted.resolve()
|
||||
try { await sign(`staged-${item}`) }
|
||||
catch (error) { if (item === 2) waitingRejected.resolve(); throw error }
|
||||
},
|
||||
})).then((result) => { settled = true; return result })
|
||||
try {
|
||||
await secondStarted.promise
|
||||
controller.abort(reason)
|
||||
await waitingRejected.promise
|
||||
expect(active).toBe(true)
|
||||
expect(settled).toBe(false)
|
||||
expect(calls).toEqual([1])
|
||||
} finally { release.resolve(); expect(await outcome).toEqual({ ok: false, error: reason }) }
|
||||
expect(active).toBe(false)
|
||||
expect(calls).toEqual([1])
|
||||
})
|
||||
|
||||
it('does not begin work when cancellation was already requested', async () => {
|
||||
const reason = new Error('already cancelled')
|
||||
const complete = vi.fn(async () => {})
|
||||
const sign = vi.fn(async () => {})
|
||||
expect(await observe(runSigningExperimentBatch([1], {
|
||||
jobs: 2, hardwareConcurrency: 2, signal: AbortSignal.abort(reason), complete, sign,
|
||||
}))).toEqual({ ok: false, error: reason })
|
||||
expect(complete).not.toHaveBeenCalled()
|
||||
expect(sign).not.toHaveBeenCalled()
|
||||
})
|
||||
@@ -36,8 +36,8 @@
|
||||
en: 6e8208d19ad510e5
|
||||
zh: eee42e8938c0f603
|
||||
/windows-signing-and-runner-operations/verification-limits:
|
||||
en: 94a471410c84a1c7
|
||||
zh: 115228e4ca36581d
|
||||
en: e04b54cec106c2b7
|
||||
zh: 1941e4b0f0249c20
|
||||
/windows-signing-and-runner-operations/dev-note:
|
||||
en: b75c8458928b72b9
|
||||
zh: 003ba4e17b4b5269
|
||||
@@ -106,6 +106,8 @@ Use `pnpm --dir apps/desktop run cache:windows-signatures --usage` to inspect st
|
||||
|
||||
Real hardware and installation evidence is owned by the [Desktop qualification record](../tests/README.md) and [installed-update procedure](../tests/installed-update/README.md). This document does not authorize PIN retries, automatic interlock deletion, device initialization or changes to the accepted isolation policy.
|
||||
|
||||
For operator-approved concurrency experiments, run `node apps/desktop/tests/windows-signing-concurrency.perf.ts --execute --config-root <absolute-packaging-directory> --rounds 2`. Without `--execute`, it only prints usage. The diagnostic compares serial completion, two completion workers with one hardware request, and two concurrent hardware requests, using fresh copies of six synthetic PE files per batch; the second round reverses mode order. It uses the machine test preset, reads public identity first, holds the account signing-stage lock and retains one hardware-attempt interlock per batch. It stops dispatch on the first failure and drains issued work; up to two hardware calls may already be in flight. Only complete signature, timestamp and file verification permits normal interlock release. Records and probes remain under `.desktop-build/qualification/signing-concurrency`; probes are never executed and no release cache is used. Timings exclude probe preparation, lock waiting and package assembly. This experiment does not enable production concurrency or establish full-release performance.
|
||||
|
||||
## Dev Note
|
||||
|
||||
None.
|
||||
@@ -106,6 +106,8 @@ Windows 打包从 `.env.windows` 或所选预设读取 `DSH_DESKTOP_WINDOWS_PACK
|
||||
|
||||
真实硬件与安装证据由[Desktop 验证记录](../tests/README.zh.md)和[已安装更新流程](../tests/installed-update/README.zh.md)维护。本文档不授权 PIN 重试、自动删除 interlock、设备初始化或改变已接受的隔离策略。
|
||||
|
||||
经操作人员授权后,可运行 `node apps/desktop/tests/windows-signing-concurrency.perf.ts --execute --config-root <打包配置目录的绝对路径> --rounds 2` 做并发实验。不加 `--execute` 只打印用法。诊断比较完全串行、两个完成任务共用单路硬件签名,以及两路硬件签名;每批使用六个合成 PE 文件的全新副本,第二轮反转模式顺序。实验读取机器 test preset,先核对公开身份,持有账户签名阶段锁,每批保留一条硬件尝试保护记录。首错停止派发并排空已发出的工作;最多可能已有两条硬件请求在途。只有全部签名、时间戳和文件验证成功才正常释放保护记录。记录与探针保留在 `.desktop-build/qualification/signing-concurrency`,探针从不执行,也不使用发布签名缓存。计时不含探针准备、等锁及包组装,实验不启用生产并发,也不代表完整发布性能。
|
||||
|
||||
## 开发备注
|
||||
|
||||
无。
|
||||
Reference in new issue
Block a user