sx-git Phase 1: blob/tree/commit/tag as content-addressed typed objects (TDD)

Objects are plain dicts over persist kv, addressed by sx1:<sha256> of the
artdag/canon canonical form (sorted dict keys) — native CIDs, extensible
fields participate in identity. 38/38.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
2026-07-03 12:01:11 +00:00
parent f561deede3
commit 9a85b52d1a
5 changed files with 453 additions and 0 deletions

81
lib/git/object.sx Normal file
View File

@@ -0,0 +1,81 @@
; lib/git/object.sx — sx-git Phase 1: blob/tree/commit/tag as content-addressed
; TYPED objects over the persist kv store. Identity = host digest of the
; canonical serialization (artdag/canon: sorted dict keys, escaped strings) —
; native CIDs, NOT git wire bytes. Objects are plain dicts: typed, extensible;
; unknown fields round-trip and participate in the CID.
; Requires: lib/persist/backend.sx, lib/persist/kv.sx, lib/artdag/dag.sx.
; ---- canonical form + content id ----
(define git/canon (fn (obj) (artdag/canon obj)))
(define
git/cid
(fn (obj) (str "sx1:" (crypto-sha256 (artdag/canon obj)))))
; ---- repo handle: a persist backend + key prefix (many repos per db) ----
(define git/repo-named (fn (db name) {:prefix name :db db}))
(define git/repo (fn (db) (git/repo-named db "git")))
(define git/repo-db (fn (repo) (get repo :db)))
(define git/obj-key (fn (repo cid) (str (get repo :prefix) "/obj/" cid)))
; ---- constructors ----
(define git/blob (fn (data) {:data data :type "blob"}))
; entries: dict of name -> entry, entry = {:kind "blob"|"tree" :cid cid ...}
(define git/tree (fn (entries) {:type "tree" :entries entries}))
(define git/tree-entry (fn (kind cid) {:kind kind :cid cid}))
; meta: open dict (:author :message :time ... anything); protected keys win
(define git/commit (fn (tree parents meta) (merge meta {:type "commit" :tree tree :parents parents})))
(define git/tag (fn (target name meta) (merge meta {:name name :type "tag" :target target})))
; ---- predicates / accessors ----
(define git/object-type (fn (obj) (get obj :type)))
(define
git/blob?
(fn (obj) (and (dict? obj) (equal? (get obj :type) "blob"))))
(define
git/tree?
(fn (obj) (and (dict? obj) (equal? (get obj :type) "tree"))))
(define
git/commit?
(fn (obj) (and (dict? obj) (equal? (get obj :type) "commit"))))
(define
git/tag?
(fn (obj) (and (dict? obj) (equal? (get obj :type) "tag"))))
(define git/blob-data (fn (obj) (get obj :data)))
(define git/tree-entries (fn (obj) (get obj :entries)))
(define git/tree-entry-for (fn (obj name) (get (get obj :entries) name)))
(define
git/tree-names
(fn (obj) (artdag/sort-strings (keys (get obj :entries)))))
(define git/entry-cid (fn (entry) (get entry :cid)))
(define git/entry-kind (fn (entry) (get entry :kind)))
(define git/commit-tree (fn (obj) (get obj :tree)))
(define git/commit-parents (fn (obj) (get obj :parents)))
(define git/commit-author (fn (obj) (get obj :author)))
(define git/commit-message (fn (obj) (get obj :message)))
(define git/tag-target (fn (obj) (get obj :target)))
(define git/tag-name (fn (obj) (get obj :name)))
; ---- object store: write/read/has, keyed by cid ----
(define
git/write
(fn
(repo obj)
(let
((cid (git/cid obj)))
(begin (persist/kv-put (get repo :db) (git/obj-key repo cid) obj) cid))))
(define
git/read
(fn (repo cid) (persist/kv-get (get repo :db) (git/obj-key repo cid))))
(define
git/has?
(fn (repo cid) (persist/kv-has? (get repo :db) (git/obj-key repo cid))))
; convenience: write a blob straight from data
(define git/write-blob (fn (repo data) (git/write repo (git/blob data))))