Application Output Knowledge and Source Adapters
A wallet app might learn about a Bitcoin output from several different places at once: an overlay network host, a wallet's own basket, or a message handed to it directly. Before this, there was no agreed way to keep those tips separate from verified fact, so an app could end up treating one source's say-so as the final word, or confusing "someone claims this output exists" with "this output is actually mine to spend." This makes it possible to build apps that take in competing, partial, and sometimes wrong reports and still end up with a trustworthy picture of what is really going on.
Reference for an AI
Everything an assistant needs to answer questions about BRC-192 accurately, including what it depends on.
Summary
- Why
- Apps receive output information from multiple overlay hosts, wallet baskets, and direct deliveries that disagree or arrive incomplete, and need a disciplined way to separate rumor from verified fact without treating any one source as the authority.
- What
- BRC-192 is a behavioral contract for an application runtime that ingests Bitcoin output reports from multiple sources and separates raw observations, verified transaction history, current-state assessment, application interpretation, and authorized actions.
- How
- A developer wires up adapters (overlay, wallet basket, direct delivery) that emit typed observations like output, spend, and withdraw, each deduplicated and keyed by partition and scope, then lets the runtime verify evidence into durable facts before the application layer decides what those facts mean.
What this lets you do
- Combine reports from multiple overlay hosts without picking one as authoritative
- Distinguish a source's claim about an output from cryptographically verified spend history
- Detect and quarantine conflicting claims (equivocation) instead of silently overwriting data
- Handle non-final transactions and spend reconciliation deterministically
- Swap in a wallet basket or other source adapter without rewriting the application
Written by claude-sonnet-5 from the specification text. Where the two differ, the original is correct.
This diagram is a placeholder based on the topic. A diagram drawn from this standard has not been generated yet.
The specification
Abstract and status
This proposal defines a replaceable application runtime that learns about Bitcoin outputs from overlays, wallet baskets and direct delivery. It separates received information, verified transaction history, assessments of current state, application interpretation and authorized actions. A music catalogue can display results from its first responding host and incorporate later changes without treating that host as the authority over every artist. A task application can use the same machinery with a wallet basket instead.
This is a proposed behavioral contract, not a claim that existing packages implement it. It is additive to BRC-100, BRC-22 and BRC-24. It neither moves wallet custody into the runtime nor establishes a new consensus protocol. The capitalized requirement words in this packet have the meanings in RFC 2119 and RFC 8174.
1. Shared representation rules
These rules also apply to BRCs 193–199 wherever those documents use JSON. Existing Bitcoin, BEEF, BRC-77 and LCH encodings retain their own rules.
Hex32 is exactly 64 lowercase hexadecimal characters. Identity is a valid compressed secp256k1 public key encoded as 66 lowercase hexadecimal characters. Bytes is canonical RFC 4648 standard base64, with required padding and no whitespace. U64 is a decimal string matching 0|[1-9][0-9]*, at most 18446744073709551615. Arithmetic MUST be exact and checked. U32 is a JSON integer from 0 through 4294967295. Times are U64 whole Unix seconds. An expiry is exclusive: a record is expired when now >= expiresAt.
JSON MUST be UTF-8 without duplicate property names, non-finite numbers or unpaired surrogates. Numbers in new protocol objects MUST be safe integers; applications needing decimals encode them as strings in their own schema. Protocol objects are recursively closed: every object, including a nested union member, admits only the fields defined for that object. This does not close application-defined query, policy payloads or extension values; those obey their selected schemas. Optional extensions use an extensions object keyed by absolute IRIs; critical is a duplicate-free array of keys present in that object. Unknown non-critical extensions are retained when relaying a signed object but not interpreted. Unknown critical extensions fail with unsupported. Missing and null are distinct; null is permitted only where specified.
For packet type T and body B, define:
preimage(T,B) = UTF8("BRC-OUTPUT/1/" + T) || 0x00 || UTF8(JCS(B))
digest(T,B) = lowercaseHex(SHA256(preimage(T,B)))
JCS is RFC 8785. The digest excludes an enclosing signature, never fields inside the body. A signed packet object is {body, signature}, with signature: Bytes containing a complete BRC-77 signature over preimage(T,B) using the anyone verifier. Verify both cryptography and the specified signer role. Identity authentication alone does not confer application authority. No document in this packet permits resolving arbitrary extension IRIs as executable code or trusted schemas.
type Chain = { network: string; genesisHash: Hex32 }
type Outpoint = { chain: Chain; txid: Hex32; outputIndex: U32 }
type Partition = { application: string; account: string; access: string }
type Scope = {
chain: Chain; provider: string; service: string;
queryDigest: Hex32; rulesDigest: Hex32; access: string; epoch: string
}
type Evidence = { txid: Hex32; outputIndex: U32; beef: Bytes }
type Extensions = { extensions?: Record<string, unknown>; critical?: string[] }
Strings without a narrower constraint MUST be nonempty UTF-8 strings of at most 1024 encoded bytes. network is an application-configured namespace, not a sender-selected switch; genesis hash further separates otherwise similarly named chains. Outpoint equality compares all three components, including both chain fields. Epochs are opaque provider identifiers, not timestamps. Access identifiers are opaque local/provider partition identifiers, never credentials. The local partition is supplied by trusted application configuration and MUST NOT be accepted from a remote payload.
2. Observations and identities
A source delivers Observation = {id, scope, kind, payload, extensions?, critical?}. id is unique within its scope epoch and stable on replay. The runtime adds local receipt time, partition, adapter identity and authenticated peer evidence. A payload cannot set those trusted fields.
The supported kind and exact payload pairs are:
| Kind | Payload |
|---|---|
output | {evidence: Evidence, context?: {schema: string, bytes: Bytes}} |
spend | {previous: Outpoint, spendingTxid: Hex32, beef: Bytes} |
withdraw | {outpoint: Outpoint, reason: string} |
assessment-invalidated | {contextId: string, reason: string} |
proposal | {proposal: SignedProposal} as defined in BRC-194 |
proposal-state | {service:string, policy:{id:string,digest:Hex32}, channel:Hex32, proposalId:Hex32, state:ProposalState} as defined in BRC-194 |
proposal-remove | {service:string, policy:{id:string,digest:Hex32}, channel:Hex32, proposalId:Hex32, reason:string} |
context is provider data, subject to byte limits and private partition rules; it is not trusted executable behavior. Its schema is an absolute IRI implemented by the application. The standard adapter schema https://bsv.brc.dev/apps/0192#legacy-lookup-context-json-v1 carries UTF-8 JSON bytes representing an existing BRC-24 context value, retaining its original JSON semantics including valid fractional numbers. The enclosing protocol does not reinterpret or canonicalize those opaque bytes. An application that does not support the schema can retain/ignore non-critical context, but cannot claim to have validated its domain meaning. A source's output means that source reports the output in its collection. withdraw removes only that source's membership assertion. Neither implies a Bitcoin spend. The same output from two hosts has two receipts and one output identity. An application object, such as a song or editable post, has a third, domain-defined identity that can span many outputs.
Deduplication keys are (partition, scope, id). An identical replay is a no-op. Reusing that key with different normalized observation bytes is equivocation; preserve the conflicting claim in bounded quarantine, invalidate continuity for that source and require recovery. Do not let a forged remote receipt overwrite trusted provenance. Evidence variants are separately keyed by digest of their exact BEEF bytes. One rejected variant MUST NOT poison the outpoint or prevent a later valid variant from being verified.
Evidence.txid selects the asserted target, not an arbitrary transaction from the bundle. The assembler MUST locate that transaction, derive its txid from its raw bytes, verify the output index, and provide a target-specific BEEF candidate to the verifier. An Atomic BEEF target MUST equal the assertion. It MUST NOT silently use the last transaction in an aggregate wallet bundle. Txid-only references are unresolved dependencies until the required bytes are present. Retrieval follows configured, bounded adapters, not arbitrary URLs supplied by a receipt.
3. Evidence, currentness and domain meaning
The verifier accepts an evidence candidate and an immutable VerificationContext containing the caller's chain view identifier, verification policy digest and cancellation signal. Its result is one of verified, invalid, unresolved, limited, cancelled or context-changed. Only verified introduces transaction-derived facts. A verified creation transaction does not establish that its output is unspent or relevant to an application.
Currentness is a separate assessment with the complete Currentness structure in section 8. state is unknown, reported-unspent, spent, conflicted or stale. spent requires a verified spending transaction and the application's selected acceptance policy; reported-unspent MUST retain its provider and scope. Two conflicting unconfirmed spends remain representable. Confirmation placement records name block hash and height, and can be invalidated by a changed chain view. No fixed confirmation count is imposed on all applications.
An assessment is usable only in its partition, policy and chain view. Late results from an older view MUST NOT overwrite assessments for the current view. Changing view invalidates affected assessments before publishing projections based on the new view. Immutable raw transaction facts can be reused; chain placement and currentness cannot be reused without reassessment. Wall-clock freshness is not blockchain finality.
A domain projector interprets scripts, controllers, lineage and application rules. It consumes a coherent accepted revision and produces records plus explicit conflicts or unresolved dependencies. It MUST be deterministic for the same accepted inputs, domain policy and context. Domain rejection is not evidence that the Bitcoin transaction is invalid. Successor selection, membership epochs, editable post permissions and vote uniqueness belong here or in an explicitly named domain protocol.
4. Interchangeable ports
The following are behavioral ports. Language bindings MAY name methods differently but MUST preserve their behavior and expose the same outcomes.
Source.open(request, signal) returns a bounded async iterator of SourceBatch objects. Each batch has the SourceBatch structure in section 8, retaining indivisible groups from ingress through accepted projection. Coverage records the source scope, phase (snapshot, live, finite), status (partial, complete, reset-required, unavailable, limited) and any named checkpoint. A complete finite response describes that source/query invocation only. It does not establish network-wide completeness or chain finality. A durable source checkpoint additionally follows BRC-193.
KnowledgeStore.commit(expectedRevision, mutation) atomically persists receipts, evidence references, membership changes, invalidations, durable pending verification work and source checkpoint advancement. Revisions are strictly increasing local U64 values. A mismatched revision is conflict with no partial write. A successful commit returns its revision. An ambiguous storage result is resolved by reading the mutation's stable idempotency key, never by assuming failure. read(revision) provides a coherent snapshot or revision-unavailable; watch(afterRevision) provides ordered coherent changes or an explicit reset. A memory adapter declares durability: volatile; it MUST NOT advertise restart recovery.
Projector.apply(revision, changes, context) commits its projection and checkpoint together, or uses replayable idempotent work. A failed projection cannot be marked current. Consumers see both knowledge and projection revision, so a lagging projection cannot masquerade as the latest result. Rebuild uses retained evidence/domain history or explicitly requests a fresh source snapshot. Retention MUST preserve unresolved work, current source fences and obligations such as paid acquisition recovery; if history needed for a guarantee is unavailable, return a reset or unsupported result.
ActionPort.request(intent) is invoked only by an authorized application workflow. Observing, verifying, projecting or replaying MUST NOT automatically sign, internalize, pay, broadcast or change wallet permissions. Automatic domain actions need separately granted authority and their own durable idempotency records. The runtime does not create a second wallet.
5. Adapter requirements
The overlay adapter retains per-host receipts before union/deduplication. Finite BRC-24 and progressive multi-host results may be adapted, but MUST declare their weaker continuity. BRC-193 is required for a replayable live claim. Callback APIs without awaited backpressure need a bounded ingress queue: overflow reports limited and triggers reconciliation; it never silently drops data while advancing a checkpoint.
The wallet adapter uses BRC-100 listOutputs, existing baskets/tags, permissions and aggregate BEEF. Offset pagination is not an atomic snapshot. Concurrent mutations require repeated reconciliation or a separately supported wallet change capability. An absent row alone is not a spend. Switching accounts closes sources, cancels work and partitions cached/private data before exposing the new account.
The direct-delivery adapter supports transports such as MessageBox, NFC and peer exchange. Receipt identity, sender authentication and delivery acknowledgement are distinct from evidence validity. Delivery acknowledgement is issued only after durable receipt or an explicitly declared volatile policy. It supplies no claim to an exhaustive collection.
A private-state adapter decrypts only within an authorized partition after validating the output/controller binding. Client-encrypted participant state and a private overlay holding a seller's key are distinct trust models. Membership epochs, late-write reconciliation and ephemeral typing/presence do not become Bitcoin consensus by entering this runtime.
6. Limits, errors and compatibility
Every adapter declares maximum batch bytes, observations, pending bytes, verification concurrency, dependency work, retained history and deadline. Configuration MUST be finite and validated before opening a source. Ingress bytes are charged before parsing nested content. Verification failures are isolated per candidate unless they invalidate a batch's integrity. Cancellation releases resources and prevents further publication from the cancelled context; already committed durable facts remain available.
Common errors are invalid, unsupported, unauthorized, conflict, equivocation, limited, unavailable, expired, reset-required, cancelled and context-changed. Retryability is explicit; budget exhaustion is never reported as invalid cryptographic evidence. Scheduling across sources MUST prevent one slow or busy host from indefinitely starving others.
Existing SDK exports, BEEF formats, lookup defaults, wallet calls and domain token formats remain unchanged. New runtime interfaces SHOULD live above the SDK with optional storage/UI/transport adapters. Replacing a database or UI framework MUST NOT change receipt identity, replay, authorization or projection semantics. GASP can synchronize either strict or federated providers, as described by BRC-183; it is not a substitute for the application contract.
7. Conformance
A conforming runtime MUST exercise two independent source adapters and a restart-capable store. Required cases are duplicate receipts; two hosts reporting one output; bad evidence followed by valid evidence; alternate BEEF proofs; aggregate target selection; unrelated withdrawal; verified spend and successor in one coherent revision; conflicting spends; context changes during verification; projection failure/rebuild; queue overflow; cancellation; restart; account switching; missing dependencies; and cursor advancement only after durable work exists. A volatile implementation may claim the corresponding narrower port conformance, never durable conformance.
The packet's qualification guide and fixtures distinguish representation tests, behavioral traces and implementation qualification. They are specification checks, not certification of a database, cryptographic verifier or deployed application.
8. Structural port contract
The following structures complete sections 2–4. LocalId is a nonempty string under section 1's bound. ChainView is caller-selected, never created by an observation. Signals are local cancellation handles, not serializable credentials. Every field below is required except those marked optional. All lists are bounded by section 9 and the smaller negotiated budgets.
type ChainView = {
id:LocalId; chain:Chain; tipHash:Hex32; tipHeight:U64;
medianTimePast:U64; chainPolicyDigest:Hex32
}
type VerificationContext = {
id: LocalId; partition: Partition; generation: U64; view: ChainView;
policyDigest: Hex32; now: U64;
limits: { bytes: U32; transactions: U32; dependencies: U32; deadline: U64 }
}
type Provenance = {
partition: Partition; generation: U64; adapter: LocalId;
scope: Scope; authentication: 'brc103' | 'configured-transport';
peer: string; receivedAt: U64
}
type EvidenceCandidate = { chain: Chain; evidence: Evidence; variantId: Hex32 }
type TransactionFact = {
chain: Chain; txid: Hex32; rawTransaction: Bytes; factId: Hex32
}
type VerificationResult =
| { status:'verified'; contextId:LocalId; variantId:Hex32;
fact:TransactionFact; placement?:{blockHash:Hex32,height:U64} }
| { status:'invalid'|'unresolved'|'limited'|'cancelled'|'context-changed';
contextId:LocalId; variantId:Hex32; reason:string; dependencies:Outpoint[] }
type Currentness = {
id: Hex32; outpoint: Outpoint;
state:'unknown'|'reported-unspent'|'spent'|'conflicted'|'stale';
contextId: LocalId; generation: U64; policyDigest: Hex32;
origin: { kind:'local' } | { kind:'source'; scope:Scope };
evidenceIds: Hex32[]; expiresAt?: U64
}
type SourceGroup = { id:string; sequence:U64; observations:Observation[] }
type Coverage = {
scope:Scope; phase:'snapshot'|'live'|'finite';
status:'partial'|'complete'|'reset-required'|'unavailable'|'limited';
through?:U64; highWater?:U64
}
type SourceBatch = {
provenance:Provenance; groups:SourceGroup[]; coverage:Coverage;
checkpoint?: { session:string; cursor:string; expiresAt:U64; replayUntil:U64 }
}
type Mutation = {
key: Hex32; body:
| { kind:'receive'; batch:SourceBatch }
| { kind:'accept'; scope:Scope; groupId:string; generation:U64;
contextId:LocalId; results:VerificationResult[]; assessments:Currentness[];
reconciled:ReconciledState }
| { kind:'reconcile'; generation:U64; contextId:LocalId;
reconciled:ReconciledState; assessments:Currentness[] }
| { kind:'invalidate'; generation:U64; assessmentIds:Hex32[]; reason:string }
| { kind:'context'; context:VerificationContext }
}
type StoreRevision = { received:U64; accepted:U64 }
type CommitResult =
| { status:'committed'|'replayed'; revision:StoreRevision }
| { status:'conflict'|'equivocation'|'limited'|'context-changed'; reason:string }
type AcceptedInput = {
partition:Partition; generation:U64; revision:StoreRevision;
context:VerificationContext; observations:Observation[];
facts:TransactionFact[]; assessments:Currentness[]; reconciled:ReconciledState;
pendingGroups:{scope:Scope,groupId:string,reason:string}[]
}
type Projection = {
acceptedRevision:U64; generation:U64; contextId:LocalId;
records:{id:string,schema:string,value:Bytes}[];
conflicts:{id:string,reason:string,observationIds:string[]}[];
unresolved:{id:string,reason:string,dependencies:Outpoint[]}[]
}
A variant ID is SHA256(decoded BEEF bytes); a fact ID is digest("transaction-fact", {chain,txid}); an assessment ID is digest("assessment", bodyWithoutId). These are different namespaces. Evidence IDs in an assessment are variant IDs with retained verification results in that assessment's context. Raw transaction bytes having the same txid but different content are an integrity failure. A source-reported unspent assessment requires origin.kind=source, its exact scope and an expiry. spent requires evidence actually consuming the named outpoint. Absence and an unspent assertion do not establish a spend. Context-changing assessments use new IDs; they do not mutate the historical result.
The trusted adapter checks the entire scope of every observation against its established source session, including configured chain, provider, service, query/rules digests, access and epoch. It compares coverage and batch scope as well. A mismatch rejects the whole batch before receipt or cursor commit. Remote assessment-invalidated only invalidates source-origin assessments whose complete scope and contextId match that same authenticated source. It cannot name a local ChainView or invalidate another source's assessment. A locally authorized context change computes affected assessment IDs from that view/policy and emits an atomic invalidation; it may affect many providers. proposal-state and proposal-remove are attributable provider assertions, not author signatures, and never fabricate a SignedProposal.
A finite source maps its entire response to one group unless its declared rules provide independent boundaries. Its sequence is zero; snapshot sequence rules come from BRC-193. receive commits all group boundaries, bytes, immutable provenance, verification jobs and the checkpoint atomically. It increments received but not accepted. An accept mutation carries exactly one complete group and a result for every evidence candidate; no member may publish independently. Unresolved/limited/cancelled/context-changed members keep the group pending and make affected old projection records visibly stale. An invalid member quarantines the whole group, invalidates that source's continuity, and requires reconciliation; it cannot publish its valid siblings. This does not discard evidence variants, independent sources or historical facts. A later valid proof uses a new observation ID and a replacement group, or a new reconciled generation; it never rewrites an equivocated observation.
Verification jobs and accept mutations are guarded by partition, generation and context. On dependency completion after restart they recheck those guards and all expiring assessments, then accept the group atomically. accepted increments only for a whole accepted group or a local reconciliation/invalidation/context/expiry event. A reconcile mutation recomputes selection over retained facts and accepted groups; it cannot introduce unverified facts or bypass a source-group barrier. Receipt, acceptance and projection revisions are separate progress indicators. A projection failure leaves its checkpoint unchanged and reports lag/staleness. All providers' accepted memberships remain distinct until domain interpretation. An expiry scheduler MUST publish local invalidation at the expiry boundary even with no incoming data; reads also evaluate the clock so a delayed timer cannot expose expired assessments as current.
A mutation key is digest("knowledge-mutation", body), with trusted local provenance frozen at first receipt and reused on retry. commit compares the expected received revision. Every successful new mutation increments received; accepted increments only as above, with checked U64 arithmetic. Replay lookup precedes revision comparison: the same key/body returns the saved revisions; different bytes for an existing key are equivocation. A lost commit result is resolved with getMutation(key) returning committed with saved result, absent, or unavailable; only absent permits repeating the operation. No caller guesses rollback. Overflow is limited before any mutation and requires a new explicit store generation/reset, not wraparound.
read(acceptedRevision) returns AcceptedInput or revision-unavailable. Projector inputs are sorted by UTF-8 bytes of JCS(scope), then UTF-8 observation ID, with facts by chain/txid and assessments by ID. Identical facts collapse by fact ID; source claims never collapse across scopes. Conflicting claims are passed together, never resolved by arrival order. Verified Bitcoin spend selection follows section 10; domain policy interprets application conflicts on that reconciled history and cannot override its actual input-consumption edges. Presentation sorting is not an event order. apply atomically records Projection plus its revision/generation/context or idempotent replay work keyed by those fields and the domain policy digest. A publication gate checks active partition/generation/context and cancellation immediately before committing the projection visible to readers. Cancellation linearizes at that gate: previously published facts remain, queued/late publications fail cancelled/context-changed. Account switching closes that gate before exposing the new partition.
watch retains contiguous accepted changes or returns reset-required with the earliest available revision. A reset never silently substitutes an unrelated latest revision. Receipt keys, group mappings, mutation results and source generations MUST be retained for the entire resumable source lifetime; unresolved groups remain until resolution or an explicit reset terminating their guarantee. Retired generations retain a permanent small closed-generation fence so late jobs cannot revive them. Paid obligations are retained independently through their specified completion/recovery terms. Explicit generation reset permits compacting historical receipt bodies once no source, unresolved work, projection rebuild or paid obligation depends on them; it cannot delete the fence.
9. Exact representation boundary
Protocol input parsing rejects duplicate decoded keys (including escaped aliases), malformed UTF-8, a BOM, non-finite numbers and lone surrogates before JCS. Numeric spelling is not an additional canonical-input rule: -0, 1.0 and 1e0 parse as safe integers and canonicalize to 0, 1, 1. An unsafe integer or fractional numeric value in a new protocol object is invalid. Signed preimages use RFC 8785 UTF-16 key ordering and ECMAScript number/string serialization; a language's generic sorted JSON encoder is not a substitute. U64 fields remain strings and reject leading zeros, signs, exponents and overflow. Opaque decoded bytes may be empty unless their installed schema or a named cryptographic format requires nonempty bytes. Base64 decoders MUST reject nonzero pad bits and noncanonical padding by round-trip re-encoding. Identity validation includes point-on-curve checking, not only length/prefix.
Default hard limits across this packet are nesting depth 32 (root counts as one), 4096 array elements, 256 keys per map, 32 extension/critical entries, and 4 MiB decoded message bytes, unless a specific field has a smaller limit. String bounds in section 1 apply except opaque base64, URLs with an expressly stated bound, and application data within bounded query/extension containers. Those containers permit strings up to the complete message bound; they do not relax nesting, map, array or numeric limits. Cryptographic signatures, raw transactions and BEEF must parse completely; input limits include decoded allocations and verification work. Unknown nested protocol keys fail invalid. Duplicate critical keys fail invalid; unsupported critical mechanisms fail unsupported. Extension values are retained under the same canonicalization/bounds and never dereferenced automatically.
Normalized equality is equality of RFC 8785 bytes of the complete object, including extensions. Different BEEF bytes under one observation ID therefore constitute equivocation even if both proofs are valid. Different observations can introduce those variants for one fact. Only the specifically documented publication/finalization evidence-variant rules exclude proof bytes from semantic request equality. These exclusions never apply to opaque LCH signed-request bytes.
10. Deterministic spend reconciliation
This section registers https://bsv.brc.dev/apps/0192#bitcoin-spend-reconciliation-v1. It is the required spend-selection contract for this BRC's stateful runtime; a representation-only implementation need not implement it. Parameters are {nonFinal:boolean} in the locally selected verification policy. False retains non-final evidence without selecting provisional replacements. True additionally tracks fully signed, structurally and Script-valid non-final candidates as described below. Neither setting authorizes signing, broadcasting, topical admission or private release. A domain with stricter transaction rules remains stricter: BRC-197 requires zero locktime and all final sequences, regardless of this setting.
10.1 Transaction history and application state
Within one authorized partition and configured chain, any source can contribute transaction evidence. Providers need not agree about catalogue membership. Validate exact target bytes, every consumed input, dependencies, value conservation and the applicable Script/covenant rules before adding a usable spend edge. Derive edges from actual transaction inputs, never from a claimed successor, a matching object ID or observation order. An input references a parent transaction even when the child's observation arrives first. Missing dependencies remain unresolved; a later verified parent completes the graph without making a consumed parent output current again.
Transaction validity, chain inclusion, selected spend history, domain acceptance and source membership are separate fields. The application validates controller/lineage/other domain rules before treating a successor as a valid application object. A domain-invalid successor does not undo a Bitcoin spend: a valid transaction can consume an application's output and create no valid replacement in that domain. In particular, selecting BRC-197's Script-valid but lineage-invalid funded-copy merge provisionally consumes its inputs; verified inclusion establishes that spend in the selected chain. Script validity alone does not choose it over a conflicting transaction. Do not resurrect the old listing because its successor is ineligible for catalogue admission.
The graph contains all verified alternatives. Selection produces a compatible subset; losing alternatives, their sources and evidence remain visible as conflicts. A selected unconfirmed final spend provisionally consumes each of its inputs together. Its finality does not establish mining or global acceptance. A selected non-final candidate only records pending replacement intent; it does not establish a spent input or a spendable successor. A selected final transaction cannot depend on an unselected or non-final unconfirmed parent. A creation proof, including an ancestor's verified inclusion, never defeats a verified eligible forward spend or establishes global unspentness.
10.2 Whose first-seen and which order
First-seen in this profile means this store's first complete selectable observation, not a provider's timestamp, a network-wide arrival time, or the worker that finished first. Before starting verification, the store atomically assigns each authorized receive a strictly increasing ingress position within a partition's durable journal. It records the active chain/finality context there. Context changes also occupy durable journal positions; elapsed wall-clock time and worker completions do not advance chain finality. Independently accepted source batches have separate positions; within one atomic batch, newly encountered raw transactions are indexed by ascending txid. No network payload assigns these values. A txid alone does not establish a transaction observation. A duplicate or alternative proof does not reset its first raw-transaction position.
The eligibility support for a candidate is its exact raw transaction, required raw predecessors or verified chain anchors, cryptographic evidence and any protocol prerequisites. A domain's object acceptance is evaluated separately and cannot conceal a spend. readyAt is the earliest ingress position at which one sufficient, valid support set had been received. This is cryptographic/protocol readiness; actual selection additionally requires usable parents and the enabled finality policy. It is the minimum, over qualifying support sets, of the greatest receipt position in that set. Include the candidate, all unconfirmed ancestors and the integrity prerequisites of their source groups. Evaluate support against the historical context and evidence available at that position, not today's anchors. Later inclusion does not retroactively shorten an earlier dependency path or change its depth. A chain anchor must have been verified for the historical context it supports; a proof on another branch is not that anchor. Verification completion time is never a member of that set. An invalid proof is not support; another valid variant may establish readiness. Evidence obtained later cannot be backdated to an earlier txid-only reference or incomplete promise.
At a shared readyAt, process ancestors before descendants. depth is zero if all unconfirmed parents have smaller readyAt (or are verified chain anchors); otherwise it is one plus the maximum depth of parents with this same readyAt. The total candidate order is (readyAt, depth, firstRaw.position, firstRaw.index, txid), with numeric comparison of integers and lexicographic display-hex comparison of txid. These choices settle ties inside a receive transaction without pretending to recover a network arrival order. readyContextId names the immutable finality context active at readyAt, retained for replay.
An earlier complete support set whose verification is unfinished creates a reconciliation barrier for its connected input-conflict/dependency component. A later worker cannot publish its competing choice while that earlier set might be eligible. Retain the previous accepted component as stale, or no selection if there was none, until the needed results are known. Other components can advance. An earlier incomplete candidate has no eligible order yet; it is reported unresolved and does not reserve priority ahead of an already complete candidate. For example, A's raw bytes arriving first does not beat B when A requires an additional predecessor first supplied after B's complete evidence. If both complete packages were present before either worker finished, reversing completion or restarting cannot change their order.
The store must determine the earliest qualifying support, rather than accepting the first successful proof job. Until earlier potentially sufficient variants are resolved, that determination remains pending. Bounded parse/dependency planning and resource policy may report limited or unsupported; those are explicit retained outcomes, not Script invalidity or a fabricated network rejection. Removing a pending candidate from a bounded local selection requires a recorded policy decision and a stale/incomplete coverage indicator. New evidence can reopen it with the original raw receipt and the newly established readiness. Garbage collection must not silently change a retained first-seen decision.
10.3 Exact finality and non-final replacement
The replacement relation follows the Genesis specification's nLockTime/nSequence section at revision 67d7de07c75d03598fb059d59c61c29ae3dbf87b. For the exact finality boundary this profile uses the pinned BSV node IsFinalTx and contextual checks at 879fc8b42168dd0e608dafd51b39c6dabad37d4d. The Genesis prose says greater-or-equal at the height/time boundary; the implementation uses strict inequality. This profile explicitly preserves the implementation's boundary rather than introducing a different consensus cutoff. The dependency registry records both sources and this distinction.
For an unconfirmed candidate, let H be selected tipHeight+1 and M be the median timestamp of the selected tip and its ten predecessors (or all available predecessors when fewer than eleven exist). Both come from the configured chain verifier, not a provider clock or VerificationContext.now. A transaction is final exactly when locktime=0, all input sequences equal 0xffffffff, or locktime is strictly below H when locktime<500000000 and strictly below M otherwise. Equality is non-final unless the zero/all-final exception applies. Included transactions are evaluated against their containing block's height and preceding-chain MTP under that chain's applicable rules. This profile covers the post-Genesis rules; other rule eras require an explicitly selected compatible verifier, not an assumed sequence interpretation.
A later candidate replaces a selected non-final candidate only if both are otherwise eligible, their complete ordered input-outpoint vectors are identical, every corresponding new sequence is at least the old sequence, and at least one is strictly greater. Compare unsigned 32-bit values component by component. Changed input count, ordering or any outpoint fails. Equal vectors, a decreasing component or incomparable vectors do not replace; a greater sum or maximum sequence is irrelevant. A locktime-only change without a sequence increase does not replace. Outputs may change only as permitted by the actual signatures, Script and selected protocol constraints.
The prior candidate must still be non-final at the new candidate's first selectable journal frontier, before considering that replacement. This is readyContext when its parents are already selected final; otherwise it is the recorded later context that makes those parents usable. The replacement can itself be final, either through all-final sequences or a satisfied locktime. Recheck all finality and transaction rules; increasing one sequence to 0xffffffff does not make all other inputs final. Once the selected transaction is final in the context of a later candidate, a higher sequence cannot replace it through this rule. Conflicting different input sets retain the earlier eligible selection, including when it is non-final. A non-final candidate cannot replace an earlier final spend. These are observed provisional choices, not proof that a miner has made the same choice.
Retain the context and reason of each replacement. Time advancing does not resurrect an older version that was permissibly replaced while non-final. Replaying uses the recorded readiness and context-change frontiers for historical replacement decisions and the current view for today's finality/currentness. Reorganization can remove chain overrides and require new eligibility assessments; it does not assign fresh arrival positions. If the historic support/context needed for an existing guarantee was discarded, return reset-required rather than deriving a new order from today's worker schedule.
10.4 Selection and accepted-chain overrides
For a complete reconciliation component, first validate the selected chain's included transactions and their necessary ancestor closure. They take precedence over incompatible unconfirmed choices regardless of first-seen. A Merkle path must resolve to the locally selected verified header chain; an arbitrary provider assertion or proof on another branch is not an override. Two contradictory spend histories claimed within one selected chain view are a verifier/context-integrity failure; stop the component with context-changed rather than picking one by txid.
Selection MUST preserve historical replacements before deriving today's usable graph. Implement the following deterministic two-pass rule, or an equivalent computation with the same result. It does not reproduce every miner's mempool or fee/resource policy.
First reserve the current selected chain's verified choices and ancestor closure. Replay the remaining candidates over journal frontiers: every readyAt, every retained chain/finality-context change and the current journal frontier, in numeric order. At each frontier, reconsider supported candidates in their total candidate order. A candidate needs all unconfirmed parents selected and final in that frontier's context; current-view forced chain ancestors are already reserved. Until then it is dependent/unresolved, not a selectable child. Record eligibleAt and its context at the first frontier where those dependencies and the enabled finality policy permit consideration, before testing competing input reservations. This is the candidate's first-seen eligibility; worker completion cannot assign it. An eligible candidate takes free inputs or replaces the single non-final selection occupying exactly its ordered inputs under section 10.3. It cannot replace multiple independently selected transactions. An alternative that loses a reservation remains a conflict in this historical pass; do not repeatedly retry it as though it had just arrived. A valid replacement records predecessor, replacement, frontier and context. A replaced version cannot be reselected later in this replay. Deferred dependencies and non-final candidates excluded by the nonFinal=false setting are reconsidered at later frontiers. This historical pass prevents later maturation from attaching a child to an old version before replay reaches the replacement that already displaced it.
For current-view rebuilding, a historical conflict is still an available alternative if the prior winner is no longer usable; historical replacement records remain distinct from that conflict status. Then collapse each valid replacement chain into its surviving candidate. That candidate inherits the original candidate's input reservation priority, because each link consumes the same ordered inputs. Reservation order is (original candidate eligibleAt, original candidate total order); candidates without a replacement chain use their own values. A candidate never selectable in historical replay has no earlier eligibleAt; if it first becomes usable during the current-view rebuild, use the current journal frontier, then its total order. Ancestors precede descendants within that frontier. In particular, nonFinal=false cannot give an earlier unsupported non-final candidate priority over a final candidate that was already selectable. Descendants retain their own eligibility/order. This inheritance prevents an intervening different-input-set conflict from taking priority merely because the replacement arrived later. With the current chain choices still reserved, rebuild the usable graph in reservation order, with ancestors before descendants. Omit replaced versions. Select a candidate only if its current dependencies are selected final, its inputs are free and its current finality is supported by the configured policy. Retain losing alternatives and reasons. Finality/currentness uses the current view, not the historical replacement context. A replacement chain preserves which version survived; it does not exempt that version from today's dependency or finality checks. Missing current evidence makes the affected component unresolved/stale.
Rebuild both passes when support, authorization, policy or accepted-chain constraints change, retaining original ingress and historical contexts. A reorg that puts a parent back behind its locktime boundary removes its descendants from the usable selection until their dependencies qualify again. This can release unrelated inputs previously reserved by those descendants; reconsider all affected candidates in the same ordered component computation. Publishing requires a compatible dependency-closed graph, never a child whose parent lost or is currently non-final. The replacement journal and today's selection are separate durable results; neither performs wallet actions.
A selected ancestor can be included while its selected child is unconfirmed: the child still consumes the ancestor's output provisionally. Conversely, accepted-chain evidence for B replacing unconfirmed A invalidates the A-derived selection and its descendants together. On reorg, remove only invalidated placement/assessment claims, reverify affected dependencies, replay the retained provisional choices and publish a coherent component revision. Historical facts, consent, acquisitions and original release evidence remain retained. Missing proof of the new view yields stale/unresolved state, never assumed confirmation or unspentness.
10.5 Membership and durable ports
Off-chain output/withdraw/re-entry order is per source generation and sequence, not Bitcoin transaction order. For BRC-193, a completed snapshot seeds that source at W; only its live groups strictly after W change that seed. Process live membership groups in ascending sequence, applying each whole group atomically. A stale replay cannot undo a later withdrawal or re-entry, and verification finishing later cannot change that order. Within a group, observations retain the provider's committed array order. Source reset stages a new snapshot generation; publication swaps that source's membership only after completion, fences late work from the old generation and leaves other sources and verified transaction history intact. Finite adapters serialize their own refresh generations and never infer a chain spend from absence.
Membership and transaction-derived currentness remain separate. A later source re-entry for an already-spent output makes that source's assertion present again; it does not make the output unspent. A withdrawal removes only that source's assertion, not the transaction fact, spend edge or another source's membership. Domain projections may require membership for discovery, but they MUST consult the reconciled graph before labelling any output current.
The following additional local structures are part of the port contract. References to transactions are scoped to the AcceptedInput partition and configured chain. Empty arrays are permitted. The optional order is absent until earliest valid support is established; eligible is absent until a selectable frontier exists; replaces is present only for a selected replacement and names its immediately superseded candidate.
type IngressPosition = { journalId:LocalId; position:U64; index:U32 }
type CandidateOrder = {
firstRaw:IngressPosition; readyAt:U64; depth:U32; readyContextId:LocalId
}
type ReconciledTransaction = {
txid:Hex32; evidenceIds:Hex32[]; firstRaw:IngressPosition;
order?:CandidateOrder; eligible?:{at:U64,contextId:LocalId};
status:'unresolved'|'invalid'|'limited'|'unsupported'|'conflicting'
|'dependent-conflict'|'selected-non-final'|'selected-final'|'included';
reason:string; dependencies:Outpoint[];
replaces?:Hex32; conflictsWith:Hex32[]
}
type SourceMembership = {
scope:Scope; generation:U64; outpoint:Outpoint; present:boolean;
phase:'snapshot'|'live'|'finite'; sequence:U64; observationId:string
}
type ReconciledState = {
profile:'https://bsv.brc.dev/apps/0192#bitcoin-spend-reconciliation-v1';
nonFinal:boolean; journalId:LocalId; through:U64; contextId:LocalId;
transactions:ReconciledTransaction[]; memberships:SourceMembership[];
replacements:{previous:Hex32,replacement:Hex32,at:U64,contextId:LocalId}[];
pendingComponents:{txids:Hex32[],reason:string}[]
}
AcceptedInput carries this ReconciledState. An alternative language binding may expose an equivalent lossless journal plus deterministic reconciler, but must reproduce every field and outcome. ChainView retains medianTimePast and chainPolicyDigest as declared in section 8. Reconcile mutations verify that supplied state equals the deterministic result of retained facts/order/context; callers cannot install arbitrary choices. The journal retains authorization provenance, raw/proof availability positions, context changes, source sequence/generation fences, assessment support and selection/replacement reasons. Replacement records retain replay frontier/order; they are not sorted by txid. Transaction rows sort by txid; membership rows by scope/generation/outpoint; conflicts/evidence lists are sorted and duplicate-free under the common encodings. dependencies retains the actual ordered input-outpoint vector; sorting it would destroy the replacement predicate. Valid Bitcoin transactions cannot repeat an input outpoint. Reconciliation and its accepted revision commit together; projector replay uses that state, not lexicographic observation presentation order as an implicit chronology. One invalidated candidate can change an entire connected component; commit all affected removals/additions/currentness together while preserving the existing source-group barrier. Reads expose pending/stale state while that work is incomplete.
The reusable reconciliation corpus exercises both verification-completion orders with restart, successor-before-predecessor evidence from different sources, incomplete versus merely unfinished earlier evidence, competing final spends overridden by actual selected-chain inclusion, reorg and descendant reassessment, pairwise sequence comparisons, finality boundaries and finalization, and output/withdraw/re-entry with stale replay. Test fixtures use actual signed transactions and independently verified inclusion bytes. A stateful adapter must reproduce those results with its real journal/store and the existing cancellation, partition, bounded-work and recovery requirements.