/** * Campaign baseline — per-cell SHA-256 hashes for three-way merge over * a project + its deliverables + their tasks. * * Cell-path scheme (flat namespace): * - `project.` — project-level scalars * - `deliverables..` — deliverable-level scalars * - `deliverables..tasks..` — task-level scalars * * Element names match the recipe field names (`description`, `status`, * `dueDate`, `funnelStage`, etc.). Labels and funnel tactics — arrays — * are hashed as a single unit per cell. * * API constraints: only tasks have a verified update endpoint * (`updateTask`). Project-level + deliverable-level cells classify for * informational purposes; under any policy the apply side has no write * primitive for them, so a `recipe-change` there is logged but never * acted on. Tasks are the meaningful three-way merge surface — and the * one place a silent clobber would hurt. */ import type { Baseline, FieldClassification } from "../../sync/index.js"; import { resolveCellByPolicy } from "../../sync/index.js"; import type { CampaignRecipe } from "./schema.js"; /** Per-cell hash map. Key shape is per the doc comment above. */ export interface CampaignBaselinePayload { schemaVersion: "1"; cells: Record; /** * Server UUID of the project row this baseline was captured against. * When present, apply prefers id-match via `getProject(id)` before * falling back to the labels-based `findProjectByName`. Survives a * displayName edit between pushes without orphaning the existing * tenant row. */ tenantId?: string; } export type CampaignBaseline = Baseline; /** * Walk a campaign recipe and emit per-cell hashes for the full nested * shape — project + every deliverable + every task. Used both at * baseline-capture time (post-apply, hash the merged recipe) and at * classification time (hash the desired and current recipes for * comparison). */ export declare const hashCampaignCells: (recipe: CampaignRecipe) => Record; /** * Build the post-apply baseline payload for a campaign recipe. * Pass the tenant `id` of the project this apply landed on so the * next push can resolve the row by id rather than by displayName / * identity labels. */ export declare const captureCampaignBaselinePayload: (recipe: CampaignRecipe, tenantId?: string) => CampaignBaselinePayload; /** * Classify every cell in a campaign three-way: desired vs current vs * baseline. Returns a map keyed by cell path. Cells present in only * one side classify against the absent-value hash on the other side. */ export declare const classifyCampaignCells: (desired: CampaignRecipe, current: CampaignRecipe, baselinePayload: CampaignBaselinePayload | undefined) => Record; /** * Re-export so existing campaign-domain consumers can keep importing * the per-cell winner selector by its local name (`cellResolution`). * The shared implementation lives in `@/sync`. */ export { resolveCellByPolicy as cellResolution }; /** * Build a merged campaign recipe where each cell takes either the * desired or current value per the policy resolution. The recipe's * shape is preserved (project + same deliverable set + same task set) * but per-element values follow the per-cell winner. * * Recipe-author intent on missing tasks: the merged recipe preserves * the desired recipe's deliverable + task set. Tenant-only * deliverables / tasks are NOT pulled into the merged recipe — the * recipe author hasn't asked for them. (This matches additive push * semantics: a recipe omitting an item does not delete it server-side, * but also does not adopt it back into the recipe.) */ export declare const mergeCampaignByPolicy: (desired: CampaignRecipe, current: CampaignRecipe, classifications: Record, policy: "error" | "recipe-wins" | "cms-wins") => { merged: CampaignRecipe; policyErrors: Array<{ path: string; classification: FieldClassification; }>; };