moonlight-delta 0.1.0.1 → 0.1.0.2
raw patch · 9 files changed
+176/−214 lines, 9 files
Files
- CHANGELOG.md +7/−0
- README.md +4/−199
- moonlight-delta.cabal +1/−1
- src-core/Moonlight/Delta/Frontier.hs +15/−1
- src-core/Moonlight/Delta/Scope.hs +16/−1
- src-core/Moonlight/Delta/Signed.hs +22/−1
- src-epoch/Moonlight/Delta/Epoch.hs +42/−11
- src-patch-public/Moonlight/Delta/Patch.hs +40/−0
- src-repair/Moonlight/Delta/Repair.hs +29/−0
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 (..),