---
name: verify-a-listing-against-its-source
description: Check an entry against the page its subject publishes, not the aggregator that carried it, and write down only what that page actually said.
runbook:
  schema_version: v1
  semver: 1.0.0
  profile: P1
  min_runtime_profile: R1
  domain: data-pipelines
  targets:
    - postgres
  trigger: manual
  execution: human-with-agent
  risk: reversible-write
  blast_radius: service
  duration: 5-30m
  capabilities:
    - cli:psql
  publisher: std
  lang: en
  license: CC-BY-4.0
  relations:
    - kind: requires
      ref: std/enrich-from-pages-you-do-not-control@1.0.0
      why: The fetch-level rules — what a refusal means, what a status code means, when a browser is needed — are there and are not repeated here.
  inputs:
    entry_id:
      type: string
      required: true
      description: the entry being verified
    source_url:
      type: string
      required: true
      description: the page published by the subject itself, not by an aggregator
  steps:
    - id: s1
      kind: decision
      title: Is this the subject's own page?
      question: Was this page published by the thing being described, or by somebody carrying it?
      branches:
        aggregator: s2
        default: s3
    - id: s2
      kind: decision
      title: Is there a page the subject publishes?
      question: Does the subject publish this itself anywhere, or does only the carrier have it?
      branches:
        carrier_only: s9
        default: s3
    - id: s3
      kind: decision
      title: Is the entry still current?
      question: Does the page's title, or a direct statement in its body, say it is gone — rather than the words appearing somewhere in the text?
      branches:
        gone: s8
        default: s4
    - id: s4
      kind: decision
      title: Is this page about only this entry?
      question: Does the page show other entries alongside — a menu, a catalogue, a list of neighbours?
      branches:
        shows_others: s5
        default: s6
    - id: s5
      kind: action
      title: Take nothing from a page of neighbours
      risk: read-only
      expect: no field is filled from this page; a neighbour's words are not this entry's
      on_fail: escalate:s9
      next: s7
    - id: s6
      kind: action
      title: Read the fields, and only what is stated
      risk: read-only
      expect: each value appears on the page as a statement about this entry, not as a word that matched
      on_fail: escalate:s9
      next: s7
    - id: s7
      kind: action
      title: Write only the empty fields
      risk: reversible-write
      tool: cli:psql
      command: psql -Atc "update entries set checked_at = now() where id = '{{inputs.entry_id}}' and checked_at is null"
      expect: fields already filled are left alone, and nothing unstated is invented
      on_fail: escalate:s9
      next: end:success
    - id: s8
      kind: action
      title: Retire the entry, and say on what evidence
      risk: reversible-write
      requires_approval: true
      expect: the entry is withdrawn, with the page and the words that established it
      on_fail: escalate:s9
      next: end:aborted
    - id: s9
      kind: escalate
      title: Leave it as it is and ask
      to: team:platform
  source:
    url: https://runbooks.directory/r/std/verify-a-listing-against-its-source/source.md
    fetched_at: 2026-09-04
    upstream_state: current
---

## When to reach for this

An entry in your system describes something somebody else publishes — a listing, a product,
an event, a filing — and you are about to act on it: show it to a reader, send somebody to
it, or take it down. What follows is how to check it without being fooled by the page.

The fetching rules — what a refusal means, what a status code means, when a page needs a
browser — are in `std/enrich-from-pages-you-do-not-control` and are not repeated here. This
is about reading the page once you have it.

## Judge by the source, not by the carrier

The rule that changes the most outcomes: **the subject's own page decides.**

An entry marked closed by an aggregator was open on the subject's own site — with details
the aggregator never carried. Acting on the aggregator would have retired a live entry. And
the reverse happens too: a page whose title looked perfectly healthy said, in its body
alone, that it did not exist. Two automated checks passed it.

An aggregator is a copy, made at some past moment, by somebody with no obligation to keep
it current.

## What a page will make you get wrong

Every one of these cost a separate investigation, and each was found on real data.

**A neighbour's words.** A level was read as junior because a side menu listed other
entries, one of them junior. The word was on the page; it was not about this entry.

**Wording that reverses.** "Contract type: permanent" contains the word *contract*, and a
matcher looking for it records the opposite of what the page says.

**A number from the next line.** Two figures sit side by side on one page and belong to
different requirements. The nearer one is not necessarily yours.

**A page of neighbours.** Some sites show dozens of other entries, with their own figures,
before the one you asked for. Nothing on such a page can be attributed — and by the rule
above, it is not the subject's page anyway.

**A form instead of a description.** The link goes straight to an application or enquiry
form: fields to fill in, and nothing that describes the thing.

**A figure that is not a range.** "Starting from" is one number, and a matcher looking for
two never sees it. Absent is not the same as none.

**Words about closure on a living page.** Accept *gone* only from the page's title or a
direct statement about this entry in its body — never from the phrase appearing somewhere in
the text.

## Write only what was stated

Three rules, and the first is the one that decays a database quietly.

**A guess looks exactly like a check.** A value put in by inference is indistinguishable
afterwards from one that was verified, and there is no later moment at which somebody
separates them. Not stated on the page means empty in the record.

**Do not overwrite what is filled.** An existing value may have been checked by hand. A
sweep that overwrites is a sweep that discards work nobody can recover.

**Never correct a name automatically.** Across one batch, 56 of 114 name discrepancies were
real and of entirely different kinds: an abbreviation expanded, an acquired subsidiary
trading under its own name, the same organisation in another language. Each needs a
different answer and none of them is a rename. The name is what the reader sees, so a wrong
correction is a wrong statement about somebody else.
