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.
"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.",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.
"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
}
}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.
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.
| Field | Values |
|---|---|
unknown_id | unk_ and a SHA-256 of domain, question, scope_digest, and comparison_conclusion_scope when present |
revision | an integer from 1 |
previous_revision_digest | the digest of the revision before, or null for revision 1 |
revision_digest | SHA-256 of the record without this field |
scope_digest | the SHA-256 of the subject artifact, or null |
question | non-empty text, whitespace collapsed |
status | open, investigating, blocked, contradicted, or resolved |
severity | low, medium, high, or critical |
domain | free text, function-comparison for rea's own comparisons |
comparison_conclusion_scope | optional: the comparison ev_ id and the dimensions that the unknown is about, only in the function-comparison domain |
supporting_evidence_ids, contradicting_evidence_ids | ev_ ids, with no id in both lists |
required_authority, required_confidence, required_environment | what evidence would settle the question |
recommended_probes | operation and rationale pairs |
relationships | depends-on, related-to, or contradicts another unk_ id |
resolution | null, or verified with at least one ev_ id, or withdrawn, or out-of-scope |
mutation_evidence_ids | the 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.
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