Initial curated chimera source from a8ae5b1db07fb49926c94585e2718e5130330974
This commit is contained in:
commit
c737a37f71
531 files changed
+119270
No files matched your search
@@ -0,0 +1,341 @@
|
||||
'use strict';
|
||||
|
||||
const assert = require('node:assert/strict');
|
||||
const crypto = require('node:crypto');
|
||||
const fs = require('node:fs');
|
||||
const os = require('node:os');
|
||||
const path = require('node:path');
|
||||
const test = require('node:test');
|
||||
const { EventEmitter } = require('node:events');
|
||||
const {
|
||||
A8_DISTINCT_CONTROL,
|
||||
A10_TRANSITION_CONTROL,
|
||||
} = require('../../src/services/ps5-native-pkg-builder-provider');
|
||||
const { createPs5UploadRuntimeService } = require('../../src/services/ps5upload-runtime-service');
|
||||
const { createPs5FpkgDistinctControlPackageService } = require('../../src/services/ps5-fpkg-distinct-control-package-service');
|
||||
const { createP15A10RuntimeStageService, CONFIRMATIONS } = require('../../src/services/ps5-p15-a10-runtime-stage-service');
|
||||
const { P15_A10_RUNTIME_PROFILE } = require('../../src/services/ps5-p15-a10-runtime-profile');
|
||||
const { IDENTITY, SCHEMA, BASE_REVISION } = require('../../src/services/ps5-shellui-transition-observation');
|
||||
|
||||
const digest = value => crypto.createHash('sha256').update(value).digest('hex');
|
||||
const fileDigest = filename => crypto.createHash('sha256').update(fs.readFileSync(filename)).digest('hex');
|
||||
|
||||
test('A10 control has one fresh immutable identity and exact frozen distribution artifact', () => {
|
||||
assert.equal(A10_TRANSITION_CONTROL.titleId, 'CHIM00003');
|
||||
assert.equal(A10_TRANSITION_CONTROL.contentId, 'UP9000-CHIM00003_00-A10CONTROL000003');
|
||||
assert.equal(A10_TRANSITION_CONTROL.confirmation, 'INSTALL PS5 A10 CONTROL CHIM00003');
|
||||
assert.notEqual(A10_TRANSITION_CONTROL.titleId, A8_DISTINCT_CONTROL.titleId);
|
||||
assert.equal(A10_TRANSITION_CONTROL.contentVersion, A8_DISTINCT_CONTROL.contentVersion);
|
||||
assert.deepEqual(A10_TRANSITION_CONTROL.assets, A8_DISTINCT_CONTROL.assets);
|
||||
assert.match(A10_TRANSITION_CONTROL.selectedArtifact.sha256, /^[a-f0-9]{64}$/);
|
||||
assert.ok(A10_TRANSITION_CONTROL.selectedArtifact.size > 1_000_000);
|
||||
const artifact = path.resolve('assets', 'ps5-controls', 'a10', A10_TRANSITION_CONTROL.selectedArtifact.filename);
|
||||
assert.equal(fs.statSync(artifact).size, A10_TRANSITION_CONTROL.selectedArtifact.size);
|
||||
assert.equal(fileDigest(artifact), A10_TRANSITION_CONTROL.selectedArtifact.sha256);
|
||||
});
|
||||
|
||||
test('A10 materialization requires the dedicated bridge capability and preserves unproven console state', async t => {
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'chimera-a10-control-'));
|
||||
t.after(() => fs.rmSync(root, { recursive: true, force: true }));
|
||||
let registered = null;
|
||||
const service = createPs5FpkgDistinctControlPackageService({
|
||||
outputRoot: path.join(root, 'output'),
|
||||
stateFile: path.join(root, 'state.json'),
|
||||
selectedArtifactPath: path.resolve('assets', 'ps5-controls', 'a10', A10_TRANSITION_CONTROL.selectedArtifact.filename),
|
||||
descriptor: A10_TRANSITION_CONTROL,
|
||||
builder: {
|
||||
probe: async () => ({ pinned: true, clean: true, controlAvailable: false, transitionControlAvailable: true, product: 'LibProsperoPkg', version: '2.6.0', revision: A10_TRANSITION_CONTROL.upstreamRevision, abi: 'chimera-prospero-bridge-v4', patched: false }),
|
||||
buildDistinctControl: async () => { throw new Error('frozen selection must not rebuild'); },
|
||||
validatePackage: async () => ({ accepted: true }),
|
||||
inspectPackage: async () => ({ type: 'FullDebug', contentId: A10_TRANSITION_CONTROL.contentId }),
|
||||
},
|
||||
assessMetadata: () => ({ qualified: true, status: 'metadata_qualified' }),
|
||||
registrar: async manifest => { registered = manifest; return { packageSetId: 73, titleId: 91 }; },
|
||||
});
|
||||
const result = await service.prepareSelectedAndRegister();
|
||||
assert.equal(result.status, 'ready');
|
||||
assert.equal(result.installed, false);
|
||||
assert.equal(result.launchVerified, false);
|
||||
assert.equal(result.productionRestorable, false);
|
||||
assert.equal(registered.controlProfile, A10_TRANSITION_CONTROL.controlProfile);
|
||||
assert.equal(registered.evidence.consoleInstall.status, 'unproven');
|
||||
assert.equal(registered.evidence.consoleLaunch.status, 'unproven');
|
||||
});
|
||||
|
||||
function diagnosticRuntime(t, { versionIdentity = IDENTITY } = {}) {
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'chimera-a10-runtime-'));
|
||||
t.after(() => fs.rmSync(root, { recursive: true, force: true }));
|
||||
const engine = Buffer.from('a10-engine');
|
||||
const payload = Buffer.from('a10-payload');
|
||||
const dpi = Buffer.from('production-dpi');
|
||||
const enginePath = path.join(root, 'engine');
|
||||
const payloadPath = path.join(root, 'payload.elf');
|
||||
const dpiPath = path.join(root, 'dpi.elf');
|
||||
fs.writeFileSync(enginePath, engine, { mode: 0o750 });
|
||||
fs.writeFileSync(payloadPath, payload);
|
||||
fs.writeFileSync(dpiPath, dpi);
|
||||
const child = new EventEmitter();
|
||||
child.stdin = { end() {} }; child.stderr = new EventEmitter(); child.kill = () => true;
|
||||
let engineOwned = false; let generation = 1;
|
||||
const diagnostic = {
|
||||
identity: IDENTITY,
|
||||
baseRevision: BASE_REVISION,
|
||||
observationSchema: SCHEMA,
|
||||
sourceSha256: '1'.repeat(64),
|
||||
diffSha256: '2'.repeat(64),
|
||||
};
|
||||
const runtime = createPs5UploadRuntimeService({
|
||||
enginePath, payloadPath, dpiPath,
|
||||
expectedEngineSha256: digest(engine), expectedPayloadSha256: digest(payload), expectedDpiSha256: digest(dpi),
|
||||
diagnostic,
|
||||
consoleHost: () => '192.0.2.55',
|
||||
fetch: async url => {
|
||||
const href = String(url);
|
||||
if (href.endsWith('/api/version')) {
|
||||
if (!engineOwned) throw new Error('not listening');
|
||||
return { ok: true, status: 200, json: async () => ({ version: '5.4.19', diagnostic_identity: versionIdentity, base_revision: BASE_REVISION, observation_schema: SCHEMA }) };
|
||||
}
|
||||
if (href.includes('/api/ps5/status')) return { ok: true, status: 200, json: async () => ({ version: '5.4.19', instance_id: String(generation), started_at_unix: generation }) };
|
||||
return { ok: true, status: 200, json: async () => ({ ok: true, users: [] }) };
|
||||
},
|
||||
spawn: () => { engineOwned = true; return child; },
|
||||
waitForEngine: async () => ({ available: true, version: '5.4.19', diagnosticIdentity: versionIdentity, baseRevision: BASE_REVISION, observationSchema: SCHEMA }),
|
||||
tcpProbe: async input => input.port !== 9040,
|
||||
enginePortProbe: async () => false,
|
||||
tcpSend: async input => { generation += 1; return { ok: true, bytes: input.bytes.length }; },
|
||||
delay: async () => {},
|
||||
processPlatform: 'win32',
|
||||
});
|
||||
return { runtime, diagnostic };
|
||||
}
|
||||
|
||||
test('A10 runtime becomes exact only after exact diagnostic engine and payload transition', async t => {
|
||||
const { runtime } = diagnosticRuntime(t);
|
||||
await runtime.startEngine();
|
||||
await runtime.startPayload();
|
||||
const identity = await runtime.runtimeIdentity();
|
||||
assert.equal(identity.lane, 'diagnostic');
|
||||
assert.equal(identity.status, 'exact');
|
||||
assert.equal(identity.engine.diagnosticIdentity, IDENTITY);
|
||||
assert.equal(identity.payload.diagnosticIdentity, IDENTITY);
|
||||
assert.equal(identity.expected.diagnostic.identity, IDENTITY);
|
||||
});
|
||||
|
||||
test('diagnostic runtime rejects a same-version engine with the wrong derivative identity', async t => {
|
||||
const { runtime } = diagnosticRuntime(t, { versionIdentity: 'chimera-p15-shellui-observe-v1' });
|
||||
await assert.rejects(() => runtime.startEngine(), error => error.code === 'PS5UPLOAD_ENGINE_START_FAILED');
|
||||
const identity = await runtime.runtimeIdentity();
|
||||
assert.equal(identity.status, 'unproven');
|
||||
assert.equal(identity.mutationAllowed, false);
|
||||
});
|
||||
|
||||
function stageFixture({
|
||||
diagnosticStartError = null,
|
||||
diagnosticStartGate = null,
|
||||
diagnosticStartProven = true,
|
||||
productionIdentityExact = true,
|
||||
productionStopResult = { stopped: true, endpointReleased: true, identityGone: true },
|
||||
diagnosticStopResult = { stopped: true, endpointReleased: true, identityGone: true },
|
||||
} = {}) {
|
||||
const calls = [];
|
||||
let currentTime = 1_000;
|
||||
let productionActive = true; let diagnosticActive = false; let payloadExact = false;
|
||||
const productionRuntime = {
|
||||
status: async () => ({ engine: { verified: true, available: productionActive, processOwned: productionActive, sha256: 'a'.repeat(64) }, payload: { verified: true, sha256: 'b'.repeat(64) } }),
|
||||
runtimeIdentity: async () => ({
|
||||
schema: 'chimera.ps5upload-runtime-identity.v1',
|
||||
lane: 'production',
|
||||
status: productionActive && productionIdentityExact ? 'exact' : 'unproven',
|
||||
mutationAllowed: productionActive && productionIdentityExact,
|
||||
engine: { observedSha256: 'a'.repeat(64) },
|
||||
payload: { observedSha256: 'b'.repeat(64) },
|
||||
}),
|
||||
stopEngine: async () => { calls.push('production-stop'); productionActive = false; return productionStopResult; },
|
||||
startEngine: async () => { calls.push('production-start'); productionActive = true; return { available: true, processOwned: true }; },
|
||||
};
|
||||
const diagnosticRuntime = {
|
||||
status: async () => ({ engine: { verified: true, available: diagnosticActive, processOwned: diagnosticActive && diagnosticStartProven, sha256: P15_A10_RUNTIME_PROFILE.engine.sha256 }, payload: { verified: true, sha256: P15_A10_RUNTIME_PROFILE.payload.sha256 } }),
|
||||
startEngine: async () => { calls.push('diagnostic-start'); if (diagnosticStartGate) await diagnosticStartGate; if (diagnosticStartError) throw diagnosticStartError; diagnosticActive = true; return { available: true, processOwned: true }; },
|
||||
stopEngine: async () => { calls.push('diagnostic-stop'); diagnosticActive = false; return diagnosticStopResult; },
|
||||
startPayload: async () => { calls.push('payload-start'); payloadExact = true; return { sent: true, managementReady: true }; },
|
||||
runtimeIdentity: async () => ({
|
||||
schema: 'chimera.ps5upload-runtime-identity.v1',
|
||||
lane: 'diagnostic',
|
||||
status: payloadExact ? 'exact' : 'unproven',
|
||||
mutationAllowed: payloadExact,
|
||||
expected: { diagnostic: { identity: P15_A10_RUNTIME_PROFILE.identity } },
|
||||
engine: { observedSha256: P15_A10_RUNTIME_PROFILE.engine.sha256 },
|
||||
payload: { observedSha256: P15_A10_RUNTIME_PROFILE.payload.sha256 },
|
||||
}),
|
||||
};
|
||||
return {
|
||||
calls,
|
||||
advance: milliseconds => { currentTime += milliseconds; },
|
||||
service: createP15A10RuntimeStageService({ productionRuntime, diagnosticRuntime, now: () => currentTime }),
|
||||
};
|
||||
}
|
||||
|
||||
test('A10 runtime stage is actor-bound, single-use and never starts the PS5 payload implicitly', async () => {
|
||||
const { calls, service } = stageFixture();
|
||||
const plan = await service.plan('session-a');
|
||||
assert.equal(plan.liveActionAuthorized, false);
|
||||
assert.equal(plan.payloadStartAuthorized, false);
|
||||
await assert.rejects(() => service.stage({ planDigest: plan.planDigest, confirmation: CONFIRMATIONS.stage }, 'session-b'), error => error.code === 'P15_A10_RUNTIME_ACTOR_MISMATCH');
|
||||
const staged = await service.stage({ planDigest: plan.planDigest, confirmation: CONFIRMATIONS.stage }, 'session-a');
|
||||
assert.equal(staged.runtimeState, 'diagnostic_engine_active');
|
||||
assert.deepEqual(calls, ['production-stop', 'diagnostic-start']);
|
||||
await assert.rejects(() => service.stage({ planDigest: plan.planDigest, confirmation: CONFIRMATIONS.stage }, 'session-a'), error => error.code === 'P15_A10_RUNTIME_PLAN_REQUIRED');
|
||||
assert.equal(calls.includes('payload-start'), false);
|
||||
const status = await service.status();
|
||||
assert.equal(status.transitionEvidence.valid, false);
|
||||
assert.equal(status.transitionEvidence.production.identity.status, 'exact');
|
||||
assert.equal(status.transitionEvidence.diagnostic, null);
|
||||
});
|
||||
|
||||
test('A10 runtime stage rejects an unproven production identity before the controlled handoff', async () => {
|
||||
const { calls, service } = stageFixture({ productionIdentityExact: false });
|
||||
await assert.rejects(() => service.plan('session-a'), error => error.code === 'P15_A10_PRODUCTION_RUNTIME_IDENTITY_UNPROVEN');
|
||||
assert.deepEqual(calls, []);
|
||||
});
|
||||
|
||||
test('A10 diagnostic start failure restores the production engine before surfacing the failure', async () => {
|
||||
const { calls, service } = stageFixture({ diagnosticStartError: new Error('bind failed') });
|
||||
const plan = await service.plan('session-a');
|
||||
await assert.rejects(() => service.stage({ planDigest: plan.planDigest, confirmation: CONFIRMATIONS.stage }, 'session-a'), error => error.code === 'P15_A10_RUNTIME_STAGE_FAILED' && error.details.productionRestored === true);
|
||||
assert.deepEqual(calls, ['production-stop', 'diagnostic-start', 'diagnostic-stop', 'production-start']);
|
||||
});
|
||||
|
||||
test('A10 unproven diagnostic start is stopped before production recovery', async () => {
|
||||
const { calls, service } = stageFixture({ diagnosticStartProven: false });
|
||||
const plan = await service.plan('session-a');
|
||||
await assert.rejects(
|
||||
() => service.stage({ planDigest: plan.planDigest, confirmation: CONFIRMATIONS.stage }, 'session-a'),
|
||||
error => error.code === 'P15_A10_RUNTIME_STAGE_FAILED'
|
||||
&& error.details?.cause === 'P15_A10_DIAGNOSTIC_RUNTIME_START_UNPROVEN'
|
||||
&& error.details?.productionRestored === true,
|
||||
);
|
||||
assert.deepEqual(calls, ['production-stop', 'diagnostic-start', 'diagnostic-stop', 'production-start']);
|
||||
});
|
||||
|
||||
test('A10 failed diagnostic start never restores production without complete diagnostic stop proof', async () => {
|
||||
const { calls, service } = stageFixture({
|
||||
diagnosticStartProven: false,
|
||||
diagnosticStopResult: { stopped: true, endpointReleased: false, identityGone: true },
|
||||
});
|
||||
const plan = await service.plan('session-a');
|
||||
await assert.rejects(
|
||||
() => service.stage({ planDigest: plan.planDigest, confirmation: CONFIRMATIONS.stage }, 'session-a'),
|
||||
error => error.code === 'P15_A10_RUNTIME_STAGE_FAILED'
|
||||
&& error.details?.diagnosticStopped === false
|
||||
&& error.details?.productionRestored === false,
|
||||
);
|
||||
assert.deepEqual(calls, ['production-stop', 'diagnostic-start', 'diagnostic-stop']);
|
||||
});
|
||||
|
||||
test('A10 staging never starts a replacement on incomplete production stop proof', async () => {
|
||||
const { calls, service } = stageFixture({
|
||||
productionStopResult: { stopped: true, endpointReleased: true, identityGone: false },
|
||||
});
|
||||
const plan = await service.plan('session-a');
|
||||
await assert.rejects(
|
||||
() => service.stage({ planDigest: plan.planDigest, confirmation: CONFIRMATIONS.stage }, 'session-a'),
|
||||
error => error.code === 'P15_A10_RUNTIME_STAGE_FAILED'
|
||||
&& error.details?.cause === 'P15_A10_PRODUCTION_RUNTIME_STOP_FAILED',
|
||||
);
|
||||
assert.deepEqual(calls, ['production-stop']);
|
||||
});
|
||||
|
||||
test('A10 runtime planning and staging reject overlapping mutations', async () => {
|
||||
let releaseDiagnostic;
|
||||
const diagnosticStartGate = new Promise(resolve => { releaseDiagnostic = resolve; });
|
||||
const { service } = stageFixture({ diagnosticStartGate });
|
||||
const firstPlan = service.plan('session-a');
|
||||
await assert.rejects(() => service.plan('session-a'), error => error.code === 'P15_A10_RUNTIME_BUSY');
|
||||
const plan = await firstPlan;
|
||||
const firstStage = service.stage({ planDigest: plan.planDigest, confirmation: CONFIRMATIONS.stage }, 'session-a');
|
||||
await new Promise(resolve => setImmediate(resolve));
|
||||
await assert.rejects(() => service.stage({ planDigest: plan.planDigest, confirmation: CONFIRMATIONS.stage }, 'session-a'), error => error.code === 'P15_A10_RUNTIME_BUSY');
|
||||
releaseDiagnostic();
|
||||
await firstStage;
|
||||
});
|
||||
|
||||
test('A10 payload and production restore stay behind separate service operations', async () => {
|
||||
const { calls, service } = stageFixture();
|
||||
const plan = await service.plan('session-a');
|
||||
await service.stage({ planDigest: plan.planDigest, confirmation: CONFIRMATIONS.stage }, 'session-a');
|
||||
const loaded = await service.startPayload({ confirmation: CONFIRMATIONS.payload });
|
||||
assert.equal(loaded.identity.status, 'exact');
|
||||
const diagnosticStatus = await service.status();
|
||||
assert.equal(diagnosticStatus.transitionEvidence.valid, true);
|
||||
assert.equal(diagnosticStatus.transitionEvidence.production.identity.lane, 'production');
|
||||
assert.equal(diagnosticStatus.transitionEvidence.diagnostic.identity.lane, 'diagnostic');
|
||||
assert.equal(diagnosticStatus.transitionEvidence.profileIdentity, P15_A10_RUNTIME_PROFILE.identity);
|
||||
const restored = await service.restore({ confirmation: CONFIRMATIONS.restore });
|
||||
assert.equal(restored.runtimeState, 'production_engine_active');
|
||||
assert.equal((await service.status()).transitionEvidence, null);
|
||||
assert.deepEqual(calls, ['production-stop', 'diagnostic-start', 'payload-start', 'diagnostic-stop', 'production-start']);
|
||||
});
|
||||
|
||||
test('A10 restore never starts production on incomplete diagnostic stop proof', async () => {
|
||||
const { calls, service } = stageFixture({
|
||||
diagnosticStopResult: { stopped: true, endpointReleased: false, identityGone: true },
|
||||
});
|
||||
const plan = await service.plan('session-a');
|
||||
await service.stage({ planDigest: plan.planDigest, confirmation: CONFIRMATIONS.stage }, 'session-a');
|
||||
await assert.rejects(
|
||||
() => service.restore({ confirmation: CONFIRMATIONS.restore }),
|
||||
error => error.code === 'P15_A10_DIAGNOSTIC_RUNTIME_STOP_FAILED',
|
||||
);
|
||||
assert.deepEqual(calls, ['production-stop', 'diagnostic-start', 'diagnostic-stop']);
|
||||
});
|
||||
|
||||
test('A10 payload start fails closed before console contact when transition evidence expired', async () => {
|
||||
const { advance, calls, service } = stageFixture();
|
||||
const plan = await service.plan('session-a');
|
||||
await service.stage({ planDigest: plan.planDigest, confirmation: CONFIRMATIONS.stage }, 'session-a');
|
||||
advance(120_000);
|
||||
await assert.rejects(() => service.startPayload({ confirmation: CONFIRMATIONS.payload }), error => error.code === 'P15_A10_TRANSITION_EVIDENCE_EXPIRED');
|
||||
assert.equal(calls.includes('payload-start'), false);
|
||||
assert.equal((await service.status()).transitionEvidence, null);
|
||||
});
|
||||
|
||||
test('server and Docker expose A10 only through separate guarded product lanes', () => {
|
||||
const registry = fs.readFileSync('src/server/create-registry.js', 'utf8');
|
||||
const server = fs.readFileSync('server.js', 'utf8');
|
||||
const docker = fs.readFileSync('Dockerfile', 'utf8');
|
||||
const preload = fs.readFileSync('preload.js', 'utf8');
|
||||
const web = fs.readFileSync('renderer/pork-web-shim.js', 'utf8');
|
||||
const modal = fs.readFileSync('renderer/modules/modal.js', 'utf8');
|
||||
const main = fs.readFileSync('main.js', 'utf8');
|
||||
const english = JSON.parse(fs.readFileSync('build/lang/en.json', 'utf8')).strings;
|
||||
const dutch = JSON.parse(fs.readFileSync('build/lang/nl.json', 'utf8')).strings;
|
||||
assert.match(registry, /ps5-native-packages:a10-transition-control-status[\s\S]*classification: 'read-only'/);
|
||||
assert.match(registry, /ps5-native-packages:a10-transition-control-install[\s\S]*classification: 'destructive'/);
|
||||
assert.match(registry, /ps5-p15-a10:runtime-stage[\s\S]*classification: 'destructive'/);
|
||||
assert.match(registry, /P15_SHELLUI_TRANSITION_DIAGNOSTIC/);
|
||||
assert.match(registry, /ps5P15A10RuntimeStage\.status\(\)/);
|
||||
assert.match(registry, /transitionEvidence\?\.valid === true/);
|
||||
assert.match(registry, /transitionEvidence\.profileIdentity === P15_A10_RUNTIME_PROFILE\.identity/);
|
||||
assert.match(server, /ps5P15A10Runtime\?\.stopEngine/);
|
||||
assert.match(server, /ps5ProfileRuntime\?\.stopEngine/);
|
||||
assert.match(preload, /ps5A10TransitionControl/);
|
||||
assert.match(preload, /ps5P15A10/);
|
||||
assert.match(web, /ps5A10TransitionControl/);
|
||||
assert.match(web, /ps5P15A10/);
|
||||
assert.match(main, /INSTALL PS5 A10 CONTROL CHIM00003/);
|
||||
assert.match(modal, /ps5A10TransitionControl\('install-plan'\)/);
|
||||
assert.match(modal, /ps5P15A10\('runtime-stage'/);
|
||||
assert.match(modal, /ps5P15A10\('payload-start'/);
|
||||
assert.match(modal, /ps5P15A10\('runtime-restore'/);
|
||||
assert.match(modal, /const runtimeEligible = \['installed_observed', 'launch_failed', 'launch_verified'\]\.includes\(op\)/);
|
||||
assert.match(modal, /ui\('stage'\)\.disabled = !runtimeEligible \|\| active/);
|
||||
assert.match(modal, /const launchEligible = op === 'installed_observed'/);
|
||||
assert.match(modal, /ui\('launch'\)\.disabled = !launchEligible \|\| !exact/);
|
||||
for (const key of ['title', 'detail', 'stage', 'payload', 'launch', 'restore', 'payloadBoundary', 'restoreBoundary']) {
|
||||
assert.equal(typeof english[`ps5NativePackages.a10Control.${key}`], 'string');
|
||||
assert.equal(typeof dutch[`ps5NativePackages.a10Control.${key}`], 'string');
|
||||
}
|
||||
assert.match(docker, new RegExp(A10_TRANSITION_CONTROL.selectedArtifact.sha256));
|
||||
assert.match(docker, /ps5-diagnostics[\\/]a10[\\/]ps5upload-engine/);
|
||||
assert.match(docker, /ps5-diagnostics[\\/]a10[\\/]ps5upload\.elf/);
|
||||
});
|
||||
Reference in new issue
Block a user