-module(bootstrap). -export([read_genesis/0, read_genesis/1, read_section/2, sections/0, section_subdir/1, default_base/0, ends_with_sx/1, build_genesis/1, verify_genesis/2, cidhash_path/1, write_cidhash/2, read_cidhash/1, load_genesis/1, strip_sx_suffix/1]). %% Genesis bundle reader per design §12.2. %% %% read_genesis/0,1 walks the seven canonical section subdirectories %% under `next/genesis/`, filters .sx files, reads each file into a %% binary, and returns a structured snapshot: %% %% {ok, [{Section :: atom, %% [{FileName :: binary, FileBytes :: binary}, ...]}, %% ...]} %% %% Step 4d will compute the bundle CID by hashing the assembled %% byte string across all entries; Step 4e will register the parsed %% definitions in the kernel registry. %% %% Port note: this module does NOT parse the .sx contents. The %% Erlang-on-SX port has no in-Erlang path from binary bytes to SX %% structured terms (same substrate gap that parked Step 3b); the %% bundle CID needs only the raw bytes, and registry registration %% will happen via an SX-side helper that the kernel hands the %% binary contents to. read_genesis/1 ignores its arg in v1 except %% to swap the BasePath — `default_base/0` is "next/genesis". %% %% Port note 2: string-literal binary segments `<<"abc">>` truncate %% to one byte in this port, so all path constants are hand-spelled %% as integer-segment binaries. %% ── Public API ────────────────────────────────────────────────── %% "next/genesis" default_base() -> <<110,101,120,116,47,103,101,110,101,115,105,115>>. read_genesis() -> read_genesis(default_base()). read_genesis(BasePath) -> {ok, lists:map( fun (S) -> {S, read_section(BasePath, S)} end, sections())}. sections() -> [activity_types, object_types, projections, validators, codecs, sig_suites, audience]. %% "activity-types" section_subdir(activity_types) -> <<97,99,116,105,118,105,116,121,45,116,121,112,101,115>>; %% "object-types" section_subdir(object_types) -> <<111,98,106,101,99,116,45,116,121,112,101,115>>; %% "projections" section_subdir(projections) -> <<112,114,111,106,101,99,116,105,111,110,115>>; %% "validators" section_subdir(validators) -> <<118,97,108,105,100,97,116,111,114,115>>; %% "codecs" section_subdir(codecs) -> <<99,111,100,101,99,115>>; %% "sig-suites" section_subdir(sig_suites) -> <<115,105,103,45,115,117,105,116,101,115>>; %% "audience" section_subdir(audience) -> <<97,117,100,105,101,110,99,101>>. read_section(BasePath, Section) -> SubDir = section_subdir(Section), %% 47 = '/' Path = <>, case file:list_dir(Path) of {ok, Names} -> SxNames = lists:filter(fun (N) -> ends_with_sx(N) end, Names), lists:map(fun (Name) -> read_one(Path, Name) end, SxNames); {error, _} -> [] end. %% Suffix check on the .sx extension. 46='.' 115='s' 120='x'. ends_with_sx(<<46, 115, 120>>) -> true; ends_with_sx(<<>>) -> false; ends_with_sx(<<_, Rest/binary>>) -> ends_with_sx(Rest). %% ── Internal ──────────────────────────────────────────────────── read_one(DirPath, Name) -> Full = <>, case file:read_file(Full) of {ok, Bytes} -> {Name, Bytes}; {error, R} -> {Name, {error, R}} end. %% ── Step 4d: bundle CID compute + verify ──────────────────────── %% %% The bundle CID is the canonical content-address of everything in %% read_genesis/0's result. We delegate to the host `cid:to_string/1` %% BIF (Step 1b substrate): it walks the term via `er-format-value`, %% feeds the deterministic textual form into `cid-from-sx`, returns %% a CIDv1 (raw codec, sha2-256 multihash) as a binary. %% %% Design §12.3: at startup the kernel computes this CID and %% compares against a hardcoded value (here: a sibling `.cidhash` %% file). A mismatch is a hard refuse-to-start. build_genesis(ReadResult) -> case ReadResult of {ok, Sections} -> Cid = cid:to_string({genesis_bundle, Sections}), {ok, [{cid, Cid}, {sections, Sections}]}; Other -> {error, {bad_read_result, Other}} end. verify_genesis(ReadResult, ExpectedCid) -> case build_genesis(ReadResult) of {ok, [{cid, Cid}, _]} -> case Cid =:= ExpectedCid of true -> ok; false -> {error, {cid_mismatch, Cid, ExpectedCid}} end; Err -> Err end. %% Sibling-file CID storage. "/.cidhash" appended to BasePath as %% an integer-segment binary (string-literal segments are broken). %% "/.cidhash" — 47='/' 46='.' c i d h a s h cidhash_path(BasePath) -> <>. write_cidhash(BasePath, Cid) -> file:write_file(cidhash_path(BasePath), Cid). read_cidhash(BasePath) -> file:read_file(cidhash_path(BasePath)). %% ── Step 4e: load_genesis → registry ──────────────────────────── %% %% Walks the read_genesis result and registers each file as a %% registry entry. The section atom is the registry kind directly %% (both name spaces are identical — see Step 4c sections/0 and %% Step 5a registry:kinds/0). The entry Name is the filename minus %% the `.sx` suffix, kept as a binary; the entry value is the %% file's raw bytes. %% %% Returns `{ok, RegistryState}` on success. Later steps (4f / the %% SX-parser bridge) will replace the raw bytes with parsed forms; %% the binary stand-in is enough to prove the bridge works. load_genesis(ReadResult) -> case ReadResult of {ok, Sections} -> {ok, load_sections(Sections, registry:new())}; Other -> {error, {bad_read_result, Other}} end. load_sections([], State) -> State; load_sections([{Kind, Entries} | Rest], State) -> load_sections(Rest, load_entries(Kind, Entries, State)). load_entries(_Kind, [], State) -> State; load_entries(Kind, [{Name, Bytes} | Rest], State) -> BaseName = strip_sx_suffix(Name), {ok, NewState} = registry:register(Kind, BaseName, Bytes, State), load_entries(Kind, Rest, NewState). %% strip_sx_suffix(Binary) — drops the trailing ".sx" if present. %% 46='.' 115='s' 120='x'. strip_sx_suffix(B) when is_binary(B) -> case ends_with_sx(B) of false -> B; true -> take_prefix(B, byte_size(B) - 3) end. take_prefix(_, 0) -> <<>>; take_prefix(<>, N) when N > 0 -> Tail = take_prefix(Rest, N - 1), <>.