/** * Canonical accessors for `decision_adr_links` — the two-way link table * between `D####` brain ledger decisions and curated ADR docs * (T13359 · spec `cleodocs-rearch-design` §4/§10 · D11165). * * Every write goes through the functions here, on the `openDualScopeDb` * chokepoint's drizzle handle ({@link getDb}) — gate 3 (no raw opens) and * gate 28 (no raw writes to syncing tables). * * Semantics implemented here (the settled tiered methodology, spec §4): * * - The `D####` ledger stays the operational authority; ADR docs are the * curated architecture record, authored when a decision graduates. * Neither tier is generated from the other — this table is the only * cross-reference, and it starts EMPTY (AC4: no ledger rewrite, no * backfill of the ~360 existing decisions). * - Identity is NATURAL over (decision_id, adr_slug): both devices derive * the same uid for one pair, so concurrent identical links coalesce via * `ON CONFLICT DO NOTHING` instead of breaking a unique index on apply * (spec §10). * - APPEND-ONLY: no DELETE exists. An unlink is a one-way monotonic latch * flipping `status` to 'revoked' — the row itself is the tombstone and * survives sync/restore. A revoked pair is never re-linked (rejection, * not resurrection), so the latch can never be observed half-flipped. * The latch is enforced at MERGE too (T13597): `SYNC_MERGE_RULES` marks * 'revoked' absorbing with no leave op, so a concurrent same-uid link op * from another device is refused by the merge engine and cannot * resurrect the tombstone by LWW. * - Neither column is a DB-level FK: `decision_id` is a cross-DB soft * reference (the ledger lives in brain.db) and `adr_slug` is a mutable * doc alias (old slugs resolve through `conduit_doc_slug_aliases`). * * @module * @task T13359 * @epic T13340 * @saga T13339 */ import type { DecisionAdrLinkRow } from './schema/cleo-project/docs.js'; /** Link states. The revoke latch only ever moves `linked` → `revoked`. */ export declare const DECISION_ADR_LINK_STATUSES: readonly ["linked", "revoked"]; /** A live link state. */ export type DecisionAdrLinkStatus = (typeof DECISION_ADR_LINK_STATUSES)[number]; /** * The natural row uid of one (decision, ADR) pair. Both devices derive this * same value, which is what makes concurrent identical links coalesce. * * @param decisionId - The `D####` ledger decision id. * @param adrSlug - The ADR doc slug. * @returns The canonical natural uid. */ export declare function decisionAdrLinkUid(decisionId: string, adrSlug: string): string; /** Input for {@link linkDecisionToAdr}. */ export interface LinkDecisionToAdrInput { /** The `D####` ledger decision id. */ readonly decisionId: string; /** The ADR doc slug (`adr-NNN-…`). */ readonly adrSlug: string; /** Agent id creating the link; omitted when unknown. */ readonly linkedBy?: string; } /** Outcome of a {@link linkDecisionToAdr} call. */ export interface LinkDecisionToAdrResult { /** The link row after the call. */ readonly row: DecisionAdrLinkRow; /** True when this call inserted the row; false for an idempotent re-link. */ readonly inserted: boolean; } /** * Link a `D####` decision to an ADR doc (append-only, natural identity). * * A repeat link of an already-linked pair is an idempotent no-op. A link of * a REVOKED pair is refused: append-only means the tombstone latch never * flips back, so re-linking would require resurrecting a dead row. * * @param projectRoot - Absolute path to the CLEO project root. * @param input - The link facts. * @returns The link row and whether it was inserted. */ export declare function linkDecisionToAdr(projectRoot: string, input: LinkDecisionToAdrInput): Promise; /** Input for {@link revokeDecisionAdrLink}. */ export interface RevokeDecisionAdrLinkInput { /** The `D####` ledger decision id. */ readonly decisionId: string; /** The ADR doc slug. */ readonly adrSlug: string; /** Agent id revoking the link; omitted when unknown. */ readonly revokedBy?: string; } /** Outcome of a {@link revokeDecisionAdrLink} call. */ export interface RevokeDecisionAdrLinkResult { /** The link row after the call. */ readonly row: DecisionAdrLinkRow; /** True when this call flipped the latch; false for an idempotent re-revoke. */ readonly revoked: boolean; } /** * Revoke a link as a TOMBSTONE LATCH (never a DELETE, never a resurrection). * * `status` flips `linked` → `revoked` exactly once and `updated_at` records * the tombstone instant; the row survives sync and restore as the permanent * record that this pair was once linked and then unlinked. Re-revoking an * already-revoked pair is an idempotent no-op. Revoking a pair that was * never linked is refused — a tombstone must reference a real link. * * @param projectRoot - Absolute path to the CLEO project root. * @param input - The revoke facts. * @returns The link row and whether the latch flipped. */ export declare function revokeDecisionAdrLink(projectRoot: string, input: RevokeDecisionAdrLinkInput): Promise; /** Read one link row by its natural key, any state. */ export declare function getDecisionAdrLink(projectRoot: string, decisionId: string, adrSlug: string): Promise; /** * The ADR docs linked to one decision — the decision→ADR direction of the * two-way surfacing (spec §4: decision lookups surface their ADRs). * * @param projectRoot - Absolute path to the CLEO project root. * @param decisionId - The `D####` ledger decision id. * @param opts.includeRevoked - Also return tombstoned links (default false). * @returns The link rows, ADR slug ascending. */ export declare function listAdrLinksForDecision(projectRoot: string, decisionId: string, opts?: { includeRevoked?: boolean; }): Promise; /** * The `D####` decisions linked to one ADR doc — the ADR→decision direction * (`cleo docs fetch adr-NNN` surfaces its source decisions, spec §4 AC2). * * @param projectRoot - Absolute path to the CLEO project root. * @param adrSlug - The ADR doc slug. * @param opts.includeRevoked - Also return tombstoned links (default false). * @returns The link rows, decision id ascending. */ export declare function listDecisionLinksForAdr(projectRoot: string, adrSlug: string, opts?: { includeRevoked?: boolean; }): Promise; /** * Count the LIVE `decision_adr_links` rows (status `linked`) — the * "decisions linked to docs" adoption metric (T13363). Revoked tombstones * are excluded: the metric counts current cross-references, not history. * Read-only, through the chokepoint handle. * * @param projectRoot - Absolute path to the CLEO project root. * @returns The number of live links. * @task T13363 */ export declare function countLinkedDecisionAdrs(projectRoot: string): Promise; /** Optional `linkedAdrs` field attached by {@link attachLinkedAdrs}. */ export interface DecisionWithLinkedAdrs { /** ADR doc slugs with a live link to this decision; absent when none. */ readonly linkedAdrs?: string[]; } /** * Attach each `D####` decision's linked ADR slugs — the decision→ADR * direction of the two-way trace (spec §4: decision lookups surface their * curated ADR records). Rows without a bare `D####` id pass through * untouched. * * @param projectRoot - Absolute path to the CLEO project root. * @param decisions - Decision rows as returned by the brain store. * @returns The same rows, with `linkedAdrs` added where links exist. */ export declare function attachLinkedAdrs(projectRoot: string, decisions: readonly T[]): Promise>; //# sourceMappingURL=decision-adr-links.d.ts.map