fix(m5): post-review pass — AND filter, advisory lock, N+1, item caps, mutation cache
Spec-reviewer + code-reviewer findings applied:
Must-fix
- Filter combinator AND-semantics: tactic+technique+subtechnique now intersect
(one IN subquery per facet) instead of being pooled into one OR. Reviewers
flagged both the wrong default semantics and the theoretical UUID-collision
risk of pooling tactic/technique/sub UUIDs into a shared list across
three columns.
- Front-end mutation cache hygiene: updateMeta + setTests both
`onSettled: invalidate` so a partial failure leaves the cache consistent.
Should-fix
- Per-scenario pg_advisory_xact_lock on set_scenario_tests — serialises
concurrent reorders, mirrors M4 /mitre/sync pattern.
- Backend/front consistency on duplicate tests in a scenario: the
UNIQUE(scenario_id, position) constraint already allows the same
test_template multiple times (chained ops), so the catalogue picker no
longer excludes already-picked items.
Nice-to-have
- N+1 eradicated in test_template view rendering: _to_views_batch
builds {uuid → MitreRow} maps in 3 queries up-front; list endpoint
now issues 4 queries total regardless of list size.
- Wire-level item length caps on tags (64) and expected_iocs (255)
via Annotated[str, StringConstraints(...)] — returns 400 instead of
bubbling up StringDataRightTruncation.
- 4 new pytest covering the AND-filter, extra="forbid" rejection,
empty mitre_tags clearing, and the 65-char tag cap. Total now
81 pytest + 38 e2e pass.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -17,7 +17,7 @@ from dataclasses import dataclass
|
||||
from datetime import datetime, timezone
|
||||
from typing import Any
|
||||
|
||||
from sqlalchemy import func, or_, select
|
||||
from sqlalchemy import func, or_, select, text
|
||||
from sqlalchemy.orm import Session, selectinload
|
||||
|
||||
_UNSET: Any = object()
|
||||
@@ -208,8 +208,20 @@ def set_scenario_tests(
|
||||
scenario_id: uuid.UUID,
|
||||
test_template_ids: list[uuid.UUID],
|
||||
) -> ScenarioTemplateView:
|
||||
"""Replace the entire ordered test list. `position` becomes the index."""
|
||||
"""Replace the entire ordered test list. `position` becomes the index.
|
||||
|
||||
Acquires a per-scenario advisory lock to serialise concurrent reorders.
|
||||
Without it, two parallel `PUT /scenario-templates/{id}/tests` calls would
|
||||
race on the wipe-then-insert sequence and deadlock on the UNIQUE(position)
|
||||
constraint under READ COMMITTED. Mirrors the M4 pattern on /mitre/sync.
|
||||
"""
|
||||
with session_scope() as s:
|
||||
# Lock keyed on the scenario UUID — different scenarios don't block
|
||||
# each other. Two-int form: high-32 = constant, low-32 = hash of UUID.
|
||||
s.execute(
|
||||
text("SELECT pg_advisory_xact_lock(:n, :m)"),
|
||||
{"n": 0x5C3, "m": hash(scenario_id) & 0xFFFFFFFF},
|
||||
)
|
||||
sc = s.get(ScenarioTemplate, scenario_id)
|
||||
if sc is None or sc.deleted_at is not None:
|
||||
raise ScenarioTemplateNotFound()
|
||||
|
||||
Reference in New Issue
Block a user