فرض کنید ابزارِ بررسیِ نام، برای رکوردِ A یک پاسخِ منفیِ ذخیره‌شده دارد و کمی بعد همان نام را برای AAAA می‌پرسد. آیا می‌توان پاسخِ قبلی را دوباره استفاده کرد؟ فقط دانستنِ «منفی‌بودن» کافی نیست: نبودنِ خودِ نام با نبودنِ دادهٔ یک نوع فرق دارد. حتی اگر دامنهٔ تطبیق درست باشد، ممکن است مهلت تمام شده باشد. این درس این دو پرسش را با دادهٔ مصنوعی و حسابِ صحیح جدا می‌کند؛ هیچ درخواستِ DNS، اندازه‌گیریِ شبکه یا تغییرِ تنظیمات انجام نمی‌شود.

۱. نبودن نام با نبودن نوع رکورد فرق دارد

قابِ موجود با یک جای خالی در کنار جایگاهِ فاقد قاب
تصویر مفهومی: قابِ موجود با یک جای خالی در کنار جایگاهِ فاقد قاب؛ بدون مقیاس عددی و مستقل از نتایج اجرای کد.

در بخشِ ۲ از RFC 2308، NXDOMAIN خطای نبودنِ نام را نشان می‌دهد. NODATA کدِ پاسخِ مستقلی نیست؛ نتیجه‌ای است که از پاسخِ NOERROR و نبودنِ جوابِ مرتبط، با توجه به محتوای پیام، تشخیص داده می‌شود. پس هر پاسخِ خالی، NODATA نیست. برنامهٔ ما این تشخیص را انجام نمی‌دهد: فراخواننده نوعِ پاسخِ منفی را از قبل و صریح داده است.

در شاهدِ دستی، نامِ ساختگیِ sensor.lab.، نوعِ A و کلاسِ IN را داریم. اگر پاسخ NXDOMAIN باشد، درخواستِ AAAA برای همان نام و همان کلاس نیز در دامنهٔ کلید قرار می‌گیرد. اگر پاسخ NODATA باشد، همان تغییرِ نوع کافی است تا استفادهٔ دوباره نامناسب شود. از نبودِ رکوردِ A، نبودِ رکوردِ AAAA را نتیجه نمی‌گیریم؛ این تمایز پیش از هر محاسبهٔ زمان مطرح است.

این «همان نام» دقیق است. node.lab. نامِ دیگری است و child.sensor.lab. نیز با sensor.lab. برابر نیست. درس، قاعدهٔ تعمیمِ NXDOMAIN به زیرشاخه‌ها را اجرا نمی‌کند. همچنین مثال‌های دارای CNAME در منبع خوانده شده‌اند، اما رابطِ ما زنجیرهٔ نامِ مستعار ندارد؛ نامِ عرضه‌شده همان نامِ نهاییِ موردِ بحث است. افزودنِ این قابلیت‌ها به یک شرطِ رشته‌ای، حل‌کنندهٔ کامل نمی‌سازد.

۲. کلید دقیق و عمر پاسخ منفی را جدا کنیم

جایگاه‌های فیزیکیِ متمایز و یک ساعت شنیِ جدا از آن‌ها
تصویر مفهومی: جایگاه‌های فیزیکیِ متمایز و یک ساعت شنیِ جدا از آن‌ها؛ بدون مقیاس عددی و مستقل از نتایج اجرای کد.

بخشِ ۵ از RFC 2308، برای NXDOMAIN زوجِ نام و کلاس و برای NODATA سه‌تاییِ نام، نوع و کلاس را مشخص می‌کند. کلاس را نیز نباید حذف کرد: در نمایهٔ کوچکِ ما IN و CH دو مقدارِ متمایزند. نام‌ها از فهرستی بسته، کوچک‌حرف و دارای نقطهٔ پایانی می‌آیند؛ برنامه نه نام را عادی‌سازی می‌کند، نه تفاوتِ بزرگیِ حروف در DNS واقعی را از این مقایسهٔ محدود نتیجه می‌گیرد.

بخش‌های ۳ تا ۵ همان سند، عمرِ پاسخِ منفی را به کوچک‌ترِ TTL رکوردِ SOA و فیلدِ MINIMUM آن وصل می‌کنند. MINIMUM را با شمارهٔ سریالِ SOA اشتباه نگیریم. در درسِ سریالِ SOA و مرزِ نیم‌دور، یک شمارهٔ نسخهٔ بی‌واحد را مقایسه می‌کردیم؛ اینجا دو مدتِ صحیحِ ثانیه‌ای، مهلتِ نگهداری را محدود می‌کنند.

در مثال، دریافت در ثانیهٔ ۱۰۰، TTL برابرِ ۲۰ و MINIMUM برابرِ ۸ است. عمر هشت ثانیه و مرزِ پایان ۱۰۸ می‌شود: در ۱۰۰ هشت ثانیه و در ۱۰۷ یک ثانیه باقی است؛ در ۱۰۸ استفادهٔ دوباره مجاز نیست. بازه از دریافت بسته و در پایان باز است. TTL صفر حتی در لحظهٔ دریافت هم فرصتِ استفاده نمی‌سازد. زمان‌ها یک مبدأِ محلیِ فرضی دارند، نه ساعتِ واقعیِ سرور یا زمانِ رفت‌وبرگشت.

وقتی هر دو فیلدِ SOA غایب باشند، مدلِ ما استفادهٔ دوباره را رد می‌کند. منبع می‌گوید چنین پاسخ‌هایی نباید کش شوند؛ ما این توصیه را به سیاستِ محافظه‌کارانهٔ رابط تبدیل کرده‌ایم، نه اینکه هر پیامِ بدونِ SOA را از نظرِ قالب باطل بنامیم. غیبتِ فقط یکی از دو فیلد، در قراردادِ ورودیِ این تمرین ناسازگار است و خطا می‌دهد.

۳. کد کامل برای یک پاسخِ ازپیش‌داده‌شده

یک نمونهٔ بی‌نوشته در ابزار وارسی و قطعه‌های آمادهٔ بررسی
تصویر مفهومی: یک نمونهٔ بی‌نوشته در ابزار وارسی و قطعه‌های آمادهٔ بررسی؛ بدون مقیاس عددی و مستقل از نتایج اجرای کد.

تابعِ negative_reuse یک تاپلِ هفت‌عضویِ پاسخ و تاپلی با حداکثر دوازده پرسش می‌گیرد. هر پرسش شاملِ نام، نوع، کلاس و زمانِ بررسی است. خروجی به همان ترتیب، زوجِ برچسبِ تشخیصی و ثانیهٔ باقی‌مانده است. این رابط، فقط یک پاسخ را وارسی می‌کند؛ برندهٔ میانِ چند پاسخ، درج یا جایگزینیِ کش، تعارضِ داده‌ها و تصمیمِ ارسالِ جواب را تعیین نمی‌کند.

نوع‌ها دقیق‌اند: bool، شناور، زیرکلاس و تبدیلِ ضمنی پذیرفته نیستند. زمان‌ها بین صفر و ۳۶۰۰ و دو فیلدِ SOA بین صفر و ۶۰۰ ثانیه‌اند. این سقف‌ها انتخابِ آموزشی‌اند، نه محدودیتِ استانداردِ DNS. انقضا می‌تواند از سقفِ زمانِ ورودی بزرگ‌تر شود: دریافتِ ۳۶۰۰ و عمرِ ۶۰۰، مرزِ ۴۲۰۰ می‌سازند؛ کد نتیجه را برای جاگرفتن در سقفِ ورودی قطع نمی‌کند.

کلِ پاسخ و دستهٔ پرسش‌ها پیش از تولیدِ هر گزارش بررسی می‌شوند. مولد، فهرست یا تاپلِ زیرکلاس خودکار مصرف نمی‌شود. برای گزارشِ رد، تقدمِ نامِ متفاوت، کلاسِ متفاوت، نوعِ متفاوتِ NODATA، SOA غایب، زمانِ پیش از دریافت و انقضا را خودمان انتخاب کرده‌ایم. این برچسب‌ها فیلدِ پیامِ DNS نیستند؛ مثلاً پرسشِ نامرتبط در لحظهٔ انقضا همچنان «نامِ متفاوت» گزارش می‌شود.

"""Supplied DNS negative-answer reuse diagnostics; no resolver or network."""
from itertools import product
import json
import platform
import sys

NAMES = ("sensor.lab.", "node.lab.", "child.sensor.lab.")
TYPES = ("A", "AAAA")
CLASSES = ("IN", "CH")
MAX_TIME_S = 3600
MAX_TTL_S = 600
MAX_QUERIES = 12


def _token(value, allowed, label):
    if type(value) is not str or value not in allowed:
        raise ValueError(label)


def _integer(value, ceiling, label):
    if type(value) is not int or not 0 <= value <= ceiling:
        raise ValueError(label)


def _admit(answer, queries):
    if type(answer) is not tuple or len(answer) != 7:
        raise ValueError("answer_shape")
    kind, name, qtype, qclass, received, soa_ttl, minimum = answer
    _token(kind, ("NXDOMAIN", "NODATA"), "kind")
    _token(name, NAMES, "qname")
    _token(qtype, TYPES, "qtype")
    _token(qclass, CLASSES, "qclass")
    _integer(received, MAX_TIME_S, "received_s")
    if soa_ttl is None or minimum is None:
        if not (soa_ttl is None and minimum is None):
            raise ValueError("soa_fields")
    else:
        _integer(soa_ttl, MAX_TTL_S, "soa_rr_ttl_s")
        _integer(minimum, MAX_TTL_S, "soa_minimum_s")
    if type(queries) is not tuple or len(queries) > MAX_QUERIES:
        raise ValueError("queries_shape")
    for query in queries:
        if type(query) is not tuple or len(query) != 4:
            raise ValueError("query_shape")
        _token(query[0], NAMES, "qname")
        _token(query[1], TYPES, "qtype")
        _token(query[2], CLASSES, "qclass")
        _integer(query[3], MAX_TIME_S, "now_s")


def _evaluate(answer, query):
    kind, name, qtype, qclass, received, soa_ttl, minimum = answer
    other_name, other_type, other_class, now = query
    # Diagnostic precedence is our policy, not a standardized DNS status field.
    if other_name != name:
        return ("different_name", 0)
    if other_class != qclass:
        return ("different_class", 0)
    if kind == "NODATA" and other_type != qtype:
        return ("different_type", 0)
    if soa_ttl is None:
        return ("no_soa", 0)
    if now < received:
        return ("before_received", 0)
    expires = received + min(soa_ttl, minimum)
    if now >= expires:
        return ("expired", 0)
    return ("reuse_nxdomain" if kind == "NXDOMAIN" else "reuse_nodata",
            expires - now)


def negative_reuse(answer, queries):
    """Return immutable (diagnostic, remaining TTL seconds) rows in query order.

    One supplied negative answer only. Closed synthetic name/type/class profile,
    integer local seconds. No packet decoding, alias handling or cache insertion.
    """
    _admit(answer, queries)
    return tuple(_evaluate(answer, query) for query in queries)


def allowed_point_reference(answer, queries):
    """Test-only relation built from declared points, not production branches."""
    kind, name, qtype, qclass, received, soa_ttl, minimum = answer
    points = set()
    if soa_ttl is not None:
        # Enumerate both independent lifetime constraints; no min/expiry formula.
        seconds = [received + age for age in range(soa_ttl) if age < minimum]
        types = TYPES if kind == "NXDOMAIN" else (qtype,)
        points = set(product((name,), types, (qclass,), seconds))
    expected = []
    for query in queries:
        if query in points:
            # Cardinality of the remaining integer-second cells, not deadline-now.
            remaining = sum(p[:3] == query[:3] and p[3] >= query[3]
                            for p in points)
            expected.append((True, remaining))
        else:
            expected.append((False, 0))
    return tuple(expected)


def rejects(call, label=None):
    try:
        call()
    except ValueError as error:
        return label is None or str(error) == label
    return False


def run_example():
    checks = {}

    def check(name, value):
        assert type(value) is bool
        checks[name] = value
        if not value:
            raise AssertionError(name)

    nx = ("NXDOMAIN", "sensor.lab.", "A", "IN", 100, 20, 8)
    nd = ("NODATA",) + nx[1:]
    queries = (("sensor.lab.", "A", "IN", 100),
               ("sensor.lab.", "AAAA", "IN", 100),
               ("sensor.lab.", "AAAA", "IN", 107),
               ("sensor.lab.", "AAAA", "IN", 108))
    nx_rows, nd_rows = negative_reuse(nx, queries), negative_reuse(nd, queries)
    check("manual_NXDOMAIN_crosses_type_while_NODATA_does_not",
          nx_rows == (("reuse_nxdomain", 8), ("reuse_nxdomain", 8),
                      ("reuse_nxdomain", 1), ("expired", 0))
          and nd_rows == (("reuse_nodata", 8), ("different_type", 0),
                         ("different_type", 0), ("different_type", 0)))
    check("exact_qname_only_neither_sibling_nor_descendant_cut",
          negative_reuse(nx, (("node.lab.", "A", "IN", 101),
                              ("child.sensor.lab.", "A", "IN", 101)))
          == (("different_name", 0),) * 2)
    check("class_is_part_of_both_negative_keys",
          all(negative_reuse(a, (("sensor.lab.", "A", "CH", 101),))
              == (("different_class", 0),) for a in (nx, nd)))
    ttl_smaller = nx[:5] + (3, 8)
    minimum_smaller = nx[:5] + (8, 3)
    same_name = (("sensor.lab.", "A", "IN", 100),
                 ("sensor.lab.", "A", "IN", 103))
    check("SOA_RR_TTL_can_be_the_limiting_duration",
          negative_reuse(ttl_smaller, same_name)
          == (("reuse_nxdomain", 3), ("expired", 0)))
    check("SOA_MINIMUM_can_be_the_limiting_duration",
          negative_reuse(minimum_smaller, same_name)
          == (("reuse_nxdomain", 3), ("expired", 0)))
    check("receipt_last_live_second_and_exact_expiry_are_distinct",
          negative_reuse(nd, tuple(("sensor.lab.", "A", "IN", t)
                                   for t in (100, 107, 108)))
          == (("reuse_nodata", 8), ("reuse_nodata", 1), ("expired", 0)))
    zero_and_absent = [negative_reuse(nx[:5] + soa, (same_name[0],))[0]
                       for soa in ((0, 8), (8, 0), (None, None))]
    check("zero_lifetime_never_reuses_and_missing_SOA_is_declined",
          zero_and_absent == [("expired", 0), ("expired", 0), ("no_soa", 0)])
    check("supplied_query_before_receipt_is_not_a_cache_hit",
          negative_reuse(nx, (("sensor.lab.", "A", "IN", 99),))
          == (("before_received", 0),))

    profiles = point_queries = api_batches = 0
    family_ok = True
    all_queries = tuple(product(NAMES, TYPES, CLASSES, range(7)))
    soa_cases = ((None, None),) + tuple(product((0, 1, 3), (0, 2, 4)))
    for kind, name, qtype, qclass, received, soa in product(
            ("NXDOMAIN", "NODATA"), NAMES[:2], TYPES, CLASSES, (0, 2), soa_cases):
        answer = (kind, name, qtype, qclass, received) + soa
        for offset in range(0, len(all_queries), MAX_QUERIES):
            batch = all_queries[offset:offset + MAX_QUERIES]
            actual = negative_reuse(answer, batch)
            expected = allowed_point_reference(answer, batch)
            actual_membership = tuple((row[0].startswith("reuse_"), row[1])
                                      for row in actual)
            family_ok &= actual_membership == expected
            family_ok &= all(not hit or actual[i][0] == "reuse_" + kind.lower()
                             for i, (hit, remaining) in enumerate(expected))
            point_queries += len(batch)
            api_batches += 1
        profiles += 1
    check("independent_allowed_point_relation_is_ONE_finite_family",
          bool(family_ok) and profiles == 320 and point_queries == 26880
          and api_batches == 2240)

    shifted_answer = nx[:4] + (400,) + nx[5:]
    shifted_queries = tuple(q[:3] + (q[3] + 300,) for q in queries)
    check("common_origin_translation_preserves_ttl_and_scope",
          negative_reuse(shifted_answer, shifted_queries) == nx_rows)
    permutation = (2, 0, 3, 1)
    check("query_permutation_only_permutates_corresponding_reports",
          negative_reuse(nx, tuple(queries[i] for i in permutation))
          == tuple(nx_rows[i] for i in permutation))

    evaluation_events = []
    saved_evaluate = globals()["_evaluate"]
    def forbidden_evaluation(*args):
        evaluation_events.append("evaluated")
        raise AssertionError("Whole batch must be admitted first")
    try:
        globals()["_evaluate"] = forbidden_evaluation
        late_rejected = rejects(lambda: negative_reuse(
            nx, (queries[0], ("sensor.lab.", "AAAA", "IN", True))), "now_s")
    finally:
        globals()["_evaluate"] = saved_evaluate
    check("late_invalid_query_rejects_before_any_report_evaluation",
          late_rejected and evaluation_events == [])

    class IntChild(int):
        def __le__(self, other):
            raise AssertionError("No numeric subclass coercion")
    class TupleChild(tuple):
        def __len__(self):
            raise AssertionError("No tuple subclass length hook")
    class StrChild(str):
        def __eq__(self, other):
            raise AssertionError("No token subclass equality hook")
    consumed = []
    def lazy_queries():
        consumed.append(True)
        yield queries[0]
    bad_calls = [lambda: negative_reuse([], ()),
                 lambda: negative_reuse(nx[:-1], ()),
                 lambda: negative_reuse(TupleChild(nx), ()),
                 lambda: negative_reuse(nx, []),
                 lambda: negative_reuse(nx, TupleChild()),
                 lambda: negative_reuse(nx, (queries[0],) * 13),
                 lambda: negative_reuse(nx, ((),)),
                 lambda: negative_reuse(nx, (TupleChild(queries[0]),)),
                 lambda: negative_reuse(nx, lazy_queries())]
    for position, values in ((0, ("SERVFAIL", "NOERROR", StrChild("NXDOMAIN"))),
                             (1, ("Sensor.lab.", "sensor.lab", "unknown.lab.")),
                             (2, ("TXT", 1)), (3, ("HS", None)),
                             (4, (-1, 3601, True, 1.0, IntChild(1))),
                             (5, (-1, 601, True, None)),
                             (6, (-1, 601, False, None))):
        for value in values:
            candidate = nx[:position] + (value,) + nx[position + 1:]
            bad_calls.append(lambda candidate=candidate: negative_reuse(candidate, ()))
    for position, value in ((0, StrChild("sensor.lab.")), (1, "TXT"),
                            (2, "HS"), (3, -1), (3, 3601), (3, True), (3, 1.0)):
        q = queries[0][:position] + (value,) + queries[0][position + 1:]
        bad_calls.append(lambda q=q: negative_reuse(nx, (q,)))
    check("strict_tokens_exact_types_caps_and_lazy_nonconsumption",
          all(rejects(call) for call in bad_calls) and consumed == [])

    before = (nx, queries)
    first, second = negative_reuse(nx, queries), negative_reuse(nx, queries)
    immutable = False
    try:
        first[0][0] = "changed"
    except TypeError:
        immutable = True
    endpoint = ("NXDOMAIN", "node.lab.", "AAAA", "CH", 3600, 600, 600)
    endpoint_rows = negative_reuse(endpoint, (("node.lab.", "AAAA", "CH", 3600),) * 12)
    check("empty_max_batch_immutable_reports_inputs_and_output_domain",
          negative_reuse(nx, ()) == () and first == second == nx_rows
          and first is not second and immutable and before == (nx, queries)
          and endpoint_rows == (("reuse_nxdomain", 600),) * 12
          and 3600 + 600 == 4200)
    assert len(checks) == 14 and all(type(v) is bool and v for v in checks.values())
    return {"python": platform.python_version(), "implementation": platform.python_implementation(),
            "units": "Supplied integer local seconds, one assumed origin; no clock read",
            "answer_fields": ["kind", "qname", "qtype", "qclass", "received_s", "soa_rr_ttl_s", "soa_minimum_s"],
            "query_fields": ["qname", "qtype", "qclass", "now_s"],
            "own_caps": {"time_s": MAX_TIME_S, "SOA_fields_s": MAX_TTL_S, "queries": MAX_QUERIES},
            "manual_answer_NXDOMAIN": nx, "manual_queries": queries,
            "manual_NXDOMAIN_rows": nx_rows, "manual_NODATA_rows": nd_rows,
            "zero_and_missing_SOA_rows": zero_and_absent,
            "independent_family": {"profiles": profiles, "point_queries": point_queries,
                                   "API_batches_inside_one_main": api_batches, "groups": 1},
            "invalid_profiles_in_ONE_group": len(bad_calls),
            "whole_batch_before_evaluation": {"rejected": late_rejected, "evaluation_events": len(evaluation_events)},
            "case_counts_not_extra_groups": True, "meaningful_named_groups": len(checks), "checks": checks,
            "scope": "One preclassified supplied negative answer only. Exact qname, not ancestor cuts. No DNS requests, wire parser, aliases, DNSSEC, cache updates, failure-cache implementation, deployment, authenticity or benchmark."}


if __name__ == "__main__":
    sys.stdout.reconfigure(encoding="utf-8", newline="\n")
    sys.stdout.write(json.dumps(run_example(), ensure_ascii=True, indent=2) + "\n")

این مرحله با خواندنِ نامِ فشردهٔ DNS نیز متفاوت است: آن برنامه بایت و اشاره‌گر می‌خواند؛ این تابع نامِ آماده می‌گیرد. موفقیتِ هرکدام، جایِ اعتبارسنجیِ سربرگ، رکوردها، اصالتِ پاسخ یا زمینهٔ درخواست را نمی‌گیرد. دو پیوندِ داخلی از متن‌های منتشرشدهٔ ذخیره‌شده خوانده شده‌اند؛ دسترسیِ زندهٔ امروز از این مطالعهٔ آفلاین ثابت نمی‌شود.

۴. مرجع مستقل و شاهدهای مرز انقضا

دو ابزارِ مستقلِ وارسی در کنار نشانه‌ای مفهومی از گذر زمان
تصویر مفهومی: دو ابزارِ مستقلِ وارسی در کنار نشانه‌ای مفهومی از گذر زمان؛ بدون مقیاس عددی و مستقل از نتایج اجرای کد.

مرجع، شاخه‌های تابعِ اصلی را دوباره اجرا نمی‌کند. برای پاسخِ معتبرِ کوچک، مجموعه‌ای از نقطه‌های مجازِ نام، نوع، کلاس و ثانیه می‌سازد. ثانیه‌ها با گذر از بازهٔ TTL و نگه‌داشتنِ سن‌های کوچک‌تر از MINIMUM ساخته می‌شوند؛ دامنهٔ نوع برای NXDOMAIN گسترده‌تر است. عضویتِ پرسش در این مجموعه، انتظارِ استفاده را می‌دهد و شمارِ خانه‌های زمانیِ باقی‌مانده، TTL باقی را تعیین می‌کند.

خانوادهٔ متناهی، هر دو نوعِ پاسخ، چند نام و کلاس، زمانِ دریافت، مدتِ صفر و SOA غایب را ترکیب می‌کند. تمامِ موردهای این خانواده فقط یک گروه‌اند. چهارده گروهِ نام‌دار، همین خانواده و شاهدهای دستیِ تغییرِ نوع، مرزِ زمان، تغییرِ مبدأ، جابه‌جاییِ ترتیب، ردِ عضوِ نامعتبرِ انتهایی و تغییرناپذیریِ خروجی را در بر می‌گیرند. این پوشش، اثباتِ همهٔ ورودی‌ها یا آزمونِ یک محصول نیست.

{
  "python": "3.14.5",
  "implementation": "CPython",
  "units": "Supplied integer local seconds, one assumed origin; no clock read",
  "answer_fields": [
    "kind",
    "qname",
    "qtype",
    "qclass",
    "received_s",
    "soa_rr_ttl_s",
    "soa_minimum_s"
  ],
  "query_fields": [
    "qname",
    "qtype",
    "qclass",
    "now_s"
  ],
  "own_caps": {
    "time_s": 3600,
    "SOA_fields_s": 600,
    "queries": 12
  },
  "manual_answer_NXDOMAIN": [
    "NXDOMAIN",
    "sensor.lab.",
    "A",
    "IN",
    100,
    20,
    8
  ],
  "manual_queries": [
    [
      "sensor.lab.",
      "A",
      "IN",
      100
    ],
    [
      "sensor.lab.",
      "AAAA",
      "IN",
      100
    ],
    [
      "sensor.lab.",
      "AAAA",
      "IN",
      107
    ],
    [
      "sensor.lab.",
      "AAAA",
      "IN",
      108
    ]
  ],
  "manual_NXDOMAIN_rows": [
    [
      "reuse_nxdomain",
      8
    ],
    [
      "reuse_nxdomain",
      8
    ],
    [
      "reuse_nxdomain",
      1
    ],
    [
      "expired",
      0
    ]
  ],
  "manual_NODATA_rows": [
    [
      "reuse_nodata",
      8
    ],
    [
      "different_type",
      0
    ],
    [
      "different_type",
      0
    ],
    [
      "different_type",
      0
    ]
  ],
  "zero_and_missing_SOA_rows": [
    [
      "expired",
      0
    ],
    [
      "expired",
      0
    ],
    [
      "no_soa",
      0
    ]
  ],
  "independent_family": {
    "profiles": 320,
    "point_queries": 26880,
    "API_batches_inside_one_main": 2240,
    "groups": 1
  },
  "invalid_profiles_in_ONE_group": 39,
  "whole_batch_before_evaluation": {
    "rejected": true,
    "evaluation_events": 0
  },
  "case_counts_not_extra_groups": true,
  "meaningful_named_groups": 14,
  "checks": {
    "manual_NXDOMAIN_crosses_type_while_NODATA_does_not": true,
    "exact_qname_only_neither_sibling_nor_descendant_cut": true,
    "class_is_part_of_both_negative_keys": true,
    "SOA_RR_TTL_can_be_the_limiting_duration": true,
    "SOA_MINIMUM_can_be_the_limiting_duration": true,
    "receipt_last_live_second_and_exact_expiry_are_distinct": true,
    "zero_lifetime_never_reuses_and_missing_SOA_is_declined": true,
    "supplied_query_before_receipt_is_not_a_cache_hit": true,
    "independent_allowed_point_relation_is_ONE_finite_family": true,
    "common_origin_translation_preserves_ttl_and_scope": true,
    "query_permutation_only_permutates_corresponding_reports": true,
    "late_invalid_query_rejects_before_any_report_evaluation": true,
    "strict_tokens_exact_types_caps_and_lazy_nonconsumption": true,
    "empty_max_batch_immutable_reports_inputs_and_output_domain": true
  },
  "scope": "One preclassified supplied negative answer only. Exact qname, not ancestor cuts. No DNS requests, wire parser, aliases, DNSSEC, cache updates, failure-cache implementation, deployment, authenticity or benchmark."
}

خروجیِ کامل، نسخهٔ اجرای واقعیِ پایتون، واحدها، ورودیِ دستی، گزارش‌ها و شمارِ موردهای داخلِ خانواده را ثبت می‌کند. کد فقط کتابخانهٔ استاندارد دارد و زمانِ سیستم را برای مدل نمی‌خواند. RFC 2308 در مارسِ ۱۹۹۸ و RFC 9520 در دسامبرِ ۲۰۲۳ منتشر شده‌اند؛ روزِ دقیقِ انتشار از سرآیندِ خوانده‌شده معلوم نیست. دسترسیِ منابع در ۲۲ اکتبرِ ۲۰۲۶، تاریخِ انتشار یا نشانهٔ خبرِ تازه نیست. هر دو سند از یک خانوادهٔ استانداردسازی‌اند، نه دو اعتبارسنجِ تجربیِ مستقل.

۵. چهار پرسش دربارهٔ نوع، زمان و خطای حل نام

اتصالِ جداشده و جای خالیِ قطعه به‌عنوان دو وضعیتِ متفاوت
تصویر مفهومی: اتصالِ جداشده و جای خالیِ قطعه به‌عنوان دو وضعیتِ متفاوت؛ بدون مقیاس عددی و مستقل از نتایج اجرای کد.

پرسش اول: پاسخِ منفیِ A را چه زمانی برای AAAA دوباره استفاده می‌کنیم؟ پاسخ: در همین مدل، تنها اگر پاسخ NXDOMAIN، نام و کلاس یکسان، SOA حاضر و زمان داخلِ بازه باشد. NODATA برای A چنین دامنه‌ای ندارد؛ ممکن است نام وجود داشته باشد ولی دادهٔ A موجود نباشد. نوعِ پاسخ را از خالی‌بودنِ خودسرانهٔ یک ظرف استنتاج نمی‌کنیم.

پرسش دوم: در ثانیهٔ ۱۰۸ِ شاهد، صفرِ باقی‌مانده چه معنایی دارد؟ پاسخ: مهلت پایان یافته و شرطِ کوچک‌تر بودن برقرار نیست. صفر، یک فرصتِ تازه برای پاسخ‌دادن نیست. با عمرِ صفر نیز بازه تهی است؛ دریافت و انقضا یکسان‌اند. عوض‌کردنِ شرط به کوچک‌تر یا مساوی، نتیجهٔ مرز را نادرست می‌کند.

پرسش سوم: TTL برابرِ سه و MINIMUM برابرِ هشت چه عمری می‌دهند؟ نبودِ SOA را چگونه جبران کنیم؟ پاسخ: عمر سه ثانیه است؛ با جابه‌جاییِ این دو مقدار هم سه می‌ماند. در نبودِ SOA عددی حدس نمی‌زنیم و مدل استفاده را رد می‌کند. سقفِ ۶۰۰ ثانیهٔ تمرین هم جانشینِ فیلدِ غایب یا پیشنهادِ تنظیمِ سرور نیست.

پرسش چهارم: آیا timeout یعنی NXDOMAIN و آیا نتیجه برای همهٔ زیرنام‌ها معتبر است؟ پاسخ: خیر. بخشِ ۲ از RFC 9520، پاسخ‌های منفیِ مفید را از شکستِ حل نام جدا می‌کند؛ نرسیدنِ پاسخ، شاهدِ نبودنِ نام نیست. در این برنامه نیز فقط تطبیقِ دقیقِ نام داریم. کشِ شکست‌ها، زیرشاخه‌ها، نامِ مستعار، DNSSEC و رفتارِ حل‌کنندهٔ واقعی بیرونِ قراردادند.

منابع و مطالعهٔ بیشتر

بررسی منابع: ۳۰ مهر ۱۴۰۵. تحلیل‌ها و پیشنهادهای اجرایی، جمع‌بندی نویسنده‌اند.