packages feed

moonlight-delta 0.1.0.1 → 0.1.0.2

raw patch · 9 files changed

+176/−214 lines, 9 files

Files

CHANGELOG.md view
@@ -1,5 +1,12 @@ # Changelog +## 0.1.0.2 - 2026-07-22++- Documentation: each public front door — `Moonlight.Delta.Patch`,+  `Moonlight.Delta.Signed`, `.Scope`, `.Frontier`, `.Epoch`, and+  `Moonlight.Delta.Repair` — now carries a module-header overview and a worked+  recipe. The README is reduced to a dependency and front-door map.+ ## 0.1.0.1 - 2026-07-21  - DeltaHash protocol v3 (`deltaHashEncodingVersion`) replaces v2. Merkle leaves
README.md view
@@ -7,205 +7,10 @@ multiplicities, invalidation scopes, frontiers, epochs, monotone operators, and bounded repair. Operations return typed incompatibilities where a transition cannot be applied. -## Main idea--A `CellPatch value` records both endpoints of one keyed transition: absent to absent,-absent to present, present to absent, or present to present. Build cells with-`assertAbsent`, `insert`, `delete`, `replace`, or `cellFromEndpoints`. `apply` checks-the before endpoint, and `compose` checks that adjacent endpoints connect.--Use patches for concurrent updates, retries, synchronization, authorization-sensitive-changes, and reconciliation.--## Use--```haskell-import Data.Map.Strict (Map)-import Data.Map.Strict qualified as Map-import Moonlight.Delta.Patch qualified as Patch--changeTitle :: Patch.Patch Int String-changeTitle = Patch.singleton 7 (Patch.replace "Draft" "Published")--committed-  :: Either (Patch.ApplyError Int String) (Map Int String)-committed = Patch.apply changeTitle (Map.singleton 7 "Draft")-```--## Cookbook--### Compose and replay patches--`compose newer older` collapses two connected transitions. `replay` applies a-chronological sequence and reports the failing index.--```haskell-import Data.Map.Strict (Map)-import Data.Map.Strict qualified as Map-import Moonlight.Delta.Patch qualified as Patch--draft, reviewed, published :: Map Int String-draft = Map.singleton 7 "Draft"-reviewed = Map.singleton 7 "Review"-published = Map.singleton 7 "Published"--draftToReview, reviewToPublished :: Patch.Patch Int String-draftToReview = Patch.diff draft reviewed-reviewToPublished = Patch.diff reviewed published--publication-  :: Either (Patch.ComposeError Int String) (Patch.Patch Int String)-publication = Patch.compose reviewToPublished draftToReview--replayed-  :: Either (Patch.ReplayError Int String) (Map Int String)-replayed = Patch.replay [draftToReview, reviewToPublished] draft--history-  :: Either (Patch.ComposeError Int String) (Patch.Patch Int String)-history =-  Patch.recordMany-    [ (7, Patch.replace "Draft" "Review")-    , (7, Patch.replace "Review" "Published")-    ]-```--Use `invert` to reverse a patch. Repeated temporal keys belong in `recordMany`;-`fromList` instead means authoritative, last-wins rows.--### Apply signed multiplicities--`Signed` stores integer changes while the state retains non-negative multiplicities.--```haskell-import Data.Map.Strict (Map)-import Data.Map.Strict qualified as Map-import Moonlight.Delta.Signed qualified as Signed--inventory :: Map String Signed.Multiplicity-inventory = Map.singleton "rose" (Signed.Multiplicity 2)--sale :: Signed.Signed String-sale = Signed.signedFromList [("rose", -1), ("lily", 2)]--afterSale-  :: Either-       (Signed.SignedApplyError String)-       (Map String Signed.Multiplicity)-afterSale = Signed.applySignedToMap sale inventory-```--An underflow returns `SignedMultiplicityUnderflow`; a zero result removes the row.-Combine changes with `combineSigned` and reverse one with `negateSigned`.--### Bound invalidation with a scope--`Scope` distinguishes no invalidation, a finite dirty set, and global invalidation.--```haskell-import Data.Set (Set)-import Data.Set qualified as Set-import Moonlight.Delta.Scope qualified as Scope--dirtyPages :: Scope.Scope (Set Int)-dirtyPages = Scope.dirtyScope (Set.fromList [2, 7])--visibleDirtyPages :: Scope.Scope (Set Int)-visibleDirtyPages = Scope.restrictScope (Set.singleton 7) dirtyPages-```--`scopeKeys` returns `Nothing` for `fullScope`.--### Track two-axis progress--`ProductFrontier2` canonicalizes a two-dimensional progress skyline by removing-dominated points.--```haskell-import Moonlight.Delta.Frontier qualified as Frontier--progress :: Frontier.ProductFrontier2 Int Int-progress = Frontier.mkProductFrontier2 [(0, 3), (1, 2), (2, 1), (3, 0)]--covered :: Bool-covered = Frontier.productFrontier2Contains (2, 2) progress-```--Use `mkFrontier` or `mkUpperFrontier` for a general `PartialOrder`.--### Transport keys across an epoch--An `EpochDelta` checks a partial transport between versioned key universes. A query is-partitioned into transported, retired, and unknown keys.--```haskell-import Data.IntMap.Strict (IntMap)-import Data.IntMap.Strict qualified as IntMap-import Data.IntSet (IntSet)-import Data.IntSet qualified as IntSet-import Moonlight.Delta.Epoch qualified as Epoch--source, target :: Epoch.Endpoint IntSet-source = Epoch.Endpoint Epoch.initialVersion (IntSet.fromList [10, 20])-target =-  Epoch.Endpoint-    (Epoch.nextVersion Epoch.initialVersion)-    (IntSet.fromList [11, 30])--advance-  :: Either-       (Epoch.DeltaViolation Int)-       (Epoch.EpochDelta (IntMap Int) IntSet)-advance =-  Epoch.epochDelta-    source-    target-    (IntMap.singleton 10 11)-    (IntSet.singleton 20)-    (IntSet.singleton 10)--query-  :: Either (Epoch.DeltaViolation Int) (Epoch.Transport (IntMap Int) IntSet)-query =-  fmap-    (\deltaValue -> Epoch.transportKeys deltaValue (IntSet.fromList [10, 20, 99]))-    advance-```--Invalid versions, universes, transports, retirements, or changed keys return a-`DeltaViolation`. Here `query` partitions `10 -> 11`, retired `20`, and unknown `99`;-changed key `10` and fresh key `30` determine the dirty target set.--### Run bounded repair--A repair `Kernel` checks obstructions, turns repairable ones into corrections, and-applies those corrections under explicit fuel.--```haskell-import Data.List.NonEmpty (NonEmpty ((:|)))-import Moonlight.Delta.Repair qualified as Repair--data TooSmall = TooSmall Int-data Increment = Increment--incrementKernel :: Int -> Repair.Kernel Int TooSmall Increment-incrementKernel target =-  Repair.Kernel-    { Repair.check = \state ->-        if state >= target-          then Repair.StepConverged state-          else Repair.StepObstructed state (TooSmall target :| [])-    , Repair.residuate = \_ -> Just Increment-    , Repair.applyKernelCorrection = \state Increment -> state + 1-    }--repaired :: Repair.Result Int TooSmall-repaired = Repair.boundedRepair (incrementKernel 2) (Repair.Config 4) 0-```--The result is `ResultConverged 2 2`; the other terminal cases are `ResultStuck` and-`ResultBudgetExhausted`. Compose kernels with `sequenceRepair`, `productRepair`, or-`focusRepair`.+Each public front door — `Moonlight.Delta.Patch`, `Moonlight.Delta.Signed`,+`.Scope`, `.Frontier`, `.Epoch`, and `Moonlight.Delta.Repair` — carries its own+overview and a worked recipe in its module header; there is no umbrella. The+table below maps each dependency to its front door.  ## Surface & boundaries 
moonlight-delta.cabal view
@@ -1,6 +1,6 @@ cabal-version:       3.0 name:                moonlight-delta-version:             0.1.0.1+version:             0.1.0.2 synopsis:            Boundary-aware delta calculus for Moonlight. description:         Categorical and state-change delta vocabulary layered over moonlight-core. license:             MIT
src-core/Moonlight/Delta/Frontier.hs view
@@ -1,6 +1,20 @@ {-# LANGUAGE DerivingStrategies #-} --- | Antichain frontiers over partial orders (lower, upper, and two-dimensional product variants), answering containment queries.+{-|+Antichain frontiers over partial orders: lower, upper, and two-dimensional+product variants, answering containment queries. @ProductFrontier2@+canonicalizes a two-dimensional progress skyline by removing dominated points.++> import Moonlight.Delta.Frontier qualified as Frontier+>+> progress :: Frontier.ProductFrontier2 Int Int+> progress = Frontier.mkProductFrontier2 [(0, 3), (1, 2), (2, 1), (3, 0)]+>+> covered :: Bool+> covered = Frontier.productFrontier2Contains (2, 2) progress++Use @mkFrontier@ or @mkUpperFrontier@ for a general @PartialOrder@.+-} module Moonlight.Delta.Frontier   ( Frontier,     UpperFrontier,
src-core/Moonlight/Delta/Scope.hs view
@@ -3,7 +3,22 @@ {-# LANGUAGE StandaloneKindSignatures #-} {-# LANGUAGE TypeFamilies #-} --- | Dirty-scope tracking with one type-indexed notion of emptiness.+{-|+Dirty-scope tracking with one type-indexed notion of emptiness. A @Scope@+distinguishes no invalidation, a finite dirty set, and global invalidation.++> import Data.Set (Set)+> import Data.Set qualified as Set+> import Moonlight.Delta.Scope qualified as Scope+>+> dirtyPages :: Scope.Scope (Set Int)+> dirtyPages = Scope.dirtyScope (Set.fromList [2, 7])+>+> visibleDirtyPages :: Scope.Scope (Set Int)+> visibleDirtyPages = Scope.restrictScope (Set.singleton 7) dirtyPages++@scopeKeys@ returns @Nothing@ for @fullScope@.+-} module Moonlight.Delta.Scope   ( ScopeCarrier (..),     Scope,
src-core/Moonlight/Delta/Signed.hs view
@@ -2,7 +2,28 @@ {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TypeFamilies #-} --- | Integer-signed multiplicities and their changes: states stay non-negative, deltas carry sign, application is checked ('SignedApplyError').+{-|+Integer-signed multiplicities and their changes: states stay non-negative,+deltas carry sign, application is checked. A @Signed@ stores integer changes+while the state retains non-negative @Multiplicity@ values.++> import Data.Map.Strict (Map)+> import Data.Map.Strict qualified as Map+> import Moonlight.Delta.Signed qualified as Signed+>+> inventory :: Map String Signed.Multiplicity+> inventory = Map.singleton "rose" (Signed.Multiplicity 2)+>+> sale :: Signed.Signed String+> sale = Signed.signedFromList [("rose", -1), ("lily", 2)]+>+> afterSale+>   :: Either (Signed.SignedApplyError String) (Map String Signed.Multiplicity)+> afterSale = Signed.applySignedToMap sale inventory++An underflow returns @SignedMultiplicityUnderflow@; a zero result removes the+row. Combine changes with @combineSigned@ and reverse one with @negateSigned@.+-} module Moonlight.Delta.Signed   ( Multiplicity (..),     MultiplicityChange (..),
src-epoch/Moonlight/Delta/Epoch.hs view
@@ -1,14 +1,45 @@--- | The epoch calculus: version-anchored key transport between endpoint--- snapshots.------ 'Version' is an opaque, unbounded epoch counter. An 'Endpoint' pairs a--- version with the result-key universe observed at that instant. An--- 'EpochDelta' is an abstract partial transport: source keys either descend to--- target keys or retire, and the carrier owns its target-side dirty set.------ 'ContextProjectionDelta' is the bounded join-semilattice of dirty-key--- pairs; its 'Data.Semigroup.Semigroup'/'Data.Monoid.Monoid' structure is--- the seam consumed upstream and is preserved verbatim.+{-|+The epoch calculus: version-anchored key transport between endpoint snapshots.++@Version@ is an opaque, unbounded epoch counter. An @Endpoint@ pairs a version+with the result-key universe observed at that instant. An @EpochDelta@ is an+abstract partial transport: source keys either descend to target keys or retire,+and the carrier owns its target-side dirty set. @ContextProjectionDelta@ is the+bounded join-semilattice of dirty-key pairs; its @Semigroup@ and @Monoid@+structure (from "Data.Semigroup" and "Data.Monoid") is the seam consumed+upstream and is preserved verbatim.++A query against an @EpochDelta@ is partitioned into transported, retired, and+unknown keys.++> import Data.IntMap.Strict (IntMap)+> import Data.IntMap.Strict qualified as IntMap+> import Data.IntSet (IntSet)+> import Data.IntSet qualified as IntSet+> import Moonlight.Delta.Epoch qualified as Epoch+>+> source, target :: Epoch.Endpoint IntSet+> source = Epoch.Endpoint Epoch.initialVersion (IntSet.fromList [10, 20])+> target =+>   Epoch.Endpoint+>     (Epoch.nextVersion Epoch.initialVersion)+>     (IntSet.fromList [11, 30])+>+> advance+>   :: Either (Epoch.DeltaViolation Int) (Epoch.EpochDelta (IntMap Int) IntSet)+> advance =+>   Epoch.epochDelta source target+>     (IntMap.singleton 10 11) (IntSet.singleton 20) (IntSet.singleton 10)+>+> query+>   :: Either (Epoch.DeltaViolation Int) (Epoch.Transport (IntMap Int) IntSet)+> query =+>   fmap (\d -> Epoch.transportKeys d (IntSet.fromList [10, 20, 99])) advance++Invalid versions, universes, transports, retirements, or changed keys return a+@DeltaViolation@. Here @query@ partitions @10 -> 11@, retired @20@, and unknown+@99@; changed key @10@ and fresh key @30@ determine the dirty target set.+-} module Moonlight.Delta.Epoch   ( Version,     initialVersion,
src-patch-public/Moonlight/Delta/Patch.hs view
@@ -1,3 +1,43 @@+{-|+Checked keyed transitions. A @CellPatch value@ records both endpoints of one+keyed change — absent→absent, absent→present, present→absent, or+present→present. Build cells with @assertAbsent@, @insert@, @delete@, @replace@,+or @cellFromEndpoints@; @apply@ checks the before endpoint and @compose@ checks+that adjacent endpoints connect. Use patches for concurrent updates, retries,+synchronization, authorization-sensitive changes, and reconciliation.++/Apply a patch./++> import Data.Map.Strict (Map)+> import Data.Map.Strict qualified as Map+> import Moonlight.Delta.Patch qualified as Patch+>+> changeTitle :: Patch.Patch Int String+> changeTitle = Patch.singleton 7 (Patch.replace "Draft" "Published")+>+> committed :: Either (Patch.ApplyError Int String) (Map Int String)+> committed = Patch.apply changeTitle (Map.singleton 7 "Draft")++/Compose and replay./ @compose newer older@ collapses two connected transitions;+@replay@ applies a chronological sequence and reports the failing index. @invert@+reverses a patch. Repeated temporal keys belong in @recordMany@; @fromList@ means+authoritative, last-wins rows.++> draft, reviewed, published :: Map Int String+> draft = Map.singleton 7 "Draft"+> reviewed = Map.singleton 7 "Review"+> published = Map.singleton 7 "Published"+>+> draftToReview, reviewToPublished :: Patch.Patch Int String+> draftToReview = Patch.diff draft reviewed+> reviewToPublished = Patch.diff reviewed published+>+> publication :: Either (Patch.ComposeError Int String) (Patch.Patch Int String)+> publication = Patch.compose reviewToPublished draftToReview+>+> replayed :: Either (Patch.ReplayError Int String) (Map Int String)+> replayed = Patch.replay [draftToReview, reviewToPublished] draft+-} module Moonlight.Delta.Patch   ( CellPatch,     PatchKey,
src-repair/Moonlight/Delta/Repair.hs view
@@ -1,5 +1,34 @@ {-# LANGUAGE DerivingStrategies #-} +{-|+Bounded obstruction repair. A repair @Kernel@ checks obstructions, turns+repairable ones into corrections, and applies those corrections under explicit+fuel. The terminal cases are @ResultConverged@, @ResultStuck@, and+@ResultBudgetExhausted@.++> import Data.List.NonEmpty (NonEmpty ((:|)))+> import Moonlight.Delta.Repair qualified as Repair+>+> data TooSmall = TooSmall Int+> data Increment = Increment+>+> incrementKernel :: Int -> Repair.Kernel Int TooSmall Increment+> incrementKernel target =+>   Repair.Kernel+>     { Repair.check = \state ->+>         if state >= target+>           then Repair.StepConverged state+>           else Repair.StepObstructed state (TooSmall target :| [])+>     , Repair.residuate = \_ -> Just Increment+>     , Repair.applyKernelCorrection = \state Increment -> state + 1+>     }+>+> repaired :: Repair.Result Int TooSmall+> repaired = Repair.boundedRepair (incrementKernel 2) (Repair.Config 4) 0++The result is @ResultConverged 2 2@. Compose kernels with @sequenceRepair@,+@productRepair@, or @focusRepair@.+-} module Moonlight.Delta.Repair   ( Kernel (..),     Step (..),