/** * Pipeline Manifest SQLite Implementation * * Reimplements all 14 pipeline manifest operations using Drizzle ORM + * Canonical docs_pipeline_manifest writes and provenance-aware legacy history reads. * * Provides a one-time migration function to import existing MANIFEST.jsonl * entries into the new table. * * @task T5581 * @epic T5576 */ import type { ManifestWithProvenance } from '@cleocode/contracts/operations/research'; import { type EngineResult } from '../engine-result.js'; import { docsPipelineManifest as pipelineManifest } from '../store/schema/cleo-project/docs.js'; declare function getBinding(cwd?: string): Promise, import("../store/dual-scope-db.js").ProjectStore>>; import type { ContradictionDetail, ExtendedManifestEntry, ResearchFilter, SupersededDetail } from './index.js'; import { filterManifestEntries } from './manifest-filter.js'; import { type ManifestIdentityIssue } from './manifest-identity.js'; export type ManifestEntry = ExtendedManifestEntry; export type { ContradictionDetail, ResearchFilter, SupersededDetail }; export { filterManifestEntries }; interface PipelineManifestListParams extends ResearchFilter { type?: string; offset?: number; } /** * Insert authentic prepared manifest rows in one guarded project transaction. * * @remarks * Preserves caller handle ownership and both storage histories. Identical complete * payloads are idempotent; differing identities and legacy rows never authorize * replacement. Any batch failure rolls back all inserted rows. * @param rows - Complete persisted payloads, including raw content and metadata. * @param projectRoot - Explicit project identity captured before asynchronous binding. * @param expectedDb - Caller-owned canonical task-domain handle for this project. * @returns Number of newly inserted rows; identical stored rows are skipped. * @throws If database identity, historical evidence, or any transactional write fails. * @example * ```ts * const inserted = await insertManifestRows(preparedRows, projectRoot, db); * ``` */ export declare function insertManifestRows(rows: ReadonlyArray, projectRoot: string, expectedDb: Awaited>['db']): Promise; /** What a legacy `needs_followup: true` reads as: follow-up needed, none named (T13338). */ export declare const LEGACY_NEEDS_FOLLOWUP = "follow-up needed (no task named)"; /** A stored manifest row whose metadata no reader accepts. */ export interface MalformedManifestRow { /** Manifest entry id. */ entryId: string; /** Tables holding the row (`docs_pipeline_manifest`, legacy `pipeline_manifest`). */ tables: string[]; /** `invalid-json`: unparseable or not an object; `field-contract`: a field has the wrong type. */ reason: 'invalid-json' | 'field-contract'; /** Offending top-level fields (`metadata_json` for the whole document). */ fields: string[]; /** What is wrong, per field. */ message: string; /** sha256 of the stored metadata bytes. */ metadataSha256: string; } /** The command that plans the repair of malformed manifest rows (writes nothing). */ export declare const MANIFEST_ROW_REPAIR_COMMAND = "cleo doctor manifest-rows --repair"; /** The command that writes the planned manifest-row repair (receipt first). */ export declare const MANIFEST_ROW_APPLY_COMMAND = "cleo doctor manifest-rows --repair --apply"; /** * Read manifest entries, SKIPPING rows whose metadata violates the stored * field contract instead of failing the whole read. For reports that must * still show everything else (orchestrate roll-up); the strict * {@link readManifestEntries} stays the default. Conflicting duplicate ids * and store failures still throw. * * @param projectRoot - Project root. * @returns The readable entries (newest first) and the skipped rows. * @task T12686 */ export declare function readManifestEntriesSkippingMalformed(projectRoot?: string): Promise<{ entries: Array>; malformed: MalformedManifestRow[]; }>; /** * List every stored manifest row (archived included) whose metadata no reader * accepts. * * @param projectRoot - Project root. * @returns The malformed rows. * @task T12686 */ export declare function listMalformedManifestRows(projectRoot?: string): Promise; /** A stored manifest row whose id, linked task ids or file reference fails T12829 validation. */ export interface ManifestIdentityProblemRow { /** Manifest entry id, truncated to 120 characters for display. */ entryId: string; /** Tables holding the row (`docs_pipeline_manifest`, legacy `pipeline_manifest`). */ tables: string[]; /** The problems: `E_VALIDATION` (malformed or unsafe) or `E_NOT_FOUND` (linked task missing). */ issues: ManifestIdentityIssue[]; } /** * List every stored manifest row (archived included) whose entry id, linked * task ids or file reference would be rejected by append today, and every * well-formed linked task id that names no task. Read-only: the rows predate * T12829 validation and are reported, never rewritten. * * @param projectRoot - Project root. * @returns The offending rows, each with its problems. * @task T12829 */ export declare function listManifestIdentityProblems(projectRoot?: string): Promise; /** One row a repair rewrote: its metadata before and after. */ export interface ManifestRepairChange { entryId: string; tables: string[]; reason: MalformedManifestRow['reason']; fields: string[]; before: string | null; after: string; } /** Receipt of a manifest metadata repair (written before the rows change). */ export interface ManifestRepairReceipt { kind: 'manifest-metadata-repair'; task: 'T12686'; createdAt: string; databasePath: string | null; receiptPath: string | null; dryRun: boolean; /** * Row rewrites. Before the write this is the plan; once applied it keeps * only the rows actually rewritten (rollback reads these). */ changes: ManifestRepairChange[]; /** * Outcome of the write, `null` for a dry run or an empty plan. `repaired` * rows were rewritten in every table holding them; `skipped` rows changed * between the read and the write and were left untouched. */ applied: ManifestSwapResult | null; } /** Outcome of applying (or rolling back) a manifest metadata repair. */ export interface ManifestSwapResult { /** Entry ids rewritten in every table that holds them. */ restored: string[]; /** Entry ids left untouched because no table still held the expected bytes. */ skipped: string[]; } /** * Repair every malformed manifest row (T12686): move each offending field * under `_malformed`, so the row reads again and nothing is lost. The receipt * (under `.cleo/backups/manifest-repair/`) is written BEFORE the change and * holds each row's metadata before and after; {@link rollbackManifestRepair} * restores from it. Each update is compare-and-set on the old bytes, in one * immediate transaction; rows in both the canonical and legacy table are * rewritten in both. A row changed since the read is skipped (reported in * `applied.skipped` and dropped from `changes`); a row changed in only one of * its two tables aborts the whole transaction with * `E_MANIFEST_CONCURRENT_CHANGE`, so the copies never diverge. * * @param projectRoot - Project root. * @param opts - `dryRun` plans without writing (no receipt file). * @returns The receipt; after a write, `changes` lists only rewritten rows. * @throws EngineResultError `E_MANIFEST_CONCURRENT_CHANGE` on a partial match. * @task T12686 */ export declare function repairMalformedManifestRows(projectRoot?: string, opts?: { dryRun?: boolean; }): Promise; /** * Undo a {@link repairMalformedManifestRows} run from its receipt: each row * whose metadata is still what the repair wrote gets its old bytes back. A row * changed since is left alone and reported. * * @param receiptPath - The receipt file. * @param projectRoot - Project root. * @returns Restored and skipped entry ids. * @task T12686 */ export declare function rollbackManifestRepair(receiptPath: string, projectRoot?: string): Promise; /** * Retrieve a manifest and resolve its local file or canonical document reference. * * @remarks * Supports `cleo://docs/` where the single percent-encoded segment is * a document slug, attachment ID, or SHA-256 accepted by the canonical docs read * model. URI syntax establishes no authority. Unsupported URIs and failed reads * are explicit failures; missing ordinary files retain `fileExists: false`. * @param researchId - Exact manifest identity, including historical entries. * @param projectRoot - Explicit project directory used for both manifest and docs. * @returns Manifest provenance and content, or a structured resolution failure. * @example * ```ts * const result = await pipelineManifestShow('T001-report', '/project'); * ``` */ export declare function pipelineManifestShow(researchId: string, projectRoot?: string): Promise; /** pipeline.manifest.list - List manifest entries with filters */ export declare function pipelineManifestList(params: PipelineManifestListParams, projectRoot?: string): Promise; /** pipeline.manifest.find - Find manifest entries by text (LIKE search on content + type) */ export declare function pipelineManifestFind(query: string, options?: { confidence?: number; limit?: number; }, projectRoot?: string): Promise; /** pipeline.manifest.pending - Get pending manifest items */ export declare function pipelineManifestPending(epicId?: string, projectRoot?: string): Promise; /** pipeline.manifest.stats - Manifest statistics */ export declare function pipelineManifestStats(epicId?: string, projectRoot?: string): Promise; /** pipeline.manifest.read - Read manifest entries with optional filter */ export declare function pipelineManifestRead(filter?: ResearchFilter, projectRoot?: string): Promise; /** Options for {@link pipelineManifestAppend}. */ export interface PipelineManifestAppendOptions { /** * Reject an entry whose linked task ids name no task in the project store * (`E_NOT_FOUND`). The CLI/dispatch path sets it; direct SDK callers that * record manifests for tasks held elsewhere may leave it off. Format checks * on the id, linked task ids and file reference always run (T12829). */ requireExistingTasks?: boolean; } /** * Append to canonical manifest storage after checking both histories for * unresolved identities. The entry id, linked task ids and file reference are * validated first (T12829): no input can name a path outside the project. */ export declare function pipelineManifestAppend(entry: ExtendedManifestEntry, projectRoot?: string, options?: PipelineManifestAppendOptions): Promise; /** pipeline.manifest.archive - Archive old manifest entries by date */ export declare function pipelineManifestArchive(beforeDate: string, projectRoot?: string): Promise; /** pipeline.manifest.compact - Dedup by contentHash (keep newest by createdAt) */ export declare function pipelineManifestCompact(projectRoot?: string): Promise; /** * Validate available manifest evidence explicitly linked to one task. * * @remarks * This exported SDK helper is not a registered CLI operation. Selection uses * exact linked-task membership; manifest ID wording establishes no ownership. * Absent evidence, missing output, and blank output invalidate the assessment. * Canonical document/read failures retain their structured diagnostics. Content * availability does not establish implementation, testing, review, or authority. * @param taskId - Exact task identity whose linked evidence is assessed. * @param projectRoot - Project captured before any asynchronous store access. * @returns Field and content assessment, or a structured diagnostic failure. * @example * ```ts * const result = await pipelineManifestValidate('T001', '/project'); * ``` */ export declare function pipelineManifestValidate(taskId: string, projectRoot?: string): Promise; /** pipeline.manifest.contradictions - Find entries with overlapping topics but conflicting key_findings */ export declare function pipelineManifestContradictions(projectRoot?: string, params?: { topic?: string; }): Promise>; /** pipeline.manifest.superseded - Identify entries replaced by newer work on same topic */ export declare function pipelineManifestSuperseded(projectRoot?: string, params?: { topic?: string; }): Promise>; /** pipeline.manifest.link - Link manifest entry to a task */ export declare function pipelineManifestLink(taskId: string, researchId: string, notes?: string, projectRoot?: string): Promise; /** The fields `cleo research update` and `link` may change on an existing entry (T13678). */ export interface ManifestEntryPatch { /** New title. */ title?: string; /** Replacement topics. */ topics?: string[]; /** Replacement key findings. */ key_findings?: string[]; /** New status. */ status?: ExtendedManifestEntry['status']; /** A task to add to `linked_tasks` (kept unique); never removes one. */ linkTask?: string; } /** * Change only the given fields of an existing manifest entry, in one * transaction (T13678). Every field the patch leaves out keeps its stored * value, and `linkTask` is added to `linked_tasks`, never replacing it. * `cleo research link` and `cleo research update` used to send a whole new * entry through `manifest.append` under the same id, which replaced the * title, topics, findings and links with placeholders. * * @param id - Exact manifest entry id. * @param patch - The fields to change. * @param projectRoot - Explicit project root. * @param options - `requireExistingTasks` rejects a `linkTask` naming no task. * @returns The updated entry id and the fields changed, `E_NOT_FOUND` for an * unknown id, or `E_VALIDATION` / `E_NOT_FOUND` for a bad `linkTask`. * @example * ```ts * await pipelineManifestUpdate('res_1', { linkTask: 'T1981' }, '/project'); * ``` * @task T13678 */ export declare function pipelineManifestUpdate(id: string, patch: ManifestEntryPatch, projectRoot?: string, options?: PipelineManifestAppendOptions): Promise; /** * Read unarchived entries from both manifest histories with physical provenance. * @param projectRoot - Explicit project root; takes priority over ambient project pins. * @returns Entries retaining the existing fields and observed source tables/database path. * @throws EngineResultError when the same identity has conflicting persisted payloads. * @throws Error when storage reads fail; failure is never represented as an empty history. * @remarks Identical rows are deduplicated only after comparing every stored scalar. * Legacy-only evidence remains readable and is never implicitly migrated or rewritten. * @example * ```ts * const entries = await readManifestEntries('/project'); * const sources = entries.map((entry) => entry.provenance.tables); * ``` */ export declare function readManifestEntries(projectRoot?: string): Promise>>; /** * Filter manifest entries by criteria (alias for backward compatibility). */ export declare function filterEntries(entries: ExtendedManifestEntry[], filter: ResearchFilter): ExtendedManifestEntry[]; /** * Distill a manifest entry to brain.db observation (Phase 3, pending). */ export declare function distillManifestEntry(_entryId: string, _projectRoot?: string): Promise; /** * Migrate existing .cleo/MANIFEST.jsonl entries into the pipeline_manifest table. * Skips entries that already exist (by id). Entries whose id, linked task ids * or file reference fail the T12829 identity checks are skipped, not inserted, * and listed in `invalid`. Renames MANIFEST.jsonl to MANIFEST.jsonl.migrated * when done. * * @returns Count of migrated and skipped entries (skipped includes invalid ones), * and every entry refused for an identity problem. */ export declare function migrateManifestJsonlToSqlite(projectRoot?: string): Promise<{ migrated: number; skipped: number; invalid: Array<{ entryId: string; issues: ManifestIdentityIssue[]; }>; }>; //# sourceMappingURL=pipeline-manifest-sqlite.d.ts.map