Skip to content
92 changes: 90 additions & 2 deletions gitnexus/src/core/run-analyze.ts
Original file line number Diff line number Diff line change
Expand Up @@ -147,6 +147,7 @@ import {
discardStagedWorkspace,
getStagedAnalyzePaths,
hasPendingPromotion,
inspectStagedWorkspaceSource,
prepareStagedWorkspace,
promoteStagedGeneration,
validateStagedGeneration,
Expand Down Expand Up @@ -1139,9 +1140,55 @@ const runFullAnalysisImpl = async (
throw new Error('`--staged` cannot be combined with `--repair-fts`; repair is in-place only.');
}

let stagedWorkspaceResumed = false;
let stagedWorkspaceExisted = false;
let canonicalDatabaseExistedAtStageStart = false;
let canonicalMetaAtStageStart: RepoMeta | null = null;
if (stagedPaths) {
const canonicalMeta = await loadMeta(canonicalMetaDir);
const prepared = await prepareStagedWorkspace(stagedPaths, canonicalMeta, repositorySource);
canonicalMetaAtStageStart = await loadMeta(canonicalMetaDir);
const stagedSourceAtStart = await inspectStagedWorkspaceSource(
stagedPaths,
canonicalMetaAtStageStart,
repositorySource,
);
const canonicalDatabaseStateAtStart = await lstatIfPresent(canonicalPaths.lbugPath);
stagedWorkspaceExisted = stagedSourceAtStart.exists;
canonicalDatabaseExistedAtStageStart = canonicalDatabaseStateAtStart !== null;

if (stagedWorkspaceExisted && !options.dropEmbeddings) {
const stagedMetaAtStart = await loadMeta(stagedPaths.stagedMetaDir);
const mayBeCompletedCandidate =
stagedSourceAtStart.matchesSource &&
!!stagedMetaAtStart &&
!stagedMetaAtStart.incrementalInProgress &&
!stagedMetaAtStart.embeddingCheckpoint &&
stagedMetaAtStart.lastCommit === currentCommit;
if (!mayBeCompletedCandidate) {
throw new Error(
'An incomplete or source-mismatched staged generation already exists. It was left ' +
'untouched for forensics. Re-run with `--drop-embeddings` only when replacing that ' +
'derived stage with a clean isolated generation is intentional.',
);
}
}
if (
!stagedWorkspaceExisted &&
canonicalDatabaseExistedAtStageStart &&
!options.dropEmbeddings
) {
Comment thread
100yenadmin marked this conversation as resolved.
throw new Error(
'Starting staged work from an existing canonical database requires explicit ' +
'`--drop-embeddings`. The canonical index is unchanged. Semantic refreshes must use ' +
'`--staged --embeddings --drop-embeddings`; graph-only rebuilds must use ' +
'`--staged --drop-embeddings`.',
);
}
const prepared = await prepareStagedWorkspace(
stagedPaths,
canonicalMetaAtStageStart,
repositorySource,
);
Comment thread
100yenadmin marked this conversation as resolved.
stagedWorkspaceResumed = prepared.resumed;
log(
prepared.resumed
? 'Resuming the existing staged generation; the canonical index remains untouched.'
Expand All @@ -1160,6 +1207,47 @@ const runFullAnalysisImpl = async (
// stores, inspect via LadybugDB, or touch sidecars until every invariant
// needed for a surgical incremental write is established.
let existingMeta = await loadMeta(metaDir);
if (stagedPaths) {
const stagedCompletedCandidate =
stagedWorkspaceResumed &&
!!existingMeta &&
!existingMeta.incrementalInProgress &&
!existingMeta.embeddingCheckpoint &&
existingMeta.lastCommit === currentCommit &&
(!canonicalMetaAtStageStart ||
canonicalMetaAtStageStart.lastCommit !== existingMeta.lastCommit ||
canonicalMetaAtStageStart.indexedAt !== existingMeta.indexedAt);

// electric.4-.7 proved that restoring or resuming embedding rows inside a
// staged copy is not a safe compatibility surface. Keep the useful part of
// staged analyze (isolated full generation + validated promotion), but make
// every semantic refresh choose an empty-table rebuild explicitly. A clean,
// already-finalized stage remains eligible for the fast-path promotion
// below; it is complete derived state, not an embedding checkpoint.
if (stagedCompletedCandidate) {
// A complete stage that died immediately before promotion should be
// promoted, even when the original invocation included --force. It is
// immutable validated output, not a cache/resume surface.
options = { ...options, force: false };
Comment thread
100yenadmin marked this conversation as resolved.
Outdated
Comment thread
100yenadmin marked this conversation as resolved.
Outdated
} else {
if (
!options.dropEmbeddings &&
(stagedWorkspaceExisted || canonicalDatabaseExistedAtStageStart)
) {
throw new Error(
'Staged generation did not qualify for completed-stage promotion and was left ' +
'untouched. Re-run with explicit `--drop-embeddings` to replace it with a clean ' +
'isolated generation.',
);
}
if (options.dropEmbeddings || (options.embeddings && !existingMeta)) {
// The target is the isolated staged DB. Forcing a full write wipes only
// that disposable copy; the canonical generation stays readable until
// the final integrity scan and journaled promotion both succeed.
options = { ...options, force: true };
}
}
}
if (options.incrementalOnly) {
if (!existingMeta) {
incrementalOnlyStop('no existing index metadata is available');
Expand Down
36 changes: 36 additions & 0 deletions gitnexus/src/core/staged-promotion.ts
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,42 @@ export const withAnalyzeOwnershipLock = async <T>(
/** @deprecated Use the common ownership lock so plain and staged writers cannot overlap. */
export const withStagedAnalyzeLock = withAnalyzeOwnershipLock;

/**
* Read-only preflight used by callers that must preserve a prior staged
* artifact instead of letting preparation replace source-mismatched derived
* state. A legacy manifest without complete source identity never matches.
*/
export const inspectStagedWorkspaceSource = async (
paths: StagedAnalyzePaths,
canonicalMeta: RepoMeta | null,
sourceRepo: RepositorySourceIdentity,
): Promise<{ exists: boolean; matchesSource: boolean }> => {
let exists = false;
try {
await fs.lstat(paths.stageRoot);
exists = true;
} catch (error) {
if (!isMissingFilesystemError(error)) throw error;
}
if (!exists) return { exists: false, matchesSource: false };

const manifest = await readManifest(paths);
if (!manifest?.sourceMetaFiles || !manifest.sourceRepo) {
return { exists: true, matchesSource: false };
}
const canonicalDb = await statRegularFile(paths.canonicalLbugPath);
const sourceMeta = canonicalMeta ? metaIdentity(canonicalMeta) : undefined;
const sourceMetaFiles = await statMetadataFiles(paths.canonicalMetaDir);
return {
exists: true,
matchesSource:
identitiesEqual(manifest.sourceMeta, sourceMeta) &&
identitiesEqual(manifest.sourceMetaFiles, sourceMetaFiles) &&
identitiesEqual(manifest.sourceDb, canonicalDb) &&
identitiesEqual(manifest.sourceRepo, sourceRepo),
};
};

/**
* Create or resume the isolated build workspace. A stage tree is removed only
* when a complete canonical generation or a durable stage intent/manifest
Expand Down
Loading
Loading