Files
stack/packages/mosaic/src/commands/brain-store-runtime.ts
T

560 lines
17 KiB
TypeScript

import { existsSync, mkdtempSync, rmSync } from 'node:fs';
import { spawnSync } from 'node:child_process';
import { tmpdir } from 'node:os';
import { isAbsolute, join, relative, resolve, sep } from 'node:path';
import {
assessCredentialResult,
deriveBrainTarget,
evaluateBrainDoctor,
planBrainDoctorFix,
type BrainDoctorFinding,
type BrainDoctorObservation,
type CredentialAssessment,
} from './brain-store.js';
const COMMIT = /^[0-9a-f]{40}$/;
const GIT_OBJECT = /^(?:[0-9a-f]{40}|[0-9a-f]{64})$/;
const MAX_PUBLISH_ENTRY_BYTES = 1024 * 1024;
export interface CommandRequest {
readonly program: 'git' | 'mosaic';
readonly args: readonly string[];
readonly cwd?: string;
readonly env: Readonly<Record<string, string>>;
readonly stdin?: Uint8Array;
}
export interface CommandResult {
readonly status: number;
readonly stdout: string;
readonly stderr: string;
}
export type CommandRunner = (request: CommandRequest) => CommandResult;
export const systemCommandRunner: CommandRunner = (request: CommandRequest): CommandResult => {
const result = spawnSync(request.program, request.args, {
cwd: request.cwd,
env: { ...process.env, ...request.env },
encoding: 'utf8',
maxBuffer: 1024 * 1024,
input: request.stdin,
});
return {
status: result.status ?? 127,
stdout: result.stdout ?? '',
stderr: result.stderr ?? result.error?.message ?? '',
};
};
export interface DoctorRuntimeReport {
readonly findings: readonly BrainDoctorFinding[];
readonly access: CredentialAssessment;
readonly refusalControl: {
readonly observed: boolean;
readonly reasonCode: string | null;
};
}
export interface BrainRefusalControlResult {
readonly observed: boolean;
readonly reasonCode: string | null;
readonly gitReasonCode: string;
readonly apiReasonCode: string;
}
export interface PublishEvidence {
readonly commit: string;
readonly remoteHead: string;
readonly reachable: boolean;
}
function commandEnv(identity: string): Readonly<Record<string, string>> {
return {
MOSAIC_GIT_IDENTITY: identity,
GIT_TERMINAL_PROMPT: '0',
};
}
function integrationFailure(): CredentialAssessment {
return {
outcome: 'indeterminate',
exitCode: 30,
reasonCode: 'unexpected-provider-shape',
diagnostic: 'indeterminate: unexpected-provider-shape',
};
}
function runGit(run: CommandRunner, identity: string, args: readonly string[]): CommandResult {
return run({ program: 'git', args, env: commandEnv(identity) });
}
function runGitWithEnv(
run: CommandRunner,
identity: string,
args: readonly string[],
env: Readonly<Record<string, string>>,
stdin?: Uint8Array,
): CommandResult {
return run({ program: 'git', args, env: { ...commandEnv(identity), ...env }, stdin });
}
export function collectBrainDoctorReport(
input: {
readonly registrySource: string;
readonly targetGitUrl: string;
readonly brainNamespace: string;
readonly identity: string;
readonly root: string;
},
run: CommandRunner,
): DoctorRuntimeReport {
const target = deriveBrainTarget(input.registrySource, input.targetGitUrl, input.brainNamespace);
const validation = run({
program: 'mosaic',
args: [
'cred',
'validate',
input.identity,
'--estate',
target.estate,
'--host',
target.host,
'--repo',
target.repo,
'--require',
'write',
'--json',
],
env: commandEnv(input.identity),
});
let access = assessCredentialResult(validation.stdout, {
identity: input.identity,
estate: target.estate,
host: target.host,
repo: target.repo,
});
if (validation.status !== access.exitCode) access = integrationFailure();
const rootExists = existsSync(input.root);
let gitRepository = false;
let remote: string | null = null;
let branch: string | null = null;
let worktreeState: BrainDoctorObservation['worktreeState'] = 'unmeasurable';
if (rootExists) {
const repository = runGit(run, input.identity, [
'-C',
input.root,
'rev-parse',
'--is-inside-work-tree',
]);
gitRepository = repository.status === 0 && repository.stdout.trim() === 'true';
if (gitRepository) {
const remoteResult = runGit(run, input.identity, [
'-C',
input.root,
'remote',
'get-url',
'origin',
]);
const branchResult = runGit(run, input.identity, [
'-C',
input.root,
'branch',
'--show-current',
]);
const statusResult = runGit(run, input.identity, ['-C', input.root, 'status', '--porcelain']);
if (remoteResult.status === 0) remote = remoteResult.stdout.trim();
if (branchResult.status === 0) branch = branchResult.stdout.trim();
if (statusResult.status === 0) {
worktreeState = statusResult.stdout.trim().length > 0 ? 'dirty' : 'clean';
}
}
}
const observation: BrainDoctorObservation = {
rootExists,
gitRepository,
remote,
branch,
worktreeState,
access,
};
const refusalMarker = `refused reason=${access.reasonCode}`;
const refusalObserved =
validation.status === 10 &&
access.outcome === 'refused' &&
access.reasonCode === 'no-token-for-identity' &&
validation.stderr.includes(refusalMarker);
return {
findings: evaluateBrainDoctor(observation, target.cloneUrl),
access,
refusalControl: {
observed: refusalObserved,
reasonCode: refusalObserved ? access.reasonCode : null,
},
};
}
export function collectBrainRefusalControl(
input: {
readonly registrySource: string;
readonly targetGitUrl: string;
readonly brainNamespace: string;
readonly refusalIdentity: string;
},
run: CommandRunner,
): BrainRefusalControlResult {
const target = deriveBrainTarget(input.registrySource, input.targetGitUrl, input.brainNamespace);
if (!/^[A-Za-z0-9][A-Za-z0-9_.-]*$/.test(input.refusalIdentity)) {
return {
observed: false,
reasonCode: 'permission-evidence-disagrees',
gitReasonCode: 'invalid-control-identity',
apiReasonCode: 'invalid-control-identity',
};
}
const apiResult = run({
program: 'mosaic',
args: [
'cred',
'validate',
input.refusalIdentity,
'--estate',
target.estate,
'--host',
target.host,
'--repo',
target.repo,
'--require',
'write',
'--json',
],
env: commandEnv(input.refusalIdentity),
});
let api = assessCredentialResult(apiResult.stdout, {
identity: input.refusalIdentity,
estate: target.estate,
host: target.host,
repo: target.repo,
});
if (apiResult.status !== api.exitCode) api = integrationFailure();
const gitResult = runGit(run, input.refusalIdentity, ['ls-remote', target.cloneUrl, 'HEAD']);
const marker = /(?:^|\s)reason=([a-z0-9-]+)(?:\s|$)/.exec(gitResult.stderr)?.[1];
const stableRefusals = new Set([
'identity-required',
'estate-required',
'estate-host-mismatch',
'cross-estate-resolution',
'no-token-for-identity',
'tea-login-missing',
'tea-login-host-mismatch',
'provider-identity-mismatch',
'credential-rejected',
'permission-denied',
'organization-membership-required',
'team-membership-required',
]);
const gitReasonCode =
gitResult.status === 0
? 'transport-accepted'
: marker !== undefined && stableRefusals.has(marker) && gitResult.stdout.length === 0
? marker
: 'transport-indeterminate';
const observed =
api.outcome === 'refused' &&
gitReasonCode !== 'transport-accepted' &&
gitReasonCode !== 'transport-indeterminate' &&
gitReasonCode === api.reasonCode;
return {
observed,
reasonCode: observed ? api.reasonCode : 'permission-evidence-disagrees',
gitReasonCode,
apiReasonCode: api.reasonCode,
};
}
export function repairBrainDoctor(
input: {
readonly registrySource: string;
readonly targetGitUrl: string;
readonly brainNamespace: string;
readonly identity: string;
readonly root: string;
},
run: CommandRunner,
): DoctorRuntimeReport {
const target = deriveBrainTarget(input.registrySource, input.targetGitUrl, input.brainNamespace);
let report = collectBrainDoctorReport(input, run);
const actions = planBrainDoctorFix({
findings: report.findings,
target,
identity: input.identity,
root: input.root,
});
for (const action of actions) {
if (action.program === 'mosaic') {
run({ program: 'mosaic', args: action.args, env: commandEnv(input.identity) });
report = collectBrainDoctorReport(input, run);
if (report.access.outcome !== 'ok') return report;
continue;
}
const result = runGit(run, input.identity, action.args);
if (result.status !== 0) return collectBrainDoctorReport(input, run);
}
return collectBrainDoctorReport(input, run);
}
function requireSuccess(result: CommandResult, operation: string): void {
if (result.status !== 0) throw new Error(`${operation}-failed`);
}
function containedRelative(root: string, path: string): string {
if (isAbsolute(path) === false) throw new Error('brain-publish-path-must-be-absolute');
const absoluteRoot = resolve(root);
const absolutePath = resolve(path);
if (absolutePath === absoluteRoot || !absolutePath.startsWith(`${absoluteRoot}${sep}`)) {
throw new Error('brain-publish-path-escaped-root');
}
return relative(absoluteRoot, absolutePath).split(sep).join('/');
}
export function publishBrainPaths(
input: {
readonly root: string;
readonly identity: string;
readonly entries: readonly {
readonly path: string;
readonly content: Uint8Array;
}[];
readonly message: string;
},
run: CommandRunner,
): PublishEvidence {
if (input.entries.length === 0) throw new Error('brain-publish-paths-empty');
if (input.message.trim().length === 0) throw new Error('brain-publish-message-empty');
const entries = input.entries.map((entry) => {
if (entry.content.byteLength > MAX_PUBLISH_ENTRY_BYTES) {
throw new Error('brain-publish-entry-too-large');
}
return {
path: containedRelative(input.root, entry.path),
content: Uint8Array.from(entry.content),
};
});
const paths = entries.map((entry): string => entry.path);
if (new Set(paths).size !== paths.length) throw new Error('brain-publish-path-duplicate');
const readHead = (): string => {
const result = runGit(run, input.identity, ['-C', input.root, 'rev-parse', 'HEAD']);
requireSuccess(result, 'brain-git-read-commit');
const value = result.stdout.trim();
if (!COMMIT.test(value)) throw new Error('brain-git-commit-shape-invalid');
return value;
};
const verifyCommitIdentity = (commit: string): void => {
const result = runGit(run, input.identity, [
'-C',
input.root,
'show',
'-s',
'--format=%an%x00%ae%x00%cn%x00%ce',
commit,
]);
requireSuccess(result, 'brain-git-read-commit-identity');
const expectedEmail = `${input.identity}@fleet.mosaicstack.dev`;
const [author, authorEmail, committer, committerEmail] = result.stdout.trimEnd().split('\0');
if (
author !== input.identity ||
authorEmail !== expectedEmail ||
committer !== input.identity ||
committerEmail !== expectedEmail
) {
throw new Error('brain-git-commit-identity-mismatch');
}
};
const expectedObjects = new Map<string, string>();
const verifyCommitObjects = (commit: string): void => {
for (const [path, expected] of expectedObjects) {
const result = runGit(run, input.identity, [
'-C',
input.root,
'rev-parse',
`${commit}:${path}`,
]);
requireSuccess(result, 'brain-git-read-commit-object');
if (result.stdout.trim() !== expected) throw new Error('brain-git-commit-content-mismatch');
}
};
const reconcileRealIndex = (): void => {
for (const [path, objectId] of expectedObjects) {
requireSuccess(
runGit(run, input.identity, [
'-C',
input.root,
'update-index',
'--add',
'--cacheinfo',
'100644',
objectId,
path,
]),
'brain-git-reconcile-checkout-index',
);
}
};
const verifyCommitPaths = (commit: string): void => {
const changed = runGit(run, input.identity, [
'-C',
input.root,
'diff-tree',
'--root',
'--no-commit-id',
'--name-only',
'-r',
'-z',
commit,
]);
requireSuccess(changed, 'brain-git-read-commit-paths');
const names = changed.stdout.split('\0').filter(Boolean);
const approved = new Set(paths);
if (names.length === 0 || names.some((name: string): boolean => !approved.has(name))) {
throw new Error('brain-git-commit-paths-unapproved');
}
};
const base = readHead();
const indexRoot = mkdtempSync(join(tmpdir(), 'mosaic-brain-index-'));
const isolatedEnv = { GIT_INDEX_FILE: join(indexRoot, 'index') };
let commit = base;
let createdCommit = false;
try {
requireSuccess(
runGitWithEnv(run, input.identity, ['-C', input.root, 'read-tree', base], isolatedEnv),
'brain-git-isolated-index-init',
);
for (const entry of entries) {
const object = runGitWithEnv(
run,
input.identity,
['-C', input.root, 'hash-object', '-w', '--stdin'],
isolatedEnv,
entry.content,
);
requireSuccess(object, 'brain-git-write-approved-object');
const objectId = object.stdout.trim();
if (!GIT_OBJECT.test(objectId)) throw new Error('brain-git-object-shape-invalid');
expectedObjects.set(entry.path, objectId);
requireSuccess(
runGitWithEnv(
run,
input.identity,
[
'-C',
input.root,
'update-index',
'--add',
'--cacheinfo',
'100644',
objectId,
entry.path,
],
isolatedEnv,
),
'brain-git-stage-approved-object',
);
}
const difference = runGitWithEnv(
run,
input.identity,
['-C', input.root, 'diff', '--cached', '--quiet', '--exit-code', base, '--', ...paths],
isolatedEnv,
);
if (difference.status === 1) {
requireSuccess(
runGitWithEnv(
run,
input.identity,
[
'-C',
input.root,
'-c',
`user.name=${input.identity}`,
'-c',
`user.email=${input.identity}@fleet.mosaicstack.dev`,
'commit',
'-m',
input.message,
],
isolatedEnv,
),
'brain-git-commit',
);
commit = readHead();
verifyCommitPaths(commit);
verifyCommitObjects(commit);
verifyCommitIdentity(commit);
reconcileRealIndex();
createdCommit = true;
} else if (difference.status !== 0) {
throw new Error('brain-git-isolated-diff-failed');
}
} finally {
rmSync(indexRoot, { recursive: true, force: true });
}
let pushed = !createdCommit;
for (let attempt = 0; createdCommit && attempt < 3; attempt += 1) {
const push = runGit(run, input.identity, ['-C', input.root, 'push', 'origin', 'HEAD:main']);
if (push.status === 0) {
pushed = true;
break;
}
const concurrentUpdate = /non-fast-forward|fetch first|\[rejected\]/i.test(push.stderr);
if (!concurrentUpdate || attempt === 2) throw new Error('brain-git-push-failed');
requireSuccess(
runGit(run, input.identity, ['-C', input.root, 'fetch', 'origin', 'main']),
'brain-git-fetch-concurrent',
);
requireSuccess(
runGit(run, input.identity, [
'-C',
input.root,
'-c',
`user.name=${input.identity}`,
'-c',
`user.email=${input.identity}@fleet.mosaicstack.dev`,
'rebase',
'origin/main',
]),
'brain-git-rebase-concurrent',
);
commit = readHead();
verifyCommitPaths(commit);
verifyCommitObjects(commit);
verifyCommitIdentity(commit);
}
if (!pushed) throw new Error('brain-git-push-failed');
requireSuccess(
runGit(run, input.identity, ['-C', input.root, 'fetch', 'origin', 'main']),
'brain-git-fetch-readback',
);
const reachableResult = runGit(run, input.identity, [
'-C',
input.root,
'merge-base',
'--is-ancestor',
commit,
'origin/main',
]);
if (reachableResult.status !== 0 && reachableResult.status !== 1) {
throw new Error('brain-git-reachability-check-failed');
}
const remoteResult = runGit(run, input.identity, ['-C', input.root, 'rev-parse', 'origin/main']);
requireSuccess(remoteResult, 'brain-git-read-remote-head');
const remoteHead = remoteResult.stdout.trim();
if (!COMMIT.test(remoteHead)) throw new Error('brain-git-remote-head-shape-invalid');
return { commit, remoteHead, reachable: reachableResult.status === 0 };
}