milestone 28: query provenance — every logged query is exactly explainable
Gates / frontend (push) Successful in 1m36s
Gates / test (push) Successful in 1m56s
Gates / test-aarch64 (push) Successful in 7m37s
Gates / package (push) Successful in 9m12s
Gates / container (push) Successful in 13s
CI / gates (push) Successful in 19m4s

query rows gain qclass, rcode, group, policy action and reason, the
matched rule or list entry with its source, cname and safe-search
targets, route kind, forward zone, and the resolver that actually
answered — the pool and local markers die. servfails are logged and
name the resolver that lost; post-parse protocol refusals become rows.
a detail page at /queries/:id renders the ordered explanation, and
coverage watermarks distinguish an empty history from a missing one.

the schema fingerprint changes: existing query history is recreated
with the old file kept aside and the reset filed as a resolved
diagnostic. fixes an oversized udp reply being rebuilt as noerror,
which handed clients a truncated nxdomain as success.
This commit is contained in:
2026-08-22 09:16:40 +02:00
parent 7e6cb507d2
commit 0fd6bbd312
65 changed files with 7036 additions and 685 deletions
+96 -28
View File
@@ -29,6 +29,7 @@ import type {
LookupResult,
PauseState,
QueriesPage,
QueryDetail,
Rule,
RuleEcho,
SettingsEnvelope,
@@ -415,76 +416,139 @@ export const sample_get_upstream_health: UpstreamHealth = {
};
export const sample_get_queries: QueriesPage = {
coverage: {
available_since: 0,
complete: false,
},
next_before: 0,
queries: [
{
block_reason: "",
blocked: true,
cache_hit: null,
client_ip: "192.0.2.11",
domain: "shop.example",
id: 0,
policy_action: "block",
policy_reason: "blocklist_wildcard",
qclass: 0,
qtype: 0,
rcode: 0,
response_time_us: 0,
route_kind: "blocked",
ts: 0,
upstream: "",
},
{
blocked: false,
cache_hit: false,
client_ip: "192.0.2.10",
domain: "news.example",
id: 0,
policy_action: "allow",
policy_reason: "no_match",
qclass: 0,
qtype: 0,
rcode: 0,
response_time_us: 0,
route_kind: "upstream",
ts: 0,
upstream: "https://dns.example/dns-query",
},
{
blocked: false,
cache_hit: true,
client_ip: "192.0.2.10",
domain: "d24.example",
id: 0,
policy_action: "allow",
policy_reason: "no_match",
qclass: 0,
qtype: 0,
rcode: 0,
response_time_us: 0,
route_kind: "cache",
ts: 0,
upstream: "https://dns.example/dns-query",
upstream: "",
},
{
block_reason: "",
blocked: false,
cache_hit: false,
client_ip: "192.0.2.10",
domain: "d23.example",
id: 0,
policy_action: "allow",
policy_reason: "no_match",
qclass: 0,
qtype: 0,
rcode: 0,
response_time_us: 0,
route_kind: "upstream",
ts: 0,
upstream: "https://dns.example/dns-query",
},
{
block_reason: "",
blocked: false,
cache_hit: true,
client_ip: "192.0.2.10",
domain: "d22.example",
id: 0,
policy_action: "allow",
policy_reason: "no_match",
qclass: 0,
qtype: 0,
rcode: 0,
response_time_us: 0,
ts: 0,
upstream: "https://dns.example/dns-query",
},
{
block_reason: "",
blocked: false,
cache_hit: false,
client_ip: "192.0.2.10",
domain: "d21.example",
id: 0,
qtype: 0,
response_time_us: 0,
ts: 0,
upstream: "https://dns.example/dns-query",
},
{
block_reason: "blocklist_domain",
blocked: true,
cache_hit: null,
client_ip: "192.0.2.10",
domain: "d20.example",
id: 0,
qtype: 0,
response_time_us: 0,
route_kind: "cache",
ts: 0,
upstream: "",
},
],
};
export const sample_get_query_detail: QueryDetail = {
group: {
id: 0,
name: "kids",
},
id: 0,
policy: {
action: "block",
matched: "||tracker.example^",
reason: "blocklist_wildcard",
source_id: 0,
source_name: "StevenBlack",
},
request: {
client: "192.0.2.11",
domain: "shop.example",
qclass: 0,
qtype: 0,
time: 0,
},
response: {
duration_us: 0,
rcode: 0,
},
rewrites: {
cname_target: "cdn.tracker.example",
safe_search_target: "",
},
route: {
forward_zone: "",
kind: "blocked",
upstream: "",
},
};
export const sample_get_stats: StatsTotals = {
avg_response_time_us: null,
blocked: 0,
cached: 0,
clients: 0,
coverage: {
available_since: 0,
complete: true,
},
period: "1h",
queries: 0,
since: 0,
@@ -501,6 +565,10 @@ export const sample_get_stats_timeseries: StatsTimeseries = {
ts: 0,
},
],
coverage: {
available_since: 0,
complete: true,
},
period: "1h",
since: 0,
until: 0,