Skip to content

tutorial

Chapter 3 of 6

Chapter 3 — Grounded Fields

by Rod Rivera Published

Every figure carries the record it came from, because a value and its citation are one type. An unsourced field renders blank, never plausible.

The obvious way to enforce provenance is to build the document from plain values and then run a validator over it that asks “is everything sourced?”.

That design fails in a specific and predictable way. The validator can only check the fields it knows to look at, so the first field somebody adds without updating the validator is unsourced and silent. The check drifts behind the document, and you find out when a client does.

A value and its citation are one type

docpkg/sources.py takes the other approach. A document value is not a string:

@dataclass(frozen=True)
class Sourced:
    value: Any
    citation: Citation

There is no constructor that produces a value without a citation. So an unsourced value cannot be built, and a field added next year is sourced or it does not render — without anyone remembering to extend a list.

A Citation names three things, and all three are needed:

@dataclass(frozen=True)
class Citation:
    source_id: str    # which file
    record_id: str    # which record inside it
    field: str        # which key of that record

A citation naming only the file is the documentary equivalent of “it was on the internet somewhere”. The source id is checked at construction against a closed registry, so a citation to an invented origin raises immediately:

def test_a_citation_to_an_undeclared_source_is_rejected(self):
    with self.assertRaises(UnknownSource):
        Citation("some-source-nobody-declared", "REC-1", "value")

Two sources, because one cannot teach this

The document draws on two fixture sources, and the split is the point:

SourceAuthoritative forKnows nothing about
Custodian position extractHoldings, valuations, chargesWhat the client wants
Client fact-findObjectives, risk profile, capacity for lossWhat anything is worth

With a single origin, “where did this come from?” has only one answer and the citation is decoration. With two, the provenance table is doing real work — and the commonest provenance bug becomes visible. It is not an unsourced figure. It is a figure sourced to a file that happens to contain a similar number for a different reason.

That is why each source records what it is authoritative_for, and why the rendered document prints those claims:

## Sources

| Source | Authoritative for |
| --- | --- |
| Approved disclosure library | regulated wording, quoted verbatim |
| Client fact-find questionnaire | objectives, risk profile and capacity for loss |
| Custodian position extract | holdings, valuations and charges |

The third surface: text that is quoted, never generated

references/disclosures.json is not a fourth integration. It is a reference surface, and the distinction matters.

A figure is a value: it can be looked up, compared, re-derived. A regulated paragraph is a sentence, and the only safe operation on a regulated sentence is to quote it verbatim with an identifier attached.

Ask a model to “summarise the risk warning” and it produces something that reads better and means something slightly different, and nobody downstream can tell which words were approved and which were improvised. So the renderer does not accept a risk warning as text. It accepts a reference id, looks it up, and emits the stored wording. The suite checks the output byte-for-byte against the library:

def test_disclosures_are_quoted_verbatim_from_the_library(self):
    approved = {d["record_id"]: d["text"] for d in library["disclosures"]}
    document = render_markdown(build_fixture_state())
    for text in approved.values():
        self.assertIn(text, document)

Unsourced renders blank

The fixture deliberately leaves the property holdings unsourced, so the shipped document has real gaps in it. A demo where every field happens to be populated cannot show what the blank rule does, and the blank rule is half the point.

| Property funds | — |
| Property funds weight | — |

An em-dash. Not N/A, not TBC, not 0, not an empty cell. A reader skimming a column of numbers has to be able to see that something is missing, and a zero reads as a figure.

The strong form of the test is not that a blank appears. It is that no digit ever appears where a blank belongs:

def test_an_unsourced_field_never_renders_a_plausible_figure(self):
    rows = [ln for ln in document.splitlines() if ln.startswith("| Property funds")]
    for row in rows:
        self.assertIn(BLANK, row)
        self.assertNotIn("£", row)

And pointing a field at a record that does not exist gets you the same blank, rather than an error or a guess:

def resolve(citation: Citation) -> Any:
    ...
    return None

None — never a default, never the nearest match. Returning a plausible substitute here would defeat every guard downstream, because downstream cannot tell a real value from a helpful one.

Why blank rather than refuse

The asymmetry is deliberate, and it is worth being explicit about which way it runs.

A blank in a client document is embarrassing, and somebody fixes it that afternoon. A plausible wrong number in a client document is a misrepresentation that nobody notices until it matters.

So an unsourced field renders blank and the document still ships with its gaps listed. A field whose citation has stopped agreeing with its record is a different condition entirely, and that one refuses. Chapter 5.