1 import Base 2 import ./src/Store.bend as Store 3 import ./src/Value.bend as Value 4 import ./src/LogicalKey.bend as LogicalKey 5 import ./src/Staging.bend as Staging 6 import ./src/History.bend as History 7 import ./src/StateTree.bend as StateTree 8 import ./src/Comparison.bend as Comparison 9 import ./src/Merging.bend as Merging 10 import ./src/Certificate.bend as Certificate 11 import ./src/JsonAdapter.bend as JsonAdapter 12 13 # Public API of ber-core. Single import for consumers; Store carries the 14 # Op/Handle session types. Level 2 delegates expose full power with internal 15 # types visible; Level 1 composes the common paths into opaque String 16 # results. Values are multi-type (text, document, binary blob), never bare 17 # text; reports are human-readable Strings, never domain values. 18 19 # --- Level 2: domain delegates (1:1) --- 20 21 # Stages one value for later commit. 22 def put_record(+session_id: String, +namespace_name: String, +record_id: String, value: Value.Value) -> Store.Op<&2, LogicalKey.LogicalKey>: 23 Staging.put_record(session_id, namespace_name, record_id, value) 24 25 # Stages a tombstone for later commit. 26 def delete_record(+session_id: String, +namespace_name: String, +record_id: String) -> Store.Op<&2, LogicalKey.LogicalKey>: 27 Staging.delete_record(session_id, namespace_name, record_id) 28 29 # Materializes stage plus base into a new commit. 30 def create_commit(+session_id: String, +parent_ids: List<&2, String>, metadata: List<&2, History.CommitMeta>) -> Store.Op<&2, History.Commit>: 31 History.create_commit(session_id, parent_ids, metadata) 32 33 # Reads a namespaced value at a commit. 34 def read_value_at(+namespace_name: String, +record_id: String, +commit_id: String) -> Store.Op<&2, Maybe<&2, Value.Value>>: 35 History.read_value_at(namespace_name, record_id, commit_id) 36 37 # Reads the tree for a commit identifier. 38 def read_tree_at(+commit_id: String) -> Store.Op<&2, Maybe<&2, StateTree.StateTreeNode>>: 39 History.read_tree_at(commit_id) 40 41 # Diffs two commits with hash pruning. 42 def compare_commits(+first_id: String, +second_id: String) -> Store.Op<&2, Comparison.CompareResult>: 43 Comparison.compare_commits(first_id, second_id) 44 45 # Merges two commits by strategy name. 46 def merge_commits(+first_id: String, +second_id: String, +strategy_name: String) -> Store.Op<&2, Merging.MergeResult>: 47 Merging.merge_commits(first_id, second_id, strategy_name) 48 49 # Verifies a merge certificate against stored trees. 50 def verify_certificate(+certificate: Merging.MergeCertificate) -> Store.Op<&2, Bool>: 51 Certificate.verify_certificate(certificate) 52 53 # --- Level 1: composed operations (primary API) --- 54 55 # Stages a value and commits in one step; returns the new commit identifier. 56 def commit_value(+session_id: String, +namespace_name: String, +record_id: String, value: Value.Value, parents: List<&2, String>) -> Store.Op<&2, String>: 57 do Store.Op<&2, String>: 58 staged_key : LogicalKey.LogicalKey <- Staging.put_record(session_id, namespace_name, record_id, value) 59 new_commit : History.Commit <- History.create_commit(session_id, parents, Nil{}) 60 return History.commit_identifier_of(new_commit) 61 62 # Stages a tombstone and commits in one step; returns the new commit identifier. 63 def remove_record(+session_id: String, +namespace_name: String, +record_id: String, parents: List<&2, String>) -> Store.Op<&2, String>: 64 do Store.Op<&2, String>: 65 staged_key : LogicalKey.LogicalKey <- Staging.delete_record(session_id, namespace_name, record_id) 66 new_commit : History.Commit <- History.create_commit(session_id, parents, Nil{}) 67 return History.commit_identifier_of(new_commit) 68 69 # Renders one diff count line. 70 def render_compare_counts(+compare_result: Comparison.CompareResult) -> String: 71 "added=" ++ Nat.show(List.length(&2, LogicalKey.LogicalKey, Comparison.added_keys_of(compare_result))) ++ " removed=" ++ Nat.show(List.length(&2, LogicalKey.LogicalKey, Comparison.removed_keys_of(compare_result))) ++ " modified=" ++ Nat.show(List.length(&2, Comparison.ModifiedEntry, Comparison.modified_entries_of(compare_result))) 72 73 # Diffs two commits and renders the counts. 74 def compare_summary(+first_id: String, +second_id: String) -> Store.Op<&2, String>: 75 do Store.Op<&2, String>: 76 compared : Comparison.CompareResult <- Comparison.compare_commits(first_id, second_id) 77 return render_compare_counts(compared) 78 79 # Renders a verified merge; failed verification is unprovable. 80 def decide_verified_text(verified_ok: Bool, +commit_id: String) -> String: 81 match verified_ok: 82 case True{}: 83 "merged:" ++ commit_id 84 case False{}: 85 "unprovable:verify-failed" 86 87 # Verifies a successful merge and renders the verdict. 88 def verify_successful_merge(+resulting_commit: History.Commit, +certificate: Merging.MergeCertificate) -> Store.Op<&2, String>: 89 do Store.Op<&2, String>: 90 verified_ok : Bool <- Certificate.verify_certificate(certificate) 91 return decide_verified_text(verified_ok, History.commit_identifier_of(resulting_commit)) 92 93 # Renders a merge outcome; conflicts count keys, unprovable names the reason. 94 def verify_merge_outcome(merge_result: Merging.MergeResult) -> Store.Op<&2, String>: 95 match merge_result: 96 case Merging.MergeSuccess{resulting_commit, certificate}: 97 verify_successful_merge(resulting_commit, certificate) 98 case Merging.MergeConflict{conflicting_keys}: 99 Store.Op.pure(&2, String, "conflict:" ++ Nat.show(List.length(&2, LogicalKey.LogicalKey, conflicting_keys))) 100 case Merging.MergeUnprovable{reason}: 101 Store.Op.pure(&2, String, "unprovable:" ++ reason) 102 103 # Merges union-disjoint and verifies; renders merged, conflict or unprovable. 104 def merge_and_verify(+first_id: String, +second_id: String) -> Store.Op<&2, String>: 105 do Store.Op<&2, String>: 106 merged_result : Merging.MergeResult <- Merging.merge_commits(first_id, second_id, "union-disjoint") 107 verify_merge_outcome(merged_result) 108 109 # Renders one found value; blobs show size, never raw bytes. 110 def render_found_value(found_value: Value.Value) -> String: 111 match found_value: 112 case Value.Text{content}: 113 "read=" ++ content 114 case Value.Object{document}: 115 "read=" ++ JsonAdapter.encode_canonical(document) 116 case Value.Blob{byte_list}: 117 "read=" 118 119 # Renders a read; absent reads as absent. 120 def render_read_value(read_value: Maybe<&2, Value.Value>) -> String: 121 match read_value: 122 case None{}: 123 "read=absent" 124 case Some{found_value}: 125 render_found_value(found_value) 126 127 # Renders a read plus diff and merge lines; the demo report shape. 128 def format_report(read_value: Maybe<&2, Value.Value>, +diff_line: String, +merge_line: String) -> String: 129 render_read_value(read_value) ++ "\n" ++ diff_line ++ "\n" ++ merge_line