Skip to main content
Some questions are about a change, not a document: “did this listing change materially after we approved it?”, “did the renewal clause change between these two versions of the contract?”, “is this edit trying to get past the moderation the original passed?”. The usual answer is a pipeline: a diff job, an edit lock during review, a re-review queue. In Vainona it is a judgment whose context recipe has previous: the engine sees the document as the judgment’s last successful evaluation saw it, beside the version it is judging now.

Moderate edits to approved listings

A marketplace approves a listing, and the seller edits it afterwards. The moderator writes attributes.approved_at when they approve, which they do anyway. This judgment asks whether an edit changed the listing materially since it was approved:
The engine sees both versions, each path once as it is now and once as it was:
Query on the threshold to build the re-review queue, with no queue of your own: ["answers.material_edit.thresholds.review", "Eq", true]. A subscription on the same filter tells you as each edit lands.

What “previous” is

previous.fields names the document’s own paths, state.* or attributes.*, rendered exactly as fields renders them, last_n and window included. Each renders as a previous.<path> entry after the fields entries. It never reads another document: for those, see related documents. It is not simply the revision before the write. The worker judges only the newest revision of a document, so if two edits land close together, or inside the judgment’s debounce_ms, nobody judged the one in between. Comparing with it would hide the first edit. Instead, with each successful evaluation, Vainona stores two renderings of the document beside the answer: the one the verdict compared against, and the one of the revision it judged. When a new revision is judged: So two quick edits are compared with the last verdict, not with each other, and a failed evaluation changes nothing: its retry compares with what the last success saw.

A re-upsert costs nothing

Many systems re-send whole records every night, whether they changed or not. A re-upsert with the same values renders equal to the stored current, so the engine would see exactly the context the last verdict saw. The context hash matches, the answer is kept for the new revision, and nothing is billed. The next real edit pays once, as any change does. An edit to a path neither fields nor previous.fields names changes nothing the engine sees either, so it costs nothing too.

Since approved: the anchor

Without an anchor, the previous moves on with every verdict. An edit the moderator rejected becomes the previous for the next edit, and a seller can walk a listing a small step at a time, each edit only slightly different from the one before, far from what was approved. With anchor, an attribute such as attributes.approved_at, the stored previous moves on only when that attribute’s value changes, and then to the revision that carries the new value: the one approved. Every later edit is compared with it, however many rejected edits come between, until the next approval. Before the anchor is first set, the first revision the judgment judged stands in for it. The anchor costs one attribute value beside the stored renderings. The approval write itself is judged once, and its answer compares the approved revision with itself.

Where it shows

  • The answer carries previous_revision: the revision its previous rendering came from, absent when it showed nothing. A failed answer keeps the last success’s, with its numbers.
  • The evaluation records previous_revision, or first_revision: true when the document had no stored rendering in its incarnation, and its stored context holds both renderings. Fetch it with include=history,context on a get, or open the document in the dashboard.
  • A re-created id (deleted, then written again) starts a new incarnation, and the old one’s renderings count as none: its first evaluation shows nothing.

Cost and limits

previous roughly doubles the context of the paths it names, and context size is the cost: see size classes. Name only the paths a change of which matters, and prefer short ones: a title and a price, not a whole description history. Over max_tokens, the compiler cuts the previous.<path> entries, the last first, before it cuts any field, so the current version is always shown whole first. The renderings are stored as part of the namespace’s data, at most two contexts’ worth per document for each judgment with previous.

Other uses

  • Contracts. A clause-level question over state.clauses.renewal and state.clauses.termination: “did the renewal terms change against the customer’s interest?”, anchored on attributes.signed_at.
  • Change monitoring. Supplier records, product catalogues or policy pages re-imported nightly: only real changes are judged, and each is judged against the last verdict.
  • Profile edits. “Does this profile edit look like an account takeover?” over the email, payout details and display name, without an anchor, so each edit is compared with the last one judged.