5.2

ResidualUnknown, record_unknown, and coverage

An unknown is a durable record with its own id and revision chain, and an empty list in any result tells you to check coverage.

The _percent_change dossier lists "callees": []. An agent reads that line and writes that the function calls nothing. The record supports less than that. It says which calls Ghidra resolved, and rea has a record type for what is still open. When you finish this section, you can read the fields of a ResidualUnknown, open one with record_unknown, and say what an empty list proves.

What an empty list proves

The dossier has an empty callees array and, 840 lines later, a limitation about it. Callees hold resolved targets only. The record says that this set is complete: relationship_coverage.status is complete, and unresolved_calls is empty. Complete means that Ghidra returned every resolved call reference. It does not mean that Ghidra found every call the program can make at run time.

an empty list and the limitation that qualifies itjsonFull log
"callees": [],
…
"Resolved reference metadata identifies computed, indirect, external, call, jump, and data edges; unresolved targetless flows remain unknown, and function classifications distinguish thunks and externals.",
Line 1052 and line 1894 of the file. Everything between them is cut.

The provider documentation states the same limit: the caller and callee lists hold only functions that Ghidra resolved.

The design rule behind every rea tool is shorter still: missing coverage means unknown, not absent.

Relationship coverage: the field relationship_coverage of an Evidence record, which states how complete the caller and callee sets are. Its status is complete, partial, or unknown. A partial value lists its limitations and can name the limited direction. An unknown value gives a reason. When a provider reports no coverage for procedure_callers or procedure_callees, rea writes unknown. A record of any other operation that reports no coverage has null.

Provider caveats stay in limitations and do not make the coverage partial. The same contract sets the limit of complete. It means that rea returned the set without a known read limit. It does not claim every edge that could occur at runtime.

One part of the dossier does declare its coverage in numbers. The value flow reports whether it was truncated and how many operations, inputs, and edges it omitted. inspect-evidence-bundle repeats those numbers for every dossier in a bundle.

the coverage numbers of one dossier in the bundle summaryjsonFull log
"native_dossier": {
  "available": true,
  "procedure_address": "0x100000500",
  "procedure_name": "_percent_change",
  "value_flow": {
    "status": "observed",
    "truncated": false,
    "omitted_operations_lower_bound": 0,
    "known_omitted_inputs": 0,
    "known_omitted_edges": 0
  }
}
One of the eight records. The fields before native_dossier are cut.

A zero here is a count that rea stands behind. An empty array elsewhere is a count with no such statement. The contract says what those fields are for: native_dossier.value_flow reports truncation, unavailable metadata, and unknown metadata on its own.

Two other empty lists in the capture carry the same lesson. rea compare in 14-compare-bundles.json lists ten records that are in one bundle only. It classifies every one as unknown with the conclusion kind unresolved_branch, because a bundle has no marker that says it is complete (compare). The JavaScript summary view in 15-js-app-view-summary.json reports "semantic_unknowns": 118 for a six-file application. It marks the semantic coverage unknown (the application graph). Figure 5.2 puts the three lists side by side.

Fig. 5.2three empty lists and what each one provescomparison
Three empty lists and what each one proves Three columns. A: the _percent_change dossier reports callees as an empty array with relationship coverage complete, which means every resolved call reference was returned, so no resolved callee was seen and calls at run time are not proven absent. B: export_evidence_bundle reports zero unknowns, because no record_unknown call was made, so no question was written and questions can still exist. C: rea compare lists a record with an empty right_evidence_ids array and classifies it unknown as an unresolved branch, because the bundle has no completeness marker, so the record is in one bundle only and removal is not concluded. A · ANALYZE_FUNCTION "callees": [] ev_b492c85e coverage: complete every resolved call reference returned no resolved callee seen runtime calls not proven B · THE BUNDLE EXPORT "unknowns": 0 10-mcp-14, bundle O1 no record_unknown call the session held no ResidualUnknown yet no question was written questions can still exist C · REA COMPARE "right_evidence_ids": [] one record of bundle O1 classification: unknown unresolved_branch, no completeness marker in one bundle only removal is not concluded
An empty callees array with complete coverage, a zero unknown count, and a one-sided bundle record each prove that nothing was observed, never that nothing exists. Read each column top to bottom, from the captured value to the record's own words to what you can say.

The ResidualUnknown record

Residual unknown: a question that the evidence does not yet settle. It is kept as a record with an unk_ id, a revision chain, a status, and the evidence that would close it. A residual unknown has these fields.

FieldValues
unknown_idunk_ and a SHA-256 of domain, question, scope_digest, and comparison_conclusion_scope when present
revisionan integer from 1
previous_revision_digestthe digest of the revision before, or null for revision 1
revision_digestSHA-256 of the record without this field
scope_digestthe SHA-256 of the subject artifact, or null
questionnon-empty text, whitespace collapsed
statusopen, investigating, blocked, contradicted, or resolved
severitylow, medium, high, or critical
domainfree text, function-comparison for rea's own comparisons
comparison_conclusion_scopeoptional: the comparison ev_ id and the dimensions that the unknown is about, only in the function-comparison domain
supporting_evidence_ids, contradicting_evidence_idsev_ ids, with no id in both lists
required_authority, required_confidence, required_environmentwhat evidence would settle the question
recommended_probesoperation and rationale pairs
relationshipsdepends-on, related-to, or contradicts another unk_ id
resolutionnull, or verified with at least one ev_ id, or withdrawn, or out-of-scope
mutation_evidence_idsthe ev_ ids of the records that changed this unknown, at least one

The schema refuses a record that breaks one of eleven rules. Four of them keep the identity and the revision chain sound. The unknown id must match the hash of its own fields. The revision digest must match. Only revision 1 has no predecessor. Only a resolved unknown carries a resolution.

The other seven check the evidence and the links. A contradicted unknown needs contradicting evidence. An unknown cannot relate to itself, and no relationship can appear twice. No evidence id can appear twice in one list, and no id can both support and contradict the unknown. A comparison scope must name a comparison that supports or contradicts the unknown. It is allowed only in the function-comparison domain.

The id makes the question the identity. createResidualUnknown collapses whitespace in the question, then hashes canonical JSON of domain, question, scope_digest, and the comparison scope when the unknown has one. The same question in the same domain about the same target always has the same unk_ id. A second record_unknown with that question therefore answers already-exists. The first status is contradicted when the input names contradicting evidence, and open otherwise.

A change is a new revision, never an edit. update_unknown takes expected_revision and the complete new state. The ledger answers not-found for an unknown id and revision-conflict when the expected revision is stale. The new revision has revision plus one and the old revision_digest as its previous_revision_digest.

list_unknowns filters by status, severity, and domain, and returns the current heads in id order. It returns all of them by default. Give offset and limit to get one page. The result gives total and next_offset, which is null on the last page.

Every mutation is itself an Evidence record. Its provider is rea-unknown-registry version 1 and its predicate type is rea.residual-unknown-mutation. Its confidence is derived and its authority is analyst-inference. It carries one limitation: "Registry mutation evidence records analyst intent, not proof of the answer." The unknown's mutation_evidence_ids name those records, so the two ledgers share one hash-linked history.

Fig. 5.3a ResidualUnknown and its revision chainstructure
A ResidualUnknown and its revision chain Left, revision 1 of the unknown that compare_functions records when the _total comparison is changed: an unk_ id hashed from domain, question, scope digest and comparison scope, revision 1 with a null predecessor, status contradicted, severity medium, domain function-comparison, a comparison scope that names the comparison and its five changed dimensions, the left dossier and the comparison record as supporting evidence and the right dossier as contradicting evidence, the artifact bytes as required authority, required confidence observed, one recommended analyze_function probe, and one mutation evidence id. Right, what update_unknown changes: expected_revision 1, revision 2, the previous revision digest, a new revision digest, status resolved with a verified resolution, and two mutation evidence ids. Below, the mutation Evidence record that every change writes, from provider rea-unknown-registry. RECORD_UNKNOWN, AS COMPARE_FUNCTIONS CALLS IT UPDATE_UNKNOWN ResidualUnknown, revision 1 unknown_id: unk_ + SHA-256 of domain, question, scope_digest, comparison scope revision: 1 previous_revision_digest: null revision_digest: SHA-256 of these rows scope_digest: the open target digest question: Function comparison is changed (comparison ev_40ba96d8) status: contradicted severity: medium domain: function-comparison comparison_conclusion_scope: ev_40ba96d8, assembly, cfg, pseudocode, references, strings_names supporting_evidence_ids: ev_a1fe53ff, ev_40ba96d8 contradicting_evidence_ids: ev_bfe3275b required_authority: the artifact bytes required_confidence: observed recommended_probes: analyze_function relationships: [], resolution: null mutation_evidence_ids: 1 registry record after update_unknown expected_revision: 1 revision: 2 previous_revision_digest: the revision 1 digest revision_digest: new status: resolved resolution: verified, rationale, evidence_ids mutation_evidence_ids: 2 unchanged: unknown_id, question, domain, scope_digest, comparison scope update mutation Evidence: provider rea-unknown-registry, version 1 predicate rea.residual-unknown-mutation, confidence derived, authority analyst-inference limitation: Registry mutation evidence records analyst intent, not proof of the answer. one more record
An unknown keeps its id across revisions, each revision names the digest of the one before, and each change writes its own Evidence record. Read left to right. Revision 1 is the unknown that registerFunctionComparisonTool.ts records for the _total comparison in the comparison capture. The right column shows the fields that an update_unknown call changes.

Unknowns that rea opens itself

Agents open unknowns with record_unknown, and rea opens some on its own. compare_functions records one whenever its status is not unchanged. The domain is function-comparison. A changed comparison gets severity medium. Its supporting evidence is the left dossier and the comparison record, and its contradicting evidence is the right dossier, so its first status is contradicted.

An unknown comparison gets severity high, and all three records support it. Each unknown names the comparison in comparison_conclusion_scope. A changed comparison names its changed dimensions there. An unknown comparison names its unknown dimensions and adds the dimension overall. Both ask for shipped-artifact authority and observed confidence, and both recommend one probe: analyze_function, with the rationale "Capture complete dossiers for both functions under the same target context."

The capture ran into this rule twice. The _total comparison in 10-mcp-21-compare-functions-total.json was changed, so it opened a medium unknown with status contradicted. The _percent_change comparison in 10-mcp-20-compare-functions-percent-change.json was unknown on its references dimension. It opened a high unknown with status open, scoped to overall and references.

No file shows either record, because both bundle exports ran before any comparison. Every count in the capture therefore reads "unknowns": 0 or "unknown_revisions": 0. The rea compare summary keeps separate counters for unknown history, unknowns_added, unknowns_advanced, and unknowns_diverged (14-compare-bundles.json), and all of them are zero for the same reason.

Workflow tools do the same when they cannot finish. The user-visible error catalog at the tag lists the question each one records, for example "Does the reconstruction satisfy every declared claim?" for reconstruction verification. Lesson 35 builds the same ledger in Python, with the question as the identity and the required authority as the closing condition.

rea (rea-agents) with the Ghidra provider rea-agents 6.7.0, Ghidra 12.1.4 · 27dc42d · 2026-10-18 · Edition 2026.10