FortyDN Dev
v3.0.0: fully autonomous engine β remove MillionVerifier, add DoH DNS worker, AI risk scorer (HF PRO, circuit breaker), i18n, normalized verdicts, remote SMTP worker
de745bf Download app/models.py from sitcod3/modul-x: direct link, hf CLI and curl.
- Browser
- Download file 5.63 kB
-
https://huggingface.co/spaces/sitcod3/modul-x/resolve/main/app/models.py
- Command line
-
hf download hf://spaces/sitcod3/modul-x/app/models.py
-
curl -L -o models.py https://huggingface.co/spaces/sitcod3/modul-x/resolve/main/app/models.py
5.63 kB
| from pydantic import BaseModel, field_validator | |
| from typing import Optional, List, Dict, Any | |
| from enum import Enum | |
| # ---- Enums ----------------------------------------------------------- | |
| class ValidationStatus(str, Enum): | |
| VALID = "valid" | |
| INVALID = "invalid" | |
| RISKY = "risky" | |
| UNKNOWN = "unknown" | |
| class RiskLevel(str, Enum): | |
| LOW = "low" | |
| MEDIUM = "medium" | |
| HIGH = "high" | |
| # ---- Layer Results ---------------------------------------------------- | |
| class SyntaxResult(BaseModel): | |
| passed: bool | |
| normalized_email: Optional[str] = None | |
| local_part: Optional[str] = None | |
| domain: Optional[str] = None | |
| error: Optional[str] = None | |
| class DNSResult(BaseModel): | |
| domain_exists: bool | |
| has_mx_records: bool | |
| mx_records: List[Dict[str, Any]] = [] | |
| a_fallback: bool = False | |
| has_spf: bool = False | |
| spf_record: Optional[str] = None | |
| has_dmarc: bool = False | |
| dmarc_record: Optional[str] = None | |
| has_dkim: bool = False | |
| # DoH dual-resolver signal (spec Β§7.3): True when Google and Cloudflare | |
| # disagree on this domain's MX set β a deterministic risk signal. | |
| dns_inconsistent: bool = False | |
| # Which resolver layer produced the answer: "doh:dual" | "doh:primary" | | |
| # "legacy" β operational provenance for /engine/status triage. | |
| mx_source: Optional[str] = None | |
| error: Optional[str] = None | |
| error_code: Optional[str] = None | |
| class DisposableResult(BaseModel): | |
| is_disposable: bool | |
| is_free_provider: bool | |
| is_role_based: bool | |
| provider_name: Optional[str] = None | |
| # Advisory ML signal (DistilBERT, unknown domains only). Never a verdict: | |
| # the deterministic layer remains the source of truth until the measured | |
| # precision gate promotes the model (see app/ml/). | |
| ml_label: Optional[str] = None | |
| ml_confidence: Optional[float] = None | |
| class CatchAllResult(BaseModel): | |
| # None = the catch-all probe could not run (e.g. port-25 egress blocked on | |
| # this host). It must NEVER be coerced to False, which would falsely claim | |
| # "domain rejects unknown recipients". | |
| is_catch_all: Optional[bool] = None | |
| confidence: float = 0.0 | |
| error: Optional[str] = None | |
| class SMTPResult(BaseModel): | |
| verified: Optional[bool] = None | |
| smtp_code: Optional[int] = None | |
| smtp_message: Optional[str] = None | |
| server_banner: Optional[str] = None | |
| supports_tls: bool = False | |
| error: Optional[str] = None | |
| via_proxy: bool = False | |
| class ScoringResult(BaseModel): | |
| score: int | |
| risk_level: RiskLevel | |
| breakdown: Dict[str, int] = {} | |
| warnings: List[str] = [] | |
| # ---- Main Response ---------------------------------------------------- | |
| class ValidationResult(BaseModel): | |
| email: str | |
| status: ValidationStatus | |
| syntax: SyntaxResult | |
| dns: Optional[DNSResult] = None | |
| disposable: Optional[DisposableResult] = None | |
| catch_all: Optional[CatchAllResult] = None | |
| smtp: Optional[SMTPResult] = None | |
| scoring: ScoringResult | |
| processing_time_ms: float | |
| validated_at: str | |
| # Provenance the frontend contract expects (src/lib/verifier.ts classify()). | |
| # checks_run lists the 7-layer taxonomy keys that actually executed; an | |
| # unchecked layer must never read as a pass. reason_code is a machine cause | |
| # for Unknown/risky rows. run_id correlates a single + its bulk chunk. | |
| checks_run: List[str] = [] | |
| reason_code: Optional[str] = None | |
| run_id: Optional[str] = None | |
| validator_version: Optional[str] = None | |
| # ---- Autonomous-engine normalized verdict (spec Phase 5) ---------------- | |
| # deterministic_verdict: the pre-AI verdict of the deterministic gauntlet β | |
| # "valid" | "invalid" | "catch_all" | "unknown" | "disposable". | |
| # ai_risk_score / ai_label: pattern-based risk classification for the | |
| # ambiguous rows (catch-all / unknown) only; None for deterministic rows. | |
| # fallback_used: the AI scorer could not reach inference and the local | |
| # rule-based heuristic decided instead ("scored_fallback" in the spec). | |
| # cache_hit: served from the domain/mailbox verdict cache within TTL. | |
| # INVARIANT: a catch_all is NEVER collapsed into "valid", regardless of | |
| # ai_risk_score β a good score means "likely exists and engages", not | |
| # "safe to send blindly". | |
| deterministic_verdict: Optional[str] = None | |
| ai_risk_score: Optional[float] = None | |
| ai_label: Optional[str] = None | |
| fallback_used: bool = False | |
| cache_hit: bool = False | |
| class SingleEmailRequest(BaseModel): | |
| email: str | |
| def strip_email(cls, v: str) -> str: | |
| return v.strip().lower() | |
| class BulkEmailRequest(BaseModel): | |
| emails: List[str] | |
| webhook_url: Optional[str] = None | |
| # Product promise: bulk runs the SAME 7-layer pipeline as single (1 credit | |
| # per email). deep=False skips catch-all + SMTP, which collapsed every bulk | |
| # row to "Unknown / insufficient depth" β so it defaults to True and is only | |
| # flipped off by an explicit caller that accepts the reduced verdict. | |
| deep: bool = True | |
| def limit_emails(cls, v: List[str]) -> List[str]: | |
| if len(v) > 10000: | |
| raise ValueError("Max 10,000 emails per bulk request") | |
| return [e.strip().lower() for e in v] | |
| class BulkValidationResponse(BaseModel): | |
| task_id: str | |
| status: str | |
| total: int | |
| message: str | |
| class TaskStatusResponse(BaseModel): | |
| task_id: str | |
| status: str | |
| progress: Optional[int] = None | |
| total: Optional[int] = None | |
| results: Optional[List[ValidationResult]] = None | |