Files
2026-09-14 15:16:26 +08:00

2698 lines
120 KiB
JavaScript

import { closeSync, constants, fstatSync, lstatSync, mkdirSync, openSync, readSync, writeFileSync } from 'node:fs';
import { Buffer } from 'node:buffer';
import { createHash, randomUUID } from 'node:crypto';
import { dirname, join } from 'node:path';
import { reference, assetstore, events, hub as hubNs, wire } from '@evomap/evolver-core';
import { AuthError, connectPublicHub, HubClientError, HubUnreachableError, isHubDryRunEnabled, MalformedAccountAssetPageError, resolveHubUrl, stripHubDeliveryMetadataForIntegrity, } from '@evomap/evolver-adapter-public';
import { loadEnvFileFromEnv } from '@evomap/evolver-mcp';
import { createRecipeHubFromEnv, resolveRecipeHubResumeIdentityFingerprint } from './recipe.js';
import { getCliVersion } from './version.js';
const DEFAULT_LIMIT = 100;
const MAX_LIMIT = 10_000;
const DEFAULT_PAGE_SIZE = 100;
const MAX_PAGE_SIZE = 1000;
const MAX_CURSOR_LENGTH = 4096;
const MAX_CONSECUTIVE_EMPTY_PAGES = 10;
const MAX_CONSECUTIVE_PAGES_WITHOUT_UNIQUE_PROGRESS = 10;
const MAX_RAW_PAGES = 20_000;
const MAX_RAW_ROWS = 100_000;
const MAX_GEPX_BYTES = 64 * 1024 * 1024;
const MAX_GEPX_ASSETS = 10_000;
const MAX_GEPX_SIDECAR_RECORDS = MAX_GEPX_ASSETS;
const GROUP = 'sync';
const USAGE = [
'usage: evolver sync [--write] [--force] [--resume] [--scope all|purchased|published] [--type Gene|Capsule] [--status draft|promoted|all] [--limit N] [--page-size N] [--purchased-cursor CURSOR] [--published-cursor CURSOR] [--json]',
' evolver sync --export <file.gepx> [--type Gene|Capsule] [--limit N] [--json]',
' evolver sync --import <file.gepx> [--write] [--force] [--type Gene|Capsule] [--limit N] [--json]',
' evolver sync --json',
].join('\n');
const VALUE_FLAGS = new Set(['--scope', '--type', '--status', '--limit', '--page-size', '--purchased-cursor', '--published-cursor', '--export', '--import']);
const GEPX_TYPE = 'evomap.gepx';
const GEPX_VERSION = 1;
const MISSING_ASSET_ID = '[missing]';
const UNVERIFIED_HUB_WRITE_PENDING_REASON = 'unverified_hub_write_pending';
export async function runSyncCommand(argv, deps = {}) {
const out = deps.stdout ?? ((line) => { process.stdout.write(`${line}\n`); });
const err = deps.stderr ?? ((line) => { process.stderr.write(`${line}\n`); });
const textOut = (line) => { out(formatTerminalText(line)); };
const parsed = parseSyncArgs(argv);
if (!parsed.ok)
return emitFailure(parsed.reason, parsed.message, parsed.jsonOut, out, err);
if ('help' in parsed) {
if (parsed.jsonOut)
out(stringifyJsonOutput({ ok: true, group: GROUP, mode: 'help', usage: USAGE }));
else
out(USAGE);
return 0;
}
try {
const env = deps.env ?? process.env;
loadSyncEnv(env);
const runtimeDeps = {
...deps,
env,
assetsDir: deps.assetsDir ?? join(events.evomapHome(env), 'assets'),
};
if (parsed.value.importPath) {
const raw = JSON.parse(readBoundedGepxText(parsed.value.importPath));
if (reference.hasEvidenceMode(raw)) {
const envelope = !Array.isArray(raw) ? reference.record(raw) : undefined;
const assets = envelope?.['assets'];
let batch = raw;
if (Array.isArray(assets)) {
if (assets.some((a) => !['Gene', 'Capsule'].includes(String(reference.record(a)['type']))))
throw new Error('reference_pair_required');
const genes = assets.filter((a) => reference.record(a)['type'] === 'Gene');
const capsules = assets.filter((a) => reference.record(a)['type'] === 'Capsule');
batch = capsules.map((capsule) => {
const matches = genes.filter((gene) => [reference.record(gene)['id'], reference.record(gene)['asset_id']].includes(reference.record(capsule)['gene']));
if (matches.length !== 1)
throw new Error('reference_gene_binding_invalid');
return { gene: matches[0], capsule };
});
if (genes.some((g) => !capsules.some((c) => [reference.record(g)['id'], reference.record(g)['asset_id']].includes(reference.record(c)['gene']))))
throw new Error('reference_orphan_gene');
}
const pairs = reference.decodeReferenceBatch(batch);
const receipt = parsed.value.write
? new reference.ReferenceStore(join(runtimeDeps.assetsDir, 'references')).import(pairs)
: { status: 'would_store_reference', count: pairs.length, executable: false };
out(stringifyJsonOutput({ ok: true, group: GROUP, ...receipt }));
return 0;
}
}
const result = parsed.value.exportPath
? await executeSyncExport(parsed.value, runtimeDeps)
: parsed.value.importPath
? parsed.value.write
? await executeGepxImportWrite(parsed.value, runtimeDeps)
: await buildGepxImportPreview(parsed.value, runtimeDeps)
: parsed.value.write
? await executeSyncWrite(parsed.value, runtimeDeps)
: await executeSyncPreview(parsed.value, runtimeDeps);
if (parsed.value.jsonOut)
out(stringifyJsonOutput(result));
else if (result.mode === 'preview')
emitTextPreview(result, textOut);
else if (result.mode === 'export')
emitTextExport(result, textOut);
else if (result.mode === 'import_preview')
emitTextImportPreview(result, textOut);
else if (result.mode === 'import')
emitTextImport(result, textOut);
else
emitTextWrite(result, textOut);
if ('failures' in result && result.failures.length > 0)
return 1;
if (result.mode === 'import' && result.blocked.length > 0)
return 1;
if (result.mode === 'write' && !result.reconciliation.consistent)
return 1;
return 0;
}
catch (error) {
const mapped = mapSyncError(error);
return emitFailure(mapped.reason, mapped.message, parsed.value.jsonOut, out, err);
}
}
function parseSyncArgs(argv) {
let scope = 'all';
let type;
let status = 'all';
let limit = DEFAULT_LIMIT;
let pageSize = DEFAULT_PAGE_SIZE;
let purchasedCursor;
let publishedCursor;
let jsonOut = argv.includes('--json');
let write = false;
let force = false;
let resume = false;
let exportPath;
let importPath;
for (let i = 0; i < argv.length; i += 1) {
const token = argv[i];
if (!token)
continue;
if (token === '--help' || token === '-h')
return { ok: true, help: true, jsonOut };
if (token === '--json') {
jsonOut = true;
continue;
}
if (token === '--write') {
write = true;
continue;
}
if (token === '--force') {
force = true;
continue;
}
if (token === '--resume') {
resume = true;
continue;
}
if (token.startsWith('--')) {
const eq = token.indexOf('=');
const flag = eq >= 0 ? token.slice(0, eq) : token;
if (!VALUE_FLAGS.has(flag)) {
return { ok: false, reason: 'invalid_arg', message: `unsupported sync argument: ${flag}`, jsonOut };
}
const value = eq >= 0 ? token.slice(eq + 1) : argv[i + 1];
if (!value || value.startsWith('--'))
return { ok: false, reason: 'invalid_arg', message: `${flag} requires a value`, jsonOut };
if (eq < 0)
i += 1;
if (flag === '--scope') {
if (value !== 'all' && value !== 'purchased' && value !== 'published') {
return { ok: false, reason: 'invalid_arg', message: '--scope must be all|purchased|published', jsonOut };
}
scope = value;
}
else if (flag === '--type') {
if (value !== 'Gene' && value !== 'Capsule')
return { ok: false, reason: 'invalid_arg', message: '--type must be Gene|Capsule', jsonOut };
type = value;
}
else if (flag === '--status') {
if (value !== 'draft' && value !== 'promoted' && value !== 'all') {
return { ok: false, reason: 'invalid_arg', message: '--status must be draft|promoted|all', jsonOut };
}
status = value;
}
else if (flag === '--limit') {
const parsedLimit = Number(value);
if (!Number.isSafeInteger(parsedLimit) || parsedLimit <= 0)
return { ok: false, reason: 'invalid_arg', message: '--limit must be a positive integer', jsonOut };
limit = Math.min(parsedLimit, MAX_LIMIT);
}
else if (flag === '--page-size') {
const parsedPageSize = Number(value);
if (!Number.isSafeInteger(parsedPageSize) || parsedPageSize <= 0)
return { ok: false, reason: 'invalid_arg', message: '--page-size must be a positive integer', jsonOut };
pageSize = Math.min(parsedPageSize, MAX_PAGE_SIZE);
}
else if (flag === '--purchased-cursor' || flag === '--published-cursor') {
if (!isValidOpaqueCursor(value)) {
return { ok: false, reason: 'invalid_arg', message: `${flag} must be a non-empty cursor of at most ${MAX_CURSOR_LENGTH} characters`, jsonOut };
}
if (flag === '--purchased-cursor')
purchasedCursor = value;
else
publishedCursor = value;
}
else if (flag === '--export') {
exportPath = value;
}
else if (flag === '--import') {
importPath = value;
}
continue;
}
return { ok: false, reason: 'invalid_arg', message: `unsupported sync argument: ${token}`, jsonOut };
}
if (force && !write) {
return { ok: false, reason: 'force_requires_write', message: '--force requires --write', jsonOut };
}
if (resume && !write) {
return { ok: false, reason: 'resume_requires_write', message: '--resume requires --write', jsonOut };
}
if (exportPath && importPath) {
return { ok: false, reason: 'invalid_arg', message: '--export and --import cannot be used together', jsonOut };
}
if (exportPath && write) {
return { ok: false, reason: 'invalid_arg', message: '--export does not use --write', jsonOut };
}
if (purchasedCursor && scope === 'published') {
return { ok: false, reason: 'invalid_arg', message: '--purchased-cursor requires --scope all|purchased', jsonOut };
}
if (publishedCursor && scope === 'purchased') {
return { ok: false, reason: 'invalid_arg', message: '--published-cursor requires --scope all|published', jsonOut };
}
if ((purchasedCursor || publishedCursor) && (exportPath || importPath)) {
return { ok: false, reason: 'invalid_arg', message: 'Hub cursors cannot be used with --export or --import', jsonOut };
}
return { ok: true, value: { scope, ...(type ? { type } : {}), status, limit, pageSize, ...(purchasedCursor ? { purchasedCursor } : {}), ...(publishedCursor ? { publishedCursor } : {}), jsonOut, write, force, resume, ...(exportPath ? { exportPath } : {}), ...(importPath ? { importPath } : {}) } };
}
async function buildSyncPreview(opts, deps, listing = {
skipPurchased: false,
skipPublished: false,
}) {
const env = deps.env ?? process.env;
validatePrivateHubUrl(env);
if (isHubDryRunEnabled(env))
return emptyPreview(opts.scope);
const hub = deps.hub ?? await createDefaultHub(deps, env);
const store = deps.store ?? new assetstore.LocalJsonlProvider(deps.assetsDir ?? events.assetsDir());
const local = await localIndex(store);
const { purchased, published, failures, inventoryComplete, nextCursors } = await listRemoteRows(hub, opts, listing);
const provenanceSnapshot = previewProvenanceSnapshot(store, deps);
const counts = {
remotePurchased: purchased.length,
remotePublished: published.length,
uniqueRemote: 0,
alreadyLocal: 0,
tombstone: 0,
idCollision: 0,
unsupported: 0,
integrityError: 0,
wouldImport: 0,
unverifiedWouldImport: 0,
forcedImport: 0,
skippedLimit: 0,
fetchFailed: 0,
writeFailed: 0,
};
const unique = new Map();
const conflictingDuplicateAssetIds = new Set();
const missingAssetId = [];
for (const row of purchased)
addUnique(unique, missingAssetId, conflictingDuplicateAssetIds, row, 'purchased');
for (const row of published)
addUnique(unique, missingAssetId, conflictingDuplicateAssetIds, row, 'published');
counts.uniqueRemote = unique.size + missingAssetId.length;
const candidates = [];
const pendingLogical = new Map(local.logical);
for (const { row, source } of [...unique.values(), ...missingAssetId]) {
const assetId = rawStringField(row, 'asset_id');
const candidate = assetId && conflictingDuplicateAssetIds.has(assetId)
? classifyConflictingRemoteDuplicate(row, source)
: classifyRemote(row, source, { ...local, logical: pendingLogical }, provenanceSnapshot);
candidates.push(candidate);
if (candidate.action === 'already_local')
counts.alreadyLocal += 1;
else if (candidate.action === 'tombstone')
counts.tombstone += 1;
else if (candidate.action === 'id_collision')
counts.idCollision += 1;
else if (candidate.action === 'would_import') {
counts.wouldImport += 1;
if (candidate.verification === 'unverified')
counts.unverifiedWouldImport += 1;
if (candidate.logicalId && (candidate.type === 'Gene' || candidate.type === 'Capsule')) {
pendingLogical.set(`${candidate.type}:${candidate.logicalId}`, candidate.assetId);
}
}
else if (candidate.action === 'force_import')
counts.forcedImport += 1;
else if (candidate.action === 'unsupported_type')
counts.unsupported += 1;
else
counts.integrityError += 1;
}
return {
ok: true,
group: GROUP,
mode: 'preview',
scope: opts.scope,
counts,
candidates,
failures,
nextCursors,
reconciliation: reconcile(candidates, failures, 0, false, inventoryComplete, 0),
};
}
async function executeSyncPreview(opts, deps) {
const env = deps.env ?? process.env;
validatePrivateHubUrl(env);
if (isHubDryRunEnabled(env))
return emptyPreview(opts.scope);
const hub = deps.hub ?? await createDefaultHub(deps, env);
const store = deps.store ?? new assetstore.LocalJsonlProvider(deps.assetsDir ?? events.assetsDir());
if (!opts.purchasedCursor && !opts.publishedCursor) {
return buildSyncPreview(opts, { ...deps, env, hub, store });
}
const baseDir = storeBaseDir(store, deps);
const syncLedger = deps.syncLedger ?? new assetstore.AssetSyncLedger(baseDir, deps.now);
const inventoryKey = syncInventoryKey(opts, env, deps);
const inventoryScan = prepareInventoryScan(syncLedger, inventoryKey, opts);
return buildSyncPreview(opts, { ...deps, env, hub, store }, inventoryScan);
}
async function executeSyncWrite(opts, deps) {
const env = deps.env ?? process.env;
validatePrivateHubUrl(env);
if (isHubDryRunEnabled(env))
return emptyWrite(opts.scope, 'dry_run');
const hub = deps.hub ?? await createDefaultHub(deps, env);
if (!hasFetchAssetById(hub))
throw new Error('sync write requires fetchAssetById support');
const store = deps.store ?? new assetstore.LocalJsonlProvider(deps.assetsDir ?? events.assetsDir());
const baseDir = storeBaseDir(store, deps);
const provenance = deps.provenance ?? new assetstore.ProvenanceStore(baseDir, deps.now);
const syncLedger = deps.syncLedger ?? new assetstore.AssetSyncLedger(baseDir, deps.now);
const runKey = syncRunKey(opts, env, deps);
const inventoryKey = syncInventoryKey(opts, env, deps);
const inventoryScan = prepareInventoryScan(syncLedger, inventoryKey, opts);
const preview = await buildSyncPreview(opts, { ...deps, env, hub, store, provenance }, inventoryScan);
const local = await localIndex(store);
const provenanceSnapshot = provenance.snapshot();
const pendingUnverifiedRecoveryIds = await reactivatePendingUnverifiedCandidates(preview.candidates, store, provenanceSnapshot);
const thinFrozenRevalidationIds = reactivateThinFrozenCandidates(preview.candidates, local, provenanceSnapshot);
const checkpointEnabled = !preview.failures.some((failure) => failure.stage === 'list');
const previousRun = opts.resume && checkpointEnabled ? syncLedger.latestIncompleteRun(runKey) : null;
const runId = previousRun?.runId ?? randomUUID();
const appendRunCheckpoint = (record) => {
if (checkpointEnabled)
syncLedger.appendRun(record);
};
const resumeSelection = previousRun
? resumePlannedCandidates(preview.candidates, previousRun, preview.reconciliation.inventoryComplete)
: { candidates: preview.candidates.map((candidate) => ({ ...candidate })), remoteMissingAssetIds: [], newRemoteMissingAssetIds: [] };
const finalCandidates = resumeSelection.candidates;
const currentPlan = actionableCandidatePlanIds(finalCandidates, opts.force);
const runPlan = previousRun?.plan ? mergeCandidatePlan(previousRun.plan, currentPlan) : currentPlan;
const hasActionable = currentPlan.length > 0;
if (previousRun?.plan) {
if (runPlan.length > previousRun.plan.length) {
appendRunCheckpoint({ runId, runKey, state: 'started', plan: runPlan });
}
}
if (hasActionable && !previousRun) {
appendRunCheckpoint({
runId,
runKey,
state: 'started',
plan: runPlan,
});
}
for (const assetId of resumeSelection.newRemoteMissingAssetIds) {
appendRunCheckpoint({
runId,
runKey,
state: 'progress',
remoteAssetId: assetId,
outcome: 'remote_missing',
});
}
const failures = [...preview.failures];
const resumed = previousRun
? await reconcileResumeCandidates({
candidates: finalCandidates,
previousRun,
store,
provenanceSnapshot,
syncLedger,
appendRunCheckpoint,
runId,
runKey,
inventoryKey,
failures,
})
: 0;
await verifyAlreadyLocalCandidates(finalCandidates, store, provenanceSnapshot, failures);
const prepared = [];
const pendingLogical = new Map(local.logical);
for (const candidate of finalCandidates) {
if (candidate.action !== 'would_import' && !(opts.force && candidate.action === 'id_collision'))
continue;
const checkpoint = previousRun?.processed.get(candidate.assetId);
if (checkpoint
&& checkpoint.outcome !== 'failed'
&& local.byAssetId.has(candidate.assetId)
&& !pendingUnverifiedRecoveryIds.has(candidate.assetId)
&& !thinFrozenRevalidationIds.has(candidate.assetId))
continue;
let preparedAsset;
try {
const fetched = await hub.fetchAssetById(candidate.assetId, { allowUnverifiedExactIdentity: true });
if (!fetched)
throw new SyncAbortError('not_found', 'Hub asset disappeared before import');
preparedAsset = prepareFetchedAsset(candidate, fetched, local.byAssetId, pendingLogical, opts.force, pendingUnverifiedRecoveryIds.has(candidate.assetId)
|| thinFrozenRevalidationIds.has(candidate.assetId));
}
catch (error) {
const mapped = mapSyncError(error);
failures.push({ stage: 'fetch', assetId: candidate.assetId, reason: mapped.reason });
appendRunCheckpoint({ runId, runKey, state: 'progress', remoteAssetId: candidate.assetId, outcome: 'failed', reason: mapped.reason });
updateFinalCandidate(finalCandidates, candidate, mapped.reason === 'integrity_error'
? { ...candidate, action: 'integrity_error' }
: mapped.reason === 'remote_revoked'
? { ...candidate, action: 'tombstone', failureReason: mapped.reason }
: { ...candidate, action: 'fetch_failed', failureReason: mapped.reason });
continue;
}
if ('blocked' in preparedAsset) {
updateFinalCandidate(finalCandidates, candidate, preparedAsset.blocked);
continue;
}
if (thinFrozenRevalidationIds.has(candidate.assetId)) {
try {
const persistedProvenance = provenance.get(candidate.assetId);
const stored = await readBackStoredAsset(store, candidate.assetId, preparedAsset.asset, false, persistedProvenance, preparedAsset.asset.type);
if (!preparedAsset.frozenUnverified
|| !preparedAsset.verificationReason
|| !preparedAsset.frozenContentId
|| !isActiveFrozenWaiver(persistedProvenance, candidate.assetId, preparedAsset.frozenContentId)
|| !assetstore.frozenAssetRecordsEqual(stored, preparedAsset.asset)) {
throw new SyncAbortError('integrity_error', 'Fetched Hub body differs from frozen local content');
}
updateFinalCandidate(finalCandidates, candidate, {
...candidate,
action: 'already_local',
verification: 'unverified',
verificationReason: preparedAsset.verificationReason,
});
appendRunCheckpoint({
runId,
runKey,
state: 'progress',
remoteAssetId: candidate.assetId,
outcome: 'already_local',
});
}
catch (error) {
const mapped = mapSyncError(error);
failures.push({ stage: 'fetch', assetId: candidate.assetId, reason: mapped.reason });
appendRunCheckpoint({
runId,
runKey,
state: 'progress',
remoteAssetId: candidate.assetId,
outcome: 'failed',
reason: mapped.reason,
});
updateFinalCandidate(finalCandidates, candidate, mapped.reason === 'integrity_error'
? { ...candidate, action: 'integrity_error', failureReason: mapped.reason }
: { ...candidate, action: 'fetch_failed', failureReason: mapped.reason });
}
continue;
}
const logicalId = rawStringField(preparedAsset.asset, 'id');
const finalCandidateBase = {
assetId: candidate.assetId,
type: candidate.type,
source: candidate.source,
...(candidate.status ? { status: candidate.status } : {}),
...(logicalId ? { logicalId } : {}),
...(preparedAsset.frozenUnverified && preparedAsset.verificationReason
? { verification: 'unverified', verificationReason: preparedAsset.verificationReason }
: {}),
};
const finalCandidate = preparedAsset.forced
? {
...finalCandidateBase,
action: 'force_import',
collisionWithAssetId: preparedAsset.collisionWithAssetId,
}
: { ...finalCandidateBase, action: 'would_import' };
updateFinalCandidate(finalCandidates, candidate, finalCandidate);
prepared.push({
candidate: finalCandidate,
asset: preparedAsset.asset,
...(preparedAsset.frozenUnverified ? { frozenUnverified: true } : {}),
...(preparedAsset.verificationReason ? { verificationReason: preparedAsset.verificationReason } : {}),
...(preparedAsset.frozenContentId ? { frozenContentId: preparedAsset.frozenContentId } : {}),
...(preparedAsset.forced ? { forced: true, collisionWithAssetId: preparedAsset.collisionWithAssetId } : {}),
});
if (logicalId)
pendingLogical.set(`${preparedAsset.asset.type}:${logicalId}`, preparedAsset.asset.asset_id);
}
const written = [];
for (const item of prepared) {
try {
const stored = item.frozenUnverified && item.verificationReason
? await assetstore.ingestUnverifiedConditional(store, provenance, item.asset, item.verificationReason, { allowLogicalCollision: opts.force }, 'hub')
: await assetstore.ingestUntrustedConditional(store, provenance, item.asset, { allowLogicalCollision: opts.force }, 'hub');
const assetId = stored.asset_id;
if (stored.status === 'logical_collision') {
updateFinalCandidate(finalCandidates, item.candidate, {
...item.candidate,
assetId,
action: 'id_collision',
collisionWithAssetId: stored.collisionWithAssetId,
});
continue;
}
const persistedProvenance = provenance.get(assetId);
await readBackStoredAsset(store, assetId, item.asset, false, persistedProvenance, item.asset.type);
if (stored.status === 'already_exists') {
updateFinalCandidate(finalCandidates, item.candidate, { ...item.candidate, assetId, action: 'already_local' });
if (item.frozenUnverified && !syncLedger.getForRunKey(runKey, assetId)) {
appendSyncRecord(syncLedger, item.candidate, item.asset, runKey, inventoryKey, {
...(item.forced ? { forced: true } : {}),
...(item.collisionWithAssetId ? { collisionWithAssetId: item.collisionWithAssetId } : {}),
});
}
appendRunCheckpoint({
runId,
runKey,
state: 'progress',
remoteAssetId: item.candidate.assetId,
outcome: item.frozenUnverified ? 'imported' : 'already_local',
});
continue;
}
const collisionWithAssetId = stored.collisionWithAssetId ?? item.collisionWithAssetId;
const forced = item.forced === true || collisionWithAssetId !== undefined;
const writtenCandidate = forced
? { ...item.candidate, action: 'force_import', collisionWithAssetId }
: item.candidate;
updateFinalCandidate(finalCandidates, item.candidate, writtenCandidate);
appendSyncRecord(syncLedger, writtenCandidate, item.asset, runKey, inventoryKey, {
...(forced ? { forced: true } : {}),
...(collisionWithAssetId ? { collisionWithAssetId } : {}),
});
appendRunCheckpoint({ runId, runKey, state: 'progress', remoteAssetId: item.candidate.assetId, outcome: 'imported' });
written.push({
assetId,
type: item.asset.type,
source: writtenCandidate.source,
stored: true,
trusted: false,
verification: item.frozenUnverified ? 'unverified' : 'verified',
...(item.verificationReason ? { verificationReason: item.verificationReason } : {}),
...(item.frozenContentId ? { frozenContentId: item.frozenContentId } : {}),
...(writtenCandidate.logicalId ? { logicalId: writtenCandidate.logicalId } : {}),
...(writtenCandidate.status ? { status: writtenCandidate.status } : {}),
...(forced ? { forced: true } : {}),
...(collisionWithAssetId ? { collisionWithAssetId } : {}),
});
}
catch (error) {
const mapped = mapSyncError(error);
failures.push({ stage: 'write', assetId: item.candidate.assetId, reason: mapped.reason });
appendRunCheckpoint({ runId, runKey, state: 'progress', remoteAssetId: item.candidate.assetId, outcome: 'failed', reason: mapped.reason });
updateFinalCandidate(finalCandidates, item.candidate, mapped.reason === 'integrity_error'
? { ...item.candidate, action: 'integrity_error', failureReason: mapped.reason }
: { ...item.candidate, action: 'write_failed', failureReason: mapped.reason });
}
}
const retryableInventoryFailure = failures.some(isRetryableAssetFailure);
let inventoryComplete = preview.reconciliation.inventoryComplete;
if (retryableInventoryFailure)
inventoryComplete = false;
let remoteAssetIds = new Set(finalCandidates.flatMap((candidate) => (candidate.assetId === MISSING_ASSET_ID ? [] : [candidate.assetId])));
let missingRemote = inventoryComplete
? countMissingRemote(syncLedger, inventoryKey, runKey, local.byAssetId, remoteAssetIds)
: 0;
let reconciliation;
let inventoryCheckpointFailed = false;
if (inventoryScan.trackable
&& inventoryScan.index === 0
&& !inventoryScan.retry
&& inventoryComplete
&& !failures.some((failure) => failure.stage === 'list')) {
try {
syncLedger.clearInventoryScan(inventoryKey);
}
catch {
failures.push({ stage: 'list', reason: 'inventory_scan_checkpoint_failed' });
inventoryComplete = false;
missingRemote = 0;
inventoryCheckpointFailed = true;
}
}
const shouldCheckpointInventory = inventoryScan.trackable
&& !inventoryCheckpointFailed
&& !failures.some((failure) => failure.stage === 'list')
&& (!inventoryComplete || inventoryScan.index > 0 || inventoryScan.retry);
if (shouldCheckpointInventory) {
const segment = inventorySegmentOutcome(finalCandidates, failures, written);
const inventoryBatch = {
scanId: inventoryScan.scanId,
inventoryKey,
scope: opts.scope,
index: inventoryScan.index,
inputCursorFingerprints: inventoryScan.inputCursorFingerprints,
nextCursorFingerprints: retryableInventoryFailure
? inventoryScan.inputCursorFingerprints
: fingerprintCursors(preview.nextCursors),
items: segment.items,
anonymousBlocked: segment.anonymousBlocked,
...(retryableInventoryFailure ? { cursorHeld: true } : {}),
};
const appended = inventoryScan.retry
? syncLedger.replaceInventoryRetryBatch(inventoryBatch)
: syncLedger.appendInventoryBatch(inventoryBatch);
const snapshot = appended ? syncLedger.latestInventoryScan(inventoryKey) : undefined;
if (!snapshot || snapshot.scanId !== inventoryScan.scanId) {
failures.push({ stage: 'list', reason: 'inventory_scan_checkpoint_failed' });
inventoryComplete = false;
missingRemote = 0;
inventoryCheckpointFailed = true;
}
else if (snapshot.complete) {
inventoryComplete = true;
remoteAssetIds = new Set(snapshot.outcomes.keys());
missingRemote = countMissingRemote(syncLedger, inventoryKey, runKey, local.byAssetId, remoteAssetIds);
reconciliation = reconcileInventorySnapshot(snapshot, missingRemote);
}
}
reconciliation ??= reconcile(finalCandidates, failures, written.length, true, inventoryComplete, missingRemote);
const counts = countsFromCandidates(preview.counts, finalCandidates);
const blocked = finalCandidates.filter(isBlockedCandidate);
const failedAssetIds = new Set(failures.flatMap((failure) => failure.assetId ? [failure.assetId] : []));
const finalCandidatesByAssetId = new Map(finalCandidates.map((candidate) => [candidate.assetId, candidate]));
const writtenAssetIds = new Set(written.map((asset) => asset.assetId));
const remoteMissingAssetIds = new Set(resumeSelection.remoteMissingAssetIds);
const actionComplete = runPlan.every((assetId) => {
if (remoteMissingAssetIds.has(assetId))
return true;
if (failedAssetIds.has(assetId))
return false;
if (local.byAssetId.has(assetId) || writtenAssetIds.has(assetId))
return true;
const candidate = finalCandidatesByAssetId.get(assetId);
return candidate?.action === 'already_local' || (candidate !== undefined && isBlockedCandidate(candidate));
});
if ((hasActionable || previousRun) && actionComplete && !inventoryCheckpointFailed) {
appendRunCheckpoint({ runId, runKey, state: 'completed' });
}
return {
ok: true,
group: GROUP,
mode: 'write',
scope: opts.scope,
counts,
candidates: finalCandidates,
written,
blocked,
failures,
nextCursors: preview.nextCursors,
resumed,
reconciliation,
};
}
async function executeSyncExport(opts, deps) {
if (!opts.exportPath)
throw new SyncAbortError('invalid_arg', '--export requires a file');
const store = deps.store ?? new assetstore.LocalJsonlProvider(deps.assetsDir ?? events.assetsDir());
const baseDir = storeBaseDir(store, deps);
const provenance = deps.provenance ?? new assetstore.ProvenanceStore(baseDir, deps.now);
const syncLedger = deps.syncLedger ?? new assetstore.AssetSyncLedger(baseDir, deps.now);
const provenanceSnapshot = provenance.snapshot();
const assets = await listLocalExportAssets(store, opts);
const provenanceRecords = assets
.map((asset) => provenanceSnapshot.get(asset.asset_id) ?? null)
.filter((record) => Boolean(record));
const syncRecords = assets
.map((asset) => syncLedger.get(asset.asset_id))
.filter((record) => Boolean(record));
const pkg = {
type: GEPX_TYPE,
version: GEPX_VERSION,
exportedAt: new Date(deps.now ? deps.now() : Date.now()).toISOString(),
assets,
malformedAssets: [],
provenance: provenanceRecords,
sync: syncRecords,
};
let serialized;
try {
serialized = `${JSON.stringify(pkg, null, 2)}\n`;
}
catch {
throw new SyncAbortError('export_failed', 'could not serialize .gepx package');
}
if (Buffer.byteLength(serialized, 'utf8') > MAX_GEPX_BYTES) {
throw new SyncAbortError('gepx_too_large', '.gepx package exceeds the 64 MiB limit');
}
try {
mkdirSync(dirname(opts.exportPath), { recursive: true });
writeFileSync(opts.exportPath, serialized, 'utf8');
}
catch {
throw new SyncAbortError('export_failed', 'could not write .gepx file');
}
return {
ok: true,
group: GROUP,
mode: 'export',
counts: {
exported: assets.length,
provenance: provenanceRecords.length,
sync: syncRecords.length,
},
assets: assets.map((asset) => ({
assetId: asset.asset_id,
type: asset.type,
...(rawStringField(asset, 'id') ? { logicalId: rawStringField(asset, 'id') } : {}),
trusted: provenanceSnapshot.get(asset.asset_id)?.trusted ?? true,
synced: Boolean(syncLedger.get(asset.asset_id)),
})),
};
}
async function buildGepxImportPreview(opts, deps) {
const pkg = readGepxPackage(opts.importPath);
const store = deps.store ?? new assetstore.LocalJsonlProvider(deps.assetsDir ?? events.assetsDir());
const local = await localIndex(store);
const candidates = classifyGepxAssets(pkg, opts, local);
return { ok: true, group: GROUP, mode: 'import_preview', counts: countsFromCandidates(emptyCounts(candidates.length), candidates), candidates };
}
async function executeGepxImportWrite(opts, deps) {
const pkg = readGepxPackage(opts.importPath);
assertNoConflictingGepxDuplicates(pkg);
const store = deps.store ?? new assetstore.LocalJsonlProvider(deps.assetsDir ?? events.assetsDir());
const local = await localIndex(store);
const finalCandidates = classifyGepxAssets(pkg, opts, local);
const failures = finalCandidates
.filter((candidate) => candidate.action === 'integrity_error')
.map((candidate) => ({
stage: 'write',
...(candidate.assetId.startsWith(MISSING_ASSET_ID) ? {} : { assetId: candidate.assetId }),
reason: candidate.failureReason ?? 'integrity_error',
}));
const assetsById = gepxImportAssetsById(pkg);
const syncById = new Map((pkg.sync ?? []).map((record) => [record.assetId, record]));
const prepared = [];
const pendingLogical = new Map(local.logical);
for (const candidate of finalCandidates) {
if (candidate.action !== 'would_import' && !(opts.force && candidate.action === 'id_collision'))
continue;
const entry = assetsById.get(candidate.assetId);
if (!entry)
throw new SyncAbortError('integrity_error', '.gepx package asset is missing');
const preparedAsset = preparePackagedAsset(candidate, entry.asset, local.byAssetId, pendingLogical, opts.force, entry.frozenUnverified);
if ('blocked' in preparedAsset) {
updateFinalCandidate(finalCandidates, candidate, preparedAsset.blocked);
continue;
}
const logicalId = rawStringField(preparedAsset.asset, 'id');
const finalCandidate = preparedAsset.forced
? {
...candidate,
...(logicalId ? { logicalId } : {}),
action: 'force_import',
collisionWithAssetId: preparedAsset.collisionWithAssetId,
}
: {
assetId: candidate.assetId,
type: candidate.type,
source: 'gepx',
...(candidate.status ? { status: candidate.status } : {}),
...(logicalId ? { logicalId } : {}),
action: 'would_import',
};
updateFinalCandidate(finalCandidates, candidate, finalCandidate);
prepared.push({
candidate: finalCandidate,
asset: preparedAsset.asset,
...(entry.frozenUnverified ? { frozenUnverified: true } : {}),
...(preparedAsset.forced ? { forced: true, collisionWithAssetId: preparedAsset.collisionWithAssetId } : {}),
});
if (logicalId)
pendingLogical.set(`${preparedAsset.asset.type}:${logicalId}`, preparedAsset.asset.asset_id);
}
const baseDir = storeBaseDir(store, deps);
const provenance = deps.provenance ?? new assetstore.ProvenanceStore(baseDir, deps.now);
const syncLedger = deps.syncLedger ?? new assetstore.AssetSyncLedger(baseDir, deps.now);
await verifyAlreadyLocalGepxCandidates(finalCandidates, assetsById, store, provenance.snapshot(), failures);
const written = [];
for (const item of prepared) {
try {
const stored = item.frozenUnverified
? await assetstore.ingestUnverifiedConditional(store, provenance, item.asset, 'unverified_gepx_import', { allowLogicalCollision: opts.force }, 'migrated')
: await assetstore.ingestUntrustedConditional(store, provenance, item.asset, { allowLogicalCollision: opts.force }, 'migrated');
const assetId = stored.asset_id;
if (stored.status === 'logical_collision') {
updateFinalCandidate(finalCandidates, item.candidate, {
...item.candidate,
assetId,
action: 'id_collision',
collisionWithAssetId: stored.collisionWithAssetId,
});
continue;
}
await readBackStoredAsset(store, assetId, item.asset, item.frozenUnverified === true);
if (stored.status === 'already_exists') {
updateFinalCandidate(finalCandidates, item.candidate, { ...item.candidate, assetId, action: 'already_local' });
continue;
}
const collisionWithAssetId = stored.collisionWithAssetId ?? item.collisionWithAssetId;
const forced = item.forced === true || collisionWithAssetId !== undefined;
const writtenCandidate = forced
? { ...item.candidate, action: 'force_import', collisionWithAssetId }
: item.candidate;
updateFinalCandidate(finalCandidates, item.candidate, writtenCandidate);
const syncRecord = syncById.get(assetId);
if (syncRecord)
syncLedger.append({
assetId,
type: item.asset.type,
source: 'hub',
scope: syncRecord.scope,
remoteAssetId: syncRecord.remoteAssetId,
...(syncRecord.logicalId ? { logicalId: syncRecord.logicalId } : {}),
...(syncRecord.status ? { status: syncRecord.status } : {}),
...(syncRecord.syncedAt ? { syncedAt: syncRecord.syncedAt } : {}),
...(syncRecord.forced ? { forced: true } : {}),
...(syncRecord.collisionWithAssetId ? { collisionWithAssetId: syncRecord.collisionWithAssetId } : {}),
});
written.push({
assetId,
type: item.asset.type,
source: 'gepx',
stored: stored.stored,
trusted: false,
...(writtenCandidate.logicalId ? { logicalId: writtenCandidate.logicalId } : {}),
...(writtenCandidate.status ? { status: writtenCandidate.status } : {}),
...(forced ? { forced: true } : {}),
...(collisionWithAssetId ? { collisionWithAssetId } : {}),
});
}
catch (error) {
const mapped = mapSyncError(error);
failures.push({ stage: 'write', assetId: item.candidate.assetId, reason: mapped.reason });
updateFinalCandidate(finalCandidates, item.candidate, {
...item.candidate,
action: 'write_failed',
failureReason: mapped.reason,
});
}
}
const counts = countsFromCandidates(emptyCounts(finalCandidates.length), finalCandidates);
return {
ok: true,
group: GROUP,
mode: 'import',
counts,
candidates: finalCandidates,
written,
blocked: finalCandidates.filter(isBlockedCandidate),
failures,
};
}
function emptyPreview(scope) {
return {
ok: true,
group: GROUP,
mode: 'preview',
scope,
counts: {
remotePurchased: 0,
remotePublished: 0,
uniqueRemote: 0,
alreadyLocal: 0,
tombstone: 0,
idCollision: 0,
unsupported: 0,
integrityError: 0,
wouldImport: 0,
unverifiedWouldImport: 0,
forcedImport: 0,
skippedLimit: 0,
fetchFailed: 0,
writeFailed: 0,
},
candidates: [],
failures: [],
nextCursors: { purchased: null, published: null },
reconciliation: {
remoteUnique: 0,
accounted: 0,
imported: 0,
alreadyLocal: 0,
blocked: 0,
failed: 0,
pending: 0,
inventoryComplete: true,
missingRemote: 0,
consistent: false,
},
};
}
function emptyCounts(uniqueRemote = 0) {
return {
remotePurchased: 0,
remotePublished: 0,
uniqueRemote,
alreadyLocal: 0,
tombstone: 0,
idCollision: 0,
unsupported: 0,
integrityError: 0,
wouldImport: 0,
unverifiedWouldImport: 0,
forcedImport: 0,
skippedLimit: 0,
fetchFailed: 0,
writeFailed: 0,
};
}
function emptyWrite(scope, mode = 'write') {
return {
...emptyPreview(scope),
mode,
written: [],
blocked: [],
resumed: 0,
};
}
async function createDefaultHub(deps, env) {
if (isPrivateHubMode(env)) {
try {
const hubUrl = requirePrivateHubUrl(env);
if (deps.connectPrivateHub)
return await deps.connectPrivateHub(env);
const { connectPrivateProxyHub } = await import('@evomap/evolver-proxy');
const runtime = await connectPrivateProxyHub({
hubUrl,
senderId: () => env['EVOMAP_NODE_ID'] ?? env['A2A_NODE_ID'],
env,
...(deps.now ? { now: deps.now } : {}),
});
return await preparePrivateSyncHub(runtime);
}
catch {
// Adapter diagnostics may contain local paths or credentials; never expose them through the CLI.
throw new SyncAbortError('private_adapter_unavailable', 'private Hub adapter is unavailable');
}
}
const hub = createRecipeHubFromEnv(env, deps.connectHub ?? connectPublicHub);
if (!isSyncAccountAssetHub(hub))
throw new Error('Hub adapter does not support account asset sync');
return hub;
}
export async function preparePrivateSyncHub(runtime) {
const hello = await runtime.hello({ rotate: false, evolverVersion: getCliVersion() });
if (!hello.ok)
throw new Error('private Hub enrollment failed');
if (!isSyncAccountAssetHub(runtime.hub))
throw new Error('private Hub adapter does not support account asset sync');
return runtime.hub;
}
async function listRemoteRows(hub, opts, listing) {
const failures = [];
const purchasedBudget = { uniqueAssetIds: new Set(), rawPages: 0, rawRows: 0 };
const publishedBudget = { uniqueAssetIds: new Set(), rawPages: 0, rawRows: 0 };
const purchasedResult = (opts.scope === 'all' || opts.scope === 'purchased') && !listing.skipPurchased
? await listAll(hub, {
scope: 'purchased',
...(opts.type ? { type: opts.type } : {}),
limit: opts.limit,
...(opts.purchasedCursor ? { cursor: opts.purchasedCursor } : {}),
}, opts.pageSize, failures, purchasedBudget)
: { rows: [], complete: true, nextCursor: undefined };
const shouldListPublished = (opts.scope === 'all' || opts.scope === 'published') && !listing.skipPublished;
const publishedResult = shouldListPublished
? await listAll(hub, {
scope: 'published',
...(opts.type ? { type: opts.type } : {}),
status: opts.status,
limit: opts.limit,
...(opts.publishedCursor ? { cursor: opts.publishedCursor } : {}),
}, opts.pageSize, failures, publishedBudget)
: { rows: [], complete: true, nextCursor: undefined };
const startsAtInventoryBeginning = !opts.purchasedCursor && !opts.publishedCursor;
return {
purchased: purchasedResult.rows,
published: publishedResult.rows,
failures,
inventoryComplete: startsAtInventoryBeginning && purchasedResult.complete && publishedResult.complete && failures.length === 0,
nextCursors: {
purchased: purchasedResult.nextCursor ?? null,
published: publishedResult.nextCursor ?? null,
},
};
}
async function listAll(hub, opts, pageSize, failures, budget) {
const rows = [];
let cursor = opts.cursor;
const seenCursors = new Set();
let consecutiveEmptyPages = 0;
let consecutivePagesWithoutUniqueProgress = 0;
while (budget.uniqueAssetIds.size < opts.limit) {
if (budget.rawPages >= MAX_RAW_PAGES) {
failures.push({ stage: 'list', scope: opts.scope, reason: 'raw_page_limit' });
break;
}
if (budget.rawRows >= MAX_RAW_ROWS) {
failures.push({ stage: 'list', scope: opts.scope, reason: 'raw_row_limit' });
break;
}
const remaining = opts.limit - budget.uniqueAssetIds.size;
const requestedPageLimit = Math.min(remaining, pageSize);
let rawResult;
try {
rawResult = await hub.listAccountAssets({ ...opts, limit: requestedPageLimit, ...(cursor ? { cursor } : {}) });
budget.rawPages += 1;
}
catch (error) {
failures.push({ stage: 'list', scope: opts.scope, reason: mapSyncError(error).reason });
break;
}
const result = asRecord(rawResult);
if (!result || !Array.isArray(result['assets']) || typeof result['hasMore'] !== 'boolean') {
failures.push({ stage: 'list', scope: opts.scope, reason: 'malformed_page' });
break;
}
const hasMore = result['hasMore'];
const nextCursor = result['nextCursor'];
const pageRows = result['assets'];
if (pageRows.length > requestedPageLimit) {
failures.push({ stage: 'list', scope: opts.scope, reason: 'oversized_page' });
break;
}
const availableRawRows = MAX_RAW_ROWS - budget.rawRows;
const boundedPageRows = pageRows.slice(0, availableRawRows);
budget.rawRows += boundedPageRows.length;
const uniqueBefore = budget.uniqueAssetIds.size;
let consumedPageRows = 0;
// The page is bounded by the remaining unique budget, so consuming it fully cannot exceed the limit.
for (const raw of boundedPageRows) {
consumedPageRows += 1;
const row = asRecord(raw);
if (!row) {
failures.push({ stage: 'list', scope: opts.scope, reason: 'invalid_remote_asset' });
continue;
}
rows.push(row);
const assetId = rawStringField(row, 'asset_id');
if (assetId)
budget.uniqueAssetIds.add(assetId);
}
const uniqueProgress = budget.uniqueAssetIds.size - uniqueBefore;
if ((!hasMore && nextCursor !== undefined) || (hasMore && (typeof nextCursor !== 'string' || !nextCursor.trim()))) {
failures.push({ stage: 'list', scope: opts.scope, reason: 'invalid_remote_pagination' });
break;
}
if (hasMore && typeof nextCursor === 'string' && !isValidOpaqueCursor(nextCursor)) {
failures.push({ stage: 'list', scope: opts.scope, reason: 'malformed_cursor' });
break;
}
if (!hasMore) {
return { rows, complete: consumedPageRows === pageRows.length, nextCursor: undefined };
}
if (typeof nextCursor !== 'string' || !nextCursor.trim()) {
failures.push({ stage: 'list', scope: opts.scope, reason: 'invalid_remote_pagination' });
break;
}
if (boundedPageRows.length < pageRows.length || budget.rawRows >= MAX_RAW_ROWS) {
failures.push({ stage: 'list', scope: opts.scope, reason: 'raw_row_limit' });
break;
}
consecutiveEmptyPages = pageRows.length === 0 ? consecutiveEmptyPages + 1 : 0;
if (consecutiveEmptyPages > MAX_CONSECUTIVE_EMPTY_PAGES) {
failures.push({ stage: 'list', scope: opts.scope, reason: 'empty_page_limit' });
break;
}
consecutivePagesWithoutUniqueProgress = uniqueProgress === 0 ? consecutivePagesWithoutUniqueProgress + 1 : 0;
if (consecutivePagesWithoutUniqueProgress > MAX_CONSECUTIVE_PAGES_WITHOUT_UNIQUE_PROGRESS) {
failures.push({ stage: 'list', scope: opts.scope, reason: 'no_unique_progress_limit' });
break;
}
if (nextCursor === cursor || seenCursors.has(nextCursor)) {
failures.push({ stage: 'list', scope: opts.scope, reason: 'invalid_remote_pagination' });
break;
}
if (budget.uniqueAssetIds.size >= opts.limit)
return { rows, complete: false, nextCursor };
if (cursor)
seenCursors.add(cursor);
cursor = nextCursor;
}
return { rows, complete: false, nextCursor: cursor };
}
function isValidOpaqueCursor(cursor) {
if (cursor.length > MAX_CURSOR_LENGTH || !cursor.trim())
return false;
for (const character of cursor) {
const codePoint = character.codePointAt(0);
if (codePoint !== undefined && (codePoint <= 0x1f || codePoint === 0x7f))
return false;
}
return true;
}
function addUnique(target, missingAssetId, conflictingDuplicateAssetIds, row, source) {
const assetId = rawStringField(row, 'asset_id');
if (!assetId) {
missingAssetId.push({ row, source });
return;
}
const existing = target.get(assetId);
if (!existing) {
target.set(assetId, { row, source });
return;
}
const merged = mergeDuplicateRemoteRows(existing.row, row, assetId);
if (!merged)
conflictingDuplicateAssetIds.add(assetId);
else
target.set(assetId, {
row: merged,
source: existing.source === 'purchased' || source === 'purchased' ? 'purchased' : existing.source,
});
}
function classifyConflictingRemoteDuplicate(row, source) {
return {
assetId: rawStringField(row, 'asset_id') ?? MISSING_ASSET_ID,
type: 'unknown',
source,
action: 'integrity_error',
failureReason: 'conflicting_remote_duplicate',
};
}
function mergeDuplicateRemoteRows(left, right, assetId) {
const leftIdentity = remoteDuplicateIdentity(left, assetId);
const rightIdentity = remoteDuplicateIdentity(right, assetId);
if (leftIdentity.invalid || rightIdentity.invalid)
return null;
if (leftIdentity.declaredType
&& rightIdentity.declaredType
&& leftIdentity.declaredType !== rightIdentity.declaredType)
return null;
if (leftIdentity.logicalId && rightIdentity.logicalId && leftIdentity.logicalId !== rightIdentity.logicalId)
return null;
if (leftIdentity.tombstone !== rightIdentity.tombstone)
return null;
if (leftIdentity.canonicalBody && rightIdentity.canonicalBody && leftIdentity.canonicalBody !== rightIdentity.canonicalBody) {
return null;
}
if (leftIdentity.canonicalBody && !rightIdentity.canonicalBody)
return left;
if (rightIdentity.canonicalBody && !leftIdentity.canonicalBody)
return right;
if (leftIdentity.synthesized !== rightIdentity.synthesized)
return leftIdentity.synthesized ? left : right;
return stableRemoteRowKey(left) <= stableRemoteRowKey(right) ? left : right;
}
function remoteDuplicateIdentity(row, assetId) {
const type = remoteType(row);
const declaredType = remoteDeclaredType(row);
const logicalId = remoteLogicalId(row);
const delivery = unwrapLiveHubDelivery(row, assetId);
let canonicalBody;
let invalid = delivery.invalid;
if (!invalid && type && declaresFullAssetPayload(delivery.content, type)) {
const cleaned = stripHubDeliveryMetadataForIntegrity(delivery.content);
if (!looksLikeFullAsset(cleaned, type) || !wire.validateWireDeep(cleaned).ok) {
invalid = true;
}
else {
try {
canonicalBody = wire.canonicalize(cleaned);
}
catch {
invalid = true;
}
}
}
return {
...(declaredType ? { declaredType } : {}),
...(logicalId ? { logicalId } : {}),
tombstone: isRemoteTombstoneDelivery(row),
...(canonicalBody ? { canonicalBody } : {}),
synthesized: Boolean(stringField(delivery.content, 'payload_backfill_reason')
?? stringField(row, 'payload_backfill_reason')),
invalid,
};
}
function stableRemoteRowKey(row) {
try {
return wire.canonicalize(row);
}
catch {
return JSON.stringify(row) ?? '';
}
}
async function localIndex(store) {
const byAssetId = new Set();
const logical = new Map();
const recordsByAssetId = new Map();
for (const kind of ['Gene', 'Capsule']) {
for (const row of await store.list(kind, Number.MAX_SAFE_INTEGER)) {
byAssetId.add(row.asset_id);
recordsByAssetId.set(row.asset_id, row);
const logicalId = rawStringField(row, 'id');
if (logicalId)
logical.set(`${kind}:${logicalId}`, row.asset_id);
}
}
return { byAssetId, logical, recordsByAssetId };
}
async function listLocalExportAssets(store, opts) {
const kinds = opts.type ? [opts.type] : ['Gene', 'Capsule'];
const assets = [];
for (const kind of kinds) {
const remaining = opts.limit - assets.length;
if (remaining <= 0)
break;
assets.push(...await store.list(kind, remaining));
}
return assets.slice(0, opts.limit);
}
function classifyRemote(row, source, local, provenanceSnapshot) {
const assetId = rawStringField(row, 'asset_id') ?? MISSING_ASSET_ID;
const type = remoteType(row);
const logicalId = remoteLogicalId(row);
const status = stringField(row, 'status');
const base = {
assetId,
type: type ?? 'unknown',
...(logicalId ? { logicalId } : {}),
source,
...(status ? { status } : {}),
};
if (assetId === MISSING_ASSET_ID)
return { ...base, action: 'integrity_error' };
if (!isContentAssetId(assetId))
return { ...base, action: 'integrity_error' };
if (unwrapLiveHubDelivery(row, assetId).invalid)
return { ...base, action: 'integrity_error' };
if (isRemoteTombstoneDelivery(row))
return { ...base, action: 'tombstone' };
if (type !== 'Gene' && type !== 'Capsule')
return { ...base, action: 'unsupported_type' };
const payloadVerification = inspectRemotePayload(row, type, assetId);
if (payloadVerification === 'invalid')
return { ...base, action: 'integrity_error' };
const isUnverified = payloadVerification === 'unverified_hub_rewrite'
|| payloadVerification === 'unverified_hub_synthesized';
const verifiedBase = isUnverified
? { ...base, verification: 'unverified', verificationReason: payloadVerification }
: base;
if (local.byAssetId.has(assetId)) {
if (payloadVerification === 'unavailable') {
const stored = local.recordsByAssetId.get(assetId);
const localComputed = stored ? wire.computeAssetId(stored) : null;
if (!stored
|| stored.asset_id !== assetId
|| stored.type !== type
|| !localComputed) {
return {
...base,
action: 'integrity_error',
failureReason: 'local_content_integrity_mismatch',
};
}
if (localComputed !== assetId) {
if (!isActiveFrozenWaiver(provenanceSnapshot.get(assetId), assetId, localComputed, true)) {
return {
...base,
action: 'integrity_error',
failureReason: 'local_frozen_waiver_mismatch',
};
}
return {
...base,
action: 'already_local',
verification: 'unverified',
};
}
}
if (payloadVerification !== 'unavailable'
&& !inlineContentMatchesLocal(row, type, assetId, local.recordsByAssetId.get(assetId), provenanceSnapshot.get(assetId))) {
return {
...base,
action: 'integrity_error',
failureReason: 'frozen_remote_content_changed',
};
}
return { ...verifiedBase, action: 'already_local' };
}
if (logicalId) {
const localAssetId = local.logical.get(`${type}:${logicalId}`);
if (localAssetId && localAssetId !== assetId)
return { ...verifiedBase, action: 'id_collision', collisionWithAssetId: localAssetId };
}
return { ...verifiedBase, action: 'would_import' };
}
function classifyGepxAssets(pkg, opts, local) {
const classified = uniqueGepxAssets(pkg)
.filter((entry) => !opts.type || entry.type === opts.type)
.map((entry) => classifyGepxAsset(entry, local));
let remaining = opts.limit;
const candidates = classified.map((candidate) => {
if (candidate.action === 'integrity_error')
return candidate;
if (remaining > 0) {
remaining -= 1;
return candidate;
}
return { ...candidate, action: 'skipped_limit', failureReason: 'limit_reached' };
});
const malformed = pkg.malformedAssets.map((entry) => ({
assetId: entry.assetId ?? `${MISSING_ASSET_ID}-${entry.index}`,
type: 'unknown',
source: 'gepx',
action: 'integrity_error',
failureReason: 'invalid_package_asset',
}));
return [...candidates, ...malformed];
}
function classifyGepxAsset(entry, local) {
const { assetId, type, logicalId } = entry;
const base = {
assetId,
type: type ?? String(entry.original.type ?? 'unknown'),
...(logicalId ? { logicalId } : {}),
source: 'gepx',
};
if (type !== 'Gene' && type !== 'Capsule')
return { ...base, action: 'unsupported_type' };
if (entry.integrityError)
return { ...base, action: 'integrity_error' };
if (local.byAssetId.has(assetId))
return { ...base, action: 'already_local' };
if (logicalId) {
const localAssetId = local.logical.get(`${type}:${logicalId}`);
if (localAssetId && localAssetId !== assetId)
return { ...base, action: 'id_collision', collisionWithAssetId: localAssetId };
}
return { ...base, action: 'would_import' };
}
function uniqueGepxAssets(pkg) {
const unique = new Map();
const missing = [];
const provenanceByAssetId = indexUniqueGepxProvenance(pkg);
for (const asset of pkg.assets) {
const normalized = normalizeGepxAsset(asset, provenanceByAssetId.get(asset.asset_id) ?? undefined);
if (normalized.assetId === MISSING_ASSET_ID) {
missing.push(normalized);
continue;
}
if (!unique.has(normalized.assetId))
unique.set(normalized.assetId, normalized);
}
return [...unique.values(), ...missing];
}
function gepxImportAssetsById(pkg) {
const assets = new Map();
for (const entry of uniqueGepxAssets(pkg)) {
if (entry.type && !entry.integrityError && !assets.has(entry.assetId))
assets.set(entry.assetId, entry);
}
return assets;
}
function indexUniqueGepxProvenance(pkg) {
const indexed = new Map();
for (const record of pkg.provenance ?? []) {
indexed.set(record.assetId, indexed.has(record.assetId) ? null : record);
}
return indexed;
}
function normalizeGepxAsset(asset, provenance) {
const cleaned = { ...asset };
const assetId = rawStringField(cleaned, 'asset_id') ?? MISSING_ASSET_ID;
const type = cleaned.type === 'Gene' || cleaned.type === 'Capsule' ? cleaned.type : undefined;
const logicalId = rawStringField(cleaned, 'id');
let integrityError = false;
let frozenUnverified = false;
if (type) {
const claimed = rawStringField(cleaned, 'asset_id');
const fullAsset = looksLikeFullAsset(cleaned, type);
const computed = fullAsset ? wire.computeAssetId(cleaned) : undefined;
const supportedFrozenProvenance = provenance
&& ((provenance.source === 'hub'
&& (provenance.reason === 'unverified_hub_rewrite' || provenance.reason === 'unverified_hub_synthesized'))
|| (provenance.source === 'migrated' && provenance.reason === 'unverified_gepx_import'));
frozenUnverified = Boolean(claimed
&& computed
&& claimed === assetId
&& computed !== claimed
&& provenance?.assetId === assetId
&& supportedFrozenProvenance
&& provenance.trusted === false
&& provenance.decision === undefined
&& provenance.decidedBy === undefined
&& provenance.promotedBy === undefined
&& provenance.frozenContentId === computed);
integrityError = assetId === MISSING_ASSET_ID
|| !claimed
|| !isContentAssetId(assetId)
|| !fullAsset
|| !computed
|| claimed !== assetId
|| (computed !== claimed && !frozenUnverified);
}
return {
original: asset,
asset: cleaned,
assetId,
...(type ? { type } : {}),
...(logicalId ? { logicalId } : {}),
integrityError,
frozenUnverified,
};
}
function countsFromCandidates(base, candidates) {
const counts = {
remotePurchased: base.remotePurchased,
remotePublished: base.remotePublished,
uniqueRemote: candidates.length,
alreadyLocal: 0,
tombstone: 0,
idCollision: 0,
unsupported: 0,
integrityError: 0,
wouldImport: 0,
unverifiedWouldImport: 0,
forcedImport: 0,
skippedLimit: 0,
fetchFailed: 0,
writeFailed: 0,
};
for (const candidate of candidates) {
if (candidate.action === 'already_local')
counts.alreadyLocal += 1;
else if (candidate.action === 'tombstone')
counts.tombstone += 1;
else if (candidate.action === 'id_collision')
counts.idCollision += 1;
else if (candidate.action === 'would_import')
counts.wouldImport += 1;
else if (candidate.action === 'force_import')
counts.forcedImport += 1;
else if (candidate.action === 'skipped_limit')
counts.skippedLimit += 1;
else if (candidate.action === 'fetch_failed')
counts.fetchFailed += 1;
else if (candidate.action === 'write_failed')
counts.writeFailed += 1;
else if (candidate.action === 'unsupported_type')
counts.unsupported += 1;
else if (candidate.action === 'integrity_error')
counts.integrityError += 1;
if (candidate.action === 'would_import' && candidate.verification === 'unverified') {
counts.unverifiedWouldImport += 1;
}
}
return counts;
}
function updateFinalCandidate(candidates, original, replacement) {
const index = candidates.findIndex((candidate) => candidate.assetId === original.assetId && candidate.source === original.source);
if (index >= 0)
candidates[index] = replacement;
}
function isBlockedCandidate(candidate) {
return candidate.action !== 'would_import'
&& candidate.action !== 'force_import'
&& candidate.action !== 'already_local'
&& candidate.action !== 'skipped_limit';
}
function inspectRemotePayload(row, type, assetId) {
const delivery = unwrapLiveHubDelivery(row, assetId);
if (delivery.invalid)
return 'invalid';
const source = delivery.content;
if (!looksLikeFullAsset(source, type)) {
return declaresFullAssetPayload(source, type) ? 'invalid' : 'unavailable';
}
const candidate = stripHubDeliveryMetadataForIntegrity(source);
if (!wire.validateWireDeep(candidate).ok)
return 'invalid';
const claimed = rawStringField(candidate, 'asset_id');
if (!claimed || !isContentAssetId(claimed) || claimed !== assetId)
return 'invalid';
const computed = wire.computeAssetId(candidate);
if (!computed)
return 'invalid';
if (computed === claimed)
return 'verified';
return stringField(source, 'payload_backfill_reason') ?? stringField(row, 'payload_backfill_reason')
? 'unverified_hub_synthesized'
: 'unverified_hub_rewrite';
}
function inlineContentMatchesLocal(row, type, assetId, local, provenance) {
const delivery = unwrapLiveHubDelivery(row, assetId);
if (delivery.invalid || !local)
return false;
const remote = stripHubDeliveryMetadataForIntegrity(delivery.content);
const remoteComputed = wire.computeAssetId(remote);
const localComputed = wire.computeAssetId(local);
if (!remoteComputed
|| !localComputed
|| local.asset_id !== assetId
|| local.type !== type
|| remoteComputed !== localComputed
|| !assetstore.frozenAssetRecordsEqual(local, remote))
return false;
if (remoteComputed === assetId)
return true;
return isActiveFrozenWaiver(provenance, assetId, remoteComputed, true);
}
function isActiveFrozenWaiver(provenance, assetId, frozenContentId, allowPending = false) {
return Boolean(provenance?.assetId === assetId
&& provenance.trusted === false
&& provenance.frozenContentId === frozenContentId
&& provenance.decision === undefined
&& provenance.decidedBy === undefined
&& provenance.promotedBy === undefined
&& ((provenance.source === 'hub'
&& (provenance.reason === 'unverified_hub_rewrite' || provenance.reason === 'unverified_hub_synthesized'))
|| (provenance.source === 'migrated' && provenance.reason === 'unverified_gepx_import')
|| (allowPending && provenance.source === 'hub' && provenance.reason === UNVERIFIED_HUB_WRITE_PENDING_REASON)));
}
function unwrapLiveHubDelivery(row, expectedAssetId) {
const nested = asRecord(row.payload);
if (Object.prototype.hasOwnProperty.call(row, 'type') || !nested) {
const directType = rawStringField(row, 'type');
const directLogicalId = rawStringField(row, 'id');
const aliasType = rawStringField(row, 'asset_type');
const aliasLogicalId = rawStringField(row, 'local_id');
return {
content: row,
invalid: Boolean(hasInvalidLiveIdentityField(row, ['asset_id', 'type', 'asset_type', 'id', 'local_id'])
|| (aliasType !== undefined && directType !== undefined && aliasType !== directType)
|| (aliasLogicalId !== undefined && directLogicalId !== undefined && aliasLogicalId !== directLogicalId)),
};
}
const outerAssetId = rawStringField(row, 'asset_id');
const outerType = rawStringField(row, 'asset_type');
const outerLogicalIds = [rawStringField(row, 'id'), rawStringField(row, 'local_id')]
.filter((value) => value !== undefined);
const innerAssetId = rawStringField(nested, 'asset_id');
const innerType = rawStringField(nested, 'type');
const innerAliasType = rawStringField(nested, 'asset_type');
const innerLogicalId = rawStringField(nested, 'id');
const innerAliasLogicalId = rawStringField(nested, 'local_id');
const invalid = Boolean(hasInvalidLiveIdentityField(row, ['asset_id', 'asset_type', 'id', 'local_id'])
|| hasInvalidLiveIdentityField(nested, ['asset_id', 'type', 'asset_type', 'id', 'local_id'])
|| outerAssetId === undefined
|| outerAssetId !== expectedAssetId
|| innerAssetId === undefined
|| innerAssetId !== expectedAssetId
|| innerType === undefined
|| (outerType !== undefined && innerType !== undefined && outerType !== innerType)
|| (innerAliasType !== undefined && innerAliasType !== innerType)
|| (outerLogicalIds.length > 0 && innerLogicalId === undefined)
|| (innerLogicalId !== undefined && outerLogicalIds.some((outerLogicalId) => outerLogicalId !== innerLogicalId))
|| (innerAliasLogicalId !== undefined && innerAliasLogicalId !== innerLogicalId));
const backfillReason = stringField(row, 'payload_backfill_reason');
return {
content: backfillReason && stringField(nested, 'payload_backfill_reason') === undefined
? { ...nested, payload_backfill_reason: backfillReason }
: nested,
invalid,
};
}
function prepareFetchedAsset(candidate, fetched, localAssetIds, logical, force, allowExistingFrozenFinalize = false) {
if (isRemoteTombstoneDelivery(fetched)) {
throw new SyncAbortError('remote_revoked', 'Hub asset was revoked before import');
}
const delivery = unwrapLiveHubDelivery(fetched, candidate.assetId);
if (delivery.invalid)
throw new SyncAbortError('integrity_error', 'Hub delivery identity changed before import');
const delivered = delivery.content;
const type = delivered.type === 'Gene' || delivered.type === 'Capsule' ? delivered.type : undefined;
if (!type)
return { blocked: { ...candidate, type: delivered.type ?? candidate.type, action: 'unsupported_type' } };
if (type !== candidate.type)
throw new SyncAbortError('integrity_error', 'Hub asset type changed before import');
const cleaned = stripHubDeliveryMetadataForIntegrity(delivered);
const claimed = rawStringField(cleaned, 'asset_id');
if (!looksLikeFullAsset(cleaned, type)) {
throw new SyncAbortError('integrity_error', 'Hub asset payload is incomplete');
}
if (!wire.validateWireDeep(cleaned).ok) {
throw new SyncAbortError('integrity_error', 'Hub asset failed wire schema validation before import');
}
const computed = wire.computeAssetId(cleaned);
if (!claimed || !computed || !isContentAssetId(claimed) || claimed !== candidate.assetId) {
throw new SyncAbortError('integrity_error', 'Hub asset identity verification failed before import');
}
const logicalId = rawStringField(cleaned, 'id');
if (candidate.logicalId && logicalId !== candidate.logicalId) {
throw new SyncAbortError('integrity_error', 'Hub asset logical id changed before import');
}
const frozenUnverified = computed !== claimed;
const verificationReason = frozenUnverified
? stringField(delivered, 'payload_backfill_reason')
? 'unverified_hub_synthesized'
: 'unverified_hub_rewrite'
: undefined;
const verification = frozenUnverified
? { frozenUnverified: true, verificationReason, frozenContentId: computed }
: {};
if (localAssetIds.has(cleaned.asset_id) && !allowExistingFrozenFinalize) {
return { blocked: { ...candidate, action: 'already_local' } };
}
if (logicalId) {
const localAssetId = logical.get(`${type}:${logicalId}`);
if (localAssetId && localAssetId !== cleaned.asset_id) {
if (force)
return { asset: cleaned, ...verification, forced: true, collisionWithAssetId: localAssetId };
return { blocked: { ...candidate, logicalId, action: 'id_collision', collisionWithAssetId: localAssetId } };
}
}
return { asset: cleaned, ...verification };
}
async function reactivatePendingUnverifiedCandidates(candidates, store, provenanceSnapshot) {
const recoveryIds = new Set();
for (const candidate of [...candidates]) {
if (candidate.action !== 'already_local' || (candidate.type !== 'Gene' && candidate.type !== 'Capsule'))
continue;
const record = provenanceSnapshot.get(candidate.assetId);
if (record?.assetId !== candidate.assetId
|| record.source !== 'hub'
|| record.trusted !== false
|| record.reason !== UNVERIFIED_HUB_WRITE_PENDING_REASON
|| !record.frozenContentId
|| record.decision !== undefined
|| record.decidedBy !== undefined
|| record.promotedBy !== undefined)
continue;
let stored;
try {
stored = await store.get(candidate.assetId);
}
catch {
continue;
}
const computed = stored ? wire.computeAssetId(stored) : null;
const storedLogicalId = stored ? rawStringField(stored, 'id') : undefined;
if (!stored
|| stored.asset_id !== candidate.assetId
|| stored.type !== candidate.type
|| !looksLikeFullAsset(stored, candidate.type)
|| !wire.validateWireDeep(stored).ok
|| !computed
|| computed === candidate.assetId
|| computed !== record.frozenContentId
|| (candidate.logicalId !== undefined && storedLogicalId !== candidate.logicalId))
continue;
recoveryIds.add(candidate.assetId);
updateFinalCandidate(candidates, candidate, {
...candidate,
action: 'would_import',
verification: 'unverified',
});
}
return recoveryIds;
}
function reactivateThinFrozenCandidates(candidates, local, provenanceSnapshot) {
const revalidationIds = new Set();
for (const candidate of [...candidates]) {
if (candidate.action !== 'already_local'
|| candidate.verification !== 'unverified'
|| candidate.verificationReason !== undefined
|| (candidate.type !== 'Gene' && candidate.type !== 'Capsule'))
continue;
const stored = local.recordsByAssetId.get(candidate.assetId);
const frozenContentId = stored ? wire.computeAssetId(stored) : null;
if (!stored
|| stored.asset_id !== candidate.assetId
|| stored.type !== candidate.type
|| !frozenContentId
|| frozenContentId === candidate.assetId
|| !isActiveFrozenWaiver(provenanceSnapshot.get(candidate.assetId), candidate.assetId, frozenContentId))
continue;
revalidationIds.add(candidate.assetId);
updateFinalCandidate(candidates, candidate, {
...candidate,
action: 'would_import',
verification: 'unverified',
});
}
return revalidationIds;
}
function resumePlannedCandidates(candidates, previousRun, inventoryComplete) {
const plan = previousRun.plan;
if (!plan) {
throw new SyncAbortError('resume_plan_drift', 'cannot resume a run without its original candidate plan');
}
const byPlanId = new Map(candidates.flatMap((candidate) => candidate.assetId === MISSING_ASSET_ID
? []
: [[candidate.assetId, candidate]]));
const knownRemoteMissing = plan.filter((assetId) => (previousRun.processed.get(assetId)?.outcome === 'remote_missing'
&& !byPlanId.has(assetId)));
const unresolvedMissing = plan.filter((assetId) => {
const processed = previousRun.processed.get(assetId);
return (!processed || processed.outcome === 'failed') && !byPlanId.has(assetId);
});
const newRemoteMissingAssetIds = inventoryComplete ? unresolvedMissing : [];
const remoteMissingAssetIds = [...knownRemoteMissing, ...newRemoteMissingAssetIds];
const remoteMissing = new Set(remoteMissingAssetIds);
const plannedCandidates = plan.flatMap((assetId) => {
const candidate = byPlanId.get(assetId);
if (candidate)
return [{ ...candidate }];
return remoteMissing.has(assetId) ? [{
assetId,
type: 'unknown',
source: 'reconciliation',
action: 'tombstone',
failureReason: 'remote_missing',
}] : [];
});
const plannedIds = new Set(plan);
const newCandidates = candidates
.filter((candidate) => candidate.assetId === MISSING_ASSET_ID || !plannedIds.has(candidate.assetId))
.map((candidate) => ({ ...candidate }));
return {
candidates: [...plannedCandidates, ...newCandidates],
remoteMissingAssetIds,
newRemoteMissingAssetIds,
};
}
function actionableCandidatePlanIds(candidates, force) {
return candidates.flatMap((candidate) => (candidate.assetId !== MISSING_ASSET_ID
&& (candidate.action === 'would_import' || (force && candidate.action === 'id_collision'))
? [candidate.assetId]
: []));
}
function mergeCandidatePlan(previous, current) {
const merged = [...previous];
const seen = new Set(previous);
for (const assetId of current) {
if (seen.has(assetId))
continue;
seen.add(assetId);
merged.push(assetId);
}
return merged;
}
function assertNoConflictingGepxDuplicates(pkg) {
const seen = new Map();
for (const asset of pkg.assets) {
const assetId = rawStringField(asset, 'asset_id');
if (!assetId)
continue;
const serialized = JSON.stringify(asset);
const previous = seen.get(assetId);
if (previous !== undefined && previous !== serialized) {
throw new SyncAbortError('integrity_error', '.gepx package contains conflicting duplicate asset IDs');
}
seen.set(assetId, serialized);
}
}
async function reconcileResumeCandidates(input) {
let resumed = 0;
for (const candidate of input.candidates) {
if (candidate.action !== 'already_local')
continue;
const checkpoint = input.previousRun.processed.get(candidate.assetId);
const provenanceRecord = input.provenanceSnapshot.get(candidate.assetId);
const syncRecord = input.syncLedger.getForRunKey(input.runKey, candidate.assetId);
const checkpointedAlreadyLocal = checkpoint?.outcome === 'already_local';
const hubImportProvenance = provenanceRecord?.assetId === candidate.assetId
&& provenanceRecord.source === 'hub'
&& !provenanceRecord.trusted;
const importedEvidence = Boolean(hubImportProvenance || syncRecord || (checkpoint && checkpoint.outcome !== 'already_local'));
if (!checkpointedAlreadyLocal && !importedEvidence)
continue;
try {
const storedAsset = await readBackStoredAsset(input.store, candidate.assetId, undefined, false, provenanceRecord, candidate.type);
if (checkpointedAlreadyLocal) {
resumed += 1;
continue;
}
if (!provenanceRecord || provenanceRecord.assetId !== candidate.assetId || provenanceRecord.source !== 'hub' || provenanceRecord.trusted) {
throw new SyncAbortError('resume_verification_failed', 'Resume provenance verification failed');
}
if (syncRecord && !syncRecordMatches(syncRecord, candidate, storedAsset)) {
throw new SyncAbortError('resume_verification_failed', 'Resume sync ledger verification failed');
}
if (!syncRecord)
appendSyncRecord(input.syncLedger, candidate, storedAsset, input.runKey, input.inventoryKey);
if (checkpoint?.outcome !== 'imported') {
input.appendRunCheckpoint({
runId: input.runId,
runKey: input.runKey,
state: 'progress',
remoteAssetId: candidate.assetId,
outcome: 'imported',
});
}
resumed += 1;
}
catch (error) {
const mapped = mapSyncError(error);
input.failures.push({ stage: 'write', assetId: candidate.assetId, reason: mapped.reason });
input.appendRunCheckpoint({
runId: input.runId,
runKey: input.runKey,
state: 'progress',
remoteAssetId: candidate.assetId,
outcome: 'failed',
reason: mapped.reason,
});
}
}
return resumed;
}
async function verifyAlreadyLocalCandidates(candidates, store, provenanceSnapshot, failures) {
const failedAssetIds = new Set(failures.flatMap((failure) => failure.assetId ? [failure.assetId] : []));
for (const candidate of candidates) {
if (candidate.action !== 'already_local' || failedAssetIds.has(candidate.assetId))
continue;
try {
await readBackStoredAsset(store, candidate.assetId, undefined, false, provenanceSnapshot.get(candidate.assetId), candidate.type);
}
catch (error) {
const mapped = mapSyncError(error);
failures.push({ stage: 'write', assetId: candidate.assetId, reason: mapped.reason });
failedAssetIds.add(candidate.assetId);
}
}
}
async function verifyAlreadyLocalGepxCandidates(candidates, assetsById, store, provenanceSnapshot, failures) {
for (const candidate of [...candidates]) {
if (candidate.action !== 'already_local')
continue;
const entry = assetsById.get(candidate.assetId);
if (!entry)
throw new SyncAbortError('integrity_error', '.gepx package asset is missing');
try {
const stored = await readBackStoredAsset(store, candidate.assetId, entry.asset, false, provenanceSnapshot.get(candidate.assetId), candidate.type);
if (!assetstore.frozenAssetRecordsEqual(stored, entry.asset)) {
throw new SyncAbortError('store_read_back_failed', 'Stored asset differs from packaged asset');
}
}
catch (error) {
const mapped = mapSyncError(error);
failures.push({ stage: 'write', assetId: candidate.assetId, reason: mapped.reason });
updateFinalCandidate(candidates, candidate, {
...candidate,
action: 'write_failed',
failureReason: mapped.reason,
});
}
}
}
async function readBackStoredAsset(store, assetId, expected, allowFrozenUnverified = false, provenance, expectedType = expected?.type) {
let stored;
try {
stored = await store.get(assetId);
}
catch {
throw new SyncAbortError('store_read_back_failed', 'Stored asset could not be read back');
}
const computed = stored ? wire.computeAssetId(stored) : null;
const expectedComputed = expected ? wire.computeAssetId(expected) : assetId;
const exactFrozenBody = Boolean(allowFrozenUnverified
&& stored
&& expected
&& assetstore.frozenAssetRecordsEqual(stored, expected)
&& computed === expectedComputed);
const provenanceBoundFrozenBody = Boolean(stored
&& computed
&& isActiveFrozenWaiver(provenance, assetId, computed));
if (!stored ||
stored.asset_id !== assetId ||
(!exactFrozenBody && !provenanceBoundFrozenBody && computed !== assetId) ||
(!exactFrozenBody && !provenanceBoundFrozenBody && expectedComputed !== assetId) ||
(expectedType !== undefined && stored.type !== expectedType)) {
throw new SyncAbortError('store_read_back_failed', 'Stored asset failed read-back verification');
}
return stored;
}
function appendSyncRecord(syncLedger, candidate, asset, runKey, inventoryKey, extra = {}) {
return syncLedger.append({
assetId: asset.asset_id,
type: asset.type,
source: 'hub',
scope: candidate.source === 'published' ? 'published' : 'purchased',
remoteAssetId: candidate.assetId,
runKey,
inventoryKey,
...(rawStringField(asset, 'id') ? { logicalId: rawStringField(asset, 'id') } : {}),
...(candidate.status ? { status: candidate.status } : {}),
...extra,
});
}
function syncRecordMatches(record, candidate, asset) {
const scope = candidate.source === 'published' ? 'published' : 'purchased';
const logicalId = rawStringField(asset, 'id');
return record.assetId === candidate.assetId &&
record.remoteAssetId === candidate.assetId &&
record.type === asset.type &&
record.source === 'hub' &&
record.scope === scope &&
(!logicalId || record.logicalId === logicalId);
}
function preparePackagedAsset(candidate, asset, localAssetIds, logical, force, frozenUnverified = false) {
const cleaned = { ...asset };
const type = cleaned.type === 'Gene' || cleaned.type === 'Capsule' ? cleaned.type : undefined;
if (!type)
return { blocked: { ...candidate, type: cleaned.type ?? candidate.type, action: 'unsupported_type' } };
if (type !== candidate.type)
return { blocked: { ...candidate, type, action: 'unsupported_type' } };
const claimed = rawStringField(cleaned, 'asset_id');
const computed = wire.computeAssetId(cleaned);
if (!claimed
|| !computed
|| (claimed !== computed && !frozenUnverified)
|| claimed !== candidate.assetId
|| !isContentAssetId(claimed)
|| !looksLikeFullAsset(cleaned, type)) {
throw new SyncAbortError('integrity_error', '.gepx package integrity verification failed before import');
}
if (localAssetIds.has(cleaned.asset_id))
return { blocked: { ...candidate, action: 'already_local' } };
const logicalId = rawStringField(cleaned, 'id');
if (logicalId) {
const localAssetId = logical.get(`${type}:${logicalId}`);
if (localAssetId && localAssetId !== cleaned.asset_id) {
if (force)
return { asset: cleaned, forced: true, collisionWithAssetId: localAssetId };
return { blocked: { ...candidate, logicalId, action: 'id_collision', collisionWithAssetId: localAssetId } };
}
}
return { asset: cleaned };
}
function looksLikeFullAsset(value, type) {
if (!stringField(value, 'schema_version'))
return false;
if (type === 'Gene') {
return Boolean(stringField(value, 'id') &&
stringField(value, 'category') &&
Array.isArray(value['signals_match']) &&
Array.isArray(value['strategy']) &&
asRecord(value['constraints']) &&
Array.isArray(value['validation']));
}
return Boolean(stringField(value, 'id') &&
stringField(value, 'gene') &&
stringField(value, 'summary') &&
asRecord(value['outcome']));
}
function declaresFullAssetPayload(value, type) {
if (Object.prototype.hasOwnProperty.call(value, 'schema_version'))
return true;
if (type === 'Gene') {
return Boolean(stringField(value, 'id')
&& stringField(value, 'category')
&& Array.isArray(value['signals_match'])
&& Array.isArray(value['strategy'])
&& asRecord(value['constraints'])
&& Array.isArray(value['validation']));
}
return Boolean(stringField(value, 'id')
&& stringField(value, 'gene')
&& stringField(value, 'summary')
&& asRecord(value['outcome']));
}
function readGepxPackage(path) {
if (!path)
throw new SyncAbortError('invalid_arg', '--import requires a file');
let parsed;
try {
parsed = JSON.parse(readBoundedGepxText(path));
}
catch (error) {
if (error instanceof SyncAbortError)
throw error;
throw new SyncAbortError('invalid_gepx', 'invalid .gepx package');
}
const obj = asRecord(parsed);
if (!obj || obj['type'] !== GEPX_TYPE || obj['version'] !== GEPX_VERSION || !Array.isArray(obj['assets'])) {
throw new SyncAbortError('invalid_gepx', 'invalid .gepx package');
}
if (obj['assets'].length > MAX_GEPX_ASSETS)
throw new SyncAbortError('invalid_gepx', 'invalid .gepx package');
if ((Array.isArray(obj['provenance']) && obj['provenance'].length > MAX_GEPX_SIDECAR_RECORDS)
|| (Array.isArray(obj['sync']) && obj['sync'].length > MAX_GEPX_SIDECAR_RECORDS)) {
throw new SyncAbortError('invalid_gepx', 'invalid .gepx package');
}
const assets = [];
const malformedAssets = [];
obj['assets'].forEach((rawAsset, index) => {
const asset = asRecord(rawAsset);
const assetId = asset ? rawStringField(asset, 'asset_id') : undefined;
if (!asset || !assetId) {
malformedAssets.push({ index, ...(assetId ? { assetId } : {}) });
return;
}
assets.push(asset);
});
const provenance = Array.isArray(obj['provenance'])
? obj['provenance'].map((record) => parseGepxProvenanceRecord(asRecord(record))).filter((record) => Boolean(record))
: [];
const sync = Array.isArray(obj['sync'])
? obj['sync'].map((record) => parseGepxSyncRecord(asRecord(record))).filter((record) => Boolean(record))
: [];
return {
type: GEPX_TYPE,
version: GEPX_VERSION,
...(stringField(obj, 'exportedAt') ? { exportedAt: stringField(obj, 'exportedAt') } : {}),
assets,
malformedAssets,
provenance,
sync,
};
}
function readBoundedGepxText(path) {
let before;
try {
before = lstatSync(path, { bigint: true });
}
catch {
throw new SyncAbortError('gepx_read_failed', 'could not read .gepx file');
}
if (!before.isFile() || before.isSymbolicLink() || before.size > BigInt(MAX_GEPX_BYTES)) {
throw new SyncAbortError('invalid_gepx', 'invalid .gepx package');
}
let descriptor;
try {
descriptor = openSync(path, constants.O_RDONLY | (constants.O_NOFOLLOW ?? 0));
}
catch {
throw new SyncAbortError('gepx_read_failed', 'could not read .gepx file');
}
let opened;
let text;
try {
opened = fstatSync(descriptor, { bigint: true });
if (!opened.isFile()
|| opened.size > BigInt(MAX_GEPX_BYTES)
|| opened.dev !== before.dev
|| opened.ino !== before.ino
|| opened.size !== before.size) {
throw new SyncAbortError('invalid_gepx', 'invalid .gepx package');
}
const chunks = [];
let total = 0;
while (total <= MAX_GEPX_BYTES) {
const remaining = MAX_GEPX_BYTES + 1 - total;
if (remaining <= 0)
break;
const chunk = Buffer.allocUnsafe(Math.min(64 * 1024, remaining));
const count = readSync(descriptor, chunk, 0, chunk.length, null);
if (count === 0)
break;
chunks.push(chunk.subarray(0, count));
total += count;
}
if (total > MAX_GEPX_BYTES)
throw new SyncAbortError('invalid_gepx', 'invalid .gepx package');
const settled = fstatSync(descriptor, { bigint: true });
if (settled.dev !== opened.dev
|| settled.ino !== opened.ino
|| settled.size !== opened.size
|| settled.mtimeNs !== opened.mtimeNs
|| settled.ctimeNs !== opened.ctimeNs) {
throw new SyncAbortError('invalid_gepx', 'invalid .gepx package');
}
text = Buffer.concat(chunks, total).toString('utf8');
}
catch (error) {
if (error instanceof SyncAbortError)
throw error;
throw new SyncAbortError('gepx_read_failed', 'could not read .gepx file');
}
finally {
closeSync(descriptor);
}
let after;
try {
after = lstatSync(path, { bigint: true });
}
catch {
throw new SyncAbortError('invalid_gepx', 'invalid .gepx package');
}
if (!after.isFile()
|| after.isSymbolicLink()
|| after.dev !== before.dev
|| after.ino !== before.ino
|| after.size !== before.size
|| after.mtimeNs !== before.mtimeNs
|| after.ctimeNs !== before.ctimeNs) {
throw new SyncAbortError('invalid_gepx', 'invalid .gepx package');
}
return text;
}
function parseGepxProvenanceRecord(value) {
if (!value)
return null;
const assetId = rawStringField(value, 'assetId');
const source = stringField(value, 'source');
const at = stringField(value, 'at');
const frozenContentId = rawStringField(value, 'frozenContentId');
if (!assetId
|| !at
|| Number.isNaN(Date.parse(at))
|| typeof value['trusted'] !== 'boolean'
|| (source !== 'local' && source !== 'migrated' && source !== 'hub')
|| (frozenContentId !== undefined && !/^sha256:[0-9a-f]{64}$/.test(frozenContentId)))
return null;
const rawDecision = value['decision'];
if (rawDecision !== undefined && rawDecision !== 'promoted' && rawDecision !== 'revoked')
return null;
const decision = rawDecision === 'promoted' || rawDecision === 'revoked'
? rawDecision
: undefined;
return {
assetId,
source,
trusted: value['trusted'],
at,
...(decision ? { decision } : {}),
...(stringField(value, 'decidedBy') ? { decidedBy: stringField(value, 'decidedBy') } : {}),
...(stringField(value, 'promotedBy') ? { promotedBy: stringField(value, 'promotedBy') } : {}),
...(stringField(value, 'reason') ? { reason: stringField(value, 'reason') } : {}),
...(frozenContentId ? { frozenContentId } : {}),
};
}
function parseGepxSyncRecord(value) {
if (!value)
return null;
const assetId = rawStringField(value, 'assetId');
const type = rawStringField(value, 'type');
const source = rawStringField(value, 'source');
const scope = rawStringField(value, 'scope');
const syncedAt = stringField(value, 'syncedAt');
const remoteAssetId = rawStringField(value, 'remoteAssetId');
if (!assetId || (type !== 'Gene' && type !== 'Capsule') || source !== 'hub' || (scope !== 'purchased' && scope !== 'published') || !syncedAt || !remoteAssetId) {
return null;
}
return {
assetId,
type,
source: 'hub',
scope,
syncedAt,
remoteAssetId,
...(rawStringField(value, 'logicalId') ? { logicalId: rawStringField(value, 'logicalId') } : {}),
...(stringField(value, 'status') ? { status: stringField(value, 'status') } : {}),
...(value['forced'] === true ? { forced: true } : {}),
...(rawStringField(value, 'collisionWithAssetId') ? { collisionWithAssetId: rawStringField(value, 'collisionWithAssetId') } : {}),
};
}
function remoteType(row) {
const raw = remoteDeclaredType(row);
return raw === 'Gene' || raw === 'Capsule' ? raw : undefined;
}
function remoteDeclaredType(row) {
const payload = asRecord(row.payload);
return rawStringField(row, 'type')
?? rawStringField(row, 'asset_type')
?? (payload ? rawStringField(payload, 'type') : undefined);
}
function remoteLogicalId(row) {
const payload = asRecord(row.payload);
if (!Object.prototype.hasOwnProperty.call(row, 'type') && payload && rawStringField(payload, 'type') !== undefined) {
return rawStringField(row, 'local_id') ?? rawStringField(payload, 'id');
}
return rawStringField(row, 'id')
?? rawStringField(row, 'local_id')
?? (payload ? rawStringField(payload, 'id') : undefined);
}
function isContentAssetId(value) {
return /^sha256:[0-9a-f]{64}$/.test(value);
}
function isRemoteTombstone(row) {
return stringField(row, 'status')?.toLowerCase() === 'revoked'
|| stringField(row, 'trust_state')?.toLowerCase() === 'revoked';
}
function isRemoteTombstoneDelivery(row) {
if (isRemoteTombstone(row))
return true;
const nested = asRecord(row.payload);
return nested ? isRemoteTombstone(nested) : false;
}
function emitTextPreview(preview, out) {
const c = preview.counts;
out(`sync preview: scope=${preview.scope} purchased=${c.remotePurchased} published=${c.remotePublished} unique=${c.uniqueRemote}`);
out(` would_import=${c.wouldImport} unverified_would_import=${c.unverifiedWouldImport} forced_import=${c.forcedImport} skipped_limit=${c.skippedLimit} fetch_failed=${c.fetchFailed} write_failed=${c.writeFailed} already_local=${c.alreadyLocal} tombstone=${c.tombstone} id_collision=${c.idCollision} unsupported=${c.unsupported} integrity_error=${c.integrityError}`);
out(` reconciliation: accounted=${preview.reconciliation.accounted}/${preview.reconciliation.remoteUnique} failed=${preview.reconciliation.failed} inventory_complete=${preview.reconciliation.inventoryComplete} missing_remote=${preview.reconciliation.missingRemote} consistent=${preview.reconciliation.consistent}`);
emitTextNextCursors(preview.nextCursors, out);
for (const candidate of preview.candidates.slice(0, 10)) {
out(` ${candidate.action}: ${candidate.type} ${candidate.assetId}${candidate.logicalId ? ` (${candidate.logicalId})` : ''}${formatHubVerification(candidate)}`);
}
}
function emitTextWrite(result, out) {
const c = result.counts;
out(`sync ${result.mode}: scope=${result.scope} purchased=${c.remotePurchased} published=${c.remotePublished} unique=${c.uniqueRemote}`);
out(` written=${result.written.length} blocked=${result.blocked.length} would_import=${c.wouldImport} unverified_would_import=${c.unverifiedWouldImport} forced_import=${c.forcedImport} skipped_limit=${c.skippedLimit} fetch_failed=${c.fetchFailed} write_failed=${c.writeFailed} already_local=${c.alreadyLocal} tombstone=${c.tombstone} id_collision=${c.idCollision} unsupported=${c.unsupported} integrity_error=${c.integrityError}`);
out(` reconciliation: accounted=${result.reconciliation.accounted}/${result.reconciliation.remoteUnique} imported=${result.reconciliation.imported} resumed=${result.resumed} failed=${result.reconciliation.failed} inventory_complete=${result.reconciliation.inventoryComplete} missing_remote=${result.reconciliation.missingRemote} consistent=${result.reconciliation.consistent}`);
emitTextNextCursors(result.nextCursors, out);
for (const asset of result.written.slice(0, 10)) {
out(` imported: ${asset.type} ${asset.assetId}${asset.logicalId ? ` (${asset.logicalId})` : ''}${formatHubVerification(asset)}`);
}
for (const candidate of result.blocked.slice(0, 10)) {
out(` blocked/${candidate.action}: ${candidate.type} ${candidate.assetId}${candidate.logicalId ? ` (${candidate.logicalId})` : ''}`);
}
}
function formatHubVerification(value) {
if (value.verification !== 'unverified')
return '';
return ` [verification=unverified${value.verificationReason ? ` reason=${value.verificationReason}` : ''}]`;
}
function emitTextNextCursors(nextCursors, out) {
if (nextCursors.purchased)
out(` next_purchased_cursor=${nextCursors.purchased}`);
if (nextCursors.published)
out(` next_published_cursor=${nextCursors.published}`);
}
function emitTextExport(result, out) {
out(`sync export: exported=${result.counts.exported} provenance=${result.counts.provenance} sync=${result.counts.sync}`);
for (const asset of result.assets.slice(0, 10)) {
out(` exported: ${asset.type} ${asset.assetId}${asset.logicalId ? ` (${asset.logicalId})` : ''}`);
}
}
function emitTextImportPreview(result, out) {
const c = result.counts;
out(`sync import preview: unique=${c.uniqueRemote}`);
out(` would_import=${c.wouldImport} forced_import=${c.forcedImport} skipped_limit=${c.skippedLimit} fetch_failed=${c.fetchFailed} write_failed=${c.writeFailed} already_local=${c.alreadyLocal} id_collision=${c.idCollision} unsupported=${c.unsupported} integrity_error=${c.integrityError}`);
for (const candidate of result.candidates.slice(0, 10)) {
out(` ${candidate.action}: ${candidate.type} ${candidate.assetId}${candidate.logicalId ? ` (${candidate.logicalId})` : ''}`);
}
}
function emitTextImport(result, out) {
const c = result.counts;
out(`sync import: written=${result.written.length} blocked=${result.blocked.length} failed=${result.failures.length} unique=${c.uniqueRemote}`);
out(` would_import=${c.wouldImport} forced_import=${c.forcedImport} skipped_limit=${c.skippedLimit} fetch_failed=${c.fetchFailed} write_failed=${c.writeFailed} already_local=${c.alreadyLocal} id_collision=${c.idCollision} unsupported=${c.unsupported} integrity_error=${c.integrityError}`);
for (const asset of result.written.slice(0, 10)) {
out(` imported: ${asset.type} ${asset.assetId}${asset.logicalId ? ` (${asset.logicalId})` : ''}`);
}
for (const candidate of result.blocked.slice(0, 10)) {
out(` blocked/${candidate.action}: ${candidate.type} ${candidate.assetId}${candidate.logicalId ? ` (${candidate.logicalId})` : ''}`);
}
for (const failure of result.failures.slice(0, 10)) {
out(` failed/${failure.reason}: ${failure.assetId ?? 'unknown'}`);
}
}
class SyncAbortError extends Error {
reason;
constructor(reason, message) {
super(message);
this.reason = reason;
this.name = 'SyncAbortError';
}
}
function loadSyncEnv(env) {
const envFile = loadEnvFileFromEnv(env);
if (envFile.error)
throw new SyncAbortError('env_file_unavailable', 'Configured environment file is unavailable');
}
function emitFailure(reason, message, jsonOut, out, err) {
const payload = { ok: false, group: GROUP, reason, message: formatTerminalText(message) };
if (jsonOut)
out(stringifyJsonOutput(payload));
else
err(formatTerminalText(`sync failed (${reason}): ${message}`));
return 1;
}
function mapSyncError(error) {
if (error instanceof SyncAbortError)
return { reason: error.reason, message: error.message };
if (error instanceof MalformedAccountAssetPageError) {
return { reason: 'malformed_page', message: 'Hub account asset page is malformed' };
}
if (error instanceof assetstore.FrozenAssetIdCollisionError) {
return { reason: 'integrity_error', message: 'Frozen Hub asset conflicts with existing content or provenance' };
}
if (error instanceof assetstore.InvalidConditionalPutResultError) {
return { reason: 'conditional_write_invalid', message: 'Conditional asset write returned an invalid result' };
}
if (error instanceof AuthError)
return { reason: 'auth_required', message: 'Hub authentication required' };
if (error instanceof HubClientError) {
if (error.status === 401 || error.status === 403)
return { reason: 'auth_required', message: 'Hub authentication required' };
if (error.status === 429 || error.status >= 500)
return { reason: 'network_error', message: 'Hub temporarily unavailable' };
return { reason: 'hub_rejected', message: 'Hub rejected the sync request' };
}
if (error instanceof HubUnreachableError)
return { reason: 'network_error', message: 'Hub unreachable' };
const message = error instanceof Error ? error.message : String(error);
if (/credential|auth|login|node_secret|token/i.test(message))
return { reason: 'auth_required', message: redact(message) };
if (/network|fetch failed|ECONN|ENOTFOUND|ETIMEDOUT|hub 5\d\d/i.test(message))
return { reason: 'network_error', message: 'Hub unreachable' };
return { reason: 'internal_error', message: 'Sync operation failed' };
}
function hasFetchAssetById(hub) {
return typeof hub.fetchAssetById === 'function';
}
function isSyncAccountAssetHub(value) {
const candidate = value;
return typeof candidate?.listAccountAssets === 'function' && typeof candidate.fetchAssetById === 'function';
}
function syncRunKey(opts, env, deps) {
const material = {
...syncInventoryIdentity(opts, env, deps),
limit: opts.limit,
pageSize: opts.pageSize,
force: opts.force,
purchasedCursor: opts.purchasedCursor ?? '',
publishedCursor: opts.publishedCursor ?? '',
};
return createHash('sha256').update(JSON.stringify(material)).digest('hex');
}
function syncInventoryKey(opts, env, deps) {
return createHash('sha256').update(JSON.stringify(syncInventoryIdentity(opts, env, deps))).digest('hex');
}
function prepareInventoryScan(syncLedger, inventoryKey, opts) {
const inputCursors = {
purchased: opts.purchasedCursor ?? null,
published: opts.publishedCursor ?? null,
};
const inputCursorFingerprints = fingerprintCursors(inputCursors);
const previous = syncLedger.latestInventoryScan(inventoryKey);
if (opts.resume
&& previous
&& !previous.complete
&& previous.retryIndex !== undefined
&& previous.scope === opts.scope
&& cursorFingerprintsMatch(previous.nextCursorFingerprints, inputCursorFingerprints)) {
return {
trackable: true,
scanId: previous.scanId,
index: previous.retryIndex,
retry: true,
inputCursorFingerprints,
skipPurchased: opts.scope === 'all'
&& previous.nextCursorFingerprints.purchased === null
&& previous.nextCursorFingerprints.published !== null,
skipPublished: opts.scope === 'all'
&& previous.nextCursorFingerprints.published === null
&& previous.nextCursorFingerprints.purchased !== null,
};
}
if (inputCursors.purchased === null && inputCursors.published === null) {
return {
trackable: true,
scanId: randomUUID(),
index: 0,
retry: false,
inputCursorFingerprints,
skipPurchased: false,
skipPublished: false,
};
}
if (previous
&& !previous.complete
&& previous.retryIndex === undefined
&& previous.scope === opts.scope
&& cursorFingerprintsMatch(previous.nextCursorFingerprints, inputCursorFingerprints)) {
return {
trackable: true,
scanId: previous.scanId,
index: previous.segmentCount,
retry: false,
inputCursorFingerprints,
skipPurchased: opts.scope === 'all' && previous.nextCursorFingerprints.purchased === null,
skipPublished: opts.scope === 'all' && previous.nextCursorFingerprints.published === null,
};
}
return {
trackable: false,
scanId: randomUUID(),
index: 0,
retry: false,
inputCursorFingerprints,
skipPurchased: false,
skipPublished: false,
};
}
function isRetryableAssetFailure(failure) {
return (failure.stage === 'fetch' || failure.stage === 'write')
&& (failure.reason === 'network_error' || failure.reason === 'internal_error');
}
function fingerprintCursors(cursors) {
return {
purchased: cursors.purchased === null ? null : createHash('sha256').update(cursors.purchased).digest('hex'),
published: cursors.published === null ? null : createHash('sha256').update(cursors.published).digest('hex'),
};
}
function cursorFingerprintsMatch(left, right) {
return left.purchased === right.purchased && left.published === right.published;
}
function syncInventoryIdentity(opts, env, deps) {
const privateMode = isPrivateHubMode(env);
const suppliedIdentityFingerprint = suppliedResumeIdentityFingerprint(deps);
return {
mode: String(env['EVOMAP_HUB_MODE'] ?? 'public').trim().toLowerCase(),
hubUrl: privateMode ? requirePrivateHubUrl(env) : resolveHubUrl(env),
account: privateMode
? firstNonEmptyEnv(env, ['EVOMAP_ENTERPRISE_SUBJECT', 'EVOMAP_PRIVATE_SUBJECT', 'PHUB_ENTERPRISE_SUBJECT', 'USER']) ?? 'evolver-proxy'
: '',
credentialFingerprint: suppliedIdentityFingerprint
?? (privateMode ? syncCredentialFingerprint(env) : publicResumeIdentityFingerprint(env, deps)),
scope: opts.scope,
type: opts.type ?? '',
status: opts.status,
};
}
function reconciliationRecords(syncLedger, inventoryKey, legacyRunKey) {
const records = new Map();
for (const record of syncLedger.listForInventoryKey(inventoryKey))
records.set(record.assetId, record);
// Records written before inventoryKey existed retain same-parameter reconciliation behavior.
for (const record of syncLedger.listForRunKey(legacyRunKey))
records.set(record.assetId, record);
return [...records.values()];
}
function countMissingRemote(syncLedger, inventoryKey, legacyRunKey, localAssetIds, remoteAssetIds) {
return reconciliationRecords(syncLedger, inventoryKey, legacyRunKey)
.filter((record) => localAssetIds.has(record.assetId) && !remoteAssetIds.has(record.remoteAssetId))
.length;
}
function inventorySegmentOutcome(candidates, failures, written) {
const failedAssetIds = new Set(failures.flatMap((failure) => failure.assetId ? [failure.assetId] : []));
const writtenAssetIds = new Set(written.map((asset) => asset.assetId));
const items = [];
let anonymousBlocked = 0;
for (const candidate of candidates) {
if (candidate.assetId === MISSING_ASSET_ID) {
anonymousBlocked += 1;
continue;
}
let outcome;
if (failedAssetIds.has(candidate.assetId))
outcome = 'failed';
else if (candidate.action === 'already_local')
outcome = 'already_local';
else if (isBlockedCandidate(candidate))
outcome = 'blocked';
else if (writtenAssetIds.has(candidate.assetId))
outcome = 'imported';
else
outcome = 'pending';
items.push({ remoteAssetId: candidate.assetId, outcome });
}
return { items, anonymousBlocked };
}
function reconcileInventorySnapshot(snapshot, missingRemote) {
let imported = 0;
let alreadyLocal = 0;
let blocked = snapshot.anonymousBlocked;
let failed = 0;
let pending = 0;
for (const outcome of snapshot.outcomes.values()) {
if (outcome === 'imported')
imported += 1;
else if (outcome === 'already_local')
alreadyLocal += 1;
else if (outcome === 'blocked')
blocked += 1;
else if (outcome === 'failed')
failed += 1;
else
pending += 1;
}
const remoteUnique = snapshot.outcomes.size + snapshot.anonymousBlocked;
const accounted = imported + alreadyLocal + blocked + failed + pending;
return {
remoteUnique,
accounted,
imported,
alreadyLocal,
blocked,
failed,
pending,
inventoryComplete: snapshot.complete,
missingRemote,
consistent: snapshot.complete
&& missingRemote === 0
&& blocked === 0
&& failed === 0
&& pending === 0
&& accounted === remoteUnique,
};
}
function publicResumeIdentityFingerprint(env, deps) {
if (!deps.hub)
return resolveRecipeHubResumeIdentityFingerprint(env);
return createHash('sha256').update(JSON.stringify({
account: firstNonEmptyEnv(env, ['EVOMAP_NODE_ID', 'A2A_NODE_ID']) ?? '',
credentialFingerprint: syncCredentialFingerprint(env),
})).digest('hex');
}
function syncCredentialFingerprint(env) {
const credentials = [
['A2A_INVITATION_TOKEN', firstNonEmptyEnv(env, ['A2A_INVITATION_TOKEN'])],
['enterprise', firstNonEmptyEnv(env, [
'EVOMAP_ENTERPRISE_TOKEN',
'EVOMAP_PRIVATE_HUB_TOKEN',
'PHUB_ENTERPRISE_TOKEN',
'PRIVATE_HUB_ENTERPRISE_TOKEN',
])],
['node', firstNonEmptyEnv(env, ['EVOMAP_NODE_SECRET', 'A2A_NODE_SECRET'])],
].filter((entry) => entry[1] !== undefined);
if (credentials.length === 0)
return '';
return createHash('sha256').update(JSON.stringify(credentials)).digest('hex');
}
function isPrivateHubMode(env) {
return String(env['EVOMAP_HUB_MODE'] ?? 'public').trim().toLowerCase() === 'private';
}
function configuredPrivateHubUrl(env) {
return firstNonEmptyEnv(env, ['EVOMAP_HUB_URL', 'A2A_HUB_URL', 'EVOLVER_DEFAULT_HUB_URL']);
}
function requirePrivateHubUrl(env) {
const hubUrl = configuredPrivateHubUrl(env);
if (!hubUrl)
throw new SyncAbortError('private_hub_url_required', 'Private Hub URL must be configured explicitly');
return hubUrl;
}
function validatePrivateHubUrl(env) {
if (isPrivateHubMode(env))
requirePrivateHubUrl(env);
}
function firstNonEmptyEnv(env, keys) {
for (const key of keys) {
const value = env[key]?.trim();
if (value)
return value;
}
return undefined;
}
function reconcile(candidates, failures, imported, final, inventoryComplete, missingRemote) {
const failedAssetIds = new Set(failures.flatMap((failure) => failure.assetId ? [failure.assetId] : []));
let alreadyLocal = 0;
let blocked = 0;
let actionable = 0;
for (const candidate of candidates) {
if (failedAssetIds.has(candidate.assetId))
continue;
if (candidate.action === 'already_local')
alreadyLocal += 1;
else if (isBlockedCandidate(candidate))
blocked += 1;
else
actionable += 1;
}
const failed = failedAssetIds.size;
const pending = Math.max(0, actionable - imported);
const accounted = imported + alreadyLocal + blocked + failed + pending;
return {
remoteUnique: candidates.length,
accounted,
imported,
alreadyLocal,
blocked,
failed,
pending,
inventoryComplete,
missingRemote,
consistent: final
&& inventoryComplete
&& missingRemote === 0
&& failures.length === 0
&& blocked === 0
&& pending === 0
&& accounted === candidates.length,
};
}
function storeBaseDir(store, deps) {
return store instanceof assetstore.LocalJsonlProvider ? store.baseDir : deps.assetsDir ?? events.assetsDir();
}
function suppliedResumeIdentityFingerprint(deps) {
const supplied = deps.resumeIdentityFingerprint;
return supplied === undefined ? undefined : createHash('sha256').update(supplied).digest('hex');
}
function previewProvenanceSnapshot(store, deps) {
if (deps.provenance)
return deps.provenance.snapshot();
if (store instanceof assetstore.LocalJsonlProvider || deps.assetsDir) {
return new assetstore.ProvenanceStore(storeBaseDir(store, deps), deps.now).snapshot();
}
return new Map();
}
function redact(value) {
return value
.replace(/\bBearer\s+[A-Za-z0-9._~+/=-]+/gi, 'Bearer [redacted]')
.replace(/\b([A-Z][A-Z0-9_]*(?:_SECRET|_TOKEN))\b\s*[:=]\s*["']?[^"',\s;}]+/g, '$1=[redacted]')
.replace(/\b(authorization|node_secret|nodeSecret|access_token|refresh_token|token|secret)\b\s*[:=]\s*["']?[^"',\s;}]+/gi, '$1=[redacted]');
}
function stringifyJsonOutput(value) {
let output = '';
for (const character of JSON.stringify(value)) {
const codePoint = character.codePointAt(0);
output += codePoint !== undefined && isTerminalControlCodePoint(codePoint)
? `\\u${codePoint.toString(16).padStart(4, '0')}`
: character;
}
return output;
}
function formatTerminalText(value) {
const withoutAnsi = redactAnsiSequences(value);
const withoutSecrets = redact(withoutAnsi)
.replace(/\b[A-Za-z]:[\\/][^\s\x22'<>|\x5b\x5d]*/g, '[redacted-path]')
.replace(/\\\\[^\s\x22'<>|\x5b\x5d]+\\[^\s\x22'<>|\x5b\x5d]*/g, '[redacted-path]')
.replace(/(^|[\s(\x22'=])\/(?:[^/\s\x22'<>|\x5b\x5d]+\/?)+/g, '$1[redacted-path]');
let output = '';
for (const character of withoutSecrets) {
const codePoint = character.codePointAt(0);
output += codePoint !== undefined && isTerminalControlCodePoint(codePoint)
? `\\u{${codePoint.toString(16).toUpperCase().padStart(4, '0')}}`
: character;
}
return output;
}
function redactAnsiSequences(value) {
let output = '';
for (let index = 0; index < value.length;) {
const code = value.charCodeAt(index);
const next = value.charCodeAt(index + 1);
const csi = code === 0x9b || (code === 0x1b && next === 0x5b);
if (csi) {
let end = index + (code === 0x1b ? 2 : 1);
while (end < value.length) {
const current = value.charCodeAt(end);
end += 1;
if (current >= 0x40 && current <= 0x7e)
break;
}
output += ' [escaped-ansi] ';
index = end;
continue;
}
const osc = code === 0x9d || (code === 0x1b && next === 0x5d);
if (osc) {
let end = index + (code === 0x1b ? 2 : 1);
while (end < value.length) {
const current = value.charCodeAt(end);
if (current === 0x07) {
end += 1;
break;
}
if (current === 0x1b && value.charCodeAt(end + 1) === 0x5c) {
end += 2;
break;
}
end += 1;
}
output += ' [escaped-ansi] ';
index = end;
continue;
}
output += value[index];
index += 1;
}
return output;
}
function isTerminalControlCodePoint(codePoint) {
return codePoint <= 0x1f
|| (codePoint >= 0x7f && codePoint <= 0x9f)
|| codePoint === 0x061c
|| codePoint === 0x200e
|| codePoint === 0x200f
|| codePoint === 0x2028
|| codePoint === 0x2029
|| (codePoint >= 0x202a && codePoint <= 0x202e)
|| (codePoint >= 0x2066 && codePoint <= 0x2069);
}
function asRecord(value) {
return value && typeof value === 'object' && !Array.isArray(value) ? value : undefined;
}
function stringField(value, key) {
const raw = value[key];
return typeof raw === 'string' && raw.trim() ? raw.trim() : undefined;
}
function rawStringField(value, key) {
const raw = value[key];
return typeof raw === 'string' && raw.length > 0 ? raw : undefined;
}
function hasInvalidLiveIdentityField(value, keys) {
const record = value;
return keys.some((key) => {
if (!Object.prototype.hasOwnProperty.call(record, key))
return false;
const raw = record[key];
return typeof raw !== 'string' || raw.length === 0 || raw !== raw.trim();
});
}