Running a Disclosure Review Board Checklist
This guide, part of Disclosure Review & Release Governance, sets out the working procedure for the board that reviews spatial data releases the automated checks cannot clear on their own.
Problem Context & Constraints
A disclosure review board — sometimes a data release committee, sometimes two senior staff — exists to make judgement calls. Should a tract-level map of overdose deaths be published with suppression at 11 or at 20? Can a researcher receive geocoded birth records masked to 500 metres? Is a new dashboard’s finest filter combination safe? These questions have no mechanical answer, and different reviewers, meeting on different days, will answer them differently unless the process gives them the same information and asks the same questions.
Boards fail in recognisable ways. Incomplete requests: the analyst submits a table with no information about what else has been published at that geography, and the board either approves blind or sends it back, costing weeks. Inconsistent decisions: similar requests get different answers because nobody remembered the precedent. Lost reasoning: the minutes say “approved”, and a year later nobody knows what conditions applied or why. Bottlenecks: every release goes to the board, including routine ones that standing rules should have cleared.
A checklist fixes most of these. It does not replace judgement; it ensures judgement is exercised on the same evidence every time and recorded in the same shape.
Prerequisites
python3.11pydantic2.7.1,pyyaml6.0.1- A board charter naming members, quorum, meeting cadence and the emergency subgroup
- The program’s policy file and release register, as described in the parent guide
- Automated check output for the product under review
- A shared location for decision records that outlives individual staff accounts
Step-by-Step Solution
Define the request as a validated schema so incomplete requests are rejected automatically, give every reviewer the same checklist, and write the decision as a structured record linked to the release register.
# Structured request, checklist and decision record for a disclosure review board.
# Pinned: pydantic==2.7.1, pyyaml==6.0.1
import datetime as dt
import logging
from enum import Enum
import yaml
from pydantic import BaseModel, Field, field_validator
logging.basicConfig(level=logging.INFO, format="%(asctime)s %(levelname)s %(message)s")
log = logging.getLogger("drb")
class Audience(str, Enum):
public = "public"
partner_agency = "partner_agency"
named_researcher = "named_researcher"
class Request(BaseModel):
product_id: str
description: str = Field(min_length=40)
geography: str # e.g. "tract_2020"
finest_stratification: list[str] # e.g. ["age_5yr", "sex", "month"]
measures: list[str]
protections: list[str] # e.g. ["suppress<11", "complementary"]
automated_findings: list[str]
related_releases: list[str] # register ids at overlapping geography/time
audience: Audience
purpose: str = Field(min_length=40)
requested_by: str
@field_validator("related_releases")
@classmethod
def must_consider_prior(cls, v):
if v == []:
raise ValueError("list related releases, or ['none-found'] after a register search")
return v
CHECKLIST = [
("identifiability", "Could any released cell, point or record identify a person to someone with local knowledge?"),
("small_populations", "Are there areas or strata with very small populations, even where counts pass the threshold?"),
("combination", "Can any protected value be recovered by combining this release with listed prior releases?"),
("external_data", "Could public data (news, obituaries, property records) be linked to released values?"),
("audience_controls", "For non-public audiences, is a signed agreement forbidding re-identification in place?"),
("necessity", "Is the requested granularity necessary for the stated purpose, or would coarser data serve?"),
("revision_risk", "Will later revisions of this product create differencing pairs?"),
]
class Decision(BaseModel):
product_id: str
outcome: str # approved | approved_with_conditions | declined | returned
conditions: list[str] = []
checklist_answers: dict[str, str]
residual_risk: str
risk_owner: str
members_present: list[str]
decided_on: dt.date
review_by: dt.date | None = None
precedent_tags: list[str] = []
def intake(path: str) -> Request | None:
try:
req = Request(**yaml.safe_load(open(path)))
except Exception as e:
log.warning("request %s returned to submitter: %s", path, e)
return None
log.info("request %s accepted for agenda", req.product_id)
return req
def decide(req: Request, answers: dict[str, str], outcome: str, quorum: list[str],
min_quorum: int, **kw) -> Decision:
missing = [k for k, _ in CHECKLIST if k not in answers]
if missing:
raise ValueError(f"checklist incomplete: {missing}")
if len(quorum) < min_quorum:
raise ValueError(f"quorum not met: {len(quorum)} < {min_quorum}")
d = Decision(product_id=req.product_id, outcome=outcome, checklist_answers=answers,
members_present=quorum, decided_on=dt.date.today(), **kw)
log.info("decision %s: %s (%d conditions)", d.product_id, d.outcome, len(d.conditions))
return d
The validator on related_releases enforces the single most important habit: the requester must have searched the register before submitting. An empty list is rejected; a list containing none-found is accepted, and records that the search was done.
Each checklist answer is free text, not a tick box. “No — all tracts exceed 1,200 residents in every age band” is useful to a future reviewer; “No” is not. The precedent_tags field lets later reviewers find earlier decisions on similar products — “tract-level mortality”, “masked points for research” — so decisions stay consistent.
Validation & Edge Cases
Conflicts of interest. The analyst who produced a product should not sit in judgement of it, and a board member whose program requested the release should declare that interest. Record declarations in the decision.
Precedent drift. Consistency requires looking back. Before deciding, search earlier decisions by precedent tag and note in the record whether this decision follows or departs from them, and why. A board that silently departs from precedent confuses everyone who submits requests.
Conditional approvals nobody checks. A condition such as “suppress at 20 rather than 11” is useless if nobody confirms it was applied. Require the requester to resubmit the final product for automated checks against the conditions, and record the confirmation before release.
Time-limited approvals. Approvals for recurring products should expire, typically after a year, so they are revisited as populations, geographies and other releases change. The review_by date drives that.
Emergency decisions. Decisions by the emergency subgroup use the same template and checklist, marked as emergency, and are reviewed by the full board at its next meeting. That review can confirm, add conditions for future releases, or withdraw the product.
Declined requests that come back unchanged. A requester who was declined may resubmit the same product with a different description. Link resubmissions to the earlier decision by product identifier and precedent tag, and require the new request to state what changed. A board that reviews the same product twice without knowing it risks approving on the second attempt what it rightly declined on the first.
Split decisions. When members disagree, record the dissent and its reasoning alongside the majority decision. Dissents are often where the next incident was foreseen, and they give the annual review of standing rules something concrete to examine.
Research extracts. For releases to named researchers, the board’s checklist answers depend heavily on the data use agreement. Attach the agreement to the decision, confirm it forbids linkage and re-identification and requires destruction at the end of the project, and note the agreement’s expiry.
Board members new to spatial data. Members from legal or program backgrounds may not see how geography interacts with small counts. A short briefing pack — two or three past cases showing differencing and local-knowledge disclosure — does more for decision quality than any amount of written policy, and should be part of onboarding for every new member.
WARNING drb request req-2026-114.yaml returned to submitter: related_releases: list related releases, or ['none-found'] after a register search
INFO drb decision od-tract-2025: approved_with_conditions (2 conditions)
Compliance Notes
Keep the charter, every request, every decision record with checklist answers, conflict declarations, condition confirmations and expiry reviews. Store them in a system with access controls and retention that outlast the board’s current membership.
Decision records may themselves contain sensitive detail — descriptions of small populations or of why a release was risky. Restrict access to board members and designated staff, and publish only a summary of decisions, such as counts by outcome and the general conditions applied, if transparency reporting requires it.
Related Topics
- Disclosure Review & Release Governance — the parent guide.
- Documenting Residual Disclosure Risk for a Release — writing the residual risk statement the decision requires.
- Validating Geomasks With Spatial K-Anonymity — evidence the board needs for masked point releases.