milestone 33: contract closure — samples, file-authority enumeration, dead code, bundle ceiling
Gates / frontend (push) Successful in 1m34s
Gates / test (push) Successful in 2m3s
Gates / test-aarch64 (push) Failing after 3h13m33s
Gates / package (push) Successful in 5m20s
Gates / container (push) Successful in 15s
CI / gates (push) Failing after 6h30m45s

This commit is contained in:
2026-08-22 23:31:37 +02:00
parent 5da4652e89
commit cc23c97218
49 changed files with 397 additions and 113 deletions
+1 -1
View File
@@ -8,7 +8,7 @@
},
"scripts": {
"dev": "vite",
"build": "vite build && node scripts/assert-css-layers.mjs && node scripts/stamp-dist.mjs",
"build": "vite build && node scripts/assert-css-layers.mjs && node scripts/assert-bundle-size.mjs && node scripts/stamp-dist.mjs",
"typecheck": "tsc -b",
"lint": "oxlint src vite.config.ts",
"format": "prettier --write .",
+55
View File
@@ -0,0 +1,55 @@
#!/usr/bin/env node
// The admin bundle is embedded in the server binary and served to a household
// LAN, so an accidental dependency or a stray asset landing in dist is a
// regression nobody would otherwise notice: every other gate passes with a
// bundle twice this size. One number, total bytes of dist/assets — not per
// chunk, not gzipped — because the failure being caught is "something big
// arrived", not chunk shape.
//
// This runs from admin/ as part of `npm run build`, before stamp-dist: a failed
// size check must not leave a fresh .src-hash beside an oversized bundle that a
// later Zig build would accept as current.
import { readdirSync, statSync } from "node:fs";
import { dirname, join } from "node:path";
import { fileURLToPath } from "node:url";
const BUDGET_BYTES = 800_000;
const distDir = join(dirname(dirname(fileURLToPath(import.meta.url))), "dist", "assets");
let entries;
try {
entries = readdirSync(distDir, { withFileTypes: true });
} catch (err) {
console.error(`assert-bundle-size: cannot read admin/dist/assets: ${err.message}`);
process.exit(1);
}
const files = entries
.filter((entry) => entry.isFile())
.map((entry) => ({ name: entry.name, bytes: statSync(join(distDir, entry.name)).size }))
.sort((a, b) => b.bytes - a.bytes);
if (files.length === 0) {
console.error("assert-bundle-size: no files in admin/dist/assets — did the build emit anything?");
process.exit(1);
}
const total = files.reduce((sum, file) => sum + file.bytes, 0);
const format = (bytes) => bytes.toLocaleString("en-US");
if (total > BUDGET_BYTES) {
console.error(
`assert-bundle-size: admin/dist/assets is ${format(total)} bytes, over the ${format(BUDGET_BYTES)} byte budget.`,
);
console.error("Largest chunks:");
for (const file of files.slice(0, 5)) console.error(` ${format(file.bytes).padStart(9)} ${file.name}`);
console.error("Drop what arrived, or raise the budget in this script with the reason in the changelog.");
process.exit(1);
}
console.log(
`admin/dist/assets is ${format(total)} bytes across ${files.length} files, ` +
`${format(BUDGET_BYTES - total)} under the ${format(BUDGET_BYTES)} byte budget`,
);
@@ -5,7 +5,7 @@ import { AuthProvider } from "@/auth/store";
import { createQueryClient } from "@/lib/queryClient";
import { createAppRouter } from "@/routes";
import type { QueryDetail } from "@/lib/types";
import { provenance } from "@/features/queries/provenanceFixture";
import { provenance } from "@/features/provenance/provenanceFixture";
import { health } from "@/lib/healthFixture";
function detail(id: number, sections: Parameters<typeof provenance>[0] = {}): QueryDetail {
@@ -11,7 +11,7 @@ import { createQueryClient } from "@/lib/queryClient";
import { createAppRouter } from "@/routes";
import { health } from "@/lib/healthFixture";
import type { Client, Coverage, QueriesPage, QueryRow } from "@/lib/types";
import { queryRow } from "@/features/queries/provenanceFixture";
import { queryRow } from "@/features/provenance/provenanceFixture";
function client(id: number, ip: string, name: string, learnedName: string): Client {
return {
@@ -15,7 +15,7 @@ import InlineError from "@/lib/InlineError";
import { queriesInfiniteQuery } from "@/lib/queries";
import type { QueryRow } from "@/lib/types";
import { useClientNames } from "@/features/clients/clientNames";
import { summarizeRow } from "@/features/queries/querySummary";
import { summarizeRow } from "@/features/provenance/querySummary";
import { styles as shared } from "@/ui/styles";
import { colors } from "@/ui/tokens.stylex";
import { ActivityCells, ActivityTableHead, activityDomainLink } from "./cells";
@@ -13,7 +13,7 @@ import { AuthProvider } from "@/auth/store";
import { createQueryClient } from "@/lib/queryClient";
import { createAppRouter } from "@/routes";
import type { Client } from "@/lib/types";
import { provenance, queryRow } from "@/features/queries/provenanceFixture";
import { provenance, queryRow } from "@/features/provenance/provenanceFixture";
import { health } from "@/lib/healthFixture";
import { FakeEventSource } from "./fakeEventSource";
+1 -1
View File
@@ -16,7 +16,7 @@ import { useEffect, useRef, useState } from "react";
import { Link } from "@tanstack/react-router";
import * as stylex from "@stylexjs/stylex";
import { useClientNames } from "@/features/clients/clientNames";
import { summarizeEvent } from "@/features/queries/querySummary";
import { summarizeEvent } from "@/features/provenance/querySummary";
import { styles as shared } from "@/ui/styles";
import { colors } from "@/ui/tokens.stylex";
import { ActivityCells, ActivityTableHead, activityDomainLink } from "./cells";
@@ -19,8 +19,8 @@ import {
qclassName,
rcodeName,
routeKindLabel,
} from "@/features/queries/provenanceCopy";
import { qtypeName } from "@/features/queries/qtype";
} from "@/features/provenance/provenanceCopy";
import { qtypeName } from "@/features/provenance/qtype";
import { styles as shared } from "@/ui/styles";
import { colors } from "@/ui/tokens.stylex";
+2 -2
View File
@@ -1,8 +1,8 @@
import { render, screen } from "@testing-library/react";
import type { QueryRow } from "@/lib/types";
import type { ClientNames } from "@/features/clients/clientNames";
import { queryRow } from "@/features/queries/provenanceFixture";
import { summarizeRow, type QuerySummary } from "@/features/queries/querySummary";
import { queryRow } from "@/features/provenance/provenanceFixture";
import { summarizeRow, type QuerySummary } from "@/features/provenance/querySummary";
import { ACTIVITY_COLUMNS, ActivityCells, ActivityTableHead, resultLabel, routeLabel } from "./cells";
const noNames: ClientNames = new Map();
+3 -3
View File
@@ -17,9 +17,9 @@ import * as stylex from "@stylexjs/stylex";
import { formatMicros, formatTime } from "@/lib/format";
import type { RouteKind } from "@/lib/types";
import { ClientName, type ClientNames } from "@/features/clients/clientNames";
import { rcodeShortName } from "@/features/queries/provenanceCopy";
import { qtypeName } from "@/features/queries/qtype";
import type { QuerySummary } from "@/features/queries/querySummary";
import { rcodeShortName } from "@/features/provenance/provenanceCopy";
import { qtypeName } from "@/features/provenance/qtype";
import type { QuerySummary } from "@/features/provenance/querySummary";
import { styles as shared } from "@/ui/styles";
import { colors } from "@/ui/tokens.stylex";
@@ -0,0 +1,58 @@
import { diagnosticsBounds, relatedBounds, RELATED_WINDOW_SECONDS } from "./relatedLinks";
import { queriesFilterOf, validateActivitySearch } from "./search";
const TS = 1_700_000_000;
test("an unbounded origin falls back to the window either side of the query", () => {
expect(relatedBounds(TS, { since: undefined, until: undefined })).toEqual({
since: TS - RELATED_WINDOW_SECONDS,
until: TS + RELATED_WINDOW_SECONDS,
});
});
test("a bounded origin carries both of its bounds through unchanged", () => {
expect(relatedBounds(TS, { since: 1, until: 2 })).toEqual({ since: 1, until: 2 });
});
test("a half-bounded origin keeps its half and falls back on the other", () => {
expect(relatedBounds(TS, { since: 1, until: undefined })).toEqual({
since: 1,
until: TS + RELATED_WINDOW_SECONDS,
});
expect(relatedBounds(TS, { since: undefined, until: 2 })).toEqual({
since: TS - RELATED_WINDOW_SECONDS,
until: 2,
});
});
test("an origin bound of zero is a bound, not a missing one", () => {
expect(relatedBounds(TS, { since: 0, until: 0 })).toEqual({ since: 0, until: 0 });
});
test("the diagnostics window is the fixed window either side of the query, never inherited", () => {
expect(diagnosticsBounds(TS)).toEqual({ since: TS - RELATED_WINDOW_SECONDS, until: TS + RELATED_WINDOW_SECONDS });
expect(diagnosticsBounds(0)).toEqual({ since: -RELATED_WINDOW_SECONDS, until: RELATED_WINDOW_SECONDS });
});
/**
* The admin half of the server's `since <= ts < until` window contract
* (queries_repo.zig): what a related link emits has to survive the validation
* the Activity route puts every search through, or the link would silently open
* a wider window than it named. Inclusion at the edges is the server's property
* and is tested there; this pins that the bounds arrive intact.
*/
test("bounds emitted by a related link round-trip through the Activity search to the same filter", () => {
const origin = { since: undefined, until: TS + 3_600 };
const emitted = { mode: "history", domain: "ads.example.com", ...relatedBounds(TS, origin) };
const applied = validateActivitySearch(emitted);
expect(applied.mode).toBe("history");
expect(applied.since).toBe(emitted.since);
expect(applied.until).toBe(emitted.until);
expect(queriesFilterOf(applied)).toEqual({
domain: "ads.example.com",
since: TS - RELATED_WINDOW_SECONDS,
until: TS + 3_600,
});
});
@@ -1,5 +1,5 @@
import type { Provenance, QueryRow } from "@/lib/types";
import { provenance, queryRow } from "@/features/queries/provenanceFixture";
import { provenance, queryRow } from "@/features/provenance/provenanceFixture";
import { RING_CAPACITY, mergeGap, pushRow, summaryOf, type LiveRow } from "./ringBuffer";
function streamed(key: number, ts: number, domain: string, sections: Parameters<typeof provenance>[0] = {}): LiveRow {
+1 -1
View File
@@ -1,5 +1,5 @@
import type { LiveQueryEvent, QueryRow } from "@/lib/types";
import { summarizeEvent, summarizeRow, type QuerySummary } from "@/features/queries/querySummary";
import { summarizeEvent, summarizeRow, type QuerySummary } from "@/features/provenance/querySummary";
/**
* A row in the live buffer. `key` is a client-side monotonic counter, because
@@ -1,7 +1,7 @@
import { act, renderHook, waitFor } from "@testing-library/react";
import { ApiError } from "@/lib/api";
import type { QueriesPage, QueryRow } from "@/lib/types";
import { provenance, queryRow } from "@/features/queries/provenanceFixture";
import { provenance, queryRow } from "@/features/provenance/provenanceFixture";
import { summaryOf, type LiveRow } from "./ringBuffer";
import { FakeEventSource } from "./fakeEventSource";
import { CAP_ERROR_THRESHOLD, useLiveQueries } from "./useLiveQueries";
+1 -1
View File
@@ -17,7 +17,7 @@ import * as stylex from "@stylexjs/stylex";
import { useNavigate, useSearch } from "@tanstack/react-router";
import CoverageNotice from "@/lib/CoverageNotice";
import InlineError from "@/lib/InlineError";
import { qtypeName } from "@/features/queries/qtype";
import { qtypeName } from "@/features/provenance/qtype";
import type { Period, StatsRoutes, StatsTypes } from "@/lib/types";
import { styles as shared } from "@/ui/styles";
import { colors } from "@/ui/tokens.stylex";
-2
View File
@@ -184,8 +184,6 @@ export const deleteBlocklist = (id: number): Promise<void> => request(`/api/bloc
export const listRules = async (): Promise<Rule[]> => (await request<{ rules: Rule[] }>("/api/rules")).rules;
export const createRule = (input: RuleInput): Promise<RuleEcho> =>
request("/api/rules", { method: "POST", body: input });
export const updateRule = (id: number, input: RuleInput): Promise<RuleEcho> =>
request(`/api/rules/${id}`, { method: "PUT", body: input });
export const deleteRule = (id: number): Promise<void> => request(`/api/rules/${id}`, { method: "DELETE" });
// Local records
+65
View File
@@ -148,6 +148,21 @@ export const sample_create_blocklist: BlocklistEcho = {
url: "https://lists.example/ads.txt",
};
export const sample_get_blocklist: Blocklist = {
checksum: null,
domain_count: 0,
enabled: false,
exception_count: 0,
id: 0,
is_suggested: false,
last_updated: null,
name: "ads",
skipped_regex_count: 0,
skipped_unsupported_count: 0,
url: "https://lists.example/ads.txt",
wildcard_count: 0,
};
export const sample_list_blocklists: { blocklists: Blocklist[] } = {
blocklists: [
{
@@ -210,6 +225,12 @@ export const sample_create_group: Group = {
safe_search: false,
};
export const sample_get_group: Group = {
id: 0,
name: "kids",
safe_search: false,
};
export const sample_update_group: Group = {
id: 0,
name: "teens",
@@ -232,6 +253,16 @@ export const sample_create_rule: RuleEcho = {
pattern: "ads.example",
};
export const sample_get_rule: Rule = {
action: "block",
created_at: 0,
group: "default",
group_id: 0,
id: 0,
kind: "exact",
pattern: "ads.example",
};
export const sample_list_rules: { rules: Rule[] } = {
rules: [
{
@@ -274,6 +305,14 @@ export const sample_create_local_record: LocalRecord = {
value: "192.168.1.10",
};
export const sample_get_local_record: LocalRecord = {
id: 0,
name: "nas.lan",
rtype: "A",
ttl: 0,
value: "192.168.1.10",
};
export const sample_list_local_records: { local_records: LocalRecord[] } = {
local_records: [
{
@@ -300,6 +339,12 @@ export const sample_create_forward_zone: ForwardZone = {
zone: "lan",
};
export const sample_get_forward_zone: ForwardZone = {
id: 0,
resolver: "udp://10.0.0.1:53",
zone: "lan",
};
export const sample_list_forward_zones: { forward_zones: ForwardZone[] } = {
forward_zones: [
{
@@ -332,6 +377,18 @@ export const sample_list_clients: { clients: Client[] } = {
],
};
export const sample_get_client: Client = {
first_seen: 0,
group: "default",
group_id: 0,
hand_edited: false,
id: 0,
ip: "192.168.1.50",
last_seen: 0,
learned_name: "",
name: "laptop",
};
export const sample_update_client: Client = {
first_seen: 0,
group: "default",
@@ -389,6 +446,14 @@ export const sample_create_upstream: UpstreamEcho = {
url: "https://dns2.example/dns-query",
};
export const sample_get_upstream: Upstream = {
enabled: true,
id: 0,
priority: 0,
tls_name: "",
url: "https://dns.example/dns-query",
};
export const sample_update_upstream: UpstreamEcho = {
enabled: true,
id: 0,
+2 -6
View File
@@ -83,7 +83,7 @@ export interface LogoutResponse {
/**
* The three closed enums `src/storage/provenance.zig` stores, as values rather
* than bare types: the copy maps in `features/queries/provenanceCopy.ts` have to
* than bare types: the copy maps in `features/provenance/provenanceCopy.ts` have to
* be proven exhaustive at runtime as well as by `tsc`, exactly as
* `DIAGNOSTIC_CODES` below.
*/
@@ -320,7 +320,7 @@ export interface StatsTimeseries {
/**
* One DNS type's share of the window. `qtype` is the numeric code as logged:
* naming it is the admin's job (`features/queries/qtype.ts`), and a row whose
* naming it is the admin's job (`features/provenance/qtype.ts`), and a row whose
* type was never recorded keeps its own `null` group rather than disappearing.
*/
export interface StatsTypeRow {
@@ -396,10 +396,6 @@ export interface GroupInput {
safe_search?: boolean;
}
export interface GroupSources {
source_ids: number[];
}
export interface Blocklist {
id: number;
url: string;