(** The layer-merge algebra: ordered directives over slugs, last writer wins per field, [empty] the identity. *) type 'r field_edit = | Set_rank of 'r | Set_colour of Colour.t | Set_subject of Subject.t | Set_name of Lang.t * string | Remove_name of Lang.t | Set_citation of Citation.part * string | Remove_citation of Citation.part [@@deriving sexp] type 'r directive = | Add of 'r Layer.entry | Suppress of Slug.t | Replace of Slug.t * 'r Layer.entry | Edit of Slug.t * 'r field_edit list [@@deriving sexp] type 'r t = { id : string; directives : 'r directive list } [@@deriving sexp] (** A directive that did not apply cleanly. Never silently dropped, never fatal: an overlay written against a slightly different base must stay usable, and an authoring error must still be visible. *) type diagnostic = { overlay : string; directive : string; slug : string; message : string } val diagnostic_to_string : diagnostic -> string (** The identity overlay: [apply l empty = (l, [])]. *) val empty : 'r t val apply : 'r Layer.t -> 'r t -> 'r Layer.t * diagnostic list (** Folds overlays in order; diagnostics accumulate in application order. *) val merge : 'r Layer.t -> 'r t list -> 'r Layer.t * diagnostic list (** Loads an overlay from a sexp file. Parse and validation failures come back as [Error], never as an exception. *) val load : (Sexplib0.Sexp.t -> 'r) -> string -> ('r t, string) result