domain tier

domain-domain-survey

Non-contact domain survey collectors: RDAP-first registration lookup, per-source rate limiting, and preserve-before-parse capture. Every result is a three-state observation.

Non-contact domain survey collectors: RDAP-first registration lookup, per-source rate limiting, and preserve-before-parse capture. Every result is a three-state observation.

Tierdomain
Roleunclassified (baselined)
Pathcrates/domain/domain-survey
Edition2021
Targetsdomain_domain_survey
Public items39 across 5 modules
Tests38

What it is for

Non-contact domain survey.

Collectors that establish what is published about a domain — who registered it, when, with which nameservers and mail configuration, what certificates exist, and whether a website ever stood there.

# Passive means we do not ask the subject

Every collector here consults a third party that already holds the answer: a registry, a certificate transparency log, a web archive, a public recursive resolver. The subject's own systems are never contacted, so the subject learns nothing — not because anything is concealed, but because nothing is requested from them.

That is a property of the sources, not of our cleverness, and it is why these collectors are DisclosureClass::Quiet rather than requiring authorization.

DisclosureClass::Quiet: domain_research_intel::DisclosureClass::Quiet

# Three rules this crate is built on

1. Preserve before parse. Bytes are hashed and kept before anything interprets them, including bytes that failed — see preserve. 2. Rate limit per source, and fail closed. An unconfigured source is denied. Overrunning a registry does not merely slow us down, it manufactures undetermined results — see rate_limit. 3. Never record an undetermined result as a negative finding. Every collector returns Observed<T>, and an absence requires a witness that cannot be minted from a failure.

Observed<T>: domain_research_intel::Observed

# Why rule 3 has a crate built around it

An ad-hoc script once recorded HTTP 502 from a certificate-transparency API as zero certificates ever issued, and that reading — combined with "no archive history" — made a legitimate eight-year-old company look like a purpose-built fraud instrument. It had 301 certificates. The API was down.

The script had no three-state discipline, no rate limiting, and preserved nothing. This crate exists so that collection does not happen that way again.

Capabilities

RawCapture

Preserve before parse.

Item
pub struct RawCapture
RawCapture :: fn preserve(source : impl Into <String>, request : impl Into <String>, captured_at : DateTime <Utc>, http_status : Option <u16>, disclosure : DisclosureClass, body : & u8,) -> Self
RawCapture :: fn parsed_by(mut self, parser_version : impl Into <String>) -> Self
RawCapture :: fn matches(& self, body : & u8) -> bool
RawCapture :: fn is_transport_success(& self) -> bool
RawCapture :: fn is_parsed(& self) -> bool

Backoff

Per-source rate limiting.

Item
pub struct Backoff
Backoff :: fn default() -> Self
Backoff :: const fn new(base_ms : i64, max_ms : i64, max_attempts : u8) -> Self
Backoff :: fn delay_before(& self, attempt : u8) -> Option <Duration>
Backoff :: const fn max_attempts(& self) -> u8

Permit

Per-source rate limiting.

Item
pub enum Permit
Permit :: const fn is_go(self) -> bool
Permit :: const fn delay(self) -> Option <Duration>

RateLimiter

Per-source rate limiting.

Item
pub struct RateLimiter
RateLimiter :: fn new() -> Self
RateLimiter :: fn with_defaults() -> Self
RateLimiter :: fn configure(& mut self, source : impl Into <String>, min_interval_ms : i64)
RateLimiter :: fn check(& self, source : & str, now : DateTime <Utc>) -> Permit
RateLimiter :: fn record(& mut self, source : & str, now : DateTime <Utc>)
RateLimiter :: fn is_configured(& self, source : & str) -> bool

rate_limit::defaults (other)

_No module-level documentation is present in the source._

Item
pub const VERISIGN_WHOIS_MS: i64
pub const REGISTRY_MX_MS: i64
pub const REGISTRO_BR_MS: i64
pub const CCTLD_DEFAULT_MS: i64
pub const RDAP_MS: i64
pub const CRT_SH_MS: i64
pub const ARCHIVE_CDX_MS: i64
pub const PUBLIC_RESOLVER_MS: i64

rdap (other)

RDAP — registration data, parsed into a three-state observation.

Item
pub const PARSER_VERSION: & str
pub const SOURCE: & str

DomainRegistration

RDAP — registration data, parsed into a three-state observation.

Item
pub struct DomainRegistration
DomainRegistration :: fn age_days(& self, now : DateTime <Utc>) -> Option <i64>
DomainRegistration :: fn days_since_change(& self, now : DateTime <Utc>) -> Option <i64>
fn parse(source : & str, http_status : u16, body : & u8) -> Observed <DomainRegistration>

ObservationRow

Survey store — where a collection run stops being a conversation.

Item
pub struct ObservationRow
ObservationRow :: fn from_observed <T : Serialize>(run_id : impl Into <String>, subject : impl Into <String>, capture : & RawCapture, observed : & Observed <T>,) -> Result <Self, StoreError>

StoreError

Survey store — where a collection run stops being a conversation.

Item
pub enum StoreError

SurveyStore

Survey store — where a collection run stops being a conversation.

Item
pub trait SurveyStore

How to use it

No examples/ target and no doctest in this crate's rustdoc. The tests listed under Verification are the closest executable usage.

Module structure

domain_domain_survey

flowchart TD
  n_domain_domain_survey["domain_domain_survey"]
  n_domain_domain_survey --> n_preserve["preserve"]
  n_domain_domain_survey --> n_rate_limit["rate_limit"]
  n_rate_limit --> n_rate_limit__defaults["defaults"]
  n_domain_domain_survey --> n_rdap["rdap"]
  n_domain_domain_survey --> n_store["store"]

Public surface

`preserve`

ItemWhat it is
pub struct RawCaptureA preserved raw response, hashed before any interpretation
RawCapture :: fn preserve(source : impl Into <String>, request : impl Into <String>, captured_at : DateTime <Utc>, http_status : Option <u16>, disclosure : DisclosureClass, body : & u8,) -> SelfPreserve bytes
RawCapture :: fn parsed_by(mut self, parser_version : impl Into <String>) -> SelfRecord which parser interpreted these bytes.
RawCapture :: fn matches(& self, body : & u8) -> boolWhether body is the material this capture preserved
RawCapture :: fn is_transport_success(& self) -> boolWhether the transport reported success
RawCapture :: fn is_parsed(& self) -> boolWhether these bytes have been interpreted yet.

`rate_limit`

ItemWhat it is
pub enum PermitThe outcome of asking to make a request.
Permit :: const fn is_go(self) -> boolWhether the request may proceed immediately.
Permit :: const fn delay(self) -> Option <Duration>How long to wait, if waiting is required.
pub struct RateLimiterPer-source rate limiter
RateLimiter :: fn new() -> SelfAn empty limiter
RateLimiter :: fn with_defaults() -> SelfA limiter preconfigured with defaults for every source this crate uses.
RateLimiter :: fn configure(& mut self, source : impl Into <String>, min_interval_ms : i64)Set the minimum interval between requests to source.
RateLimiter :: fn check(& self, source : & str, now : DateTime <Utc>) -> PermitMay we call source at now? An unconfigured source is denied, not permitted
RateLimiter :: fn record(& mut self, source : & str, now : DateTime <Utc>)Record that a request to source was made at now
RateLimiter :: fn is_configured(& self, source : & str) -> boolWhether source has a configured limit.
pub struct BackoffBackoff schedule for retrying an undetermined result
Backoff :: fn default() -> Self
Backoff :: const fn new(base_ms : i64, max_ms : i64, max_attempts : u8) -> SelfA custom schedule.
Backoff :: fn delay_before(& self, attempt : u8) -> Option <Duration>How long to wait before attempt number attempt (1-based), or None when the budget is spent.
Backoff :: const fn max_attempts(& self) -> u8The attempt budget.

`rate_limit::defaults`

ItemWhat it is
pub const VERISIGN_WHOIS_MS: i64Verisign WHOIS (.com/.net) — bans by IP for hours.
pub const REGISTRY_MX_MS: i64registry.mx (.mx/.com.mx) — few free queries before refusal.
pub const REGISTRO_BR_MS: i64registro.br (.br/.com.br) — documented published limit, strict.
pub const CCTLD_DEFAULT_MS: i64Any other ccTLD registry — assume strict until measured.
pub const RDAP_MS: i64RDAP endpoints — generally friendlier than port-43 WHOIS.
pub const CRT_SH_MS: i64crt.sh — frequently 502s under load.
pub const ARCHIVE_CDX_MS: i64Internet Archive CDX — 429s under load.
pub const PUBLIC_RESOLVER_MS: i64Public recursive resolver — effectively free.

`rdap`

ItemWhat it is
pub const PARSER_VERSION: & strParser identity recorded on every capture, so a reparse is attributable.
pub const SOURCE: & strThe rate-limit source key
pub struct DomainRegistrationWhat a registry says about a domain
DomainRegistration :: fn age_days(& self, now : DateTime <Utc>) -> Option <i64>Age in whole days at now, when a registration date was returned.
DomainRegistration :: fn days_since_change(& self, now : DateTime <Utc>) -> Option <i64>Days since the record last changed, when the registry returned one.
fn parse(source : & str, http_status : u16, body : & u8) -> Observed <DomainRegistration>Parse an RDAP response into an observation

`store`

ItemWhat it is
pub struct ObservationRowOne recorded observation about one subject from one source
ObservationRow :: fn from_observed <T : Serialize>(run_id : impl Into <String>, subject : impl Into <String>, capture : & RawCapture, observed : & Observed <T>,) -> Result <Self, StoreError>Build a row from an observation, keeping the three states distinct
pub enum StoreErrorWhat a store can refuse to do.
pub trait SurveyStorePersistence for a survey run

Re-exports. Exported here, defined elsewhere.

ExportDefined in
DomainRegistrationrdap::DomainRegistration
sha256_hexfoundation_audit_log::hash::sha256_hex
{AdmiraltyGrade,Confidence,DisclosureClass,EmptySuccess,InfoCredibility,Observed,ObservedExt,Presence,SourceReliability,Unknown,}domain_research_intel::{AdmiraltyGrade,Confidence,DisclosureClass,EmptySuccess,InfoCredibility,Observed,ObservedExt,Presence,SourceReliability,Unknown,}
{Backoff,Permit,RateLimiter}rate_limit::{Backoff,Permit,RateLimiter}
{ObservationRow,StoreError,SurveyStore}store::{ObservationRow,StoreError,SurveyStore}
{sha256_hex,RawCapture}preserve::{sha256_hex,RawCapture}

Boundary

Reaches into foundation.

Shares tier domain with 41 other crates: domain-agreements, domain-ai-report, domain-billing, domain-catalog, domain-classify, domain-comments, domain-competitive-intel, domain-contact, … (41 total).

_What this crate deliberately does NOT own is a judgment. No committed registry records one for it, so none is stated here._

Where it sits

Tier (ontology)domain
Architectural role (taxonomy)unclassified (baselined)
Locationcrates/domain/domain-survey
Vocabulary in force (lexicon)current

Tier flow. Which tiers this crate's own edges cross.

flowchart LR
  n_domain["domain"] --> n_foundation["foundation"]

Dependencies

Runtime, in this workspace.

CrateTierOptionalOnly on
`domain-research`domainnoalways
`domain-research-intel`domainnoalways
`foundation-audit-log`foundationnoalways

Runtime, from outside the workspace.

CrateRequirementFeaturesOptionalOnly on
chrono^0.4serdenoalways
serde^1derivenoalways
serde_json^1noalways
thiserror^2noalways
uuid^1v4, v7, serde, jsnoalways

Development, from outside the workspace.

CrateRequirementFeaturesOptionalOnly on
tokio-test^0.4noalways

Build. None.

Depended on by. 2 workspace crates.

Signal flow — what reaches this crate, and what it reaches.

flowchart LR
  n_application_domain_survey["application-domain-survey"] -->|uses| SELF
  n_infrastructure_acquire["infrastructure-acquire"] -->|uses| SELF
  SELF["domain-domain-survey"]
  SELF -->|runtime| n_domain_research["domain-research"]
  SELF -->|runtime| n_domain_research_intel["domain-research-intel"]
  SELF -->|runtime| n_foundation_audit_log["foundation-audit-log"]
  classDef self fill:#1f883d,stroke:#1f883d,color:#fff;
  class SELF self;

Feature flags

No Cargo features are defined: every capability is unconditional, so no consumer can receive a half-wired crate.

Targets

KindNameSource
libdomain_domain_survey`src/lib.rs`

Error model

Error typeNamed by
StoreErrordeclared, no public signature returns it

Operational characteristics

PropertyEvidence
async public surfacenone detected
async runtimenone detected
database accessnone detected
network I/Onone detected
unsafe codenone detected
environment variablesnone detected

No unsafe block, unsafe fn, unsafe impl or unsafe trait was found by the parser anywhere in this crate's source.

Configuration

No environment variable is read with a literal name anywhere in this crate. A variable whose key is computed at run time cannot be listed here, and is not claimed to be absent.

2 workspace crates depend on this one: application-domain-survey, infrastructure-acquire.

Verification

KindCount
Unit tests38
Integration tests0
Examples0
Doctests0

Evidence by module. How often each public module is named by something executable.

ModuleTestsExamplesConsumers
preserve100
rate_limit301
rate_limit::defaults800
rdap403
store300

What the tests establish, by name:

Documentation coverage

MeasureDocumentedTotal
Public items with rustdoc3839
Public modules with a //! block45
pie showData
    title Public items with rustdoc
    "Documented" : 38
    "No rustdoc detected" : 1

Metrics

MetricValue
Rust source files5
Source lines1333
Code lines844
Public API items39
Public modules5
Tests38
Examples0
Cargo features0
Direct runtime dependencies8
Workspace reverse dependencies2
pie showData
    title Public API by kind
    "constant" : 10
    "enum" : 2
    "function" : 1
    "method" : 20
    "struct" : 5
    "trait" : 1
pie showData
    title Rust source composition
    "Code" : 844
    "Blank or comment" : 489

Generation

Rendered by tools-corpus corpus readme from repository evidence alone, renderer schema 2, lexicon current. No model, network service or database was consulted. Regenerate with tools-corpus corpus readme --write; verify with --check.

All domain · Manual