134 lines
4.9 KiB
TypeScript
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;
|
|
});
|
|
}
|