Skip to content

Affidavits & Provenance

An affiant is one who swears to the truth of a written statement. Affiant borrows the metaphor directly: every AI-proposed database mutation becomes an Affidavit — a sworn statement, one field at a time, about where each value came from and how much the framework trusts it.

This page covers the two types that make that possible — Affidavit and ProvenanceTag — and the rule that makes them trustworthy: provenance is never optional, never omitted, and never invented.

Affidavit is defined in Affiant.Abstractions.Models and is the core write-side contract of the framework:

public sealed record Affidavit(
string OperationType,
string EntityType,
string? EntityId,
AffidavitField[] Fields,
float AggregateConfidence,
string[] Warnings,
bool RequiresConfirmation);
  • OperationType — a domain-specific label such as "UpdateCustomer" or "CreateWorkOrder". It is a plain string, not a closed enum; hosts choose their own vocabulary.
  • EntityType — the domain entity being mutated, e.g. "WorkOrder".
  • EntityId — the primary key of the entity being changed. It is null for create operations, where no identity exists yet.
  • Fields — every field the mutation touches, each one an AffidavitField (below). This array is never empty for a real proposal — an Affidavit with zero fields swears to nothing.
  • AggregateConfidence — the minimum confidence across all fields. A single low-confidence field drags the whole Affidavit down; the framework never lets one weak field hide behind a strong average.
  • Warnings — business-rule violations detected while assembling the proposal, surfaced to the reviewer alongside the fields.
  • RequiresConfirmation — whether this Affidavit needs a human reviewer at all. A host’s IApprovalPolicy can override this for auto-approved (Standing Order) cases — see Review Gate & Write Executors.

An Affidavit never causes a write by itself — see the Seven Normative Rules (Rule 3) and Tool Envelopes for how a WriteProposal carrying an Affidavit reaches a human before any row changes.

Each entry in Fields is an AffidavitField:

public sealed record AffidavitField(
string Name,
object? Value,
object? PreviousValue,
ProvenanceChain Provenance,
bool IsMandatory = false);
  • Name — the domain field name, e.g. "priority" or "customerEmail".
  • Value — the proposed new value.
  • PreviousValue — the current value, or null for a create operation where there is nothing to compare against.
  • Provenance — the full ProvenanceChain for this field, described below.
  • IsMandatory — whether this field is required for the operation to make domain sense.

IsMandatory is not decorative. When a host implements ITaskInferenceStrategy to drive structured-output inference (see Tool Envelopes), it declares its field schema as a list of TaskInferenceField records:

public record TaskInferenceField(
string Name,
string JsonType,
string Description,
int? MaxLength = null,
string? Pattern = null,
IReadOnlyList<string>? Enum = null,
bool Required = false);

Every TaskInferenceField declared with Required = true must project to an AffidavitField with IsMandatory == true in the resulting Affidavit. Affiant’s compliance harness — see The Compliance Harness — checks this projection mechanically, so a strategy cannot silently drop a required field’s mandatory status on the way to the Evidence Card.

What IsMandatory does not do: it does not block approval by itself, and it does not force a value to be present. A mandatory field can legitimately arrive at review with Value unset and provenance ProvenanceSource.Empty — the field is still empty, but sworn to be empty rather than silently missing. Deciding whether an empty mandatory field is acceptable to approve is a reviewer-UI concern: the Evidence Card renders IsMandatory and ProvenanceSource.Empty together as a visual flag, and the human reviewer — not the framework — makes the call. This mirrors Rule 2 of the seven normative rules: the same data serves the LLM’s reasoning and the UI’s rendering without either side inventing meaning the other didn’t provide.

Every field value the framework tracks carries a ProvenanceTag:

public sealed record ProvenanceTag(
ProvenanceSource Source,
float Confidence,
string? Evidence,
int? ConversationTurn)
{
public static ProvenanceTag Empty { get; }
public static ProvenanceTag FromTool(string toolName, float confidence = 0.9f);
public static ProvenanceTag FromInference(string fieldName, float confidence = 0.6f);
public static ProvenanceTag FromDefault(string reason, float confidence = 0.3f);
public static ProvenanceTag FromUser(string fieldName);
}
  • Source — which of the seven ProvenanceSource values produced this value. Covered in full below.
  • Confidence — a 0.0–1.0 score. FromUser always assigns 1.0f; the other factories default to 0.9f (FromTool), 0.6f (FromInference), and 0.3f (FromDefault), each overridable via the optional parameter.
  • Evidence — a human-readable explanation of why this source and confidence were assigned, e.g. "Extracted from SearchCustomers" or "LLM inferred: priority". This is what a reviewer reads on the Evidence Card to understand why, not just what.
  • ConversationTurn — which conversation turn produced the value, or null for sources that aren’t conversational (an external API lookup has no turn to point to).

The four non-Empty, non-UserStated factories exist so plugin and inference code never hand-constructs tags with ad hoc confidence numbers. FromTool is for values lifted directly out of a deterministic tool result; FromInference is for values an LLM inferred from conversational signal; FromDefault is for deterministic fallback rules; FromUser is for values the user stated directly, always at confidence 1.0. Reach for ProvenanceTag.Empty — not a hand-rolled tag — whenever there is genuinely nothing to swear to.

A single tag captures a field’s current provenance. ProvenanceChain captures its history — the answer to “how did this field arrive at its current value?”:

public sealed record ProvenanceChain(
ProvenanceTag Current,
IReadOnlyList<ProvenanceTag> Prior)
{
public static ProvenanceChain From(ProvenanceTag tag);
public ProvenanceChain Append(ProvenanceTag newer);
public ProvenanceChain Merge(ProvenanceTag candidate);
public ProvenanceChain AppendChain(ProvenanceChain other);
}

Prior is ordered newest-first. Append unconditionally promotes a new tag to Current and pushes the old Current onto Prior — used when a later turn definitively supersedes an earlier value, such as a reviewer’s amendment.

Merge is the more interesting operation, because it encodes the determinism hierarchy directly:

public ProvenanceChain Merge(ProvenanceTag candidate)
{
var candidateWins =
candidate.Confidence > Current.Confidence ||
(candidate.Confidence == Current.Confidence &&
(int)candidate.Source < (int)Current.Source);
// candidateWins: candidate becomes Current, old Current moves to Prior.
// otherwise: Current is unchanged, candidate is recorded in Prior.
}

Higher confidence wins outright. When two tags tie on confidence, the tag whose Source has the lower enum ordinal wins — which is exactly the determinism hierarchy below, because the enum is declared in that order. The hierarchy is a single ordered list rather than a separate ranking table for exactly this reason: the ranking is the enum’s declaration order, and Merge reads it directly off (int)Source. Either way, the losing tag is never discarded — it lands in Prior.

Merge is what happens, for example, when a task-inference step produces an LLM-inferred value for a field the Context Fabric already holds from a higher-confidence source: the higher-confidence value wins, and the loser is preserved — not discarded — in the chain.

ProvenanceSource is a closed, seven-value enum. The declaration order is the trust order, most deterministic first:

public enum ProvenanceSource
{
UserStated,
External,
Computed,
Conversation,
Inferred,
Default,
Empty
}
Source Meaning Default confidence from ProvenanceTag factories
UserStated The user explicitly stated this value in chat — e.g. “my email is [email protected]”. Maximal trust. 1.0
External Fetched from an authoritative external system: an API lookup, a database read, a third-party service response. — (no dedicated factory; construct directly)
Computed Derived by deterministic business logic — tax calculation, date math, priority-based SLA computation. Reproducible from inputs, not guessed. — (no dedicated factory; construct directly)
Conversation Mentioned in conversation context through a tool result, but not directly stated as a value by the user. 0.9 via FromTool
Inferred LLM-inferred from conversational signals rather than read or stated directly. Requires reviewer confirmation before committing. 0.6 via FromInference
Default A system default or fallback value applied when no conversational basis exists. 0.3 via FromDefault
Empty Provenance is unknown. Not “low trust” — the explicit, sworn statement that the framework has no basis for a value at all. 0.0 via ProvenanceTag.Empty

One deliberate omission: there is no HumanCorrected source. When a reviewer amends a field during approval, the amendment is recorded as a new tag with source UserStated — a reviewer’s correction is, evidentially, the same kind of ground truth as a user’s direct statement. The original tag is not discarded; it survives in the chain’s Prior list. The taxonomy stays clean this way: ProvenanceSource describes where a value came from, not what subsequently happened to it — that second question is what ProvenanceChain is for.

This is the seventh of the framework’s Seven Normative Rules: every Affidavit field carries provenance, no exceptions. If a field’s origin is genuinely unknown, it must be tagged ProvenanceSource.Empty — never left untagged, never omitted from the array.

The reasoning is adversarial, not stylistic. A field with no provenance tag and a field tagged Empty look identical to a naive reader — but the framework does not let the first case exist at all: AffidavitField.Provenance is a non-nullable ProvenanceChain, and ProvenanceChain.Current is a non-nullable ProvenanceTag. There is no code path that produces a field without provenance; there is only the path that produces ProvenanceTag.Empty. Omission is not a lesser evil than a wrong tag — it is a category the type system refuses to allow, because a missing tag is indistinguishable from “the framework forgot to track it,” while an Empty tag is a positive, checkable assertion that nothing is known.

The reviewer-facing consequence: the Evidence Card renders provenance as a visual indicator per field — green for UserStated, amber for Inferred, grey for Default, and so on — so a reviewer approving an Affidavit is never guessing which fields are solid and which are the LLM’s best guess. A value present alongside ProvenanceSource.Empty is treated by Affiant’s own compliance tooling as a hollow signature — a value asserted with nothing sworn about its origin. The compliance harness enforces this mechanically for any host-authored inference strategy: a populated field with Empty provenance fails verification outright, because it is the exact shape of a regression where a filter produced values without tracking where they came from.

Affidavit is what a WriteProposal carries out of a write tool — see Tool Envelopes. It is what the Docket holds while a mutation awaits review, and what the Evidence Card renders for the reviewer. Once approved, it is what a host’s IWriteExecutor receives — see Review Gate & Write Executors — as the only path by which a proposed value is allowed to reach the database.