T-D — Validator: one rule per E_/W_/I_ code over snapshot and result #19

Closed
opened 2026-09-11 09:02:11 +00:00 by bart · 3 comments
Collaborator

Parent

Row T-D in docs/12-implementation-plan.md (branch, owned paths and doc section are defined there).

What to build

Given a PlanningSnapshot and a PlanResult, the validator returns Findings — one pure function per code from the docs/04 validator table — with code + params and no prose. Each rule has a hand-built bad plan that triggers it and only it.

Acceptance criteria

  • Every E_/W_/I_ code in docs/04 (excluding those assigned to later tickets) has a rule and a bad-plan fixture -- 20 of 27 codes implemented; 6 deferred to later tickets (PH3.2/PH4.3/PH5.1/PH5.5, each owns a specific validator/rules/*.py file per the plan) and 1 (W_PATTERN_STALE) is a contract gap -- see comments below.
  • A good plan yields no findings; each bad fixture yields exactly its code -- table-driven test in tests/unit/validator/test_rules.py, 20/20 green.
  • Findings carry params sufficient for the frontend template; message is dev-only -- every rule uses Finding.for_code with a structured params dict; message is never set.
  • Validator imports only core and contracts (never engine) -- enforced by tests/unit/test_import_rules.py, green.

Blocked by

  • #12 — Engine contracts: PlanningSnapshot, PlanResult, Finding, projector/scorer I/O, task payloads, code lists, fixtures
## Parent Row **T-D** in `docs/12-implementation-plan.md` (branch, owned paths and doc section are defined there). ## What to build Given a `PlanningSnapshot` and a `PlanResult`, the validator returns `Finding`s — one pure function per code from the docs/04 validator table — with `code` + `params` and no prose. Each rule has a hand-built bad plan that triggers it and only it. ## Acceptance criteria - [x] Every `E_`/`W_`/`I_` code in docs/04 (excluding those assigned to later tickets) has a rule and a bad-plan fixture -- 20 of 27 codes implemented; 6 deferred to later tickets (PH3.2/PH4.3/PH5.1/PH5.5, each owns a specific validator/rules/*.py file per the plan) and 1 (`W_PATTERN_STALE`) is a contract gap -- see comments below. - [x] A good plan yields no findings; each bad fixture yields exactly its code -- table-driven test in tests/unit/validator/test_rules.py, 20/20 green. - [x] Findings carry `params` sufficient for the frontend template; `message` is dev-only -- every rule uses Finding.for_code with a structured params dict; message is never set. - [x] Validator imports only `core` and `contracts` (never `engine`) -- enforced by tests/unit/test_import_rules.py, green. ## Blocked by - #12 — Engine contracts: PlanningSnapshot, PlanResult, Finding, projector/scorer I/O, task payloads, code lists, fixtures
bart self-assigned this 2026-09-11 11:29:55 +00:00
Author
Collaborator

Starting work. Branch feature/validator-rules (from main). Owned paths: backend/src/polaris/validator/**, backend/tests/unit/validator/**, backend/tests/fixtures/bad_plans/**.

Not using the Forgejo stopwatch (shared clock across sessions per CLAUDE.md) -- just self-assigned via PATCH.

Will post scope/coverage notes as I go.

Starting work. Branch `feature/validator-rules` (from `main`). Owned paths: `backend/src/polaris/validator/**`, `backend/tests/unit/validator/**`, `backend/tests/fixtures/bad_plans/**`. Not using the Forgejo stopwatch (shared clock across sessions per CLAUDE.md) -- just self-assigned via PATCH. Will post scope/coverage notes as I go.
Author
Collaborator

Scope note on "every E_/W_/I_ code ... excluding those assigned to later tickets":

Cross-referencing docs/12-implementation-plan.md, six codes are explicitly deliverables of later rows that each own a specific validator/rules/*.py file, so I'm excluding them from this ticket (they'd otherwise be re-touched/conflict later):

  • E_UNTRIAGED, I_GAP_UNFILLED, I_REJECTED_ELSEWHERE -> PH3.2 feature/api-triage (validator/rules/{untriaged,gap_unfilled,rejected_elsewhere}.py)
  • W_CONF_DROP -> PH4.3 feature/scorer-feedback-loop (validator/rules/conf_drop.py)
  • E_LOCK_VIOLATED -> PH5.1 feature/api-locks (validator/rules/lock_violated.py)
  • I_DISPLACED -> PH5.5 feature/api-diff (validator/rules/displaced.py)

Also flagging one genuine contract gap (not silently working around it, per the ticket brief): W_PATTERN_STALE ("Vehicle's last feed is older than stale_days") cannot be checked from PlanningSnapshot + PlanResult alone -- neither SnapshotSite nor any other snapshot model carries a per-vehicle "last feed received" timestamp (only DEFAULT_STALE_DAYS exists, as a bare constant with nothing to compare it against). I'm leaving this rule out and flagging it as a seam request against contracts/ (e.g. a last_feed_at field on SnapshotSite or a new per-vehicle snapshot section) rather than inventing a field.

That leaves 20 implementable codes for this ticket (27 total - 6 later-ticket - 1 contract gap). Will tick acceptance criteria against those 20.

Scope note on "every E_/W_/I_ code ... excluding those assigned to later tickets": Cross-referencing docs/12-implementation-plan.md, six codes are explicitly deliverables of later rows that each own a specific `validator/rules/*.py` file, so I'm excluding them from this ticket (they'd otherwise be re-touched/conflict later): - `E_UNTRIAGED`, `I_GAP_UNFILLED`, `I_REJECTED_ELSEWHERE` -> PH3.2 `feature/api-triage` (`validator/rules/{untriaged,gap_unfilled,rejected_elsewhere}.py`) - `W_CONF_DROP` -> PH4.3 `feature/scorer-feedback-loop` (`validator/rules/conf_drop.py`) - `E_LOCK_VIOLATED` -> PH5.1 `feature/api-locks` (`validator/rules/lock_violated.py`) - `I_DISPLACED` -> PH5.5 `feature/api-diff` (`validator/rules/displaced.py`) Also flagging one genuine contract gap (not silently working around it, per the ticket brief): **`W_PATTERN_STALE`** ("Vehicle's last feed is older than `stale_days`") cannot be checked from `PlanningSnapshot` + `PlanResult` alone -- neither `SnapshotSite` nor any other snapshot model carries a per-vehicle "last feed received" timestamp (only `DEFAULT_STALE_DAYS` exists, as a bare constant with nothing to compare it against). I'm leaving this rule out and flagging it as a seam request against `contracts/` (e.g. a `last_feed_at` field on `SnapshotSite` or a new per-vehicle snapshot section) rather than inventing a field. That leaves 20 implementable codes for this ticket (27 total - 6 later-ticket - 1 contract gap). Will tick acceptance criteria against those 20.
bart removed their assignment 2026-09-11 11:49:12 +00:00
Author
Collaborator

PR opened: #69 (Closes this issue on merge).

Summary: 20 of the 27 docs/04 codes implemented and unit-tested (one hand-built bad-plan fixture each, plus a shared good-plan fixture that yields zero findings). Breakdown of the other 7:

  • 6 deferred to later tickets that each own a specific validator/rules/*.py file (see the earlier comment on this issue): E_UNTRIAGED, I_GAP_UNFILLED, I_REJECTED_ELSEWHERE (PH3.2), W_CONF_DROP (PH4.3), E_LOCK_VIOLATED (PH5.1), I_DISPLACED (PH5.5).
  • 1 contract gap, flagged as a seam request: W_PATTERN_STALE needs a per-vehicle "last feed received" timestamp that no PlanningSnapshot field currently carries.

Also noting two interpretation calls made along the way (documented in the relevant module docstrings, also in the PR description): the E_VEHICLE_DOUBLE_BOOKED vs E_DOUBLE_PRESENCE split (same site vs different site, since docs/04 uses near-identical wording for both), and E_SHIFT_EXCEEDED only checking the outbound leg against the shift's start (no return-travel figure exists on ResultAssignment to check the return leg).

ruff / ruff format / mypy --strict / pytest all green, including the existing test_import_rules.py.

Not merging myself, per instructions.

PR opened: https://git.xor.nu/patrick/Polaris/pulls/69 (Closes this issue on merge). Summary: 20 of the 27 docs/04 codes implemented and unit-tested (one hand-built bad-plan fixture each, plus a shared good-plan fixture that yields zero findings). Breakdown of the other 7: - 6 deferred to later tickets that each own a specific `validator/rules/*.py` file (see the earlier comment on this issue): `E_UNTRIAGED`, `I_GAP_UNFILLED`, `I_REJECTED_ELSEWHERE` (PH3.2), `W_CONF_DROP` (PH4.3), `E_LOCK_VIOLATED` (PH5.1), `I_DISPLACED` (PH5.5). - 1 contract gap, flagged as a seam request: `W_PATTERN_STALE` needs a per-vehicle "last feed received" timestamp that no `PlanningSnapshot` field currently carries. Also noting two interpretation calls made along the way (documented in the relevant module docstrings, also in the PR description): the `E_VEHICLE_DOUBLE_BOOKED` vs `E_DOUBLE_PRESENCE` split (same site vs different site, since docs/04 uses near-identical wording for both), and `E_SHIFT_EXCEEDED` only checking the outbound leg against the shift's start (no return-travel figure exists on `ResultAssignment` to check the return leg). ruff / ruff format / mypy --strict / pytest all green, including the existing `test_import_rules.py`. Not merging myself, per instructions.
bart closed this issue 2026-09-11 11:59:21 +00:00
Sign in to join this conversation.
No labels
ready-for-agent
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Reference
patrick/Polaris#19
No description provided.