← 3b Projects

Stores messy human text in an append-only encrypted ledger, then uses AI to search, summarize, and generate new entries without requiring a rigid database schema.

The ledger stays immutable:

  • Entry 001: original note
  • Entry 002: correction to Entry 001
  • Entry 003: update to Entry 001

Then the AI/agent produces the current “view”:

  • current version = Entry 001 + Entry 002 + Entry 003

So nothing is edited in place. The system only appends.

26.0703

lets do Lab 005b – Ledger (your first standalone component). ??
Yes. I think this is the perfect point to branch into Part 1: Projects, because the ledger is your own application, not an integration with someone else's system.
I would keep Lab 005b deliberately tiny.
Goal
Create an append-only ledger with hash chaining.
Each approved action creates one ledger entry.
•	Mattermost
      ↓
Python agent
      ↓
Odoo update
      ↓
MongoDB
      ↓
Ledger
      ↓
Mattermost confirmation
Ledger file
ledger.jsonl
One JSON object per line.
Example:
•	{
  "entry": 1,
  "timestamp": "...",
  "event": "odoo_product_frozen",
  "product_id": 50,
  "action": "APPROVE 50",
  "prev_hash": "000000...",
  "hash": "a92d..."
}
Hash algorithm
Very simple:
•	hash =
SHA256(
    timestamp +
    event +
    product_id +
    action +
    prev_hash
)
Every entry stores the previous hash.
•	Entry1
hash=A

Entry2
prev=A
hash=B

Entry3
prev=B
hash=C
If anyone edits Entry2,
•	hash(B) changes
↓
Entry3 prev_hash no longer matches
Tampering is immediately detected.
________________________________________
Lab 005b
Functions would be tiny:
•	load_last_hash()
compute_hash()
append_entry()
verify_ledger()
The nice thing is that this becomes a reusable component for all future projects:
•	Security Assistant
        │
Job Radar
        │
DITA Assistant
        │
Odoo Agent
        │
GitHub Agent
        │
...
        ▼
Ledger
I actually think this is the first piece that deserves to become its own Part 1 project rather than just another lab. Every future agent can write to it, making it a foundational component of your ecosystem.

# ledger.py

import json
import hashlib
from datetime import datetime, timezone
from pathlib import Path

LEDGER_FILE = Path("ledger.jsonl")
GENESIS_HASH = "0" * 64

def canonical_json(data):
    return json.dumps(data, sort_keys=True, separators=(",", ":"))

def sha256_text(text):
    return hashlib.sha256(text.encode("utf-8")).hexdigest()

def read_entries():
    if not LEDGER_FILE.exists():
        return []

    entries = []
    with LEDGER_FILE.open("r", encoding="utf-8") as f:
        for line in f:
            if line.strip():
                entries.append(json.loads(line))

    return entries

def append_event(event):
    entries = read_entries()

    entry_number = len(entries) + 1
    prev_hash = entries[-1]["hash"] if entries else GENESIS_HASH

    entry = {
        "entry": entry_number,
        "timestamp": datetime.now(timezone.utc).isoformat(),
        "event": event,
        "prev_hash": prev_hash,
    }

    entry["hash"] = sha256_text(canonical_json(entry))

    with LEDGER_FILE.open("a", encoding="utf-8") as f:
        f.write(json.dumps(entry) + "\n")

    return entry

def verify():
    entries = read_entries()

    prev_hash = GENESIS_HASH

    for entry in entries:
        stored_hash = entry["hash"]

        check_entry = dict(entry)
        del check_entry["hash"]

        expected_hash = sha256_text(canonical_json(check_entry))

        if stored_hash != expected_hash:
            return False, f"Bad hash at entry {entry['entry']}"

        if entry["prev_hash"] != prev_hash:
            return False, f"Bad prev_hash at entry {entry['entry']}"

        prev_hash = stored_hash

    return True, f"Ledger OK. Entries: {len(entries)}"

def show():
    entries = read_entries()

    for entry in entries:
        print(json.dumps(entry, indent=2))

if __name__ == "__main__":
    test_event = {
        "event_type": "test_event",
        "message": "Ledger test entry",
    }

    entry = append_event(test_event)
    print("Appended entry:")
    print(json.dumps(entry, indent=2))

    ok, msg = verify()
    print()
    print(msg)


$ python ledger.py 
Appended entry:
{
  "entry": 2,
  "timestamp": "2026-07-03T15:43:29.053906+00:00",
  "event": {
    "event_type": "test_event",
    "message": "Ledger test entry"
  },
  "prev_hash": "7513ac62591ae57bccfe21828445e4851454f7a5573ac162e64e0df6cefb3a56",
  "hash": "b1fb2eb66235e3cc44378d459405f7e7ed01dbd3e8c7117c9ef006381cecdeda"
}

Ledger OK. Entries: 2
(.venv) 
terry@LAPTOP-HKPDHF7M MINGW64 ~/Downloads/odoo_demo


ledger.jsonl
{"entry": 1, "timestamp": "2026-07-03T15:43:14.438900+00:00", "event": {"event_type": "test_event", "message": "Ledger test entry"}, "prev_hash": "0000000000000000000000000000000000000000000000000000000000000000", "hash": "7513ac62591ae57bccfe21828445e4851454f7a5573ac162e64e0df6cefb3a56"}
{"entry": 2, "timestamp": "2026-07-03T15:43:29.053906+00:00", "event": {"event_type": "test_event", "message": "Ledger test entry"}, "prev_hash": "7513ac62591ae57bccfe21828445e4851454f7a5573ac162e64e0df6cefb3a56", "hash": "b1fb2eb66235e3cc44378d459405f7e7ed01dbd3e8c7117c9ef006381cecdeda"}

$ python ledger.py (after manually modifying the ledger)
Appended entry:
{
  "entry": 3,
  "timestamp": "2026-07-03T15:52:36.864478+00:00",
  "event": {
    "event_type": "test_event",
    "message": "Ledger test entry"
  },
  "prev_hash": "b1fb2eb66235e3cc44378d459405f7e7ed01dbd3e8c7117c9ef006381cecdeda",
  "hash": "2b65c7b44d02fa089ec48163f850de02cb4831cf0c97bce7c03fee7c6e4f36bf"
}

Bad hash at entry 2
(.venv) 
terry@LAPTOP-HKPDHF7M MINGW64 ~/Downloads/odoo_demo
Excellent. Ledger v1 works.
The reason it says:
Entries: 2
is simply that ledger.jsonl already had one test entry from a previous run.
Now you have your first reusable standalone component:
ledger.append_event(event)
ledger.verify()
ledger.show()
Next step is Lab 005b: import ledger.py into the Odoo/Mattermost/Mongo agent and append the approved Odoo action to the ledger.


26.0703 (v1 26.0620)