T-D — Validator: one rule per E_/W_/I_ code over snapshot and result #19
Labels
No labels
ready-for-agent
No milestone
No project
No assignees
1 participant
Notifications
Due date
No due date set.
Blocks
Depends on
#23 PH3.1 — Nightly run job and replay-run by id
patrick/Polaris
#24 PH3.2 — Triage: accept, reject, accept-all, pin and rejection locks, E_UNTRIAGED, I_GAP_UNFILLED, I_REJECTED_ELSEWHERE
patrick/Polaris
#26 PH3.4 — Publish and discard: synchronous final validation, 409 with findings, warning acknowledgement
patrick/Polaris
#33 PH5.1 — Locks: every plan_lock type with checks, GET /locks, clear, expiry, E_LOCK_VIOLATED
patrick/Polaris
#37 PH5.5 — Diff between plan versions and I_DISPLACED
patrick/Polaris
Reference
patrick/Polaris#19
Loading…
Reference in a new issue
No description provided.
Delete branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Parent
Row T-D in
docs/12-implementation-plan.md(branch, owned paths and doc section are defined there).What to build
Given a
PlanningSnapshotand aPlanResult, the validator returnsFindings — one pure function per code from the docs/04 validator table — withcode+paramsand no prose. Each rule has a hand-built bad plan that triggers it and only it.Acceptance criteria
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.paramssufficient for the frontend template;messageis dev-only -- every rule uses Finding.for_code with a structured params dict; message is never set.coreandcontracts(neverengine) -- enforced by tests/unit/test_import_rules.py, green.Blocked by
Starting work. Branch
feature/validator-rules(frommain). 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.
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/*.pyfile, 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.2feature/api-triage(validator/rules/{untriaged,gap_unfilled,rejected_elsewhere}.py)W_CONF_DROP-> PH4.3feature/scorer-feedback-loop(validator/rules/conf_drop.py)E_LOCK_VIOLATED-> PH5.1feature/api-locks(validator/rules/lock_violated.py)I_DISPLACED-> PH5.5feature/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 thanstale_days") cannot be checked fromPlanningSnapshot+PlanResultalone -- neitherSnapshotSitenor any other snapshot model carries a per-vehicle "last feed received" timestamp (onlyDEFAULT_STALE_DAYSexists, as a bare constant with nothing to compare it against). I'm leaving this rule out and flagging it as a seam request againstcontracts/(e.g. alast_feed_atfield onSnapshotSiteor 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.
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:
validator/rules/*.pyfile (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).W_PATTERN_STALEneeds a per-vehicle "last feed received" timestamp that noPlanningSnapshotfield 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_BOOKEDvsE_DOUBLE_PRESENCEsplit (same site vs different site, since docs/04 uses near-identical wording for both), andE_SHIFT_EXCEEDEDonly checking the outbound leg against the shift's start (no return-travel figure exists onResultAssignmentto check the return leg).ruff / ruff format / mypy --strict / pytest all green, including the existing
test_import_rules.py.Not merging myself, per instructions.