272 lines
8.4 KiB
TypeScript
272 lines
8.4 KiB
TypeScript
import { existsSync } from 'node:fs';
|
|
import { parseRequestedOwner, resolveProviderDurableOwner } from './brain-owner-resolver.js';
|
|
import {
|
|
createBrainSkeleton,
|
|
deriveBrainTarget,
|
|
discoverBrainMigration,
|
|
ensureBrainRootPrivate,
|
|
migrateBrainState,
|
|
type MigrationResult,
|
|
type MigrationOwnerResolution,
|
|
type MigrationPublishEntry,
|
|
} from './brain-store.js';
|
|
import {
|
|
collectBrainDoctorReport,
|
|
collectBrainRefusalControl,
|
|
publishBrainPaths,
|
|
type CommandRequest,
|
|
type CommandResult,
|
|
type CommandRunner,
|
|
} from './brain-store-runtime.js';
|
|
import type { OwnerFetch } from './brain-owner-resolver.js';
|
|
|
|
export interface ProvisionResult {
|
|
readonly status: 'provisioned' | 'blocked' | 'failed';
|
|
readonly reasonCode: string;
|
|
readonly findings: readonly {
|
|
readonly code: string;
|
|
readonly reasonCode: string | null;
|
|
}[];
|
|
readonly owner: Pick<MigrationOwnerResolution, 'verdict' | 'reasonCode'> | null;
|
|
readonly migration: Pick<MigrationResult, 'status' | 'reported'> | null;
|
|
}
|
|
|
|
function commandEnv(identity: string): Readonly<Record<string, string>> {
|
|
return { MOSAIC_GIT_IDENTITY: identity, GIT_TERMINAL_PROMPT: '0' };
|
|
}
|
|
|
|
function findingView(
|
|
findings: readonly { readonly code: string; readonly reasonCode: string | null }[],
|
|
): readonly { readonly code: string; readonly reasonCode: string | null }[] {
|
|
return findings.map((finding): { readonly code: string; readonly reasonCode: string | null } => ({
|
|
code: finding.code,
|
|
reasonCode: finding.reasonCode,
|
|
}));
|
|
}
|
|
|
|
function blocked(
|
|
reasonCode: string,
|
|
findings: readonly { readonly code: string; readonly reasonCode: string | null }[],
|
|
owner: MigrationOwnerResolution | null = null,
|
|
migration: MigrationResult | null = null,
|
|
): ProvisionResult {
|
|
return {
|
|
status: 'blocked',
|
|
reasonCode,
|
|
findings: findingView(findings),
|
|
owner: owner === null ? null : { verdict: owner.verdict, reasonCode: owner.reasonCode },
|
|
migration:
|
|
migration === null ? null : { status: migration.status, reported: migration.reported },
|
|
};
|
|
}
|
|
|
|
function failed(
|
|
reasonCode: string,
|
|
findings: readonly { readonly code: string; readonly reasonCode: string | null }[],
|
|
owner: MigrationOwnerResolution | null = null,
|
|
migration: MigrationResult | null = null,
|
|
): ProvisionResult {
|
|
return {
|
|
...blocked(reasonCode, findings, owner, migration),
|
|
status: 'failed',
|
|
};
|
|
}
|
|
|
|
export async function provisionBrain(
|
|
input: {
|
|
readonly estateRegistrySource: string;
|
|
readonly ownerPolicySource: string;
|
|
readonly targetGitUrl: string;
|
|
readonly requestedOwner: string;
|
|
readonly identity: string;
|
|
readonly refusalIdentity: string;
|
|
readonly root: string;
|
|
readonly sourceRoot: string;
|
|
readonly seat: string;
|
|
readonly lane: string;
|
|
readonly laneActive: boolean;
|
|
},
|
|
dependencies: {
|
|
readonly run: CommandRunner;
|
|
readonly fetch: OwnerFetch;
|
|
readonly absentControlName: () => string;
|
|
readonly approveMigrationContent?: (path: string, content: Uint8Array) => boolean;
|
|
},
|
|
): Promise<ProvisionResult> {
|
|
const brainNamespace = parseRequestedOwner(input.requestedOwner);
|
|
if (brainNamespace === null) return blocked('owner-name-invalid', []);
|
|
const target = deriveBrainTarget(input.estateRegistrySource, input.targetGitUrl, brainNamespace);
|
|
if (existsSync(input.root)) {
|
|
try {
|
|
ensureBrainRootPrivate(input.root);
|
|
} catch {
|
|
return blocked('brain-root-permissions-unsafe', []);
|
|
}
|
|
}
|
|
const doctorInput = {
|
|
registrySource: input.estateRegistrySource,
|
|
targetGitUrl: input.targetGitUrl,
|
|
brainNamespace,
|
|
identity: input.identity,
|
|
root: input.root,
|
|
};
|
|
let report = collectBrainDoctorReport(doctorInput, dependencies.run);
|
|
if (report.access.outcome !== 'ok') {
|
|
return blocked('credential-postcondition-failed', report.findings);
|
|
}
|
|
|
|
const refusalControl = collectBrainRefusalControl(
|
|
{
|
|
registrySource: input.estateRegistrySource,
|
|
targetGitUrl: input.targetGitUrl,
|
|
brainNamespace,
|
|
refusalIdentity: input.refusalIdentity,
|
|
},
|
|
dependencies.run,
|
|
);
|
|
if (!refusalControl.observed) {
|
|
return blocked('refusal-control-failed', [
|
|
...report.findings,
|
|
{
|
|
code: 'brain-refusal-control-indeterminate',
|
|
reasonCode: refusalControl.reasonCode,
|
|
},
|
|
]);
|
|
}
|
|
|
|
const owner = await resolveProviderDurableOwner(
|
|
{
|
|
estateRegistrySource: input.estateRegistrySource,
|
|
ownerPolicySource: input.ownerPolicySource,
|
|
host: target.host,
|
|
requestedOwner: input.requestedOwner,
|
|
},
|
|
{
|
|
fetch: dependencies.fetch,
|
|
absentControlName: dependencies.absentControlName,
|
|
},
|
|
);
|
|
if (owner.verdict !== 'resolved') {
|
|
const plan = discoverBrainMigration(
|
|
{
|
|
sourceRoot: input.sourceRoot,
|
|
brainRoot: input.root,
|
|
seat: input.seat,
|
|
lane: input.lane,
|
|
laneActive: input.laneActive,
|
|
},
|
|
(): MigrationOwnerResolution => owner,
|
|
);
|
|
const migration = migrateBrainState(
|
|
plan,
|
|
(): never => {
|
|
throw new Error('blocked owner cannot publish');
|
|
},
|
|
input.root,
|
|
);
|
|
return blocked(owner.reasonCode, report.findings, owner, migration);
|
|
}
|
|
|
|
if (report.findings.some((finding): boolean => finding.code === 'brain-clone-missing')) {
|
|
try {
|
|
ensureBrainRootPrivate(input.root);
|
|
} catch {
|
|
return failed('brain-root-permissions-unsafe', report.findings);
|
|
}
|
|
const clone: CommandRequest = {
|
|
program: 'git',
|
|
args: ['clone', '--branch', 'main', '--single-branch', target.cloneUrl, input.root],
|
|
env: commandEnv(input.identity),
|
|
};
|
|
const cloneResult: CommandResult = dependencies.run(clone);
|
|
if (cloneResult.status !== 0) return failed('brain-clone-failed', report.findings);
|
|
try {
|
|
ensureBrainRootPrivate(input.root);
|
|
} catch {
|
|
return failed('brain-root-permissions-unsafe', report.findings);
|
|
}
|
|
report = collectBrainDoctorReport(doctorInput, dependencies.run);
|
|
}
|
|
|
|
const blockingCloneFindings = report.findings.filter(
|
|
(finding): boolean =>
|
|
finding.code === 'brain-clone-missing' ||
|
|
finding.code === 'brain-not-git-repository' ||
|
|
finding.code === 'brain-remote-mismatch' ||
|
|
finding.code === 'brain-branch-mismatch' ||
|
|
finding.code === 'brain-uncommitted-state' ||
|
|
finding.code === 'brain-git-state-indeterminate' ||
|
|
finding.code === 'brain-root-permissions-unsafe' ||
|
|
finding.code.startsWith('brain-write-access-'),
|
|
);
|
|
if (blockingCloneFindings.length > 0) {
|
|
return blocked('brain-postcondition-failed', report.findings);
|
|
}
|
|
|
|
let skeletonEntries: readonly MigrationPublishEntry[];
|
|
try {
|
|
const skeleton = createBrainSkeleton(input.root);
|
|
skeletonEntries = skeleton.publicationEntries;
|
|
} catch {
|
|
return failed('brain-skeleton-failed', report.findings);
|
|
}
|
|
if (skeletonEntries.length > 0) {
|
|
try {
|
|
const evidence = publishBrainPaths(
|
|
{
|
|
root: input.root,
|
|
identity: input.identity,
|
|
entries: skeletonEntries,
|
|
message: 'chore: seed durable brain layout',
|
|
},
|
|
dependencies.run,
|
|
);
|
|
if (!evidence.reachable) return failed('brain-skeleton-not-reachable', report.findings);
|
|
} catch {
|
|
return failed('brain-skeleton-publish-failed', report.findings);
|
|
}
|
|
}
|
|
|
|
const plan = discoverBrainMigration(
|
|
{
|
|
sourceRoot: input.sourceRoot,
|
|
brainRoot: input.root,
|
|
seat: input.seat,
|
|
lane: input.lane,
|
|
laneActive: input.laneActive,
|
|
},
|
|
(): MigrationOwnerResolution => owner,
|
|
dependencies.approveMigrationContent,
|
|
);
|
|
const migration = migrateBrainState(
|
|
plan,
|
|
(brainRoot: string, entries: readonly MigrationPublishEntry[]) =>
|
|
publishBrainPaths(
|
|
{
|
|
root: brainRoot,
|
|
identity: input.identity,
|
|
entries,
|
|
message: `migrate: archive ${input.lane} working memory`,
|
|
},
|
|
dependencies.run,
|
|
),
|
|
input.root,
|
|
);
|
|
|
|
if (migration.status === 'failed') {
|
|
return failed('brain-migration-publish-failed', report.findings, owner, migration);
|
|
}
|
|
|
|
report = collectBrainDoctorReport(doctorInput, dependencies.run);
|
|
if (report.findings.length > 0) {
|
|
return blocked('brain-final-postcondition-failed', report.findings, owner, migration);
|
|
}
|
|
return {
|
|
status: 'provisioned',
|
|
reasonCode: 'brain-provisioned',
|
|
findings: [],
|
|
owner: { verdict: owner.verdict, reasonCode: owner.reasonCode },
|
|
migration: { status: migration.status, reported: migration.reported },
|
|
};
|
|
}
|