spec milestone 21: re-verify anchors against m20, record authority-mode interaction
Gates / test (push) Successful in 1m27s
Gates / test-aarch64 (push) Successful in 5m56s
Gates / frontend (push) Successful in 49s
Gates / package (push) Successful in 3m54s
Gates / container (push) Failing after 2m20s
CI / gates (push) Failing after 14m27s
Gates / test (push) Successful in 1m27s
Gates / test-aarch64 (push) Successful in 5m56s
Gates / frontend (push) Successful in 49s
Gates / package (push) Successful in 3m54s
Gates / container (push) Failing after 2m20s
CI / gates (push) Failing after 14m27s
This commit is contained in:
+54
-26
@@ -5,10 +5,10 @@ entries scoped below operator rules and above list blocks (tier 1), and add a
|
|||||||
`regex` rule kind for operator rules backed by a homegrown linear-time engine
|
`regex` rule kind for operator rules backed by a homegrown linear-time engine
|
||||||
(tier 2). Nothing else from the ABP syntax enters scope.
|
(tier 2). Nothing else from the ABP syntax enters scope.
|
||||||
|
|
||||||
Design written 2026-08-09 against HEAD `ffc3ca6`. Every `file:line` anchor
|
Design written 2026-08-09 against HEAD `ffc3ca6`; every anchor re-verified
|
||||||
below was read at that commit. The milestone amends PLAN §2.2, which currently
|
2026-08-11 against `a8e0fe4` after milestone 20 landed. The milestone amends
|
||||||
rules regex out permanently; the amendment is part of session S3, not a
|
PLAN §2.2, which currently rules regex out permanently; the amendment is part
|
||||||
side effect.
|
of session S3, not a side effect.
|
||||||
|
|
||||||
## Implementation contract (read first)
|
## Implementation contract (read first)
|
||||||
|
|
||||||
@@ -17,7 +17,7 @@ side effect.
|
|||||||
only `std` (`src/filter/parsers.zig:1-14`). The new `src/filter/regex.zig`
|
only `std` (`src/filter/parsers.zig:1-14`). The new `src/filter/regex.zig`
|
||||||
obeys the same constraint.
|
obeys the same constraint.
|
||||||
- Every new `src/**.zig` file must be listed in `src/tests.zig`
|
- Every new `src/**.zig` file must be listed in `src/tests.zig`
|
||||||
(`build.zig:495-540` fatals otherwise).
|
(`build.zig:494-539` fatals otherwise).
|
||||||
- Frozen DDL is frozen: schema changes are new migration steps
|
- Frozen DDL is frozen: schema changes are new migration steps
|
||||||
(`src/storage/config_schema.zig:1-6`, `src/storage/migrations.zig:21-22`).
|
(`src/storage/config_schema.zig:1-6`, `src/storage/migrations.zig:21-22`).
|
||||||
- After any API shape change, regenerate the contract samples
|
- After any API shape change, regenerate the contract samples
|
||||||
@@ -65,19 +65,21 @@ covers the bodies in order list, wild, allow — because SHA-256 of
|
|||||||
already-published checksum stays valid, and `Manager.loadSource`
|
already-published checksum stays valid, and `Manager.loadSource`
|
||||||
(`src/filter/manager.zig:543-598`) treats a missing `<id>.allow` file as an
|
(`src/filter/manager.zig:543-598`) treats a missing `<id>.allow` file as an
|
||||||
empty body. No refetch is forced by upgrading. `bodyChecksum`
|
empty body. No refetch is forced by upgrading. `bodyChecksum`
|
||||||
(manager.zig:1564) follows the same order; `source_file_suffixes`
|
(manager.zig:1572) follows the same order; `source_file_suffixes`
|
||||||
(manager.zig:1581) gains `.allow.tmp` and `.allow` with longest-suffix-first
|
(manager.zig:1589) gains `.allow.tmp` and `.allow` with longest-suffix-first
|
||||||
order preserved; the on-disk header (manager.zig:222-242) gains
|
order preserved; the on-disk header (manager.zig:221-241) gains
|
||||||
`# exceptions {d}` after the `# wildcards` line and the pinning test at
|
`# exceptions {d}` after the `# wildcards` line and the pinning test at
|
||||||
manager.zig:1872-1900 is extended, not weakened.
|
manager.zig:1914-1946 is extended, not weakened.
|
||||||
|
|
||||||
### 4. Exception counts persist and surface
|
### 4. Exception counts persist and surface
|
||||||
|
|
||||||
Migration step 3 (`ddl_v3`, appended at `src/storage/migrations.zig:23-26`):
|
Migration step 3 (`ddl_v3`, appended at `src/storage/migrations.zig:23-26`):
|
||||||
`ALTER TABLE blocklist_sources ADD COLUMN exception_count INTEGER NOT NULL
|
`ALTER TABLE blocklist_sources ADD COLUMN exception_count INTEGER NOT NULL
|
||||||
DEFAULT 0;`. `sources_repo.SourceRow` and `updateSourceStats`
|
DEFAULT 0;`. `sources_repo.SourceRow` and `updateSourceStats`
|
||||||
(`src/storage/repositories/sources_repo.zig:91-166`) carry it;
|
(`src/storage/repositories/sources_repo.zig:80-93,159-169`) carry it, and the
|
||||||
`SourceStatus` rehydration (`manager.zig:1457-1494`) restores it alongside the
|
checksum doc line at sources_repo.zig:100 is updated alongside
|
||||||
|
`bodyChecksum`'s per ruling 3;
|
||||||
|
`SourceStatus` rehydration (`manager.zig:1458-1502`) restores it alongside the
|
||||||
existing three counts; `StatusView` (`src/web/handlers/blocklists.zig:52-78`)
|
existing three counts; `StatusView` (`src/web/handlers/blocklists.zig:52-78`)
|
||||||
gains `exceptions: u32`; the blocklists UI shows it where `skipped_regex`
|
gains `exceptions: u32`; the blocklists UI shows it where `skipped_regex`
|
||||||
already shows (`web/src/features/blocklists/SourceStatusSection.tsx`,
|
already shows (`web/src/features/blocklists/SourceStatusSection.tsx`,
|
||||||
@@ -112,11 +114,15 @@ time). The engine is a fuzz-module root like parsers.zig and imports only
|
|||||||
|
|
||||||
Migration step 4 (`ddl_v4`): the 12-step rebuild of `rules` with
|
Migration step 4 (`ddl_v4`): the 12-step rebuild of `rules` with
|
||||||
`CHECK(kind IN ('exact','wildcard','regex'))` — the frozen v1 DDL
|
`CHECK(kind IN ('exact','wildcard','regex'))` — the frozen v1 DDL
|
||||||
(`src/storage/config_schema.zig:65-72`) cannot be edited. `model.RuleKind`
|
(`src/storage/config_schema.zig:65-72`) cannot be edited. The rebuilt table
|
||||||
(`src/config/model.zig:253-269`) gains `.regex`; the exhaustive switches in
|
keeps the name `rules`: `config_schema.table_names`
|
||||||
`config/validate.zig:1067-1098` (compile the pattern, report
|
(config_schema.zig:112-119) and the invariant tests at
|
||||||
|
config_schema.zig:120-127 and migrations.zig:356 assert the schema's table
|
||||||
|
set, and a rename would fail both. `model.RuleKind`
|
||||||
|
(`src/config/model.zig:259-275`) gains `.regex`; the exhaustive switches in
|
||||||
|
`config/validate.zig:1099-1130` (compile the pattern, report
|
||||||
`"... is not a valid regex pattern"` through the existing error path at
|
`"... is not a valid regex pattern"` through the existing error path at
|
||||||
validate.zig:862-869) and `src/filter/rules.zig:62-68` extend. `RuleSet`
|
validate.zig:891-902) and `src/filter/rules.zig:62-68` extend. `RuleSet`
|
||||||
(`rules.zig:28-36`) grows `regex_allow` and `regex_block` slices holding
|
(`rules.zig:28-36`) grows `regex_allow` and `regex_block` slices holding
|
||||||
compiled `Program`s plus their pattern texts (for `Decision.matched`);
|
compiled `Program`s plus their pattern texts (for `Decision.matched`);
|
||||||
`bucketOf` (rules.zig:133-143) becomes a six-bucket layout;
|
`bucketOf` (rules.zig:133-143) becomes a six-bucket layout;
|
||||||
@@ -135,12 +141,12 @@ the per-query cost lands on cache misses only.
|
|||||||
|
|
||||||
`src/web/handlers/rules.zig`: `toInput` accepts `"regex"`; the 400 string at
|
`src/web/handlers/rules.zig`: `toInput` accepts `"regex"`; the 400 string at
|
||||||
rules.zig:42 becomes `"kind must be 'exact', 'wildcard' or 'regex'"`.
|
rules.zig:42 becomes `"kind must be 'exact', 'wildcard' or 'regex'"`.
|
||||||
`src/web/openapi.yaml:1875-1917`: all three `enum: [exact, wildcard]` become
|
`src/web/openapi.yaml:1948,1962,1976`: all three `enum: [exact, wildcard]` become
|
||||||
`[exact, wildcard, regex]`. `web/src/lib/types.ts:195`:
|
`[exact, wildcard, regex]`. `web/src/lib/types.ts:195`:
|
||||||
`RuleKind = "exact" | "wildcard" | "regex"`; the rules page kind selector
|
`RuleKind = "exact" | "wildcard" | "regex"`; the rules page kind selector
|
||||||
gains the option. Contract samples regenerated. `nxdns export` / `import`
|
gains the option. Contract samples regenerated. `nxdns export` / `import`
|
||||||
round-trip the new kind with no extra work once `RuleKind.toDb/fromDb` extend
|
round-trip the new kind with no extra work once `RuleKind.toDb/fromDb` extend
|
||||||
— the existing round-trip test at model.zig:722-723 is extended to prove it.
|
— the enum round-trip test at model.zig:782 is extended to prove it.
|
||||||
|
|
||||||
### 8. PLAN amendments land with the code, in S3
|
### 8. PLAN amendments land with the code, in S3
|
||||||
|
|
||||||
@@ -151,10 +157,28 @@ counted and skipped; `$` modifiers (except the `$important` suffix of ruling
|
|||||||
out. PLAN.md:26 (§2.1 filtering sentence), PLAN.md:106 (§3.9), PLAN.md:108-117
|
out. PLAN.md:26 (§2.1 filtering sentence), PLAN.md:106 (§3.9), PLAN.md:108-117
|
||||||
(§3.10 precedence) and PLAN.md:700 (decision B) are updated to match rulings
|
(§3.10 precedence) and PLAN.md:700 (decision B) are updated to match rulings
|
||||||
2 and 6. In-code echoes of the old §2.2 move with it:
|
2 and 6. In-code echoes of the old §2.2 move with it:
|
||||||
`src/filter/wildcard.zig:6-8,22-24`, `src/filter/parsers.zig:25`,
|
`src/filter/wildcard.zig:6-7,20-23`, `src/filter/parsers.zig:25`,
|
||||||
`src/filter/parser_abp.zig:5-6`, `src/filter/compiler.zig:129-131`.
|
`src/filter/parser_abp.zig:5-6`, `src/filter/compiler.zig:129-131`.
|
||||||
|
|
||||||
### 9. Fuzz invariants move, never lapse
|
### 9. Milestone 20's authority modes bound where rules are written
|
||||||
|
|
||||||
|
m20 classifies POST/PUT/DELETE `/api/rules` as `.config_write`
|
||||||
|
(`src/web/routes.zig:94-97`), and under `.managed_file` authority the router
|
||||||
|
answers 403 (`src/web/router.zig:173-178`). Everything in this milestone
|
||||||
|
works in both modes, but the write path differs: in `.database` mode regex
|
||||||
|
rules arrive through the API; in `.managed_file` mode they arrive through the
|
||||||
|
config file and `src/config/reconcile.zig` (`reconcileRules`,
|
||||||
|
reconcile.zig:566-598), whose enum comparison carries `.regex` with no code
|
||||||
|
change. S3 owns a reconcile test proving a file-declared regex rule converges
|
||||||
|
into the table. Every S3 API acceptance check runs a server in `.database`
|
||||||
|
authority (no `--config`). The `SELECT *` dump helpers
|
||||||
|
(`src/config/import.zig:167-186`, `src/config/reconcile.zig:981`) will emit
|
||||||
|
the new `exception_count` column after `ddl_v3`; golden dump assertions in
|
||||||
|
those suites are updated in S1, which owns that migration. Reconcile's
|
||||||
|
runtime-column protection (reconcile.zig:11-15,435) covers `exception_count`
|
||||||
|
with no change — the column survives reconciles untouched.
|
||||||
|
|
||||||
|
### 10. Fuzz invariants move, never lapse
|
||||||
|
|
||||||
`tests/fuzz/blocklist_fuzz.zig` header invariant "covers_apex only on
|
`tests/fuzz/blocklist_fuzz.zig` header invariant "covers_apex only on
|
||||||
`.wildcard`" (its stated form at :4-28) becomes "only on `.wildcard` or
|
`.wildcard`" (its stated form at :4-28) becomes "only on `.wildcard` or
|
||||||
@@ -183,7 +207,10 @@ Owns: `src/filter/parsers.zig`, `src/filter/parser_abp.zig`,
|
|||||||
sentence only), `src/web/openapi.yaml` (StatusView shape only),
|
sentence only), `src/web/openapi.yaml` (StatusView shape only),
|
||||||
`web/src/features/blocklists/*`, `web/src/lib/types.ts` (source-stat fields
|
`web/src/features/blocklists/*`, `web/src/lib/types.ts` (source-stat fields
|
||||||
only), `web/src/lib/contractSamples.gen.ts`,
|
only), `web/src/lib/contractSamples.gen.ts`,
|
||||||
`tests/fuzz/blocklist_fuzz.zig`, `tests/fuzz/compiler_fuzz.zig`.
|
`tests/fuzz/blocklist_fuzz.zig`, `tests/fuzz/compiler_fuzz.zig`, and the
|
||||||
|
dump-golden assertions in `src/config/import.zig` and
|
||||||
|
`src/config/reconcile.zig` test suites only (ruling 9's `exception_count`
|
||||||
|
fallout from `ddl_v3`; S1 touches no reconcile logic).
|
||||||
|
|
||||||
- S1.1 `Kind.exception` in parsers.zig; parser_abp emits it per ruling 1;
|
- S1.1 `Kind.exception` in parsers.zig; parser_abp emits it per ruling 1;
|
||||||
parser_hosts and parser_domains never emit it (no change beyond the enum).
|
parser_hosts and parser_domains never emit it (no change beyond the enum).
|
||||||
@@ -191,7 +218,7 @@ only), `web/src/lib/contractSamples.gen.ts`,
|
|||||||
- S1.3 manager: suffixes, header line, `bodyChecksum`, `loadSource`
|
- S1.3 manager: suffixes, header line, `bodyChecksum`, `loadSource`
|
||||||
missing-file-is-empty, `Snapshot.Compiled.allow_body`,
|
missing-file-is-empty, `Snapshot.Compiled.allow_body`,
|
||||||
`prepareRefresh`/`publishRefresh`/`applyLoadOutcomes` carry the count.
|
`prepareRefresh`/`publishRefresh`/`applyLoadOutcomes` carry the count.
|
||||||
`rejectedWithoutEntries` (manager.zig:1559-1562) treats a compile with only
|
`rejectedWithoutEntries` (manager.zig:1563-1570) treats a compile with only
|
||||||
exceptions as loadable, not rejected.
|
exceptions as loadable, not rejected.
|
||||||
- S1.4 matcher: `SourceSets.exceptions`, `Reason.blocklist_exception`,
|
- S1.4 matcher: `SourceSets.exceptions`, `Reason.blocklist_exception`,
|
||||||
the new evaluate level per ruling 2, `memoryBytes` includes the new sets.
|
the new evaluate level per ruling 2, `memoryBytes` includes the new sets.
|
||||||
@@ -227,7 +254,7 @@ target only).
|
|||||||
matching; negated classes; `{n,m}` bounds; each error case; the
|
matching; negated classes; `{n,m}` bounds; each error case; the
|
||||||
pathological backtracker-killers (`(a+)+b` against `aaaaaaaaaaaaaaaaaaaaX`,
|
pathological backtracker-killers (`(a+)+b` against `aaaaaaaaaaaaaaaaaaaaX`,
|
||||||
nested alternation) complete within the step bound.
|
nested alternation) complete within the step bound.
|
||||||
- S2.3 the fuzz target per ruling 9.
|
- S2.3 the fuzz target per ruling 10.
|
||||||
|
|
||||||
Acceptance (S2):
|
Acceptance (S2):
|
||||||
- [ ] `zig build test` passes with the new file in `src/tests.zig`.
|
- [ ] `zig build test` passes with the new file in `src/tests.zig`.
|
||||||
@@ -242,9 +269,10 @@ Owns: `src/storage/migrations.zig` (step 4), `src/storage/config_schema.zig`
|
|||||||
`src/storage/repositories/rules_repo.zig`, `src/web/handlers/rules.zig`,
|
`src/storage/repositories/rules_repo.zig`, `src/web/handlers/rules.zig`,
|
||||||
`src/web/handlers/mutations.zig` (only if `checkRule` needs the kind),
|
`src/web/handlers/mutations.zig` (only if `checkRule` needs the kind),
|
||||||
`src/web/openapi.yaml`, `web/src/features/rules/*`, `web/src/lib/types.ts`,
|
`src/web/openapi.yaml`, `web/src/features/rules/*`, `web/src/lib/types.ts`,
|
||||||
`web/src/lib/contractSamples.gen.ts`, `PLAN.md`, `src/filter/wildcard.zig`
|
`web/src/lib/contractSamples.gen.ts`, `src/config/reconcile.zig` (test per
|
||||||
(comments), `src/filter/parsers.zig` (comment), `src/filter/parser_abp.zig`
|
ruling 9), `PLAN.md`, `src/filter/wildcard.zig` (comments),
|
||||||
(comment), `src/filter/compiler.zig` (comment), `tools/bench.zig`.
|
`src/filter/parsers.zig` (comment), `src/filter/parser_abp.zig` (comment),
|
||||||
|
`src/filter/compiler.zig` (comment), `tools/bench.zig`.
|
||||||
|
|
||||||
- S3.1 migration step 4 per ruling 6; repo and model layers.
|
- S3.1 migration step 4 per ruling 6; repo and model layers.
|
||||||
- S3.2 validate at both edges (config import, API) per rulings 6 and 7.
|
- S3.2 validate at both edges (config import, API) per rulings 6 and 7.
|
||||||
@@ -280,7 +308,7 @@ and `/api/lookup`. Record deviations in `## Recorded (implementation)`.
|
|||||||
|
|
||||||
New files:
|
New files:
|
||||||
- `src/filter/regex.zig` — the linear-time engine (ruling 5).
|
- `src/filter/regex.zig` — the linear-time engine (ruling 5).
|
||||||
- `tests/fuzz/regex_fuzz.zig` — its fuzz target (ruling 9).
|
- `tests/fuzz/regex_fuzz.zig` — its fuzz target (ruling 10).
|
||||||
|
|
||||||
Deleted surface: none.
|
Deleted surface: none.
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user