Files
stack/packages/mosaic/src/materialize-fixture.mjs
T

128 lines
7.3 KiB
JavaScript

// Synthetic-only publication simulator. No caller-selected filesystem root,
// production credential adapter, real Pi runner, service or live activation.
import { mkdtemp, mkdir, open, rename, rm, lstat } from 'node:fs/promises';
import { constants } from 'node:fs';
import { randomUUID } from 'node:crypto';
import { FixtureError, exact, refuse, resolveFixtureExecution, validateFixtureCredential } from './execution.mjs';
import { isFixtureStore } from './fixture-store.mjs';
import { refreshFixtureCredential, validateRefreshOptions } from './refresh-fixture.mjs';
const anchor = h => `/proc/self/fd/${h.fd}`;
async function openDir(path) { return open(path, constants.O_RDONLY | constants.O_DIRECTORY | constants.O_NOFOLLOW); }
async function verifyJson(dir, name, value) {
const expected = Buffer.from(JSON.stringify(value, null, 2) + '\n');
const h = await open(`${anchor(dir)}/${name}`, constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK);
try {
const s = await h.stat();
if (!s.isFile() || s.uid !== process.getuid() || (s.mode & 0o777) !== 0o600 || s.size !== expected.length) refuse('generation-validation-failed');
const actual = Buffer.alloc(expected.length + 1);
let size = 0;
while (size < actual.length) {
const { bytesRead } = await h.read(actual, size, actual.length - size, null);
if (!bytesRead) break;
size += bytesRead;
}
if (size !== expected.length || !actual.subarray(0, size).equals(expected)) refuse('generation-validation-failed');
} finally { await h.close(); }
}
async function writeJson(dir, name, value) {
const content = JSON.stringify(value, null, 2) + '\n';
if (Buffer.byteLength(content) > 1024 * 1024) refuse('generation-too-large');
const h = await open(`${anchor(dir)}/${name}`, constants.O_WRONLY | constants.O_CREAT | constants.O_EXCL | constants.O_NOFOLLOW, 0o600);
try { await h.writeFile(content); await h.sync(); }
finally { await h.close(); }
}
export async function createFixtureWorkspace() {
if (process.platform !== 'linux') refuse('unsupported-platform');
const path = await mkdtemp('/tmp/mosaic-materialization-fixture-');
const root = await openDir(path), identity = await root.stat();
let closed = false, active = 0;
const handles = {};
try {
for (const name of ['claims', 'pending', 'generations']) {
await mkdir(`${anchor(root)}/${name}`, { mode: 0o700 });
handles[name] = await openDir(`${anchor(root)}/${name}`);
}
} catch (e) {
for (const h of Object.values(handles)) await h.close();
await root.close(); await rm(path, { recursive: true, force: true }); throw e;
}
return Object.freeze({
fixtureOnly: true, path,
async generate(registry, request, store, options = {}) {
if (closed) refuse('workspace-closed');
if (!isFixtureStore(store)) refuse('fixture-store-required');
exact(options, [], ['fault', 'refresh']);
if (options.fault !== undefined && !['after-auth', 'before-publish', 'after-publish'].includes(options.fault)) refuse('invalid-fixture-option');
options = { fault: options.fault, refresh: options.refresh === undefined ? undefined : validateRefreshOptions(options.refresh) };
const plan = resolveFixtureExecution(registry, request), id = plan.scope.executionId;
const pending = `${anchor(handles.pending)}/${id}`, claimPath = `${anchor(handles.claims)}/${id}`;
let claim, generation, claimed = false, published = false;
active++;
try {
try { await mkdir(claimPath, { mode: 0o700 }); claimed = true; }
catch { refuse('execution-already-claimed'); }
claim = await openDir(claimPath);
await writeJson(claim, 'started.json', { fixtureOnly: true, executionId: id, state: 'started' });
const result = await store.transaction(Object.values(plan.accounts), async draft => {
const auth = Object.create(null);
for (const [provider, ref] of Object.entries(plan.accounts)) {
let credential = validateFixtureCredential(draft.get(ref), plan.types[provider]);
if (credential !== null && (options.refresh !== undefined ||
(credential.type === 'oauth' && credential.expires <= Date.now() + 300000))) {
credential = await refreshFixtureCredential(provider, credential, options.refresh ?? {});
draft.set(ref, credential);
}
if (credential !== null) auth[provider] = credential;
}
await mkdir(pending, { mode: 0o700 }); generation = await openDir(pending);
await writeJson(generation, 'auth.json', auth);
if (options.fault === 'after-auth') refuse('injected-generation-failure');
await writeJson(generation, 'models.json', plan.models);
const manifest = { manifestVersion: 1, fixtureOnly: true, manifestId: randomUUID(),
scope: plan.scope, profile: plan.profile, accounts: plan.accounts, fork: plan.fork,
artifacts: ['auth.json', 'models.json'], state: 'ready' };
// Manifest contains no token, expiry or credential-content digest.
await writeJson(generation, 'manifest.json', manifest);
if (options.fault === 'before-publish') refuse('injected-generation-failure');
await verifyJson(generation, 'auth.json', auth);
await verifyJson(generation, 'models.json', plan.models);
await verifyJson(generation, 'manifest.json', manifest);
await generation.sync(); await generation.close(); generation = undefined;
const target = `${anchor(handles.generations)}/${id}`;
try { await lstat(target); refuse('generation-exists'); }
catch (e) { if (e.code !== 'ENOENT') throw e; }
// Retained exclusive claim serializes this execution ID, including
// failed attempts. Published generations are never replaced by API.
await rename(pending, target); published = true;
if (options.fault === 'after-publish') refuse('injected-generation-failure');
await handles.generations.sync();
await writeJson(claim, 'result.json', { fixtureOnly: true, state: 'published', manifestId: manifest.manifestId });
return { fixtureOnly: true, manifest, generationPath: `${path}/generations/${id}` };
});
return result;
} catch (e) {
if (generation) { await generation.close(); generation = undefined; }
if (claimed && !published) await rm(pending, { recursive: true, force: true });
const code = published ? 'publication-uncertain' : e instanceof FixtureError ? e.code : 'generation-failed';
if (claim) {
try { await writeJson(claim, 'failure.json', { fixtureOnly: true, state: published ? 'uncertain' : 'failed', code }); }
catch { throw new FixtureError('recording-failed'); }
}
throw new FixtureError(code);
} finally {
if (claim) await claim.close(); active--;
}
},
async close() {
if (closed) return;
if (active) refuse('workspace-busy');
closed = true;
for (const h of Object.values(handles)) await h.close();
await root.close();
const current = await lstat(path);
if (current.dev !== identity.dev || current.ino !== identity.ino || current.isSymbolicLink()) refuse('workspace-path-changed');
await rm(path, { recursive: true, force: true });
},
});
}