src/History.bend source
src/History.bend on the hub · documented module
import Baseimport ./Reading.bend as Readingimport ./LogicalKey.bend as LogicalKeyimport ./ContentHash.bend as ContentHashimport ./StateTree.bend as StateTreeimport ./Staging.bend as Stagingimport ./ContentObject.bend as ContentObjectimport ./JsonAdapter.bend as JsonAdapterimport ./Value.bend as Valueimport ./Store.bend as Storeimport bend-kit-json@0.3.0.0/json.bend as Json# Linear history: staging -> merged tree -> commit -> per-key version index.# Commits serialize as canonical JSON without timestamp/certificate (both# re-materialize on load: 0 / None). Loads re-serialize the parsed fields and# compare against the stored text: a mismatch yields None, never corruption.# Kit is named for the Val TYPE only; operations go through JsonAdapter.# Effects are Store Op actions composed in do-blocks. Reads resolve to Values.# One metadata key/value field.type CommitMeta is Data: Field{field_key: String, field_value: String}# Identifier, parents, tree hash, timestamp, metadata and certificate.type Commit is Data: Make{commit_identifier: String, parent_commit_identifiers: List<&2, String>, tree_hash: String, timestamp: Nat, metadata: List<&2, CommitMeta>, certificate_hash: Maybe<&2, String>}# Projects the identifier out of a commit.def commit_identifier_of(commit_value: Commit) -> String: match commit_value: case Make{commit_identifier, parent_commit_identifiers, tree_hash, timestamp, metadata, certificate_hash}: commit_identifier# Projects the tree hash out of a commit.def commit_tree_of(commit_value: Commit) -> String: match commit_value: case Make{commit_identifier, parent_commit_identifiers, tree_hash, timestamp, metadata, certificate_hash}: tree_hash# Encodes one metadata field as a two-item array.def meta_pair_to_json(meta_field: CommitMeta) -> Json.Val: match meta_field: case Field{field_key, field_value}: JsonAdapter.make_arr(Con{JsonAdapter.make_str(field_key), Con{JsonAdapter.make_str(field_value), Nil{}}})# Encodes the metadata list.def meta_list_to_json(metadata: List<&2, CommitMeta>) -> List<&2, Json.Val>: match metadata: case Nil{}: Nil{} case Con{head_field, remaining_fields}: Con{meta_pair_to_json(head_field), meta_list_to_json(remaining_fields)}# Encodes parent identifiers as strings.def encode_parent_list(parent_commit_identifiers: List<&2, String>) -> List<&2, Json.Val>: match parent_commit_identifiers: case Nil{}: Nil{} case Con{head_parent, remaining_parents}: Con{JsonAdapter.make_str(head_parent), encode_parent_list(remaining_parents)}# Encodes the parents/tree/meta document canonically.def finalize_commit_document(parents_field: Sigma<&2, &2, String, _ => Json.Val>, tree_field: Sigma<&2, &2, String, _ => Json.Val>, meta_field: Sigma<&2, &2, String, _ => Json.Val>) -> String: JsonAdapter.encode_canonical(JsonAdapter.make_obj(Con{parents_field, Con{tree_field, Con{meta_field, Nil{}}}}))# Serializes parents, tree and metadata; the hashed bytes.def serialize_commit(parent_commit_identifiers: List<&2, String>, metadata: List<&2, CommitMeta>, tree_hash_value: String) -> String: finalize_commit_document(JsonAdapter.make_kv("parents", JsonAdapter.make_arr(encode_parent_list(parent_commit_identifiers))), JsonAdapter.make_kv("tree", JsonAdapter.make_str(tree_hash_value)), JsonAdapter.make_kv("meta", JsonAdapter.make_arr(meta_list_to_json(metadata))))# Conses a decoded meta field onto the tail.def prepend_meta_field(field_key: String, field_value: String, parsed_tail: Maybe<&2, List<&2, CommitMeta>>) -> Maybe<&2, List<&2, CommitMeta>>: match parsed_tail: case None{}: None{} case Some{tail_fields}: Some{Con{Field{field_key, field_value}, tail_fields}}# Unwraps the field value, then prepends.def prepend_meta_value(field_key: String, value_maybe: Maybe<&2, String>, parsed_tail: Maybe<&2, List<&2, CommitMeta>>) -> Maybe<&2, List<&2, CommitMeta>>: match value_maybe: case None{}: None{} case Some{field_value}: prepend_meta_field(field_key, field_value, parsed_tail)# Unwraps the key text, then continues.def prepend_meta_texts(key_maybe: Maybe<&2, String>, value_maybe: Maybe<&2, String>, parsed_tail: Maybe<&2, List<&2, CommitMeta>>) -> Maybe<&2, List<&2, CommitMeta>>: match key_maybe: case None{}: None{} case Some{field_key}: prepend_meta_value(field_key, value_maybe, parsed_tail)# Decodes one [key, value] array; None on wrong shape.def prepend_meta_items(pair_items: List<&2, Json.Val>, parsed_tail: Maybe<&2, List<&2, CommitMeta>>) -> Maybe<&2, List<&2, CommitMeta>>: match pair_items: case Con{key_json, Con{value_json, Nil{}}}: prepend_meta_texts(JsonAdapter.as_str(key_json), JsonAdapter.as_str(value_json), parsed_tail) case _: None{}# Unwraps the pair array, then decodes.def prepend_parsed_meta(pair_maybe: Maybe<&2, List<&2, Json.Val>>, parsed_tail: Maybe<&2, List<&2, CommitMeta>>) -> Maybe<&2, List<&2, CommitMeta>>: match pair_maybe: case None{}: None{} case Some{pair_items}: prepend_meta_items(pair_items, parsed_tail)# Decodes one metadata item.def prepend_meta_entry(head_item: Json.Val, parsed_tail: Maybe<&2, List<&2, CommitMeta>>) -> Maybe<&2, List<&2, CommitMeta>>: prepend_parsed_meta(JsonAdapter.as_arr(head_item), parsed_tail)# Decodes the metadata array; None on any bad item.def parse_meta_list(meta_items: List<&2, Json.Val>) -> Maybe<&2, List<&2, CommitMeta>>: match meta_items: case Nil{}: Some{Nil{}} case Con{head_item, remaining_items}: prepend_meta_entry(head_item, parse_meta_list(remaining_items))# Accepts only when re-serialization matches stored bytes.def verify_commit_text(text_matches: Bool, +requested_identifier: String, +parent_list: List<&2, String>, +tree_text: String, +parsed_meta: List<&2, CommitMeta>) -> Maybe<&2, Commit>: match text_matches: case True{}: Some{Make{requested_identifier, parent_list, tree_text, 0n, parsed_meta, None{}}} case False{}: None{}# Continues commit extraction once metadata decodes.def finish_commit_meta(+requested_identifier: String, +stored_text: String, +parent_list: List<&2, String>, +tree_text: String, meta_maybe: Maybe<&2, List<&2, CommitMeta>>) -> Maybe<&2, Commit>: match meta_maybe: case None{}: None{} case Some{+parsed_meta}: verify_commit_text(String.eq(serialize_commit(parent_list, parsed_meta, tree_text), stored_text), requested_identifier, parent_list, tree_text, parsed_meta)# Continues commit extraction once the meta items decode.def finish_commit(+requested_identifier: String, +stored_text: String, +parent_list: List<&2, String>, +tree_text: String, meta_items_maybe: Maybe<&2, List<&2, Json.Val>>) -> Maybe<&2, Commit>: match meta_items_maybe: case None{}: None{} case Some{meta_items}: finish_commit_meta(requested_identifier, stored_text, parent_list, tree_text, parse_meta_list(meta_items))# Unwraps the meta array, then continues.def finish_meta_array(+requested_identifier: String, +stored_text: String, +parent_list: List<&2, String>, +tree_text: String, meta_maybe: Maybe<&2, Json.Val>) -> Maybe<&2, Commit>: match meta_maybe: case None{}: None{} case Some{meta_json}: finish_commit(requested_identifier, stored_text, parent_list, tree_text, JsonAdapter.as_arr(meta_json))# Unwraps the tree field as text.def tree_maybe_as_text(tree_maybe: Maybe<&2, Json.Val>) -> Maybe<&2, String>: match tree_maybe: case None{}: None{} case Some{tree_json}: JsonAdapter.as_str(tree_json)# Continues commit extraction once the tree text decodes.def extract_commit_meta(+requested_identifier: String, +stored_text: String, +parent_list: List<&2, String>, tree_text_maybe: Maybe<&2, String>, +document: Json.Val) -> Maybe<&2, Commit>: match tree_text_maybe: case None{}: None{} case Some{tree_text}: finish_meta_array(requested_identifier, stored_text, parent_list, tree_text, JsonAdapter.get_field(document, "meta"))# Continues commit extraction once parents decode.def extract_commit_tree(+requested_identifier: String, +stored_text: String, parent_strings_maybe: Maybe<&2, List<&2, String>>, tree_maybe: Maybe<&2, Json.Val>, +document: Json.Val) -> Maybe<&2, Commit>: match parent_strings_maybe: case None{}: None{} case Some{parent_list}: extract_commit_meta(requested_identifier, stored_text, parent_list, tree_maybe_as_text(tree_maybe), document)# Unwraps the parent items, then continues.def extract_parents_items(+requested_identifier: String, +stored_text: String, items_maybe: Maybe<&2, List<&2, Json.Val>>, +document: Json.Val) -> Maybe<&2, Commit>: match items_maybe: case None{}: None{} case Some{parent_items}: extract_commit_tree(requested_identifier, stored_text, ContentObject.extract_each_string(parent_items), JsonAdapter.get_field(document, "tree"), document)# Unwraps the parents field, then continues.def extract_parents_array(+requested_identifier: String, +stored_text: String, parents_maybe: Maybe<&2, Json.Val>, +document: Json.Val) -> Maybe<&2, Commit>: match parents_maybe: case None{}: None{} case Some{parents_json}: extract_parents_items(requested_identifier, stored_text, JsonAdapter.as_arr(parents_json), document)# Reads the parents field of the document.def extract_commit_parents(+requested_identifier: String, +stored_text: String, +document: Json.Val) -> Maybe<&2, Commit>: extract_parents_array(requested_identifier, stored_text, JsonAdapter.get_field(document, "parents"), document)# Builds the commit from parsed JSON; None on malformed input.def commit_from_parse(requested_identifier: String, stored_text: String, parse_result: Maybe<&2, Json.Val>) -> Maybe<&2, Commit>: match parse_result: case None{}: None{} case Some{+document}: extract_commit_parents(requested_identifier, stored_text, document)# Parses stored commit text.def parse_commit_text(requested_identifier: String, +stored_text: String) -> Maybe<&2, Commit>: commit_from_parse(requested_identifier, stored_text, JsonAdapter.parse_text(stored_text))# Parses loaded text; missing storage reads as None.def parse_commit(requested_identifier: String, stored_maybe: Maybe<&2, String>) -> Maybe<&2, Commit>: match stored_maybe: case None{}: None{} case Some{stored_text}: parse_commit_text(requested_identifier, stored_text)# Loads and verifies a commit by identifier.def load_commit(+requested_identifier: String) -> Store.Op<&2, Maybe<&2, Commit>>: do Store.Op<&2, Maybe<&2, Commit>>: stored_maybe : Maybe<&2, String> <- Store.oget(LogicalKey.build_commit_storage_key(requested_identifier)) return parse_commit(requested_identifier, stored_maybe)# Empty Sess result of unit type.def empty_unit() -> Store.Op<&2, Unit>: do Store.Op<&2, Unit>: return Unit{}# Empty Sess result of entry-list type.def empty_pair_list() -> Store.Op<&2, List<&2, Reading.ChainEntry>>: do Store.Op<&2, List<&2, Reading.ChainEntry>>: return Nil{}# Empty Sess result of tree type.def empty_tree_maybe() -> Store.Op<&2, Maybe<&2, StateTree.StateTreeNode>>: do Store.Op<&2, Maybe<&2, StateTree.StateTreeNode>>: return None{}# Serializes, hashes and stores a commit.def persist_commit(+parent_commit_identifiers: List<&2, String>, +metadata: List<&2, CommitMeta>, +tree_hash_value: String) -> Store.Op<&2, Commit>: do Store.Op<&2, Commit>: +commit_text : String = serialize_commit(parent_commit_identifiers, metadata, tree_hash_value) +commit_identifier : String = ContentHash.compute_sha256_hex(commit_text) stored_unit : Unit <- Store.oput(LogicalKey.build_commit_storage_key(commit_identifier), commit_text) return Make{commit_identifier, parent_commit_identifiers, tree_hash_value, 0n, metadata, None{}}# Hashes and stores a merged entry list as a tree.def persist_tree(+merged_entries: List<&2, Reading.ChainEntry>) -> Store.Op<&2, String>: do Store.Op<&2, String>: +tree_hash_value : String = StateTree.compute_tree_hash(merged_entries) stored_unit : Unit <- Store.oput(LogicalKey.build_tree_storage_key(tree_hash_value), StateTree.serialize_tree(merged_entries, tree_hash_value)) return tree_hash_value# Writes one version-index entry per merged entry.def write_each_index(merged_entries: List<&2, Reading.ChainEntry>, +commit_value: Commit) -> Store.Op<&2, Unit>: match merged_entries: case Nil{}: empty_unit() case Con{+head_entry, remaining_entries}: do Store.Op<&2, Unit>: head_unit : Unit <- Store.oput(LogicalKey.build_version_index_key(Reading.entry_key_of(head_entry), commit_identifier_of(commit_value)), Reading.entry_value_of(head_entry)) tail_unit : Unit <- write_each_index(remaining_entries, commit_value) return tail_unit# Removes staged keys consumed by the commit.def clear_stage_keys(+session_identifier: String, merged_entries: List<&2, Reading.ChainEntry>) -> Store.Op<&2, Unit>: match merged_entries: case Nil{}: empty_unit() case Con{head_entry, remaining_entries}: do Store.Op<&2, Unit>: cleared_unit : Unit <- Store.odel(LogicalKey.build_stage_storage_key_combined(session_identifier, Reading.entry_key_of(head_entry))) cleared_tail : Unit <- clear_stage_keys(session_identifier, remaining_entries) return cleared_tail# Writes the index and clears the stage in sequence.def persist_index_and_clear(+session_identifier: String, +merged_entries: List<&2, Reading.ChainEntry>, +commit_value: Commit) -> Store.Op<&2, Unit>: do Store.Op<&2, Unit>: indexed_unit : Unit <- write_each_index(merged_entries, commit_value) cleared_unit : Unit <- clear_stage_keys(session_identifier, merged_entries) index_cleared_unit : Unit <- Store.odel(Staging.stage_index_key(session_identifier)) return index_cleared_unit# Persists tree, commit and index, then clears the stage.def materialize_commit(+session_identifier: String, +parent_commit_identifiers: List<&2, String>, metadata: List<&2, CommitMeta>, +merged_entries: List<&2, Reading.ChainEntry>) -> Store.Op<&2, Commit>: do Store.Op<&2, Commit>: +tree_hash_value : String <- persist_tree(merged_entries) +commit_value : Commit <- persist_commit(parent_commit_identifiers, metadata, tree_hash_value) cleared_unit : Unit <- persist_index_and_clear(session_identifier, merged_entries, commit_value) return commit_value# Parses loaded tree text.def parse_tree_text(tree_hash_value: String, tree_maybe: Maybe<&2, String>) -> Maybe<&2, StateTree.StateTreeNode>: match tree_maybe: case None{}: None{} case Some{tree_text}: StateTree.parse_tree_document(tree_text)# Loads a tree by hash.def load_tree_text(+tree_hash_value: String) -> Store.Op<&2, Maybe<&2, StateTree.StateTreeNode>>: do Store.Op<&2, Maybe<&2, StateTree.StateTreeNode>>: tree_maybe : Maybe<&2, String> <- Store.oget(LogicalKey.build_tree_storage_key(tree_hash_value)) return parse_tree_text(tree_hash_value, tree_maybe)# Loads the tree a commit points at.def load_tree_by_commit(commit_maybe: Maybe<&2, Commit>) -> Store.Op<&2, Maybe<&2, StateTree.StateTreeNode>>: match commit_maybe: case None{}: empty_tree_maybe() case Some{found_commit}: load_tree_text(commit_tree_of(found_commit))# Reads the tree for a commit identifier.def read_tree_at(+commit_identifier: String) -> Store.Op<&2, Maybe<&2, StateTree.StateTreeNode>>: do Store.Op<&2, Maybe<&2, StateTree.StateTreeNode>>: commit_maybe : Maybe<&2, Commit> <- load_commit(commit_identifier) loaded_tree : Maybe<&2, StateTree.StateTreeNode> <- load_tree_by_commit(commit_maybe) return loaded_tree# Missing base trees read as empty.def base_or_empty(tree_maybe: Maybe<&2, StateTree.StateTreeNode>) -> List<&2, Reading.ChainEntry>: match tree_maybe: case None{}: Nil{} case Some{parent_tree}: StateTree.tree_entries(parent_tree)# Loads the first parent entries.def load_parent_tree(head_parent: String) -> Store.Op<&2, List<&2, Reading.ChainEntry>>: do Store.Op<&2, List<&2, Reading.ChainEntry>>: tree_maybe : Maybe<&2, StateTree.StateTreeNode> <- read_tree_at(head_parent) return base_or_empty(tree_maybe)# Loads base entries; root commits start empty.def load_base_entries(parent_commit_identifiers: List<&2, String>) -> Store.Op<&2, List<&2, Reading.ChainEntry>>: match parent_commit_identifiers: case Nil{}: empty_pair_list() case Con{head_parent, remaining_parents}: load_parent_tree(head_parent)# Skips missing staged values, keeps present ones.def prepend_pair_maybe(+head_key: String, head_value_maybe: Maybe<&2, String>, tail_pairs: List<&2, Reading.ChainEntry>) -> List<&2, Reading.ChainEntry>: match head_value_maybe: case None{}: tail_pairs case Some{head_value}: Con{Reading.make_entry(head_key, head_value), tail_pairs}# Loads one staged value and prepends it.def prepend_loaded_pair(+session_identifier: String, +head_key: String, tail_action: Store.Op<&2, List<&2, Reading.ChainEntry>>) -> Store.Op<&2, List<&2, Reading.ChainEntry>>: do Store.Op<&2, List<&2, Reading.ChainEntry>>: head_value_maybe : Maybe<&2, String> <- Store.oget(LogicalKey.build_stage_storage_key_combined(session_identifier, head_key)) tail_pairs : List<&2, Reading.ChainEntry> <- tail_action return prepend_pair_maybe(head_key, head_value_maybe, tail_pairs)# Loads every staged key in the manifest.def load_each_pair(+session_identifier: String, staged_keys: List<&2, String>) -> Store.Op<&2, List<&2, Reading.ChainEntry>>: match staged_keys: case Nil{}: empty_pair_list() case Con{head_key, remaining_keys}: prepend_loaded_pair(session_identifier, head_key, load_each_pair(session_identifier, remaining_keys))# Collects the session staged pairs.def collect_staged_pairs(+session_identifier: String) -> Store.Op<&2, List<&2, Reading.ChainEntry>>: do Store.Op<&2, List<&2, Reading.ChainEntry>>: staged_keys : List<&2, String> <- Staging.load_stage_index(session_identifier) collected_pairs : List<&2, Reading.ChainEntry> <- load_each_pair(session_identifier, staged_keys) return collected_pairs# Materializes stage plus base into a new commit.def create_commit(+session_identifier: String, +parent_commit_identifiers: List<&2, String>, metadata: List<&2, CommitMeta>) -> Store.Op<&2, Commit>: do Store.Op<&2, Commit>: staged_pairs : List<&2, Reading.ChainEntry> <- collect_staged_pairs(session_identifier) base_entries : List<&2, Reading.ChainEntry> <- load_base_entries(parent_commit_identifiers) finished_commit : Commit <- materialize_commit(session_identifier, parent_commit_identifiers, metadata, StateTree.merge_sorted_entries(staged_pairs, base_entries)) return finished_commit# Versioned reads walk the first-parent chain with Nat fuel; unknown# commits contribute "" links that never match a real index key.def commit_parents_of(commit_value: Commit) -> List<&2, String>: match commit_value: case Make{commit_identifier, parent_commit_identifiers, tree_hash, timestamp, metadata, certificate_hash}: parent_commit_identifiers# Empty Sess result of string-list type.def empty_string_list() -> Store.Op<&2, List<&2, String>>: do Store.Op<&2, List<&2, String>>: return Nil{}# Empty Sess result of entry type.def empty_entry_maybe() -> Store.Op<&2, Maybe<&2, Reading.ChainEntry>>: do Store.Op<&2, Maybe<&2, Reading.ChainEntry>>: return None{}# Empty Op result of value type.def empty_value_maybe() -> Store.Op<&2, Maybe<&2, Value.Value>>: do Store.Op<&2, Maybe<&2, Value.Value>>: return None{}# Empty Op result of text type (commit identifiers, not values).def empty_text_maybe() -> Store.Op<&2, Maybe<&2, String>>: do Store.Op<&2, Maybe<&2, String>>: return None{}# Heads the parent list; root commits yield empty.def first_parent_name(parent_identifiers: List<&2, String>) -> String: match parent_identifiers: case Nil{}: "" case Con{head_parent, remaining_parents}: head_parent# Heads the parent list; unknown commits yield empty.def first_parent_text(commit_maybe: Maybe<&2, Commit>) -> String: match commit_maybe: case None{}: "" case Some{found_commit}: first_parent_name(commit_parents_of(found_commit))# Walks first-parents with fuel; newest first.def walk_commit_chain(remaining_fuel: Nat, +commit_identifier: String) -> Store.Op<&2, List<&2, String>>: match remaining_fuel: case 0n: empty_string_list() case 1n+fuel_left: do Store.Op<&2, List<&2, String>>: commit_maybe : Maybe<&2, Commit> <- load_commit(commit_identifier) tail_chain : List<&2, String> <- walk_commit_chain(fuel_left, first_parent_text(commit_maybe)) return Con{commit_identifier, tail_chain}# A stored value wins; otherwise the tail result.def decide_search_hit(stored_maybe: Maybe<&2, String>, +lookup_key: String, tail_result: Maybe<&2, Reading.ChainEntry>) -> Maybe<&2, Reading.ChainEntry>: match stored_maybe: case None{}: tail_result case Some{stored_value}: Some{Reading.make_entry(lookup_key, stored_value)}# Searches the ancestor chain newest-first.def search_chain_entries(ancestor_chain: List<&2, String>, +namespace_name: String, +record_identifier: String) -> Store.Op<&2, Maybe<&2, Reading.ChainEntry>>: match ancestor_chain: case Nil{}: empty_entry_maybe() case Con{head_commit, remaining_commits}: do Store.Op<&2, Maybe<&2, Reading.ChainEntry>>: +lookup_key : String = Reading.encode_lookup_key(namespace_name, record_identifier) stored_maybe : Maybe<&2, String> <- Store.oget(LogicalKey.build_version_index_key(lookup_key, head_commit)) tail_result : Maybe<&2, Reading.ChainEntry> <- search_chain_entries(remaining_commits, namespace_name, record_identifier) return decide_search_hit(stored_maybe, lookup_key, tail_result)# Projects the payload out of a loaded object.def payload_or_empty(loaded_maybe: Maybe<&2, ContentObject.ContentObject>) -> Maybe<&2, Value.Value>: match loaded_maybe: case None{}: None{} case Some{loaded_object}: Some{ContentObject.object_payload_of(loaded_object)}# Loads an object payload by content hash.def load_object_payload(+value_hash: String) -> Store.Op<&2, Maybe<&2, Value.Value>>: do Store.Op<&2, Maybe<&2, Value.Value>>: loaded_maybe : Maybe<&2, ContentObject.ContentObject> <- ContentObject.load_content_object(value_hash) return payload_or_empty(loaded_maybe)# Tombstones read as absent; hashes load payloads.def decide_resolve_value(is_tombstone: Bool, +stored_value: String) -> Store.Op<&2, Maybe<&2, Value.Value>>: match is_tombstone: case True{}: empty_value_maybe() case False{}: load_object_payload(stored_value)# Resolves one stored index value.def resolve_entry_value(+stored_value: String) -> Store.Op<&2, Maybe<&2, Value.Value>>: decide_resolve_value(String.eq(stored_value, "TOMBSTONE"), stored_value)# Resolves a found chain entry to its payload.def resolve_found_entry(found_maybe: Maybe<&2, Reading.ChainEntry>) -> Store.Op<&2, Maybe<&2, Value.Value>>: match found_maybe: case None{}: empty_value_maybe() case Some{found_entry}: resolve_entry_value(Reading.entry_value_of(found_entry))# Reads a namespaced value at a commit.def read_value_at(+namespace_name: String, +record_identifier: String, +commit_identifier: String) -> Store.Op<&2, Maybe<&2, Value.Value>>: do Store.Op<&2, Maybe<&2, Value.Value>>: ancestor_chain : List<&2, String> <- walk_commit_chain(10000n, commit_identifier) found_entry : Maybe<&2, Reading.ChainEntry> <- search_chain_entries(ancestor_chain, namespace_name, record_identifier) resolved_value : Maybe<&2, Value.Value> <- resolve_found_entry(found_entry) return resolved_value