~ioiojo/pond

Plaintext double-entry accounting system
specify 256 bits
fix dependencies
replace sha256 with blake3 from funcool/buddy-core

refs

master
browse  log 

clone

read-only
https://git.sr.ht/~ioiojo/pond
read/write
git@git.sr.ht:~ioiojo/pond

You can also use your local clone with git send-email.

#Pond

Pond is a double-entry accounting system inspired by Nix's approach to reproducible builds. Just as Nix tracks dependencies and transformations through cryptographic hashes, Pond tracks financial transactions and their derived states through a similar content-addressed approach.

#Core Concepts

#Nix-Inspired Design

  • Each transformation of financial state is deterministic and reproducible
  • The hash of any state depends on all inputs that led to that state
  • Changes to any input create a different hash, ensuring audit trail integrity
  • All transformations are immutable and can be "replayed"

#Double-Entry Accounting

  • Every transaction must balance (debits = credits)
  • All account states are derived from transaction history
  • Support for standard accounting concepts (assets, liabilities, equity, etc.)
  • Generates standard financial reports (balance sheet, income statement, etc.)

#Programmable Transformations

  • Users can define custom transformations in .pond files
  • Transformations can trigger cascading effects (e.g., tax calculations)
  • All transformations are tracked and reproducible
  • "Step through" debugging of financial state changes

#Technical Implementation

  • Built in Clojure with rewrite-clj for transform parsing
  • Journal entries in plain text (EDN format)
  • Content-addressed storage of all states
  • Functional transformation pipeline

#Example Usage

#Defining Transformations

Transformations are defined in .pond files:

;; $XDG_DATA_HOME/pond/transforms/my_company/price_resolution.pond
(ns my-company.transforms.price-resolution
  (:require
   [clojure.string :as str] ; Core Clojure lib
   [pond.core.price :as price])) ; Core Pond module

(transform price-resolution
  {:inputs []}  ; No transform inputs needed
  [{:keys [entry state env]} ctx]
  (let [prices (:prices env)
        resolve-price (fn [posting]
                        (if-let [price (price/get-price prices
                                         (:asset-code (:amount posting))
                                         (:date entry))]
                          (assoc-in posting [:amount :meta :exchange-rate] price)
                          posting))]
    {:entry (update entry :postings #(map resolve-price %))
     :state state
     :env env
     :context (assoc-in ctx [:transform-data :price-resolution :resolved] true)}))

#REPL Usage

;; Initialize registry
user=> (require '[pond.registry :as r])
nil
user=> (def registry (r/create-registry))
#'user/registry

;; Load transformation
user=> (r/load-transform! registry "$XDG_DATA_HOME/pond/transforms/my_company/price_resolution.pond")
{:namespace 'my-company.transforms.price-resolution
 :hash "b3-256:8f4K9zjJD6K2Vz+wYP..."  ; Hash of source
 :status :loaded}

;; Create test state and environment
user=> (def initial-state
         {:entries
          [{:date "2024-01-20"
            :postings
            [{:account {:silo :asset
                        :path [:investments :stocks]}
              :amount {:asset-code "VWRA"
                       :asset-quantity 200}}]}]})
user=> (def env
         {:runtime {:clojure-version "1.11.1"    ; Runtime versions
                    :java-version "17"}
          :price-db {"VWRA" {:date "2024-01-20"
                             :price 5.00M
                             :currency "USD"}}})

;; Apply transformation
user=> (r/apply-transform registry 'my-company.transforms.price-resolution initial-state env {})
{:result {...}  ; New state with resolved prices
 :hash "b3-256:Kj82nX7..."  ; Depends on:
                            ; - Runtime versions
                            ; - Initial state
                            ; - Transform source
                            ; - Price data
 :inputs [{:id :runtime :hash "b3-256:7dK9..."}
          {:id :initial-state :hash "b3-256:9dF3..."}
          {:id :transform :hash "b3-256:8f4K..."}
          {:id :price-data :hash "b3-256:92nK..."}]}

;; Verify reproducibility
user=> (r/verify-reproducible! "b3-256:Kj82nX7...")
{:reproducible? true
 :verification-hash "b3-256:Kj82nX7..."
 :time-taken 42}

#Project Organization

pond/
├── src/
│   └── pond/
│       ├── core.clj           # Core functionality
│       ├── parser.clj         # Transform parser
│       ├── registry.clj       # Transform registry
│       ├── hash.clj           # Hash computation
│       ├── dgraph.clj         # Dependency graph
│       └── transforms/        # Core transforms
│           ├── price_resolution.pond
│           └── balance_validation.pond
├── test/
│   └── pond/
│       ├── parser_test.clj
│       ├── dgraph_test.clj
│       └── core_test.clj
└── $XDG_DATA_HOME/pond/
    └── transforms/            # User transforms
        └── my_company/
            └── custom.pond

#Development

Dependencies:

  • Clojure 1.12.0
  • Java JDK 21

Setup:

# Run tests
clj -M:test

# Run specific test namespace
clj -M:test --focus pond.parser-test

# Start REPL
clj -M:dev -m nrepl.cmdline --middleware "[cider.nrepl/cider-middleware]"

#Project Status

Currently in early development. Basic features:

  • [ ] Transform parsing and validation
  • [ ] Transform registry
  • [ ] Hash-based state tracking
  • [ ] Journal entry parsing
  • [ ] Double-entry validation
  • [ ] Basic state tracking
  • [ ] Transaction replay
  • [ ] Basic reporting

#Design Goals

  1. Reproducibility

    • All calculations must be reproducible
    • All inputs must be tracked via hashing
    • Runtime versions must be specified
    • All state changes must be verifiable
  2. Auditability

    • Clear transform definitions
    • Explicit state changes
    • Complete hash trail
    • Traceable calculations
  3. Extensibility

    • User-defined transforms in .pond files
    • Custom validation rules
    • Pluggable tax systems
    • Transform composition
  4. Developer Experience

    • Clear error messages
    • Source-mapped errors
    • Step-through debugging
    • State visualization

#Transform Development

  1. Create a .pond file:
(ns my-company.transforms.custom
  (:require [pond.core.price :as price]))

(transform custom
  {:inputs []}
  [{:keys [entry state env]} ctx]
  ;; Transform implementation...
  )
  1. Place in transform directory:
$XDG_DATA_HOME/pond/transforms/my_company/custom.pond
  1. Load into registry:
(r/load-transform! registry "path/to/custom.pond")
  1. Apply transform:
(r/apply-transform registry 'my-company.transforms.custom state env ctx)

#Future Directions

  1. Transform Development

    • Interactive development environment
    • Better error messages
    • Transform debugging tools
    • Performance profiling
  2. Web Interface

    • Transform visualization
    • State inspection
    • Transaction explorer
    • Report generation
  3. Tax Integration

    • Automatic form generation
    • Multiple jurisdiction support
    • Tax rule plugins
    • Basis tracking
  4. Ecosystem Support

    • Transform registry
    • Standard library
    • Community packages
    • Documentation tools

#Contributing

See DESIGN.md for architectural details and implementation.md for technical specifics.

#Development Process

  1. Install dependencies:
clj -P
  1. Run tests:
clj -M:test
  1. Start REPL:
clj -M:dev -m nrepl.cmdline --middleware "[cider.nrepl/cider-middleware]"

#Writing Transforms

  1. Follow the transform specification:

    • Use standard namespace declarations
    • Proper argument structure
    • Clear input/output contracts
    • Comprehensive tests
  2. Test thoroughly:

    • Unit tests for transform
    • Integration tests with pipeline
    • Hash verification tests
    • Error case tests
  3. Document clearly:

    • Purpose and usage
    • Input requirements
    • Output guarantees
    • Example usage

#Licensing

CeCILL License. See LICENSE.md for details.

#Contribution

Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the CECILL-2.1 license, will be licensed as above, without any additional terms or conditions.

Do not follow this link