BRC-514 Companion — MAP State Resolution

Companion to scripts/0514.md (branch draft/brc-514-map-state) · mechanism diagrams, deployed-code evidence, open items · drafted 2026-08-27 · published 2026-08-27 13:29 UTC
companion document fold checked against the deployed resolver 1 of 6 commands deployed 4 questions for review

What this is. A companion to BRC-514 holding two things: diagrams of the fold the spec defines in prose, and the evidence line between what is deployed in 1sat-stack and what this document specifies for the first time. The spec says what resolution is; this shows how it runs, and how much of it already does.

Section numbers in the form §5.6 refer to BRC-514. Other BRCs are cited by number. BRC-515 is the first consumer; its required follow-up changes are listed at the end.

Unlike the BRC-515 companion, this page accompanies a fresh draft: the open questions are decisions a reviewer still has to ratify, each with a recommendation.

The fold

Resolution is a fold: start from the empty state at the origin, apply every revision in order, read the result. A record's revisions are the links of its 1Sat chain, so the sequence is structural — the spend graph fixes it, and a reorg cannot reorder it. “The state in effect at the tip” means nothing more than the fold over all revisions; the state at any earlier point is the fold cut short (§7).

This is §5.7's example, traced. Four revisions exercise five of the six commands — SELECT appears as context inside revision 2. The one not exercised is REMOVE.

voids rev 2's writes rev 0 — origin SET name Genesis ::: ADD tags a b rev 1 ADD tags b c rev 2 SELECT tx0 DELETE tags a ::: SET title Renamed rev 3 — tip CLEAR tx2 spend spend spend fold fold fold fold state after rev 0 name = [Genesis] tags = [a, b] state after rev 1 name = [Genesis] tags = [a, b, c] b already present — not re-appended (§5.3) state after rev 2 name = [Genesis] tags = [b, c] title = [Renamed] resolved state at the tip name = [Genesis] tags = [a, b, c] revisions 0 and 1 stand; rev 2 folds as a no-op The state at revision n is the fold of revisions 0 through n. Sequence-addressed history (§7) is the same fold, cut at n — ?seq=2 on this record returns the third column, exactly.
§5.7 as a trace. Revision 1 shows the dedup rule: ADD tags b c appends only c, because value equality is byte equality and b is already a member. Revision 3's CLEAR reaches backward — the dashed arrow — and is the reason the tip state matches the state after revision 1, with title gone and a restored. That backward reach is drawn out in CLEAR re-folds.
Why the substrate matters. The fold needs a total order, and the 1Sat chain gives one for free: each transfer spends the last, so position in the chain is position in the sequence. Nothing depends on timestamps or confirmation order. A system authorizing writes another way (an identity signature, say) MUST supply its own total order and document it (§3.2) — the fold on top is unchanged.

Four orderings, nested

Every revision applies in exactly one place in a total order, fixed at four nesting levels — chain, script, instruction set, push. The two inner levels are where independent implementations diverge today, because nothing wrote them down: the deployed parser reads only the first command of a segment, so a ::: in the wild is silently swallowed into the previous command's pushes.

1 · CHAIN ORDER — §3.1 rev 0 · origin rev 1 rev 2 rev 3 tip 2 · SCRIPT ORDER WITHIN THE LINK'S OUTPUT — §3.3 (1) lock OP_RETURN B (content) | MAP segment A | MAP segment B Segment A folds before segment B. MAP anywhere else in the transaction is not part of this record (§3.1). 3 · INSTRUCTION-SET ORDER WITHIN A SEGMENT — §3.3 (2) MAP prefix instruction set 1 ::: instruction set 2 First to last. One command each. An unparseable set is ignored as a unit; its neighbours still apply (§6). 4 · PUSH ORDER WITHIN A COMMAND — §3.3 (3) SET key₁ value₁ key₂ value₂ Pairs apply in order; a key repeated within one command keeps its last pair (§3.4). A trailing key with no value is dropped, complete pairs kept (§6). Levels 3 and 4 are where implementations diverge today: the deployed parser has no ::: handling at all.
The full ordering, outermost first. A resolver holds all four at once: to place one key/value pair in the fold it must know its revision's chain position, its segment's script position, its instruction set's ::: position, and its own push position.

What counts as a revision — and what does not

A revision is the MAP data on the chain link's own output. Everything else is excluded, and the two exclusions are load-bearing: they are what keep SELECT and CLEAR — commands that name arbitrary txids — from becoming write access to other people's records.

SELECT tx0 — an earlier revision of the same record: folds (§5.1) rev 0 · tx0 the record's origin rev 1 · tx1 rev 2 · tx2 link output only unrelated transaction SELECT tx1 SET name Stolen not in the chain — MUST NOT fold (§2, §5.1) tx2, another output data carrier · SELECT tx0 … same transaction as rev 2 not the link's own output — not a revision (§3.1)
The three SELECT cases. Omitted: the instruction set addresses its own record, because the carrying transaction is a revision of it. Naming an earlier revision's txid: same record, folds — the form BRC-503 requires for REMOVE and DELETE. Naming anything else: ignored for this record. SELECT never time-travels — the command after it operates on the state at the point in the fold where its revision sits.
The gate comes first. Resolution is defined over authorized writes only, and authorization is someone else's (§2): spend authority for a 1Sat token, an AIP or Sigma signature, or whatever the record's owner-system defines. An indexer that folds every syntactically valid MAP command it finds has given every network participant write access to every record — MAP itself has no ownership model, and SELECT's whole purpose is naming other transactions.

CLEAR re-folds

Five of the six commands are functions of the accumulated state: give SET, ADD, REMOVE, DELETE, or SELECT the state so far, and the next state follows. CLEAR is the exception. It voids the writes of a named earlier transaction, and the accumulated state no longer remembers which member came from which revision. The only general implementation is the one §5.6 defines the result by: re-fold the sequence with the named revisions skipped.

THE SEQUENCE AS WRITTEN rev 0 rev 1 rev 2 · tx2 rev 3: CLEAR tx2 resolve = re-fold with the named revisions skipped (§5.6) THE EQUIVALENT FOLD rev 0 rev 1 rev 2 rev 3 (tip) name=[Genesis] tags=[a,b,c] skipped — its DELETE and SET never happened for this record still a chain link; contributes nothing further A CLEAR applies at its own position, names only transactions that already exist, and voids per record: it touches only the writes those transactions made to the record being resolved.
Retroactive and non-monotonic: state that was gone comes back (tags=[a,b,c]), and state that existed disappears (title). An indexer MAY compute it any way it likes; the result MUST equal this re-fold. The same context constraint as SELECT confines it — a txid outside the record's own revision sequence is ignored.
Settled, and BRC-515 was corrected. An earlier draft of BRC-515 §2.1 read CLEAR as erasing an object's entire resolved state. MAP and BRC-503 §2.6 scope it to the writes of the named transactions, which is what this document follows. §2.1 now reads: CLEAR <txid> folds per BRC-514 §5.6: it voids the writes of the named revision, not the whole record.” An object cleared at one revision can still be placed by another.

Deployed versus specified

BRC-514 is part documentation, part first specification, and §8 draws the line. The picture for an implementer: the deployed resolver in 1sat-stack gets the outer machinery right — the sequence index, chain-order fold, revision boundary, script-order merge, historical addressing — and folds exactly one of the six commands.

SET deployed ADD SELECT REMOVE DELETE CLEAR parsed as a command word, then dropped — empty decode, no error Evidence: DecodeMap reads the command push, then enters its pair-reading loop only if cmd == MapCmdSet. Its constant table declares SET, DEL, ADD, SELECT — and DEL is a command no MAP spec defines. The chains it serves have so far written nothing beyond SET, which is why the gap has been invisible.
§8's finding, drawn. The resolver is incomplete rather than divergent: on every command it applies it matches this fold, and it becomes non-conforming only on chains that carry list or removal commands.

The ledger, verified in code

Each row was checked against current source, and the evidence column names what carries it. Files: 1sat-stack/pkg/ordfs/ordfs.go, interfaces.go, and pkg/template/bitcom/map.go.

behaviorstatusevidence
Chain-order fold, origin → tip, per-key last-write-winsdeployedloadMergedMap iterates GetAllMapUpTo(origin, seq) in sequence order and overwrites per key
Sequencing substrate: per-origin sequence indexdeployedOriginStore key layout — org: / seq: / rev: / map: / par: per origin per sequence
Revision = the chain link's own output, nothing else in the txdeployedthe map: entry points at the chain-link output itself
Piped MAP segments in one output merge in script orderdeployedparseOutput walks protocol segments in order
Sequence-addressed historical resolution (§7)deployedGetLatestMapBefore, seq-parameterised resolution
Partial SET: trailing key without value dropped, prior pairs kept (§6)deployedDecodeMap's read loop reverts the dangling key and keeps what parsed
Repeated key in one SET: last pair wins (§3.4)deployedfalls out of DecodeMap's map assignment in read order
Fold semantics of ADD, DELETE, REMOVE, SELECT, CLEAR (§5)specified first heredeployed parser matches SET only; other five decode to nothing
Ordered-set value model with byte-equality dedup (§4)specified first hereno deployed lists exist; warrant is MAP's own JS-Set framing — see Q2
::: ordering within a segment (§3.3)specified first heredeployed parser reads only the first command of a segment
Non-canonical substrates: supplied total order, output-index tiebreak (§3.2)specified first herenothing deployed exercises it; required by the authorization-agnostic scope

Deployed, and deliberately left out

Three behaviors run in production and are not in the spec, on purpose: the NUL-byte→space sanitization of keys and values (a serialization convenience, not fold semantics); the nested-JSON expansion of subTypeData and royalties (a 1Sat-collections concern, owned by that consumer); and the merged: cache layer (an optimization — §5.6 already permits any implementation whose result equals the re-fold).

Open questions, with recommendations

Decisions a reviewer must ratify before this draft hardens. Q1 has since been settled in 514's favour and 515 corrected; the rest are open. Each card states the choice the draft made, the alternative, and a recommendation. Four smaller items follow in the table.

highresolved Q1 — CLEAR: BRC-514 and BRC-515 disagreed

The draft follows MAP and BRC-503 §2.6: CLEAR <txid> voids the named transactions' writes and nothing else. An earlier draft of BRC-515 §2.1 said CLEAR “erases the object's entire resolved MAP state”. Under this draft, CLEAR <spent-tip-txid> voids one revision's writes and the object may remain placed by an earlier one. BRC-515 has been rewritten to match.

Recommendation: 514's reading stood, and 515 §2.1 has been rewritten to match: CLEAR folds per 514 §5.6; writers wanting to unplace use SELECT <txid> REMOVE quadkey; erasing everything means naming every revision that wrote state. If the owner prefers 515's whole-state reading instead, 514 §5.6 needs an explicit special case and diverges from BRC-503 §2.6 — that divergence should then be stated loudly, in both documents.

medopen Q2 — ADD: ordered set, or list with duplicates?

The draft makes every value list an ordered set: ADD dedups by byte equality, first occurrence keeps its position. The alternative is a plain list that appends duplicates — closer to the word “append”, simpler to implement, but it makes DELETE ambiguous: remove one occurrence, or all?

Recommendation: the set reading stands. MAP's own README motivates ADD/DELETE via a JavaScript Set, and the set model is the one under which DELETE has exactly one meaning.

medopen Q3 — SELECT breadth: any earlier revision, or the spent tip only?

514 §5.1 lets SELECT name any earlier revision of the record. BRC-515 §2.1 binds <txid> to the transaction carrying the record being edited. Compatible — every 515 write is 514-conforming — but the reverse case needs a decision: what does a 515 indexer do with a SELECT naming the origin txid, valid under 514 and outside 515's writer profile?

Recommendation: 515 keeps its writer MUST and its indexer ignores rather than errors, citing 514 §5.1. A stricter writer profile over a general fold is the right shape; a stricter reader would fork resolved state between consumers.

medopen Q4 — bare REMOVE and DELETE inside a chain

§5.1's first bullet makes SELECT optional in-chain: an instruction set with no SELECT addresses the record its revision belongs to. That is derived, from MAP's rule that omitting SELECT makes the current transaction the context, plus the fact that the current transaction is a revision. BRC-503's letter says REMOVE and DELETE are “used with SELECT”, so this is an extension, and a reviewer must ratify or strike it.

Recommendation: ratify. In-chain, the SELECT is pure ceremony — it can only name what the chain already fixes — and requiring it forces every writer to look up a txid it already spent.

lowopen Q5 — a conformance class for SET-only resolvers?

§8 calls the deployed resolver incomplete rather than divergent and says implementations SHOULD converge on the full command set. The open choice: name a SET-only conformance subset, or make full six-command support a MUST for any conformance claim.

Recommendation: no named subset. A SET-only resolver is correct on every chain that only writes SET and silently wrong on any other — a named subset would bless the silent case. Keep the SHOULD while the corpora are SET-only; tighten to MUST when a consumer spec starts writing list commands.

Smaller items

#itemstatusdisposition
Q6MAP README grammar defectsclosedRaised against a pre-merge README. On opldotdev/MAP master the DELETE block carries its <key> and the SELECT list reads SET | REMOVE | ADD | DELETE. Nothing to fix.
Q7Object storage (JSON / BSON / MSGPACK)closedThe merged README documents no such feature — only JSON-fenced worked examples. There is nothing to fold.
Q8Authorship blockownerDraft lists Luke as author, David Case as contributor (he built the resolver being documented). Confirm against the corpus OPL header before upstreaming.
Q9File placement and linkspendingRelative links assume a sibling of tokens/scripts/, next to 0503, is the intended home. BRC-502/503/504 are cited by number without links until opldotdev/BRCs PR #1 lands; convert then. 514 was verified free across all branches; the brc-314-native-app-substrate branch informally heading for this slot should take 516.

What BRC-515 changed once 514 existed

BRC-515 anticipated this document, and its interim resolution rule carried its own supersession clause. Three of these edits are applied on PR #3, two do not apply, and one remains a choice.

wherechangestate
§5, resolution paragraphDelete the interim rule; cite BRC-514. The citation replaces the wording rather than sitting beside it: the interim “within one transaction, output order” is superseded by 514's sharper canonical rule (the chain-link output only). Outcomes agree for well-formed 515 writers.applied
§2.1, CLEAR sentenceRewrite per Q1: CLEAR voids the named revision's writes; unplacing is SELECT <txid> REMOVE quadkey; erasing everything means naming every writing revision.applied
§2.1, command table514 §5.1 makes bare REMOVE quadkey / DELETE quadkey <cell> valid in-chain. Keep the SELECT forms as the writer profile or admit both — either way, cite 514.open
Stale-anchor ruleRephrase as a read-layer rule. 514 §6 forbids consumer filtering from altering resolved state, so an un-accompanied SET quadkey means the resolved coordinates.* / geohash / h3 are disregarded as 515's anchor — indexers do not drop them from the state, and other consumers of those keys still see them.n/a
ReferencesAdd BRC-514 as a numbered footnote; point “the quadkey in effect at the tip” at 514 §1's definition of resolved state.applied
Schema & implementation sectionsUnaffected.n/a
What BRC-514 is. One fold, four orderings, six commands, and a hard scope line: authorization stays with whoever owns the record. Half of it documents behavior that has run in production for years and was written down nowhere; the other half specifies the five commands nothing yet folds, before divergent implementations of them exist to reconcile. Those four questions, plus the smaller items below, are what a reviewer has to decide — everything else on this page is either deployed fact or a diagram of the spec's own rules.