/** * GraphPatch — a typed graph MUTATION over the keystone {@link DocumentGraph} IR * (P5b). The structural differ's counterpart: where `document-graph-address.ts` * MINTS identity, this module MOVES between two identities by a tagged delta. * * LOCKED DECISION: **tagged-delta**. A patch is a `_tag`/`_version` envelope * (C6 discipline) carrying the `base` graph id it applies to plus an ordered set * of {@link PatchOp}s (add/remove/update of nodes; add/remove of edges). It is a * VALUE, never a closure: it serializes, content-addresses, and replays. * * Identity is RE-MINTED only through `sealGraph` (→ `contentAddressOf`, the one * kernel; never cborg/JSON): {@link apply} runs the ops then RE-ADDRESSES, so the * result's `id`/`digest` update and a patched graph is indistinguishable from one * authored fresh. "Written data needs a reader": {@link apply} and * {@link validate} ARE the readers of a patch — a patch nobody applies is inert. * * {@link diff} is the inverse: `apply(a, diff(a, b))` deep-equals `b`. It uses the * node `id` (content address) set difference — because a node's id IS the * `contentAddressOf` its payload, a changed payload surfaces as a remove+add of * the same logical cell, collapsed into a single `update` op for readability while * remaining a faithful remove-then-add at apply time. * * {@link receipt} composes the patch's `resultId` onto the receipt byte law * ({@link Receipt}); concurrent-patch fork detection composes onto * {@link DAG.merge} via {@link forkOf}. * * @module */ import { Effect } from 'effect'; import type { ContentAddress, HLC } from './brands.js'; import type { DocumentGraph, DocumentGraphNode, DocumentGraphEdge, NodeFamily } from './document-graph.js'; import type { PlanValidationError } from './plan.js'; import { type ReceiptEnvelope } from './receipt.js'; import { DAG, type MergeResult } from './dag.js'; /** A node-level mutation: add/remove/update a single addressed {@link DocumentGraphNode}. */ export interface NodePatchOp { readonly op: 'add' | 'remove' | 'update'; readonly family: NodeFamily; readonly node: DocumentGraphNode; } /** An edge-level mutation: add/remove a single {@link DocumentGraphEdge}. */ export interface EdgePatchOp { readonly op: 'add' | 'remove'; readonly edge: DocumentGraphEdge; } /** The tagged-delta operation: a discriminated union over node- and edge-level mutations. */ export type PatchOp = NodePatchOp | EdgePatchOp; /** * A typed, content-addressable graph mutation (C6). `base` is the graph id the * delta applies to; `resultId` (when present) is the `apply` result's id — the * seam {@link receipt} and {@link forkOf} bind to. */ export interface GraphPatch { readonly _tag: 'GraphPatch'; readonly _version: 1; /** The id of the {@link DocumentGraph} this patch applies to. */ readonly base: ContentAddress; readonly ops: readonly PatchOp[]; /** The id of the graph `apply(base, this)` produces (set by `propose`/`apply`). */ readonly resultId?: ContentAddress; } /** * Propose a patch from a `base` graph and an op list, stamping `resultId` by * previewing the apply. The patch is a pure value — proposing never mutates * `base`. */ export declare function propose(base: DocumentGraph, ops: readonly PatchOp[]): GraphPatch; /** * Apply a patch's ops to a graph, then RE-ADDRESS via {@link sealGraph} so the * result's `id`/`digest` reflect the new content. Node ops key on the node `id` * (content address); edge ops key on the structural triple. `update` is a * remove-then-add of the carried node (its id already encodes the new payload). * Idempotent per op kind: removing an absent node/edge is a no-op; adding an * existing one dedups. */ export declare function apply(graph: DocumentGraph, patch: GraphPatch): DocumentGraph; /** * Preview a patch — {@link apply} without committing. Same bytes as `apply`, * named for the intent: callers use `preview` to inspect/validate a candidate * result without implying it has been persisted. */ export declare function preview(graph: DocumentGraph, patch: GraphPatch): DocumentGraph; /** * Validate a patch by RE-RUNNING {@link validateGraph} on its apply result: * structural integrity (no cycles, no dangling edge endpoints) of the graph the * patch WOULD produce. A patch that introduces a cycle or a dangling edge fails * here, before anyone commits it. */ export declare function validate(graph: DocumentGraph, patch: GraphPatch): { readonly ok: true; } | { readonly ok: false; readonly errors: readonly PlanValidationError[]; }; /** * Structural differ: the tagged delta that carries `a` to `b`. Nodes diff by * `id` set difference (a payload change is a remove+add of the same logical cell, * collapsed into one `update` op when family + logical key match); edges diff by * structural triple. `apply(a, diff(a, b))` deep-equals `b` (round-trip). */ export declare function diff(a: DocumentGraph, b: DocumentGraph): GraphPatch; /** * The logical identity of a node: family plus the family's stable user-facing key * (signal axis, component/pose name, …), falling back to the node id. Used by `diff` * to COLLAPSE a remove+add of the same cell into one `update`, AND by `apply` to * REPLACE that cell (drop the prior node sharing this key before installing the new * one) — so an `update` is a true logical replacement, not an orphaning add. Families * with no stable non-payload key (entity/policy/export) fall back to the id, so each is * its own cell and `diff` never collapses them into `update`. */ /** Stable logical identity for a node — marker names for DPU patches, diff collapse keys. */ export declare function nodeLogicalKey(node: DocumentGraphNode): string; /** * The receipt subject id for a patch: a content address over `{ base, ops }`, so * structurally-equal patches share a receipt subject (the mutation's identity, * minted through the one kernel — distinct from the sha256 receipt byte law). */ export declare function patchId(patch: GraphPatch): ContentAddress; /** * Compose the patch's `resultId` onto the {@link Receipt} byte law: a single * genesis-or-linked envelope whose payload is a {@link TypedRef} over the * mutation, subject-keyed by the patch identity. Effect-returning because the * receipt byte law hashes via `crypto.subtle` (SHA-256) — the same async kernel * `Receipt.createEnvelope` rides on; folding it to a sync value would force a * second, divergent hashing path. `timestamp`/`previous` default to a genesis * stamp; pass them to chain this patch onto a prior receipt. */ export declare function receipt(patch: GraphPatch, options?: { readonly timestamp?: HLC; readonly previous?: string | readonly string[]; }): Effect.Effect; /** * Concurrent-patch fork detection, composed onto {@link DAG.merge}: ingest a set * of patch receipts into a receipt DAG; `merge` enforces the single-writer * anti-fork rule and reports whether the head diverged. Use when two patches * race off a shared `base` and you must decide if they forked the chain. */ export declare function forkOf(local: DAG.Graph, patchReceipts: readonly ReceiptEnvelope[]): MergeResult; /** * The ONE `_version` this build's GraphPatch reader understands. A patch stamped * with a different `_version` is rejected fail-closed by {@link decode}. */ export declare const SUPPORTED_PATCH_VERSION: 1; /** * VERSION-AWARE, FAIL-CLOSED reader for an UNTRUSTED GraphPatch value (a patch * lowered from persisted JSON / a model proposal). {@link apply} trusts its * `patch` argument's `_version`; a host that reconstructs a patch from outside * the program must run it through THIS gate first, so a future-version * (`_version: 2`) patch is rejected with ONE canonical tagged `ParseError` * — never silently misparsed and replayed as a v1 delta. Scope is intentionally * the `_tag`/`_version` ENVELOPE only (the deeper op-shape validation lives in * {@link validate}, which re-runs structural integrity on the apply result). * * @throws `ParseError` (`source: 'GraphPatch'`) when the value is not a * record, carries the wrong `_tag`, or an unsupported `_version`. */ export declare function decode(value: unknown): GraphPatch; /** * GraphPatch namespace — the tagged-delta mutation surface over * {@link DocumentGraph}. Propose a delta, apply/preview it (re-addressing through * the one kernel), validate the would-be result, diff two graphs, and mint a * receipt / detect concurrent forks. * * @example * ```ts * import { GraphPatch } from '@czap/core'; * * const patch = GraphPatch.propose(base, [{ op: 'add', family: 'signal', node }]); * const next = GraphPatch.apply(base, patch); // re-addressed: next.id !== base.id * const check = GraphPatch.validate(base, patch); // { ok: true } | { ok: false, errors } * const back = GraphPatch.diff(base, next); // apply(base, back) deep-equals next * ``` */ export declare const GraphPatch: { propose: typeof propose; apply: typeof apply; preview: typeof preview; validate: typeof validate; diff: typeof diff; decode: typeof decode; patchId: typeof patchId; receipt: typeof receipt; forkOf: typeof forkOf; }; export declare namespace GraphPatch { /** Alias for {@link PatchOp}. */ type Op = PatchOp; /** Alias for {@link NodePatchOp}. */ type NodeOp = NodePatchOp; /** Alias for {@link EdgePatchOp}. */ type EdgeOp = EdgePatchOp; } //# sourceMappingURL=graph-patch.d.ts.map