macula_record (macula v15.4.0)
View SourceRecords in the signed-object format of DESIGN_PQ_SIGNED_FRAMES_AND_RECORDS.md.
A record is the signed object {key, tbs, signature} under the label MACULA-PQ-RECORD-V1. Its tbs holds type, alg, version, schema, lifetime_class, created_at, expires_at and payload, and subject on a domain type (tags 0x20 to 0xFF) or a built-in tag wire 1.0 does not define. A constructor returns an unsigned record. sign/2 takes the signer's key, refuses a key whose purpose does not fit the type, and adds key, key_id, alg, schema, lifetime_class, tbs and signature. verify/2,3 reads a record as its reader, in the design's order, and keeps its tbs bytes, so encode/1 sends them unchanged; custodian_verify/3 is a station's check before it stores a record, which stores types, schemas and content-id tags a reader does not know (wire 1.0, macula#100).
A record is named by the key id of its key: the node_id for node records, procedure advertisements, content announcements and station endpoints, and the MACULA-KEY-ID-V1 key id for realm, org and foundation records and for every domain type. A tombstone is named as the type it withdraws. A node record is stored under its node_id, and every other record under SHA-256 over MACULA-PQ-STORAGE-KEY-V1, a zero byte, the type and the type's fields.
A procedure advertisement carries its provider authorization inside its payload. verify/2,3 treats it as opaque, so a station that stores records never parses it; verify_authorization/3 is the caller's check.
Summary
Types
The realm trust a provider authorization is checked against: the caller's crypto profile, and either the carried realm key it pins for one realm, or the foundation realm trust list's pairs of realm id to realm key id, for the advertisement's realm.
One entry of a foundation realm trust list: a realm id paired with the realm key id that signs the realm's records (DESIGN_PQ_SIGNED_FRAMES_AND_RECORDS.md, Foundation realm trust list).
station_version names the release the station runs (its app vsn), text of 1 to ?MAX_STATION_VERSION_BYTES bytes, so a reader outside the fleet can tell it under the station's signature. It is self-attested: the signature says who claims it, not that the running code matches.
Functions
The storage key of an app record, from the parts of the app's MRI mri:app:<realm>/<org>/<app>: the realm id (macula_realm:id/1 of the realm name), the org name and the app name.
An org's statement of one of its apps (macula#75), signed by the org key OrgKeyId: the app's version and its services, each naming the procedures it serves, all in the org's own namespace. It carries the realm-signed org directory that ties the org name to OrgKeyId, so verify_app/3 checks it without a lookup, as an advertisement's authorization is checked. Stored under app_key/3 of the app's MRI parts; it lives at most 30 minutes, like the procedure delegations it is issued with.
How far a record's clocks may disagree with a verifier's: a record is accepted this long before its created_at and after its expires_at (D22).
A node's announcement, signed by the node, that it shares the content with this tag 2 content id, naming where it is served (D27): the realm, the station the node is reachable through, and the node's content procedure.
The storage key of a content id's announcements.
Apply a STORE_REFRESH to a record a custodian already holds (macula-station#107, D28). A refresh carries only the re-signed expiry delta the publisher produced with refresh/2,3 -- the new version, created_at, expires_at and signature -- and NO payload. The custodian reconstructs the signed tbs over the HELD payload with the delta's version and timestamps, exactly as sign/2 built it, and runs the delta's signature through the same custodian_verify/3 a full STORE gets. When it verifies, the returned record is byte-for-byte the one a full STORE of the publisher's refresh would have produced, so encode/1 of it sends the publisher's bytes unchanged.
Verify a record as its custodian, a station that stores records it need not understand (wire 1.0, macula#83 D4, WIRE_1_0.md 5.2 steps 1 to 9). In order, first failure wins: the wire form fits 256 KiB (record_too_large); the signed object decodes (malformed); its tbs decodes with a text alg (malformed), since alg selects the verifier; the alg is the verifier's own profile's (alg_unsupported); the carried key is in that profile's form (malformed); the signature verifies (signature_invalid); the tbs holds exactly its core fields, with subject and ext where present (malformed); the clock (not_yet_valid, expired); the lifetime (lifetime_reversed, then lifetime_class_unknown for a row outside the table, then lifetime_too_long against the row); a known type naming a row other than its own (lifetime_class_mismatch); the slot: a known type's slot fields present and typed (malformed), and a built-in tag it does not know carries a subject (subject_missing). The payload is read no further than the slot fields and a tombstone's withdrawn type, so a type, schema or content-id tag a reader does not know is stored. The record comes back with its key_id: for a built-in tag wire 1.0 does not define, as for a domain type, the MACULA-KEY-ID-V1 id of its key whatever the key's purpose, never a node_id. storage_key/1 places it.
Deep normalization of a payload that arrived in wire form, the exact inverse of macula_frame:to_wire/1 over the payload term: {text, K} map keys become binary keys, {text, V} values become their binaries, null becomes undefined, and maps and lists are walked recursively. Binaries and integers are already canonical and pass through. A term with no wire-form artifacts comes back equal to itself, so the call is idempotent on already-decoded payloads.
Check an unsigned domain record before a pool signs it as its node: a domain type, a payload map, a subject that is absent or a non-empty binary, a lifetime that runs forward and fits the type's maximum, never shortened, and a payload and subject of at most 256 KiB together that nest at most 63 levels. Returns the refusal by name.
Whether a record is of a domain type (tags 0x20 to 0xFF): its owner sets its payload rules, and its slot is its signer's key id, with its subject when it has one.
The wire form of a signed or verified record: its {key, tbs, signature} map, tbs unchanged.
An unsigned record of a domain type (tags 0x20 to 0xFF). The subject_id option names the record's subject, a non-empty binary: an empty subject would name a slot apart from no subject. The schema option names the version of the owner's payload rules, 1 unless given: a domain type's schema is its owner's to read.
A foundation parameter, signed by a foundation key.
A foundation's list of trusted realms: each entry pairs a realm id with the realm key id that signs the realm's records, signed by a foundation key. Its payload holds exactly realms_trusted (D28).
The storage key of a foundation's realm trust list, from the foundation key id. The station computes it to fetch the list without holding its record.
A foundation's seed list, signed by a foundation key.
A foundation's tier 3 attestation of a station, signed by a foundation key.
The frozen lifetime-class table of wire 1.0 (macula#100): a class names the longest a record lives, created_at to expires_at, in milliseconds. Every record names its class in its tbs, so a custodian bounds a type it does not know. The table only grows, and under an LTS line a class may tighten, never loosen.
The longest window a realm member endorsement may have, valid_from to valid_until, in milliseconds: 30 days. Its builder refuses a longer one, and macula_hyparview_endorsement:verify_endorsement/3 refuses one it receives.
The longest window a station authorization may have, valid_from to valid_until, in milliseconds: 30 minutes.
A node record about the node NodeId, which signs it.
Whether a node signs this record about itself: a node record, a procedure advertisement or a content announcement, whose payload names the signing node. A tombstone is not one: it withdraws a record, and whoever signs it checks that the record was theirs.
A realm's statement, signed by the realm key, that the org OrgName is held by the key with key id OrgKeyId.
The storage key of an org directory record, from the realm id and the org name.
Whether a procedure advertisement is in its advertiser's own namespace, and admissible there (D25 item 6, revised 2026-09-24): a procedure ~<node_id>/<name>, where <node_id> is the 64 lowercase hex characters of the advertisement's advertiser_node, carrying no authorization. Verifying the advertisement binds advertiser_node to its signer, so only that node can sign for its namespace. not_own_namespace for an org procedure, a procedure without a namespace or another node's namespace; malformed for a ~ namespace that is not 64 lowercase hex; authorization_not_allowed when one is attached. The one rule the SDK and the station's admissions share.
Check a payload before anything is encoded: its external size is at most 256 KiB, and it nests at most 63 levels of maps and lists, which a record's tbs leaves it under the decoder's 64. Returns record_too_large or malformed.
A payload field, read whatever key form it arrived in: {text, Name}, the binary, or an existing atom. A text value is returned as its binary.
A provider's advertisement of a procedure in a realm, signed by the provider, naming the 1 to 8 stations it serves the procedure through (macula-station#55, K > 1): one record per provider and procedure, so the slot holds the provider's whole set, sorted and without repeats. For a procedure with an org namespace the authorization option carries the provider authorization: org_directory and procedure_delegation as the records' wire form, the only authorization form. The builder refuses any other. The kem_key option names the provider's KEM key as carried (E2E design, amendment A1), which the advertisement carries with its kem_key_id: a caller seals its request to that key.
The longest a procedure advertisement lives, created_at to expires_at; every verifier refuses a longer one.
An org's grant, signed by its org key, that the node Advertiser may serve procedures under the org.
The storage key of a procedure delegation, from the org key id and the advertiser's node_id.
The storage key of a procedure's advertisements, from the realm id and the procedure name.
A procedure's org namespace: the text before the first "/" of its name, when there is one and it is not "_".
The trusted realms of a foundation realm trust list, as realm id to realm key id (D28).
A realm's directory record, signed by the realm key: its name and the key id of its admin key.
A realm's statement, signed by the realm key, that a node is a member with roles. Its window, valid_from to valid_until, is at most 30 days and never ends before it starts: a longer one raises a badmatch on {error, endorsement_window_too_long}, and a reversed one on {error, endorsement_window_reversed}.
The storage key of a realm member endorsement, from the realm id and the member's node_id: the slot that holds the realm's endorsement of that member and, once the realm revokes it, the realm's tombstone of it. A lookup of this key is what macula_hyparview_endorsement:slot_endorsement/3,4 reads.
The stations serving a realm, signed by the realm key.
The record with a new version, created now, with the same lifetime, signed again with Key.
The same, ending the record at NotAfter when that comes before its lifetime runs out: an ABSOLUTE time in milliseconds, to cap a record against something else, such as a delegation's own expiry. A bound at or before the clock this signs on returns {error, not_after_passed} and signs nothing.
Sign a record with a key whose purpose fits its type. Raises key_purpose_mismatch for a key of another purpose, key_id_mismatch when the payload names a signer other than this key, {malformed, Type} for a record whose fields, subject or payload verify/3 would refuse, and record_too_large past 256 KiB. The field and payload checks are verify/3's own, run before anything is signed, so sign/2 never returns a record verify/3 refuses apart from the clock.
signer_entry/4 at the current time.
The entry one signer holds among the entries a lookup of a slot returns, as wire forms or {key, tbs, signature} maps. Expected is {key_id, Id} for a key named by its MACULA-KEY-ID-V1 key id, or {node_id, Id} for an identity key. Every entry is read as far as its carried key, whatever the answer's order or length, and no signature is checked to select. Only the entries under the expected key are verified: highest claimed version first, stopping at the first that verifies and names Id as its key_id, and at most 4. So an entry under another key costs no verify, and forged entries under the expected key cost a bounded few. The outcome is that record, not_found when no entry is under the key, or the refusal of the last entry verified. The stats count the entries under the key, the entries verified, and the entries past the 80 a slot holds, which a station that keeps to its slot places never sends.
A realm's statement, signed by the realm key, that the node StationNode serves it as a station at Hostname, in Roles, valid over a window from valid_from to valid_until of at most 30 minutes (?STATION_AUTHORIZATION_MAX_WINDOW_MS): a longer one raises a badmatch on {error, station_authorization_window_too_long}, a reversed one on {error, station_authorization_window_reversed}.
The storage key of a station's authorization, from the realm id and the station's node_id: the slot that holds the realm's authorization of that station and, once the realm revokes it, the realm's tombstone of it.
A station's dialable endpoint, signed by the station and stored under its node_id.
The storage key of a station's endpoint record, from the station's node_id.
The 32-byte DHT storage key of a record. A record stored under its signer needs its key_id, so it must be signed or verified; a record named by its payload does not.
A tombstone that withdraws a record: it names the record's type, version and slot fields, takes the record's slot, and lives until the record has expired plus the clock tolerance, so no replica serves the record again after the tombstone lapses. Sign it with the key that signed the record.
The lifetime class a type's records name in their tbs: one for each 1.0 built-in type other than the tombstone (which names its withdrawn type's), the reserved successor tag and the domain types; none for any other tag, whose records are bounded by the class they name.
The org directory type tag, for a caller that must tell a directory from the tombstone that withdrew it before it reads the directory's org key.
The procedure advertisement type tag, for callers that look records up by type.
The type tag reserved for the successor (key-rotation) record (macula#83). No reader takes it yet.
Verify a record as its reader, given as its wire form or as its {key, tbs, signature} map, under the verifier's profile and clock. Refusals are returned, never raised. After every custodian_verify/3 check, a reader refuses, in order (WIRE_1_0.md 5.2, steps 10 to 12): a type it does not know (type_unknown), a built-in type's schema other than 1 (schema_unsupported), an ext entry with critical 1 (extension_unsupported; records define no extension yet, and a critical 0 one is ignored), a payload that breaks its type's rules (malformed), a content id with a tag other than 2 (mcid_tag_unknown), and a payload that names a signer other than the key (key_id_mismatch).
The caller's check of a verified app record against the realm trust it holds (macula#75), pure: the org directory the record carries verifies, is signed by the realm key (carried or named by the trust list's pairs, as for verify_authorization/3), names the record's realm and org, and holds the org key that signed the record; and the record expires no later than that directory. The shape rules (an org namespace, every procedure in it) are payload rules: verify/3 has already refused a record that breaks them.
The caller's check of a verified advertisement's provider authorization, against the realm trust it holds: the realm-signed org directory and the org-signed procedure delegation, the only authorization form. The realm key is the carried realm key the caller pins, or the realm key id the foundation realm trust list's pairs name for the advertisement's realm_id (D28). A procedure with an org namespace needs an authorization for that org, a procedure in its advertiser's own namespace (~<node_id>/<name>, own_namespace/1) carries none and needs no realm key, a procedure without a namespace carries none, and the advertisement expires no later than any part of its authorization. An authorization in any other form, a certificate chain included, is refused as authorization_form_unsupported: 11.0.0 has no certificate form.
The caller's check that a node is an admitted member of an org in a realm, pure: the same chain an advertisement carries (verify_authorization/3), asked about a node instead of an advertisement. The org directory verifies, is signed by the realm key the trust holds (carried or named by the trust list's pairs), and names the realm and the org; the delegation verifies, is signed by the org key that directory holds, and names the node. The answer is the earlier of the two expiries, after which the question must be asked again: that is how a realm's revocation, which stops reissuing the delegation and tombstones it, reaches a caller that caches the answer.
Check a record given as its wire form, or as a signed map, before it is decoded or encoded: a wire form is a binary of at most 256 KiB, and a signed map's key, tbs and signature are binaries of at most 256 KiB together. Returns record_too_large or malformed.
Types
The realm trust a provider authorization is checked against: the caller's crypto profile, and either the carried realm key it pins for one realm, or the foundation realm trust list's pairs of realm id to realm key id, for the advertisement's realm.
-type authorization_refusal() ::
malformed | no_authorization | authorization_not_allowed | authorization_form_unsupported |
no_realm_key | org_directory_invalid | org_directory_wrong_realm | org_directory_wrong_org |
delegation_invalid | delegation_mismatch | authorization_outlived | org_key_mismatch.
-type content_announcement_opts() :: #{realm_id := <<_:256>>, serving_station := <<_:256>>, procedure := binary(), name => binary(), size => non_neg_integer(), chunk_count => non_neg_integer(), ttl_ms => pos_integer()}.
-type foundation_parameter_opts() :: #{valid_from => pos_integer(), valid_until => pos_integer(), prior_version => version(), ttl_ms => pos_integer()}.
One entry of a foundation realm trust list: a realm id paired with the realm key id that signs the realm's records (DESIGN_PQ_SIGNED_FRAMES_AND_RECORDS.md, Foundation realm trust list).
-type foundation_realm_trust_list_entry() :: #{realm_id := <<_:256>>, realm_key_id := <<_:256>>}.
-type foundation_realm_trust_list_opts() :: #{ttl_ms => pos_integer()}.
-type foundation_seed() :: #{node_id := <<_:256>>, addresses := [map()], tier := 3 | 4}.
-type foundation_seed_list_opts() :: #{valid_from => pos_integer(), valid_until => pos_integer(), ttl_ms => pos_integer()}.
-type foundation_t3_attestation_opts() :: #{valid_until => pos_integer(), notes => binary(), ttl_ms => pos_integer()}.
-type m_record() :: #{type := type_tag(), version := version(), created_at := non_neg_integer(), expires_at := non_neg_integer(), payload := map(), subject => binary(), schema => pos_integer(), lifetime_class => non_neg_integer(), key => binary(), key_id => <<_:256>>, alg => binary(), tbs => binary(), signature => binary()}.
-type node_record_opts() :: #{station_id => <<_:256>>, caps_hint => binary(), display_name => binary(), ttl_ms => pos_integer(), hostname => binary(), endpoint => binary(), city => binary(), country => binary(), lat => float() | integer(), lng => float() | integer(), kind => binary(), peers => [<<_:256>>]}.
-type procedure_advertisement_opts() :: #{authorization => map(), ttl_ms => pos_integer(), kem_key => binary()}.
-type realm_directory_opts() :: #{policy_url => binary(), ttl_ms => pos_integer()}.
-type realm_member_endorsement_opts() :: #{valid_from => pos_integer(), valid_until => pos_integer(), ttl_ms => pos_integer()}.
-type realm_station_entry() :: #{station_id := <<_:256>>, roles := [binary()]}.
-type realm_stations_opts() :: #{ttl_ms => pos_integer()}.
-type reason() :: shutdown | moved | revoked.
-type refresh_delta() :: #{version := version(), created_at := non_neg_integer(), expires_at := non_neg_integer(), signature := binary()}.
-type refusal() ::
record_too_large | malformed | signature_invalid | not_yet_valid | expired | key_id_mismatch |
lifetime_too_long | lifetime_reversed | lifetime_class_unknown | lifetime_class_mismatch |
type_unknown | schema_unsupported | mcid_tag_unknown | alg_unsupported | subject_missing |
extension_unsupported.
-type signer_entry_stats() :: #{matching := non_neg_integer(), verified := non_neg_integer(), beyond_capacity := non_neg_integer()}.
-type station_authorization_opts() :: #{valid_from => pos_integer(), valid_until => pos_integer(), ttl_ms => pos_integer(), attestation_key_id => <<_:256>>, attestation_required => 1}.
-type station_endpoint_opts() :: #{host_advertised => [binary()], alpn => binary(), ttl_ms => pos_integer(), station_version => binary()}.
-type tombstone_opts() :: #{detail => binary(), ttl_ms => pos_integer()}.
station_version names the release the station runs (its app vsn), text of 1 to ?MAX_STATION_VERSION_BYTES bytes, so a reader outside the fleet can tell it under the station's signature. It is self-attested: the signature says who claims it, not that the running code matches.
-type trust() :: #{profile := macula_crypto_profile:profile(), realm_key => binary(), realm_pairs => #{<<_:256>> => <<_:256>>}}.
-type type_tag() :: 1..255.
-type version() :: <<_:128>>.
Functions
The storage key of an app record, from the parts of the app's MRI mri:app:<realm>/<org>/<app>: the realm id (macula_realm:id/1 of the realm name), the org name and the app name.
-spec app_record(<<_:256>>, <<_:256>>, binary(), binary(), #{version := binary(), services := [#{name := binary(), procedures := [binary()]}], org_directory := binary(), ttl_ms => pos_integer()}) -> m_record().
An org's statement of one of its apps (macula#75), signed by the org key OrgKeyId: the app's version and its services, each naming the procedures it serves, all in the org's own namespace. It carries the realm-signed org directory that ties the org name to OrgKeyId, so verify_app/3 checks it without a lookup, as an advertisement's authorization is checked. Stored under app_key/3 of the app's MRI parts; it lives at most 30 minutes, like the procedure delegations it is issued with.
-spec clock_tolerance_ms() -> pos_integer().
How far a record's clocks may disagree with a verifier's: a record is accepted this long before its created_at and after its expires_at (D22).
-spec content_announcement(<<_:256>>, <<_:400>>, content_announcement_opts()) -> m_record().
A node's announcement, signed by the node, that it shares the content with this tag 2 content id, naming where it is served (D27): the realm, the station the node is reachable through, and the node's content procedure.
-spec content_key(<<_:400>>) -> <<_:256>>.
The storage key of a content id's announcements.
-spec created_at(m_record()) -> non_neg_integer().
-spec custodian_refresh(m_record() | binary(), refresh_delta(), macula_crypto_profile:profile(), integer()) -> {ok, m_record()} | need_full.
Apply a STORE_REFRESH to a record a custodian already holds (macula-station#107, D28). A refresh carries only the re-signed expiry delta the publisher produced with refresh/2,3 -- the new version, created_at, expires_at and signature -- and NO payload. The custodian reconstructs the signed tbs over the HELD payload with the delta's version and timestamps, exactly as sign/2 built it, and runs the delta's signature through the same custodian_verify/3 a full STORE gets. When it verifies, the returned record is byte-for-byte the one a full STORE of the publisher's refresh would have produced, so encode/1 of it sends the publisher's bytes unchanged.
ANY failure returns need_full, never a partial or stale record: a reconstructed tbs that does not verify (the held payload is not the one the delta was signed over), a signature over timestamps outside the type's lifetime class, an expired or not-yet-valid clock, or a created_at/expires_at outside the range a record may carry (a wire integer reaches 2^64-1, a record timestamp is bounded by ?MAX_PROTOCOL_INT, so a decoded frame cannot crash the custodian). The sender then sends a full STORE. Version monotonicity against the held entry is the slot's concern (macula_dht_slots:later/2), applied by the caller as for a full STORE; this function reconstructs and verifies, it does not place. Held is a verified record the caller looked up by {key, signer}; when the caller holds no base entry it answers need_full without calling this.
Held may also be the record's WIRE BYTES, the form a custodian holds in its slot. They are decoded without a signature step -- they were verified at admission, and the delta's signature is re-verified over the tbs reconstructed from them, so the forgery bar is a full STORE's and a wrong held record only answers need_full. This is the only verify-free decode of a record and it never leaves this function: a caller cannot use it to obtain an unverified m_record(). Held bytes that do not decode to a readable record answer need_full and never raise.
-spec custodian_verify(binary() | map(), macula_crypto_profile:profile(), integer()) -> {ok, m_record()} | {error, refusal()}.
Verify a record as its custodian, a station that stores records it need not understand (wire 1.0, macula#83 D4, WIRE_1_0.md 5.2 steps 1 to 9). In order, first failure wins: the wire form fits 256 KiB (record_too_large); the signed object decodes (malformed); its tbs decodes with a text alg (malformed), since alg selects the verifier; the alg is the verifier's own profile's (alg_unsupported); the carried key is in that profile's form (malformed); the signature verifies (signature_invalid); the tbs holds exactly its core fields, with subject and ext where present (malformed); the clock (not_yet_valid, expired); the lifetime (lifetime_reversed, then lifetime_class_unknown for a row outside the table, then lifetime_too_long against the row); a known type naming a row other than its own (lifetime_class_mismatch); the slot: a known type's slot fields present and typed (malformed), and a built-in tag it does not know carries a subject (subject_missing). The payload is read no further than the slot fields and a tombstone's withdrawn type, so a type, schema or content-id tag a reader does not know is stored. The record comes back with its key_id: for a built-in tag wire 1.0 does not define, as for a domain type, the MACULA-KEY-ID-V1 id of its key whatever the key's purpose, never a node_id. storage_key/1 places it.
Deep normalization of a payload that arrived in wire form, the exact inverse of macula_frame:to_wire/1 over the payload term: {text, K} map keys become binary keys, {text, V} values become their binaries, null becomes undefined, and maps and lists are walked recursively. Binaries and integers are already canonical and pass through. A term with no wire-form artifacts comes back equal to itself, so the call is idempotent on already-decoded payloads.
Handlers receive call args and pubsub facts in wire form (macula_subscriber delivers what the frame carried), and consumers used to hand-roll this unwrapping — badly: mcl-sec-guard matched atom keys on the wire form and silently discarded every fact (macula-services/mcl-sec-guard#2). One shared, tested inverse beats per-consumer copies that each get it slightly wrong.
-spec domain_record_checked(term()) -> ok | {error, not_a_domain_type | invalid_subject | lifetime_too_long | lifetime_reversed | record_too_large | malformed}.
Check an unsigned domain record before a pool signs it as its node: a domain type, a payload map, a subject that is absent or a non-empty binary, a lifetime that runs forward and fits the type's maximum, never shortened, and a payload and subject of at most 256 KiB together that nest at most 63 levels. Returns the refusal by name.
Whether a record is of a domain type (tags 0x20 to 0xFF): its owner sets its payload rules, and its slot is its signer's key id, with its subject when it has one.
The wire form of a signed or verified record: its {key, tbs, signature} map, tbs unchanged.
An unsigned record of a domain type (tags 0x20 to 0xFF). The subject_id option names the record's subject, a non-empty binary: an empty subject would name a slot apart from no subject. The schema option names the version of the owner's payload rules, 1 unless given: a domain type's schema is its owner's to read.
-spec expires_at(m_record()) -> non_neg_integer().
-spec foundation_parameter(binary(), foundation_parameter_value()) -> m_record().
A foundation parameter, signed by a foundation key.
-spec foundation_parameter(binary(), foundation_parameter_value(), foundation_parameter_opts()) -> m_record().
-spec foundation_realm_trust_list([foundation_realm_trust_list_entry()]) -> m_record().
A foundation's list of trusted realms: each entry pairs a realm id with the realm key id that signs the realm's records, signed by a foundation key. Its payload holds exactly realms_trusted (D28).
-spec foundation_realm_trust_list([foundation_realm_trust_list_entry()], foundation_realm_trust_list_opts()) -> m_record().
-spec foundation_realm_trust_list_key(<<_:256>>) -> <<_:256>>.
The storage key of a foundation's realm trust list, from the foundation key id. The station computes it to fetch the list without holding its record.
-spec foundation_seed_list([foundation_seed()]) -> m_record().
A foundation's seed list, signed by a foundation key.
-spec foundation_seed_list([foundation_seed()], foundation_seed_list_opts()) -> m_record().
-spec foundation_t3_attestation(<<_:256>>, pos_integer()) -> m_record().
A foundation's tier 3 attestation of a station, signed by a foundation key.
-spec foundation_t3_attestation(<<_:256>>, pos_integer(), foundation_t3_attestation_opts()) -> m_record().
-spec key_id(m_record()) -> <<_:256>>.
-spec lifetime_classes() -> #{non_neg_integer() => pos_integer()}.
The frozen lifetime-class table of wire 1.0 (macula#100): a class names the longest a record lives, created_at to expires_at, in milliseconds. Every record names its class in its tbs, so a custodian bounds a type it does not know. The table only grows, and under an LTS line a class may tighten, never loosen.
-spec max_endorsement_window_ms() -> pos_integer().
The longest window a realm member endorsement may have, valid_from to valid_until, in milliseconds: 30 days. Its builder refuses a longer one, and macula_hyparview_endorsement:verify_endorsement/3 refuses one it receives.
-spec max_station_authorization_window_ms() -> pos_integer().
The longest window a station authorization may have, valid_from to valid_until, in milliseconds: 30 minutes.
-spec node_record(<<_:256>>, [<<_:256>>], non_neg_integer()) -> m_record().
A node record about the node NodeId, which signs it.
-spec node_record(<<_:256>>, [<<_:256>>], non_neg_integer(), node_record_opts()) -> m_record().
Whether a node signs this record about itself: a node record, a procedure advertisement or a content announcement, whose payload names the signing node. A tombstone is not one: it withdraws a record, and whoever signs it checks that the record was theirs.
A realm's statement, signed by the realm key, that the org OrgName is held by the key with key id OrgKeyId.
-spec org_directory_key(<<_:256>>, binary()) -> <<_:256>>.
The storage key of an org directory record, from the realm id and the org name.
-spec own_namespace(m_record()) -> ok | {error, not_own_namespace | malformed | authorization_not_allowed}.
Whether a procedure advertisement is in its advertiser's own namespace, and admissible there (D25 item 6, revised 2026-09-24): a procedure ~<node_id>/<name>, where <node_id> is the 64 lowercase hex characters of the advertisement's advertiser_node, carrying no authorization. Verifying the advertisement binds advertiser_node to its signer, so only that node can sign for its namespace. not_own_namespace for an org procedure, a procedure without a namespace or another node's namespace; malformed for a ~ namespace that is not 64 lowercase hex; authorization_not_allowed when one is attached. The one rule the SDK and the station's admissions share.
-spec payload_bounded(term()) -> ok | {error, record_too_large | malformed}.
Check a payload before anything is encoded: its external size is at most 256 KiB, and it nests at most 63 levels of maps and lists, which a record's tbs leaves it under the decoder's 64. Returns record_too_large or malformed.
A payload field, read whatever key form it arrived in: {text, Name}, the binary, or an existing atom. A text value is returned as its binary.
A provider's advertisement of a procedure in a realm, signed by the provider, naming the 1 to 8 stations it serves the procedure through (macula-station#55, K > 1): one record per provider and procedure, so the slot holds the provider's whole set, sorted and without repeats. For a procedure with an org namespace the authorization option carries the provider authorization: org_directory and procedure_delegation as the records' wire form, the only authorization form. The builder refuses any other. The kem_key option names the provider's KEM key as carried (E2E design, amendment A1), which the advertisement carries with its kem_key_id: a caller seals its request to that key.
-spec procedure_advertisement(<<_:256>>, <<_:256>>, binary(), [<<_:256>>, ...], procedure_advertisement_opts()) -> m_record().
-spec procedure_advertisement_max_lifetime_ms() -> pos_integer().
The longest a procedure advertisement lives, created_at to expires_at; every verifier refuses a longer one.
-spec procedure_delegation(<<_:256>>, <<_:256>>) -> m_record().
An org's grant, signed by its org key, that the node Advertiser may serve procedures under the org.
-spec procedure_delegation_key(<<_:256>>, <<_:256>>) -> <<_:256>>.
The storage key of a procedure delegation, from the org key id and the advertiser's node_id.
-spec procedure_key(<<_:256>>, binary()) -> <<_:256>>.
The storage key of a procedure's advertisements, from the realm id and the procedure name.
A procedure's org namespace: the text before the first "/" of its name, when there is one and it is not "_".
-spec read_foundation_realm_trust_list(m_record()) -> #{<<_:256>> => <<_:256>>}.
The trusted realms of a foundation realm trust list, as realm id to realm key id (D28).
-spec read_procedure_delegation(m_record()) -> #{org_key := <<_:256>>, advertiser := <<_:256>>}.
-spec read_station_authorization(m_record()) -> #{realm_id := <<_:256>>, station_node := <<_:256>>, hostname := binary(), roles := [binary()], valid_from := non_neg_integer(), valid_until := non_neg_integer(), attestation_key_id => <<_:256>>, attestation_required => 1}.
A realm's directory record, signed by the realm key: its name and the key id of its admin key.
-spec realm_directory(<<_:256>>, binary(), <<_:256>>, realm_directory_opts()) -> m_record().
-spec realm_member_endorsement(<<_:256>>, #{realm := <<_:256>>, member_node := <<_:256>>, roles := [binary()]}) -> m_record().
A realm's statement, signed by the realm key, that a node is a member with roles. Its window, valid_from to valid_until, is at most 30 days and never ends before it starts: a longer one raises a badmatch on {error, endorsement_window_too_long}, and a reversed one on {error, endorsement_window_reversed}.
-spec realm_member_endorsement(<<_:256>>, #{realm := <<_:256>>, member_node := <<_:256>>, roles := [binary()]}, realm_member_endorsement_opts()) -> m_record().
-spec realm_member_endorsement_key(<<_:256>>, <<_:256>>) -> <<_:256>>.
The storage key of a realm member endorsement, from the realm id and the member's node_id: the slot that holds the realm's endorsement of that member and, once the realm revokes it, the realm's tombstone of it. A lookup of this key is what macula_hyparview_endorsement:slot_endorsement/3,4 reads.
-spec realm_stations(<<_:256>>, [realm_station_entry()]) -> m_record().
The stations serving a realm, signed by the realm key.
-spec realm_stations(<<_:256>>, [realm_station_entry()], realm_stations_opts()) -> m_record().
-spec refresh(m_record(), macula_node_keys:node_key()) -> m_record().
The record with a new version, created now, with the same lifetime, signed again with Key.
-spec refresh(m_record(), macula_node_keys:node_key(), non_neg_integer()) -> {ok, m_record()} | {error, not_after_passed}.
The same, ending the record at NotAfter when that comes before its lifetime runs out: an ABSOLUTE time in milliseconds, to cap a record against something else, such as a delegation's own expiry. A bound at or before the clock this signs on returns {error, not_after_passed} and signs nothing.
⚠ ONE CLOCK READ DECIDES BOTH ENDS, and that is the point. Writing the bound into a record and refreshing it afterwards does not work: refresh keeps a record's LIFETIME, so a bound written as expires_at is re-anchored to the later clock read and the record ends at the bound plus its own age at signing time. That was the defect here until 2026-09-22, and the gap it opened is not bounded by anything: it is however long the record sat unsigned.
-spec sign(m_record(), macula_node_keys:node_key()) -> m_record().
Sign a record with a key whose purpose fits its type. Raises key_purpose_mismatch for a key of another purpose, key_id_mismatch when the payload names a signer other than this key, {malformed, Type} for a record whose fields, subject or payload verify/3 would refuse, and record_too_large past 256 KiB. The field and payload checks are verify/3's own, run before anything is signed, so sign/2 never returns a record verify/3 refuses apart from the clock.
-spec signer_entry([binary() | map()], {key_id | node_id, <<_:256>>}, macula_crypto_profile:profile()) -> {{ok, m_record()} | {error, not_found | refusal()}, signer_entry_stats()}.
signer_entry/4 at the current time.
-spec signer_entry([binary() | map()], {key_id | node_id, <<_:256>>}, macula_crypto_profile:profile(), integer()) -> {{ok, m_record()} | {error, not_found | refusal()}, signer_entry_stats()}.
The entry one signer holds among the entries a lookup of a slot returns, as wire forms or {key, tbs, signature} maps. Expected is {key_id, Id} for a key named by its MACULA-KEY-ID-V1 key id, or {node_id, Id} for an identity key. Every entry is read as far as its carried key, whatever the answer's order or length, and no signature is checked to select. Only the entries under the expected key are verified: highest claimed version first, stopping at the first that verifies and names Id as its key_id, and at most 4. So an entry under another key costs no verify, and forged entries under the expected key cost a bounded few. The outcome is that record, not_found when no entry is under the key, or the refusal of the last entry verified. The stats count the entries under the key, the entries verified, and the entries past the 80 a slot holds, which a station that keeps to its slot places never sends.
-spec station_authorization(<<_:256>>, #{realm := <<_:256>>, station_node := <<_:256>>, hostname := binary(), roles := [binary()]}) -> m_record().
A realm's statement, signed by the realm key, that the node StationNode serves it as a station at Hostname, in Roles, valid over a window from valid_from to valid_until of at most 30 minutes (?STATION_AUTHORIZATION_MAX_WINDOW_MS): a longer one raises a badmatch on {error, station_authorization_window_too_long}, a reversed one on {error, station_authorization_window_reversed}.
attestation_key_id, when carried, is the full 32-byte SHA-256 of the station's hardware attestation public key (never a prefix of it), and attestation_required = 1 says the realm requires a live attestation for this node. The co-presence bound holds at the builder and at payload_ok/2 alike: a record that demands an attestation but names no key is refused; a key id without the flag is a legitimate optional attestation. The attestation statement and its verification belong to macula-station#62; this record only carries the pair.
-spec station_authorization(<<_:256>>, #{realm := <<_:256>>, station_node := <<_:256>>, hostname := binary(), roles := [binary()]}, station_authorization_opts()) -> m_record().
-spec station_authorization_key(<<_:256>>, <<_:256>>) -> <<_:256>>.
The storage key of a station's authorization, from the realm id and the station's node_id: the slot that holds the realm's authorization of that station and, once the realm revokes it, the realm's tombstone of it.
-spec station_endpoint(1..65535) -> m_record().
A station's dialable endpoint, signed by the station and stored under its node_id.
-spec station_endpoint(1..65535, station_endpoint_opts()) -> m_record().
-spec station_endpoint_key(<<_:256>>) -> <<_:256>>.
The storage key of a station's endpoint record, from the station's node_id.
-spec storage_key(m_record()) -> <<_:256>>.
The 32-byte DHT storage key of a record. A record stored under its signer needs its key_id, so it must be signed or verified; a record named by its payload does not.
A tombstone that withdraws a record: it names the record's type, version and slot fields, takes the record's slot, and lives until the record has expired plus the clock tolerance, so no replica serves the record again after the tombstone lapses. Sign it with the key that signed the record.
-spec tombstone(m_record(), reason(), tombstone_opts()) -> m_record().
-spec type_lifetime_class(type_tag()) -> non_neg_integer() | none.
The lifetime class a type's records name in their tbs: one for each 1.0 built-in type other than the tombstone (which names its withdrawn type's), the reserved successor tag and the domain types; none for any other tag, whose records are bounded by the class they name.
-spec type_org_directory() -> type_tag().
The org directory type tag, for a caller that must tell a directory from the tombstone that withdrew it before it reads the directory's org key.
-spec type_procedure_advertisement() -> type_tag().
The procedure advertisement type tag, for callers that look records up by type.
-spec type_successor() -> type_tag().
The type tag reserved for the successor (key-rotation) record (macula#83). No reader takes it yet.
-spec verify(binary() | map(), macula_crypto_profile:profile()) -> {ok, m_record()} | {error, refusal()}.
Verify a record as its reader, given as its wire form or as its {key, tbs, signature} map, under the verifier's profile and clock. Refusals are returned, never raised. After every custodian_verify/3 check, a reader refuses, in order (WIRE_1_0.md 5.2, steps 10 to 12): a type it does not know (type_unknown), a built-in type's schema other than 1 (schema_unsupported), an ext entry with critical 1 (extension_unsupported; records define no extension yet, and a critical 0 one is ignored), a payload that breaks its type's rules (malformed), a content id with a tag other than 2 (mcid_tag_unknown), and a payload that names a signer other than the key (key_id_mismatch).
-spec verify_app(m_record(), trust(), integer()) -> ok | {error, authorization_refusal()}.
The caller's check of a verified app record against the realm trust it holds (macula#75), pure: the org directory the record carries verifies, is signed by the realm key (carried or named by the trust list's pairs, as for verify_authorization/3), names the record's realm and org, and holds the org key that signed the record; and the record expires no later than that directory. The shape rules (an org namespace, every procedure in it) are payload rules: verify/3 has already refused a record that breaks them.
-spec verify_authorization(m_record(), trust(), integer()) -> ok | {error, authorization_refusal()}.
The caller's check of a verified advertisement's provider authorization, against the realm trust it holds: the realm-signed org directory and the org-signed procedure delegation, the only authorization form. The realm key is the carried realm key the caller pins, or the realm key id the foundation realm trust list's pairs name for the advertisement's realm_id (D28). A procedure with an org namespace needs an authorization for that org, a procedure in its advertiser's own namespace (~<node_id>/<name>, own_namespace/1) carries none and needs no realm key, a procedure without a namespace carries none, and the advertisement expires no later than any part of its authorization. An authorization in any other form, a certificate chain included, is refused as authorization_form_unsupported: 11.0.0 has no certificate form.
-spec verify_membership(binary(), binary(), #{realm_id := <<_:256>>, org := binary(), node_id := <<_:256>>}, trust(), integer()) -> {ok, non_neg_integer()} | {error, authorization_refusal()}.
The caller's check that a node is an admitted member of an org in a realm, pure: the same chain an advertisement carries (verify_authorization/3), asked about a node instead of an advertisement. The org directory verifies, is signed by the realm key the trust holds (carried or named by the trust list's pairs), and names the realm and the org; the delegation verifies, is signed by the org key that directory holds, and names the node. The answer is the earlier of the two expiries, after which the question must be asked again: that is how a realm's revocation, which stops reissuing the delegation and tombstones it, reaches a caller that caches the answer.
It exists for a subscriber: a delivery's publisher is the node that signed the publication, and nothing in the wire says that node may speak for an org, so a service that must hear only an org's own nodes asks this about it.
-spec wire_bounded(term()) -> ok | {error, record_too_large | malformed}.
Check a record given as its wire form, or as a signed map, before it is decoded or encoded: a wire form is a binary of at most 256 KiB, and a signed map's key, tbs and signature are binaries of at most 256 KiB together. Returns record_too_large or malformed.