Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,7 @@ function updateSelection(
outcome: RuntimeHostUpdateSelection['outcome'],
): RuntimeHostUpdateSelection {
const candidate = {
kind: 'npm_registry' as const,
version: '2.0.0',
integrity: INTEGRITY,
compatibility: 7,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,7 @@ describe('managed Runtime Host update discovery', () => {
'https://registry.npmjs.org/',
]);
assert.deepEqual(candidate, {
kind: 'npm_registry',
version: '2.0.0-beta.1',
integrity: INTEGRITY,
compatibility: 7,
Expand Down Expand Up @@ -142,6 +143,7 @@ describe('managed Runtime Host update discovery', () => {

it('admits only exact current or compatible target identities', () => {
const candidate = (version: string, compatibility?: number) => ({
kind: 'npm_registry' as const,
version,
integrity: INTEGRITY,
...(compatibility === undefined ? {} : { compatibility }),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ describe('managed Runtime Host update package acquisition', () => {
it('binds the official archive to its extracted release evidence', async () => {
const calls: string[][] = [];
const candidate = {
kind: 'npm_registry' as const,
version: '2.0.0',
integrity: INTEGRITY,
compatibility: 7,
Expand Down Expand Up @@ -96,6 +97,7 @@ describe('managed Runtime Host update package acquisition', () => {
await assert.rejects(
withRuntimeHostRegistryUpdatePackage(
{
kind: 'npm_registry',
version: '2.0.0',
integrity: `sha512-${Buffer.alloc(64).toString('base64')}`,
},
Expand All @@ -118,7 +120,12 @@ describe('managed Runtime Host update package acquisition', () => {

await assert.rejects(
withRuntimeHostRegistryUpdatePackage(
{ version: '2.0.0', integrity: INTEGRITY, compatibility: 7 },
{
kind: 'npm_registry',
version: '2.0.0',
integrity: INTEGRITY,
compatibility: 7,
},
async () => assert.fail('invalid manifest must not expose a package'),
async (args) => {
if (args[0] === 'pack') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -225,7 +225,12 @@ describe('managed Runtime Host update reconciliation', () => {
assert.deepEqual(options.expectedTarget, TARGET);
return {
selector: options.selector,
candidate: { version: '2.0.0', integrity: INTEGRITY, compatibility: 1 },
candidate: {
kind: 'npm_registry',
version: '2.0.0',
integrity: INTEGRITY,
compatibility: 1,
},
outcome: { kind: 'unattended_update', compatibility: 1 },
currentCliPath: '/managed/current/cli.js',
service: SERVICE,
Expand Down Expand Up @@ -292,7 +297,12 @@ describe('managed Runtime Host update reconciliation', () => {
await writeRuntimeHostManagedUpdatePolicy(deploymentRoot, null);
return {
selector: options.selector,
candidate: { version: '2.0.0', integrity: INTEGRITY, compatibility: 1 },
candidate: {
kind: 'npm_registry',
version: '2.0.0',
integrity: INTEGRITY,
compatibility: 1,
},
outcome: { kind: 'unattended_update', compatibility: 1 },
currentCliPath: '/managed/current/cli.js',
service: SERVICE,
Expand Down Expand Up @@ -356,7 +366,7 @@ describe('managed Runtime Host update reconciliation', () => {
createBackend: () => unusedBackend(),
resolveSelection: async (options) => ({
selector: options.selector,
candidate: { version: '2.0.0', integrity: INTEGRITY },
candidate: { kind: 'npm_registry', version: '2.0.0', integrity: INTEGRITY },
outcome: { kind: 'manual_action', reason: 'target_compatibility_unknown' },
currentCliPath: '/managed/current/cli.js',
service: SERVICE,
Expand Down
6 changes: 3 additions & 3 deletions packages/cli/src/runtime-host-update-discovery.ts
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ import {
RUNTIME_HOST_SERVICE_ERROR_CODE_MAX_BYTES,
RUNTIME_HOST_SERVICE_ERROR_MESSAGE_MAX_BYTES,
type RuntimeHostServiceManagementFrame,
type RuntimeHostNpmDeploymentIdentity,
} from '@maka/runtime-host/operator';
import {
manageRuntimeHostService,
Expand All @@ -51,9 +52,7 @@ const REGISTRY_TIMEOUT_MS = 30_000;
const REGISTRY_OUTPUT_MAX_BYTES = 64 * 1024;
const MANIFEST_MAX_BYTES = 64 * 1024;

export interface RuntimeHostUpdateCandidate {
readonly version: string;
readonly integrity: string;
export interface RuntimeHostUpdateCandidate extends RuntimeHostNpmDeploymentIdentity {
readonly compatibility?: number;
}

Expand Down Expand Up @@ -275,6 +274,7 @@ export async function resolveRuntimeHostRegistryUpdateCandidate(
}
const compatibility = positiveInteger(metadata[COMPATIBILITY_FIELD]);
return {
kind: 'npm_registry',
version,
integrity,
...(compatibility === undefined ? {} : { compatibility }),
Expand Down
5 changes: 2 additions & 3 deletions packages/cli/src/runtime-host-update-package.ts
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import { createReadStream } from 'node:fs';
import { lstat, mkdir, mkdtemp, readFile, readdir, realpath, rm, stat } from 'node:fs/promises';
import { tmpdir } from 'node:os';
import { join } from 'node:path';
import { isProductReleaseVersion, isSha512PackageIntegrity } from '@maka/runtime-host/operator';
import { isRuntimeHostNpmDeploymentIdentity } from '@maka/runtime-host/operator';
import type { RuntimeHostUpdateCandidate } from './runtime-host-update-discovery.js';

const PACKAGE_NAME = 'maka-agent';
Expand Down Expand Up @@ -53,8 +53,7 @@ export async function withRuntimeHostRegistryUpdatePackage<T>(
runNpm: RunNpm = runNpmCommand,
): Promise<T> {
if (
!isProductReleaseVersion(candidate.version) ||
!isSha512PackageIntegrity(candidate.integrity) ||
!isRuntimeHostNpmDeploymentIdentity(candidate) ||
(candidate.compatibility !== undefined &&
(!Number.isInteger(candidate.compatibility) || candidate.compatibility <= 0))
) {
Expand Down
3 changes: 3 additions & 0 deletions packages/runtime-host/src/operator/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -59,5 +59,8 @@ export {
export {
compareProductReleaseVersions,
isProductReleaseVersion,
isRuntimeHostNpmDeploymentIdentity,
isSha512PackageIntegrity,
type RuntimeHostDeploymentIdentity,
type RuntimeHostNpmDeploymentIdentity,
} from './update-package-evidence.js';
26 changes: 26 additions & 0 deletions packages/runtime-host/src/operator/update-package-evidence.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,28 @@ interface ProductReleaseVersion {
readonly prerelease: readonly string[];
}

export interface RuntimeHostNpmDeploymentIdentity {
readonly kind: 'npm_registry';
readonly version: string;
readonly integrity: string;
}

/** Exact artifact evidence the Runtime Host can currently verify. */
export type RuntimeHostDeploymentIdentity = RuntimeHostNpmDeploymentIdentity;

export function isRuntimeHostNpmDeploymentIdentity(
value: unknown,
): value is RuntimeHostNpmDeploymentIdentity {
return (
isRecord(value) &&
value.kind === 'npm_registry' &&
typeof value.version === 'string' &&
isProductReleaseVersion(value.version) &&
typeof value.integrity === 'string' &&
isSha512PackageIntegrity(value.integrity)
);
}

export function isProductReleaseVersion(value: string): boolean {
return parseProductReleaseVersion(value) !== undefined;
}
Expand Down Expand Up @@ -78,3 +100,7 @@ function parseProductReleaseVersion(value: string): ProductReleaseVersion | unde
prerelease,
};
}

function isRecord(value: unknown): value is Record<string, unknown> {
return value !== null && typeof value === 'object' && !Array.isArray(value);
}