feat: add deployment foundation and cross-device handoff
This commit is contained in:
@@ -0,0 +1,195 @@
|
||||
// Run after building the native executable. No npm packages are required.
|
||||
import { test } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { spawnSync } from 'node:child_process';
|
||||
import { readFileSync, writeFileSync, mkdtempSync, readdirSync, rmSync } from 'node:fs';
|
||||
import { createHash } from 'node:crypto';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { resolve, join } from 'node:path';
|
||||
|
||||
const binary = resolve(process.env.DEPLOYCTL_BIN ?? (process.platform === 'win32' ? 'dist/deployctl.exe' : 'dist/deployctl'));
|
||||
const intent = JSON.parse(readFileSync(new URL('../protocol/examples/plan-request.json', import.meta.url), 'utf8'));
|
||||
|
||||
test('real CLI artifact verification rejects missing metadata without writes', () => {
|
||||
const directory = mkdtempSync(join(tmpdir(), 'deployctl-artifact-smoke-'));
|
||||
try {
|
||||
const result = spawnSync(binary, ['verify-artifacts'], {
|
||||
encoding:'utf8', timeout:10000,
|
||||
input:JSON.stringify({directory, artifactDirectory:directory, suite:'resolute', architecture:'amd64', versions:{}}),
|
||||
});
|
||||
assert.notEqual(result.status, 0);
|
||||
assert.equal(result.stdout, '');
|
||||
assert.equal(result.stderr.includes('unsupported command'), false);
|
||||
assert.equal(result.stderr.includes(directory), false);
|
||||
assert.deepEqual(readdirSync(directory), []);
|
||||
} finally {
|
||||
rmSync(directory, {recursive:true});
|
||||
}
|
||||
});
|
||||
|
||||
test('real CLI rejects caller-asserted repository authentication', () => {
|
||||
const result = spawnSync(binary, ['verify-repository'], {
|
||||
encoding: 'utf8', timeout: 10000,
|
||||
input: JSON.stringify({directory:'secret-relative', suite:'resolute', architecture:'amd64',
|
||||
versions:{}, repositoryAuthenticated:true}),
|
||||
});
|
||||
assert.notEqual(result.status, 0);
|
||||
assert.equal(result.stdout, '');
|
||||
assert.equal(result.stderr.includes('secret-relative'), false);
|
||||
});
|
||||
|
||||
test('real CLI environment draft rejects malformed locks without writing', () => {
|
||||
const directory = mkdtempSync(join(tmpdir(), 'deployctl-environment-smoke-'));
|
||||
try {
|
||||
const probe = spawnSync(binary, ['preflight'], { encoding: 'utf8', timeout: 10000 });
|
||||
assert.equal(probe.status, 0, probe.stderr);
|
||||
const report = JSON.parse(probe.stdout).report;
|
||||
const suite = report.runtime.os === 'linux' ? report.distribution.codename : 'resolute';
|
||||
const arch = report.runtime.os === 'linux' ? report.runtime.architecture : 'amd64';
|
||||
const lock = { protocolVersion: 1, repository: 'https://download.docker.com/linux/ubuntu',
|
||||
suite, architecture: arch, releaseDigest: `sha256:${'a'.repeat(64)}`,
|
||||
packages: ['docker-ce', 'docker-ce-cli', 'containerd.io', 'docker-buildx-plugin', 'docker-compose-plugin'].map(name => ({
|
||||
name, version: '1.2.3-1', filename: `dists/${suite}/pool/stable/${arch}/${name}_1.2.3-1_${arch}.deb`, digest: `sha256:${'b'.repeat(64)}`, size: 123,
|
||||
})),
|
||||
};
|
||||
const run = () => spawnSync(binary, ['plan-environment'], { cwd: directory, encoding: 'utf8', timeout: 10000, input: JSON.stringify({lock}) });
|
||||
const result = run();
|
||||
assert.equal(result.status, 0, result.stderr);
|
||||
const draft = JSON.parse(result.stdout).draft;
|
||||
assert.equal(draft.executable, false);
|
||||
assert.equal(draft.repositoryAuthenticated, false);
|
||||
assert.ok(draft.blockers.includes('dependency_transaction_unresolved'));
|
||||
assert.equal(draft.requestedPackages.length, 5);
|
||||
lock.packages[0].version = 'secret;reboot';
|
||||
const invalid = run();
|
||||
assert.notEqual(invalid.status, 0);
|
||||
assert.equal(invalid.stdout, '');
|
||||
assert.equal(invalid.stderr.includes('secret'), false);
|
||||
assert.deepEqual(readdirSync(directory), []);
|
||||
} finally {
|
||||
rmSync(directory, { recursive: true });
|
||||
}
|
||||
});
|
||||
|
||||
test('real CLI preflight reports blockers without creating files', () => {
|
||||
const directory = mkdtempSync(join(tmpdir(), 'deployctl-preflight-smoke-'));
|
||||
try {
|
||||
const result = spawnSync(binary, ['preflight'], { cwd: directory, encoding: 'utf8', timeout: 10000 });
|
||||
assert.equal(result.status, 0, result.stderr);
|
||||
const response = JSON.parse(result.stdout);
|
||||
assert.equal(response.mode, 'local-environment-proposal');
|
||||
assert.equal(response.proposal.executable, false);
|
||||
assert.ok(response.proposal.blockers.includes('package_versions_unresolved'));
|
||||
assert.ok(response.proposal.blockers.includes('host_identity_unverified'));
|
||||
assert.deepEqual(readdirSync(directory), []);
|
||||
} finally {
|
||||
rmSync(directory, { recursive: true });
|
||||
}
|
||||
});
|
||||
|
||||
test('real CLI previews and verifies without writing into its working directory', () => {
|
||||
const directory = mkdtempSync(join(tmpdir(), 'deployctl-smoke-'));
|
||||
const run = (command, input) => spawnSync(binary, [command], {
|
||||
input: JSON.stringify(input), encoding: 'utf8', cwd: directory, timeout: 10000,
|
||||
});
|
||||
try {
|
||||
const preview = run('plan', intent);
|
||||
assert.equal(preview.error, undefined);
|
||||
assert.equal(preview.status, 0, preview.stderr);
|
||||
const response = JSON.parse(preview.stdout);
|
||||
assert.equal(response.executable, false);
|
||||
assert.equal(response.mode, 'offline-preview');
|
||||
assert.equal(response.plan.projectName, 'sd-git-one');
|
||||
const check = run('verify-plan', { plan: response.plan, current: intent });
|
||||
assert.equal(check.status, 0, check.stderr);
|
||||
assert.equal(JSON.parse(check.stdout).valid, true);
|
||||
const changed = run('verify-plan', { plan: response.plan, current: { ...intent, hostId: 'different-host' } });
|
||||
assert.notEqual(changed.status, 0);
|
||||
assert.equal(changed.stdout, '');
|
||||
const apply = run('apply', response.plan);
|
||||
assert.notEqual(apply.status, 0);
|
||||
assert.equal(apply.stdout, '');
|
||||
assert.deepEqual(readdirSync(directory), []);
|
||||
} finally {
|
||||
// Only remove the unique test directory this test just created.
|
||||
rmSync(directory, { recursive: true });
|
||||
}
|
||||
});
|
||||
|
||||
test('real CLI inspects locally and verifies packages without claiming execution safety', () => {
|
||||
const inspection = spawnSync(binary, ['inspect'], { encoding: 'utf8', timeout: 10000 });
|
||||
assert.equal(inspection.status, 0, inspection.stderr);
|
||||
assert.equal(JSON.parse(inspection.stdout).deploymentReady, false);
|
||||
const directory = mkdtempSync(join(tmpdir(), 'deployctl-package-smoke-'));
|
||||
const digest = bytes => `sha256:${createHash('sha256').update(bytes).digest('hex')}`;
|
||||
try {
|
||||
const payload = 'services: {}\n';
|
||||
const manifest = JSON.stringify({
|
||||
protocolVersion: 1, appId: 'example', version: '1.0.0', runtime: 'compose',
|
||||
entrypoint: 'compose.yaml', platforms: ['linux/amd64'],
|
||||
components: [{ name: 'server', image: `example/server@sha256:${'a'.repeat(64)}` }],
|
||||
files: [{ path: 'compose.yaml', digest: digest(payload) }],
|
||||
});
|
||||
writeFileSync(join(directory, 'manifest.json'), manifest);
|
||||
writeFileSync(join(directory, 'compose.yaml'), payload);
|
||||
const result = spawnSync(binary, ['verify-package'], {
|
||||
encoding: 'utf8', timeout: 10000,
|
||||
input: JSON.stringify({ directory, expectedDigest: digest(manifest) }),
|
||||
});
|
||||
assert.equal(result.status, 0, result.stderr);
|
||||
const verified = JSON.parse(result.stdout);
|
||||
assert.equal(verified.verified, true);
|
||||
assert.equal(verified.executable, false);
|
||||
assert.equal(verified.publisherAuthenticated, false);
|
||||
assert.deepEqual(readdirSync(directory).sort(), ['compose.yaml', 'manifest.json']);
|
||||
assert.equal(readFileSync(join(directory, 'compose.yaml'), 'utf8'), payload);
|
||||
} finally {
|
||||
rmSync(directory, { recursive: true });
|
||||
}
|
||||
});
|
||||
|
||||
test('real CLI separates package integrity from restricted policy and rejects tampering', () => {
|
||||
const directory = mkdtempSync(join(tmpdir(), 'deployctl-policy-smoke-'));
|
||||
const digest = bytes => `sha256:${createHash('sha256').update(bytes).digest('hex')}`;
|
||||
const image = `example/server@sha256:${'a'.repeat(64)}`;
|
||||
const document = {
|
||||
services: { server: { image, user: '1000:1000', read_only: true, cap_drop: ['ALL'],
|
||||
security_opt: ['no-new-privileges:true'], restart: 'no', networks: ['backend'], volumes: [] } },
|
||||
networks: { backend: { internal: true } }, volumes: {},
|
||||
};
|
||||
const run = (command, expectedDigest) => spawnSync(binary, [command], {
|
||||
encoding: 'utf8', timeout: 10000, cwd: directory,
|
||||
input: JSON.stringify({ directory, expectedDigest }),
|
||||
});
|
||||
const writePackage = () => {
|
||||
const payload = JSON.stringify(document);
|
||||
const manifest = JSON.stringify({ protocolVersion: 1, appId: 'example', version: '1.0.0', runtime: 'compose',
|
||||
entrypoint: 'compose.json', platforms: ['linux/amd64'], components: [{ name: 'server', image }],
|
||||
files: [{ path: 'compose.json', digest: digest(payload) }],
|
||||
});
|
||||
writeFileSync(join(directory, 'manifest.json'), manifest);
|
||||
writeFileSync(join(directory, 'compose.json'), payload);
|
||||
return digest(manifest);
|
||||
};
|
||||
try {
|
||||
const pin = writePackage();
|
||||
const check = run('check-package', pin);
|
||||
assert.equal(check.status, 0, check.stderr);
|
||||
const result = JSON.parse(check.stdout);
|
||||
assert.equal(result.policyPassed, true);
|
||||
assert.equal(result.executable, false);
|
||||
assert.equal(result.publisherAuthenticated, false);
|
||||
assert.equal(result.digest, pin);
|
||||
writeFileSync(join(directory, 'compose.json'), '{}');
|
||||
assert.notEqual(run('check-package', pin).status, 0);
|
||||
document.services.server.privileged = true;
|
||||
const unsafePin = writePackage();
|
||||
assert.equal(run('verify-package', unsafePin).status, 0);
|
||||
const rejected = run('check-package', unsafePin);
|
||||
assert.notEqual(rejected.status, 0);
|
||||
assert.equal(rejected.stdout, '');
|
||||
assert.deepEqual(readdirSync(directory).sort(), ['compose.json', 'manifest.json']);
|
||||
} finally {
|
||||
rmSync(directory, { recursive: true });
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user