/** * `cleo release reconcile ` — Phase 1 of T9492 (v2 reconcile verb). * * Post-publish: backfills the 11 provenance tables (commits, task_commits, * commit_files, pull_requests, pr_commits, pr_tasks, releases, release_commits, * release_changes, release_artifacts, brain_release_links) from `git log` and * `gh api` output. * * Implements SPEC-T9345 §4.4 (R-080 through R-113) and §8 (R-330 through R-347): * * - R-080 .. R-083 — pre-conditions (plan present, tag present, gh release * tagName matches, ADR-051 evidence atoms re-validated for staleness). * - R-090 .. R-099 — per-table side effects + auto BRAIN observation + plan * archive on success. * - R-100 — every insert runs in a single SQLite transaction; any * failure rolls back the entire transaction with `E_PROVENANCE_FAILED` * and `error.details.table` identifying the failure. * - R-110 .. R-113 — post-conditions (status='reconciled', every commit * represented, every plan task has release_changes row, BRAIN linked). * - R-313 — staleness check (commit reachability + file sha256 + test-run * sha256 unchanged). * - R-330 .. R-340 — per-table UPSERT invariants and link_type taxonomy. * - R-345 .. R-347 — transactionality + full idempotency (re-running on an * already-reconciled release is a no-op modulo `meta.reReconciled=true`). * * Named `releaseReconcileV2` to coexist with the legacy `releaseReconcile` * from `./pipeline.js` (T1597 4-step pipeline). Phase 6 of T9492 will retire * the legacy verb; this implementation is the new canonical path. * * @task T9526 * @epic T9492 * @adr ADR-T9345 * @spec .cleo/rcasd/T9345/research/SPEC-T9345-release-pipeline-v2.md §4.4, §8 */ import { type ReleasePlan } from '@cleocode/contracts'; import { type EngineResult } from '../engine-result.js'; /** * Result produced by {@link synthesizePlanForReconcile} when plan synthesis * fires on the tag-driven path (no `.cleo/release/.plan.json` exists * but the git tag is present). * * The plan is always in-memory — it is written to disk only after * {@link releaseReconcileV2} successfully commits the provenance transaction * (or skipped entirely when `opts.dryRun = true`). */ export interface SynthesizedPlanReport { /** The synthesised `ReleasePlan` object, ready for reconcile. */ plan: ReleasePlan; /** Previous tag inferred from `git tag --sort=creatordate`. `null` on first-ever release. */ prevTag: string | null; /** * T#### tokens derived from the CHANGELOG.md section for this version. * Empty when no CHANGELOG section was found. */ changelogTaskIds: string[]; /** * T#### tokens derived from merged-PR titles/branches between prevTag..tag. * Empty when gh is unavailable. */ prTaskIds: string[]; /** * T#### tokens derived from git log subjects between prevTag..tag. * Populated even without gh access. */ commitTaskIds: string[]; /** * PR numbers discovered via merge-commit subjects (`Merge pull request #NNN`) * in the prevTag..tag range. */ discoveredPrNumbers: number[]; /** True when a CHANGELOG.md section for this version was found. */ changelogSectionFound: boolean; } /** * Synthesise a minimal {@link ReleasePlan} for a tag-driven release that has * no `.cleo/release/.plan.json` on disk (DHQ-080 · T11977). * * Derivation order (highest-fidelity source wins): * (a) CHANGELOG.md section for `version` — task-ID tokens + summaries. * (b) Merged PRs between prevTag..tag (`git log --merges`) — T#### from titles. * (c) All commits between prevTag..tag — T#### from subjects/bodies. * * The synthesised plan carries `meta.origin = 'tag-reconcile-synthesized'` * so honest plans (from `cleo release plan`) remain distinguishable. Fields * that cannot be derived without a prior `cleo release plan` run (evidenceAtoms, * gates, prUrl, mergeCommitSha, workflowRunUrl) are left empty/null. * * Does NOT write to disk — the caller decides whether to persist or dry-run. * * @param version — Version tag to synthesise a plan for (e.g. `v2026.6.14`). * @param projectRoot — Absolute project root. * @returns `SynthesizedPlanReport` with the in-memory plan + derivation metadata. * * @task T11977 */ export declare function synthesizePlanForReconcile(version: string, projectRoot: string): Promise; /** * Options for {@link releaseReconcileV2}. * * `fromWorkflow` mirrors the SPEC §4.4.1 `--from-workflow` flag (affects * logging verbosity only). `rollback` flips behavior for the rollback path * (deferred to T9527/T9528 — set false in this phase). * * `backfill` (T9528) signals that this invocation is part of a historical * backfill walk: the tag is by definition reachable (it is being reconciled * AFTER the fact), so the ADR-051 evidence-staleness check (R-313) is * skipped. Without this flag, historical commits referenced by old plan * evidence atoms may have been GC'd, file paths may have moved, and the * staleness gate would reject every historical release. `forceOverwrite` * (also T9528) signals the caller wants existing rows UPDATED — currently * implemented as a UPSERT-on-conflict-DO-UPDATE semantic which already * matches the reconcile insert pattern, so this flag is informational * (audit-logged by callers) but does not alter the SQL path. */ export interface ReleaseReconcileV2Options { /** Project root override (defaults to CLEO_ROOT or cwd). */ projectRoot?: string; /** When true, indicates the verb is invoked from `release-publish.yml`. */ fromWorkflow?: boolean; /** When true, drives the rollback flow (not implemented in T9526). */ rollback?: boolean; /** When true (T9528), skips evidence-staleness re-validation per R-313. */ backfill?: boolean; /** * When true (T9528), forces UPDATE of existing rows on conflict. The * underlying SQL already UPSERTs on conflict, so this flag is currently * informational; the backfill verb audit-logs overwrites separately. */ forceOverwrite?: boolean; /** * When true (T11977 · DHQ-080), print the synthesised plan derivation * without writing to the DB or disk. Only meaningful on the tag-driven * path (no plan file exists); has no effect when a plan file is present. * * The result envelope carries `dryRun: true` and `synthesizedPlan` with * the plan that WOULD be written, plus the derivation metadata. No DB * rows are inserted. */ dryRun?: boolean; } /** Successful reconcile result envelope (SPEC §4.4.5 `data` payload). */ export interface ReleaseReconcileV2Result { /** Version string, e.g. `v2026.6.0`. */ version: string; /** Git tag name (typically equals `version`). */ tag: string; /** Full 40-char SHA the tag points at. */ tagSha: string; /** Number of commits ingested into `commits` for this release. */ commitCount: number; /** Number of plan tasks ingested into `release_changes`. */ taskCount: number; /** Number of `release_changes` rows written (equals `taskCount` minus dedups). */ changeCount: number; /** Number of `release_artifacts` rows written. */ artifactCount: number; /** Number of `brain_release_links` rows written. */ brainLinkCount: number; /** Commits in the release range with no extractable T#### token. */ orphanCommits: string[]; /** True when the release was already reconciled and this call was a no-op (R-347). */ reReconciled?: boolean; /** T#### tokens encountered that did not validate against `tasks.id` (R-331). */ unknownTokens?: string[]; /** * Task IDs present in the runtime task store but UNRESOLVABLE by the * provenance `task_id` foreign keys (their FK parent — the bare `tasks` table * on a consolidated cleo.db — has no matching row). Their provenance links * were skipped-with-warn or NULLed rather than aborting the reconcile * (DHQ-051 · T11659). */ skippedTaskRefs?: string[]; /** Total wall-clock duration (filled into envelope `meta.durationMs`). */ durationMs?: number; /** Total inserts performed (filled into envelope `meta.txSize`). */ txSize?: number; /** * Present when reconcile synthesised the plan at runtime (DHQ-080 · T11977): * no `.cleo/release/.plan.json` existed but the git tag was found. * The provenance flag `meta.origin = 'tag-reconcile-synthesized'` is written * into the plan rows so honest plans (from `cleo release plan`) are * distinguishable in the `releases` table. */ synthesized?: { /** Number of task IDs derived from CHANGELOG.md section. */ changelogTaskCount: number; /** Number of task IDs derived from git log commit subjects. */ commitTaskCount: number; /** PR numbers discovered from merge-commit subjects. */ discoveredPrNumbers: number[]; /** Previous tag inferred from `git tag --sort=creatordate`. Null on first release. */ prevTag: string | null; /** True when a CHANGELOG.md section for this version was found. */ changelogSectionFound: boolean; }; /** * Present when `opts.dryRun = true` and synthesis fires (T11977 · DHQ-080). * The result reflects what WOULD be written without any DB mutation. * `commitCount`, `taskCount`, `changeCount`, `artifactCount`, `brainLinkCount` * reflect plan-level derivation only (no git log walk was performed). */ dryRun?: boolean; } /** * Sanitised PR shape ready for insertion: any FK columns pointing at a * commit that is NOT in the inserted-commits set are NULLed out (soft FKs) * and the `commits[]` list is filtered to only include in-range SHAs (hard * FK — NULLing is not an option, so the row is dropped instead). */ export interface SanitisedPR { headSha: string | null; mergeCommitSha: string | null; commits: { sha: string; }[]; } /** * Make a PR row FK-safe before inserting into `pull_requests` / `pr_commits`. * * Background (C3 bug — T9686): `pull_requests.headSha` and * `mergeCommitSha` are soft FKs to `commits.sha` (ON DELETE SET NULL), but * SQLite still enforces them at INSERT time. `pr_commits.commitSha` is a * NOT NULL FK with ON DELETE CASCADE — for rows referring to out-of-range * commits, the only safe action is to skip the row. * * A PR can reference commits OUTSIDE the current reconcile range because: * - `headSha` (PR-branch tip pre-merge) usually never lands on `main`; * the merge commit replaces it. * - `mergeCommitSha` may belong to an earlier release range. * - `commits[]` (from `gh api`) may include rebased / squash-removed SHAs. * * @param pr — the raw PR shape as returned from `fetchPR`. * @param insertedShas — set of commit SHAs already inserted into `commits`. * @returns FK-safe PR projection — NULL on dangling soft FK; filtered * `commits[]`. */ export declare function sanitisePrShasForFk(pr: { headSha: string | null; mergeCommitSha: string | null; commits: { sha: string; }[]; }, insertedShas: ReadonlySet): SanitisedPR; /** * Run the v2 `cleo release reconcile` verb. * * On success returns `engineSuccess(ReleaseReconcileV2Result)`. On any * documented failure path returns `engineError(, ...)`. * * @param version — the version string (e.g. `v2026.6.0`). MUST equal the git * tag exactly per R-082. * @param opts — see {@link ReleaseReconcileV2Options}. * * @returns EngineResult envelope. */ export declare function releaseReconcileV2(version: string, opts?: ReleaseReconcileV2Options): Promise>; //# sourceMappingURL=reconcile.d.ts.map