/** * Atomic analysis-generation identity. * * Analysis artifacts were written individually and daemon caches keyed primarily * on artifact mtime, so a multi-artifact reader could observe a MIXTURE of an old * and a new analysis — one file from before a rebuild, another from after. Each * full analysis now publishes a generation manifest binding every required * artifact to one identity and content digest, and readers validate that identity * before AND after a multi-artifact read (change `harden-spec-workflow-lifecycle`, * decision 64e6eb87). * * The manifest is what makes a generation CURRENT: it is written last, after * every required artifact is durable. Writers serialize the complete required * write set and this commit point under the analysis lock. Readers that do not * participate in that lock still verify content digests and refuse a mixture. * * Renaming the whole analysis directory was rejected — databases, runtime * sidecars, and platform-specific rename behavior make whole-directory * replacement unnecessarily risky for the guarantee actually needed, which is * that a reader never ACCEPTS a mixture. */ /** Name of the manifest inside the analysis output directory. */ export declare const GENERATION_MANIFEST_FILE = "generation.json"; export declare const GENERATION_MANIFEST_VERSION = 1; /** * The artifacts a generation must contain to become current. * * Deliberately the set every multi-artifact reader consumes — not every file * `analyze` writes. Side artifacts (style fingerprint, parse health) are * fail-soft by design and must not be able to block a publication. */ export declare const REQUIRED_ANALYSIS_ARTIFACTS: readonly ["repo-structure.json", "llm-context.json", "dependency-graph.json", "fingerprint.json"]; export interface GenerationArtifactRecord { /** Artifact file name, relative to the analysis directory. */ path: string; sha256: string; bytes: number; } export interface GenerationManifest { version: typeof GENERATION_MANIFEST_VERSION; /** Opaque identity of this generation. Never derived from content or time. */ generationId: string; publishedAt: string; artifacts: GenerationArtifactRecord[]; /** * `manifest` for a generation this code published; `legacy` for the synthesized * identity of an analysis produced before manifests existed. */ compatibility: 'manifest' | 'legacy'; /** Full rebuild, or an honest watcher patch over a potentially stale repo survey. */ coherence: 'full' | 'incremental'; } export declare function manifestPathOf(analysisDir: string): string; /** Verify one artifact against a manifest record without trusting mtime granularity. */ export declare function artifactMatchesGeneration(analysisDir: string, manifest: GenerationManifest, name: string, maxBytes?: number): Promise; /** * Publish a new current generation. * * Call ONLY after every required artifact is durable: this is the commit point. * The manifest uses the durable atomic-write primitive, so a reader sees either * the previous manifest or the new one, never a partially written one. * * Returns `null` when a required artifact is missing — an analysis that did not * produce its full artifact set must not become current. */ export declare function publishGeneration(analysisDir: string, requiredArtifacts: string[], options?: { coherence?: GenerationManifest['coherence']; }): Promise; /** * Read the current generation manifest. * * An analysis with no manifest is not an error: it is a legacy generation, given * a disclosed synthetic identity derived from artifact mtimes so caches can still * key on it. It is upgraded to a real manifest on the next analyze. */ export declare function readCurrentGeneration(analysisDir: string, legacyArtifacts?: string[], maxBytes?: number): Promise; /** * Identity for an analysis published before manifests existed. * * Derived from the artifact set's mtimes and sizes — weaker than a manifest, and * labelled `legacy` so a consumer can tell the difference rather than believing * it has manifest-grade coherence. */ export declare function synthesizeLegacyGeneration(analysisDir: string, artifacts: string[]): Promise; export type GenerationSnapshot = { state: 'ok'; value: T; generationId: string; compatibility: GenerationManifest['compatibility']; coherence: GenerationManifest['coherence']; } | { state: 'analysis-unavailable'; } | { state: 'analysis-changed'; message: string; }; /** * Run a multi-artifact read against ONE generation. * * Validates the current generation identity before and after `read`. A change * mid-read means the snapshot may be mixed, so it retries once against the new * generation and then reports `analysis-changed` rather than returning evidence * it cannot vouch for. Mixed-generation evidence is never labelled fresh. */ export declare function readGenerationSnapshot(analysisDir: string, legacyArtifacts: string[], read: (generationId: string) => Promise, allowedArtifactMismatches?: (value: T) => Iterable): Promise>; /** Remove a manifest, e.g. when abandoning an interrupted publication. */ export declare function discardGeneration(analysisDir: string): Promise; /** * Publish an explicit non-generation before a writer replaces a multi-artifact set. * A malformed-but-present manifest is intentionally different from an absent manifest: * readers return `analysis-unavailable` instead of downgrading the in-flight files to a * synthesized legacy generation. */ export declare function markGenerationUnavailable(analysisDir: string): Promise; /** * Is the present manifest the deliberate in-flight-publish sentinel? * * {@link readCurrentGeneration} answers `null` for it BY DESIGN, and callers that only * see that null cannot tell a healthy publish in progress from a damaged manifest. This * is the narrow read that recovers the distinction: it returns true only for the exact * well-formed sentinel {@link markGenerationUnavailable} writes, and false for an absent, * refused, malformed, or ordinary manifest. Diagnostic only — never a permission to serve. */ export declare function generationPublishInProgress(analysisDir: string): Promise; //# sourceMappingURL=analysis-generation.d.ts.map