Groundcheck · Methodology

How Groundcheck actually works.

Groundcheck publishes trust determinations about real, named businesses. This page is the public explanation of the mechanism behind every determination: what we ingest, how we bind a subject to a canonical identity, how we score, the fail-closed gate that decides whether a score is publishable, the retention chain that lets any finding be re-checked, the limits we know about, and why this is not a background check and not an FCRA-regulated consumer report.

For the shorter, one-page framing built around evidence tiers, see /how-it-works. For the model card describing the LLM role and pins, see /methodology/model-card.

§ 1What we ingest

Groundcheck draws on multiple public-record corpus classes. Each source is a deliberate, named, versioned adapter — not a generic web crawl — and each carries a refresh cadence, a refresh method, and a staleness caveat that follows the datum all the way onto the report page.

  • Secretary-of-State business filings. Formation records, standing, officer filings, and dissolution events. The role of this corpus is identity anchoring (binding a subject to a canonical entity) and predecessor detection (a formation whose principals mirror a previously dissolved entity). Refreshed per state on cadences that vary by source; some states are refreshed daily, some monthly, some sit behind managed anti-bot boards and cannot refresh at all.
  • Statewide contractor-license rosters. Where a state issues a statewide general-contractor licence, we ingest the official roster and match subjects directly. See §6 on the states that do notissue a statewide GC licence — “no record here” in those states is not a data gap.
  • Municipal permit records. Building and trade permits from city and county permit offices, used for trade-history signal and jurisdictional footprint. Cadence is per source; permit corpora are among the freshest we hold.
  • Federal adverse-record sources. SAM.gov exclusions, OSHA establishment records, CourtListener federal dockets, SEC EDGAR, USASpending. These feed the adverse-signal lane and are subject to the integrity gate in §4.
  • Retrieval-provider “web-search floor.” For a subject in a state with neither an ingested roster nor a free per-record portal, we run a live public retrieval against the retrieval provider (see PR #1697) and cite the returned URL. This is a weaker tier than a direct roster lookup and is labeled as such on the report.

Coverage is not row count.Two authorities govern what “covered” actually means for a given state: public.gc_state_license_required (which states require a statewide GC licence) and licenseLaneForState() in src/lib/trust/coverage-resolver/taxonomy.ts (which mechanism serves the state). The current split is produced by node scripts/ci/state-answer-matrix.mjs — the number that page shows is the number that is currently true. This page does not restate it.

§ 2How we resolve identity

Every report starts by binding a free-text subject query to one canonical entity. Identity anchoring is the single most consequential step, because every downstream finding is attributed to whatever entity the anchor picked. A wrong-entity anchor is the worst failure mode this system has, and it is what the guardrails below exist to catch.

The anchor is produced by search_roster_entities (a Postgres function reading the ingested roster and SOS corpora, called from src/app/api/trust/autocomplete/route.ts) and src/lib/trust/entity-search.ts. It considers name, state, jurisdiction, and — where present — identifiers like a state-issued licence number or filing ID.

When the anchor cannot pin the subject to a single verified identity, the report is suppressed at output, not answered with the closest guess. The refusal surfaces the specific reason code orphan_no_contractor_binding (see §4 for the reason vocabulary). This is the fail-closed default: an unbound subject is never scored, and an ambiguous match is never scored as if it were unambiguous.

§ 3How we score

A Groundcheck score is produced by a deterministic pipeline. LLM assistance is used only for narrow field-level extraction (see the model card at /methodology/model-card). The stages, in order, are:

STAGE 1

Identity anchor

Bind subject → canonical entity (§2). Unbindable subjects short-circuit here into a refusal.

STAGE 2

Per-source acquisition

Each in-scope source runs via runScraper and lands rows in trust_evidence keyed by trust_evidence.source_key. Ten dispatch forms — switch cases, source-key branches, descriptor engines, a DB-driven permits path — coexist, and are all modeled.

STAGE 3

Per-source verdict

Each source emits a per-source verdict plus a confidence weight (trust_source_registry.confidence_weight). A source can only ever contribute what its confidence weight allows — no source is unweighted, and no source’s verdict is treated as ground truth on its own.

STAGE 4

Composite score

Verdicts are combined into a composite score. The scoring path is shared code with the Broker Defense product (see docs/PRODUCT_BOUNDARY.md), so scoring changes are covered by characterization suites on both sides of that boundary.

STAGE 5

Adverse-claim gate

Before anything is published, the deterministic integrity gate (§4) can suppress a score, strip adverse content, or block publication entirely. This is the last line, and it fires against the composite score, not against individual sources.

Reference maps and full path detail live in docs/ARCHITECTURE-groundcheck.md.

§ 4The fail-closed integrity gate

The integrity gate is the reason Groundcheck can publish adverse determinations about named real businesses without publishing them on a guess. It is deterministic TypeScript and SQL, not an LLM — the decision to publish an adverse claim is never made by a language model.

The gate has two arms:

  1. gateAdverseForIntegrity in src/lib/trust/fcra-adverse-integrity-gate.ts — validates that any surfaced adverse content is supported by the evidence attached to the report. Adverse content that fails the gate is stripped from the render, not softened.
  2. Evidence-floor scrub — a Postgres trigger (enforce_trust_score_evidence_floor) that fires at write time. A score backed by insufficient corroborated coverage never reaches the client to begin with; the report renders a refusal code instead of a numeric verdict.

When the gate suppresses a score, the report renders coverage-framed copy explaining why there is no score. The canonical reason vocabulary lives in src/lib/trust/refusal-copy.ts — four codes, plus a fail-closed default for anything unknown:

Reason codeShown to user asWhat it means
orphan_no_contractor_bindingNo verified identity matchThe subject could not be bound to a single verified contractor identity, so no score is published. This is a limit of records matching — not a finding about the business.
data_integrity_not_okSource data incompleteOne or more public-record sources returned incomplete or degraded data, so no score is published. This is not an adverse finding.
low_score_no_adverse_evidenceNot enough corroborated coverageNo corroborated adverse records were identified, and verified coverage was insufficient to publish a score. The withheld low score is never surfaced, because doing so would itself be the adverse read the scrub exists to suppress.
unspecifiedNo verified scoreGeneric fail-closed for any unknown or absent code.

Fail closed, never open. Weak signal, uncertain identity, or degraded source data suppresses output — Groundcheck never emits an adverse claim on a guess. This is enforced in code and in the database, not in policy alone.

§ 5The retention chain

Every retained scrape is a two-part artifact: the extracted rows in trust_evidence, and the raw upstream payload in the private trust-documents bucket. The bucket path is stored on the evidence row as response_storage_path, so any published finding can be traced back to the exact bytes the source returned at the time we saw them.

The chain matters because a dispute (§9) is not answered from a re-scrape — it is answered from the stored artifact plus a fresh source check. That gives us three things at once: an audit trail that a report was fair at the time of publication, a way to see whether a source itself has changed since, and a way to reproduce a finding without re-hitting a rate-limited or already-changed public source.

A subset of older evidence rows pre-date the retention chain and do not carry a stored artifact. New writes carry the path; older rows are labeled as such on the report and handled with an extra caveat on the datum.

§ 6Known limits

These are the failure modes Groundcheck’s design acknowledges. Each is a live constraint, not a hypothetical, and each is disclosed here because opaqueness about limits is itself a defamation risk.

Stale corpora

Some state licence rosters have gone weeks without a successful refresh, and the FL Secretary-of-State corpus is a frozen ingest whose live source is Cloudflare-blocked. Every surfaced datum on a report carries an as-of / staleness caveat, because stale-cited-as-current is the single largest defamation risk in this system.

Walled boards

Several state boards sit behind managed anti-bot interstitials or CFAA/ToS boundaries that reputable vendors will not proxy. For subjects in those states, licence status is served — when it can be served at all — via the web-search floor, not a deterministic roster lookup. The walled set moves only via legal or credential action, not by writing new scrapers.

States with no statewide general-contractor licence

Not every US state issues a statewide general-contractor licence. In the 17 jurisdictions below, construction is licensed municipally, or not licensed as a distinct trade at the state level at all. A report for a subject in one of these states says so plainly, and never treats absence of a state licence as a red flag.

No statewide general-contractor licence required — verify municipally. Sourced live from the same table the scoring engine reads (gc_state_license_required).

CODEIAILINKSKYMEMONENHNYOHSDTXVTWY

Phoenix / predecessor-dissolution lane is gate-deadlocked

The predecessor-dissolution detector emits candidate chains where a newly formed entity mirrors the principals of a recently dissolved one, but publication is gated on tier and corroboration being satisfied by disjoint sets. A database CHECK constraint (gc_phoenix_chains_contractor_surface_corroboration_check) enforces the deadlock at write time, so the surface flag cannot be flipped by hand for an under-corroborated chain. Publishable population today is zero; the honest ceiling is not much larger and is bounded by acquisition, not by algorithm.

Coverage-matrix status is a stored value, not a live measurement

The gc_source_coverage_matrix table records per-source-per-state reachability. Its reconciliation job is currently demote-only, and validated in that table means reachable, not covering. Reports do not treat a validated cell as a coverage guarantee, and any score derived from that matrix is a floor that drifts down as data is acquired.

Web-search floor is a live dependency

For subjects in states without an ingested roster or a free portal, the retrieval provider is the only licence-answer mechanism. Its liveness depends on external credential state and is monitored by a scheduled probe. When it is down, subjects in those states get the honest “no record” result, not a stale cached answer.

Bindable-but-thin subjects

Identity anchoring can succeed while the corroboration needed for a score does not — the report renders low_score_no_adverse_evidence and suppresses the numeric verdict rather than surface a low score built on thin evidence.

§ 7Not FCRA, not a background check

Groundcheck is a business verification engine. It does not run criminal, credit, or employment vetting on individuals. It is not a Consumer Reporting Agency, and its outputs are not “consumer reports” as defined by 15 U.S.C. §1681a(d). Reports cover business entities (LLCs, corporations, partnerships), not persons.

Some internal code symbols carry FCRA-styled names (gateAdverseForIntegrity, FCRA_NAME_BOUNDARY_ENFORCED, a §1681i-styled dispute route, files under src/lib/compliance/fcra-*). These are internal shorthand for our defamation/accuracy guardrails, not a legal representation that FCRA applies. The naming is flagged for a later rename.

The real legal exposure surface is defamation and accuracy, because reports name real entities and can carry adverse determinations. The failure modes that create liability are wrong-entity anchoring, ungated adverse claims, and stale data presented as current. The whole architecture above — identity gate, deterministic score, fail- closed integrity gate, retention chain, as-of stamps — is a direct response to those three failure modes.

§ 8Calibration

Calibration is the process by which we measure whether the score we publish matches the observed outcome on a labeled subject. We are working on publishing a calibration surface (a confusion matrix over a labeled evaluation set, with the confidence bins visible) so that a reader can see for themselves how well-calibrated the score is at each risk band.

When live, the calibration surface will be linked here. Today the internal artifact is the docs/AUDIT-groundcheck-2026-08-02.md file — a twelve-dimension measured audit against production, with every figure timestamped by in-query select now() — and the daily scoreboard emitted by node scripts/ci/dod-scoreboard.mjs.

A calibrated score is not the same as a correct score on any single subject — it is a claim about the population. The dispute route in §9 is the individual-subject correction path. The calibration surface is the population-level accountability path. Both matter, and neither substitutes for the other.

§ 9How to dispute a finding

Any subject named in a Groundcheck report can dispute the finding. The dispute route lives at /trust/dispute. The route is styled after FCRA §1681i in structure — reasonable-period reinvestigation, source contact where possible, correction or suppression on confirmed error — as an accuracy discipline, not as a legal representation that FCRA applies (see §7).

A dispute triggers a re-check of the identity binding and the underlying sources against the retained artifact (§5). If the finding cannot be reproduced from the public record, the report is suppressed or corrected. Reviewed by a person, not an automated correction.

For anything about the methodology itself — including a report that disagrees with what this page describes — reach us at trust@earthmove.io. A report that disagrees with the methodology described here is a bug, not a policy question.