Files
stack/packages/mosaic/src/commands/brain-provision-command.ts
T

134 lines
4.9 KiB
TypeScript

import { randomUUID } from 'node:crypto';
import { homedir } from 'node:os';
import { join } from 'node:path';
import type { Command } from 'commander';
import { readBrainConfigSecure } from './brain-secure-config.js';
import { provisionBrain, type ProvisionResult } from './brain-provision.js';
import { systemCommandRunner, type CommandRunner } from './brain-store-runtime.js';
import type { OwnerFetch } from './brain-owner-resolver.js';
export const BRAIN_PROVISION_COMMAND = '__brain-provision';
interface BrainProvisionCommandOptions {
readonly mosaicHome: string;
readonly home: string;
readonly identity: string;
readonly refusalIdentity: string;
readonly targetUrl: string;
readonly owner: string;
readonly lane: string;
readonly sourceRoot?: string;
readonly brainRoot?: string;
readonly ownerPolicy?: string;
readonly registry?: string;
}
interface BrainProvisionCommandDependencies {
readonly run: CommandRunner;
readonly fetch: OwnerFetch;
readonly absentControlName: () => string;
}
function configFailure(reasonCode: string): ProvisionResult {
return {
status: 'failed',
reasonCode,
findings: [{ code: `brain-${reasonCode}`, reasonCode }],
owner: null,
migration: null,
};
}
export async function executeBrainProvisionCommand(
options: BrainProvisionCommandOptions,
dependencies: BrainProvisionCommandDependencies,
): Promise<ProvisionResult> {
const registry = options.registry ?? join(options.mosaicHome, 'cred', 'estates.json');
const ownerPolicy = options.ownerPolicy ?? join(options.mosaicHome, 'brain', 'owners.json');
let estateRegistrySource: string;
try {
estateRegistrySource = readBrainConfigSecure(registry, options.mosaicHome);
} catch {
return configFailure('estate-registry-unavailable');
}
let ownerPolicySource: string;
try {
ownerPolicySource = readBrainConfigSecure(ownerPolicy, options.mosaicHome);
} catch {
return configFailure('owner-policy-unavailable');
}
try {
return await provisionBrain(
{
estateRegistrySource,
ownerPolicySource,
targetGitUrl: options.targetUrl,
requestedOwner: options.owner,
identity: options.identity,
refusalIdentity: options.refusalIdentity,
root: options.brainRoot ?? join(options.home, '.mosaic'),
sourceRoot: options.sourceRoot ?? join(options.mosaicHome, 'memory'),
seat: options.identity,
lane: options.lane,
laneActive: false,
},
dependencies,
);
} catch {
return configFailure('brain-provision-exception');
}
}
export function registerBrainProvisionCommand(program: Command): void {
program
.command(BRAIN_PROVISION_COMMAND, { hidden: true })
.description('Internal installer P7 durable-brain provisioner')
.requiredOption('--identity <name>', 'explicit fleet identity')
.requiredOption('--target-url <url>', 'configured target git URL')
.requiredOption('--refusal-identity <name>', 'explicit out-of-estate negative control')
.requiredOption('--owner <owner>', 'policy-bound durable owner candidate')
.requiredOption('--lane <name>', 'source lane to migrate')
.option('--mosaic-home <path>', 'installed Mosaic home')
.option('--home <path>', 'principal home')
.option('--source-root <path>', 'legacy memory root')
.option('--brain-root <path>', 'per-estate brain checkout root')
.option('--owner-policy <path>', 'durable-owner allowlist policy')
.option('--registry <path>', 'estate registry path')
.action(async (raw: Record<string, string | undefined>): Promise<void> => {
const home = raw['home'] ?? homedir();
const mosaicHome =
raw['mosaicHome'] ?? process.env['MOSAIC_HOME'] ?? join(home, '.config', 'mosaic');
const result = await executeBrainProvisionCommand(
{
mosaicHome,
home,
identity: raw['identity']!,
targetUrl: raw['targetUrl']!,
refusalIdentity: raw['refusalIdentity']!,
owner: raw['owner']!,
lane: raw['lane']!,
...(raw['sourceRoot'] === undefined ? {} : { sourceRoot: raw['sourceRoot'] }),
...(raw['brainRoot'] === undefined ? {} : { brainRoot: raw['brainRoot'] }),
...(raw['ownerPolicy'] === undefined ? {} : { ownerPolicy: raw['ownerPolicy'] }),
...(raw['registry'] === undefined ? {} : { registry: raw['registry'] }),
},
{
run: systemCommandRunner,
fetch,
absentControlName: (): string => `mosaic-absent-${randomUUID()}`,
},
);
process.stdout.write(
`${JSON.stringify({
status: result.status,
reasonCode: result.reasonCode,
findings: result.findings,
owner: result.owner,
migration: result.migration,
})}\n`,
);
if (result.status !== 'provisioned') process.exitCode = result.status === 'blocked' ? 30 : 20;
});
}