feat: classify gear and weapon mods
Validate and deploy production / validate (push) Successful in 24s
Validate and deploy staging / validate (push) Successful in 44s
Validate and deploy production / deploy (push) Failing after 29s
Validate and deploy staging / deploy (push) Failing after 6s

This commit is contained in:
jb
2026-07-31 21:54:47 -04:00
parent 1bd9facd3f
commit f48cc878aa
21 changed files with 595 additions and 39 deletions
@@ -7,6 +7,7 @@ from pathlib import Path
import pytest
from game_data_capture.domain.validation import validate_classification
from game_data_capture.storage import CaptureCorpus, CaptureRepository, ContentAddressedBlobStore
from game_data_capture.storage.blobs import BlobCorruptionError, BlobLockError
from game_data_capture.storage.database import StorageDatabase
@@ -24,6 +25,17 @@ def _label(*, view: str = "detail", records: list[str] | None = None) -> dict[st
}
def _mod_label(family: str) -> dict[str, object]:
return {
"family": family,
"view": "detail",
"gear_slot": None,
"record_types": [family],
"confidence": 1.0,
"abstention_reason": None,
}
@pytest.fixture
def corpus(tmp_path: Path) -> tuple[StorageDatabase, CaptureRepository, CaptureCorpus]:
root = (tmp_path / "utility").resolve()
@@ -420,6 +432,91 @@ def test_label_transaction_rolls_back_on_evidence_failure(
assert stored is not None and '"view":"detail"' in stored["classification_json"]
def test_mod_labels_round_trip_through_restart_projections_and_shared_validation(
corpus: tuple[StorageDatabase, CaptureRepository, CaptureCorpus],
monkeypatch: pytest.MonkeyPatch,
) -> None:
database, repository, port = corpus
weapon_mod = _mod_label("weapon_mod")
gear_mod = _mod_label("gear_mod")
_receive(repository, "capture-0001", "receipt-0001")
_receive(repository, "capture-0002", "receipt-0002")
assert port.label("capture-0001", weapon_mod) == "updated"
assert port.label("capture-0002", gear_mod) == "updated"
shared_validator = validate_classification
validated: list[dict[str, object]] = []
def record_shared_validation(value: object) -> None:
assert isinstance(value, dict)
validated.append(value.copy())
shared_validator(value)
monkeypatch.setattr(
"game_data_capture.storage.corpus.validate_classification", record_shared_validation
)
restarted = CaptureCorpus(
database, ContentAddressedBlobStore(database.root), max_media_bytes=1024
)
listed = {
record.capture_id: record.classification for record in restarted.list_captures(limit=2)
}
assert listed == {"capture-0001": weapon_mod, "capture-0002": gear_mod}
weapon_detail = restarted.get_capture("capture-0001")
gear_detail = restarted.get_capture("capture-0002")
assert weapon_detail is not None and weapon_detail.classification == weapon_mod
assert gear_detail is not None and gear_detail.classification == gear_mod
assert validated == [gear_mod, weapon_mod, weapon_mod, gear_mod]
def test_invalid_mod_label_leaves_current_label_and_evidence_unchanged(
corpus: tuple[StorageDatabase, CaptureRepository, CaptureCorpus],
) -> None:
database, repository, port = corpus
weapon_mod = _mod_label("weapon_mod")
gear_mod = _mod_label("gear_mod")
_receive(repository, "capture-0001", "receipt-0001")
assert port.label("capture-0001", weapon_mod) == "updated"
assert port.label("capture-0001", gear_mod) == "updated"
with database.connect() as connection:
current = connection.execute(
"SELECT classification_json, revision FROM capture_labels WHERE capture_id = ?",
("capture-0001",),
).fetchone()
assert current is not None
before_current = tuple(current)
before_evidence = [
tuple(row)
for row in connection.execute(
"SELECT replaced_revision, classification_json FROM capture_label_evidence "
"WHERE capture_id = ? ORDER BY replaced_revision",
("capture-0001",),
)
]
malformed = {**weapon_mod, "gear_slot": "Mask"}
unsupported = {**weapon_mod, "view": "blueprint"}
for invalid in (malformed, unsupported):
with pytest.raises(ValueError, match="frozen taxonomy shape"):
port.label("capture-0001", invalid)
with database.connect() as connection:
current = connection.execute(
"SELECT classification_json, revision FROM capture_labels WHERE capture_id = ?",
("capture-0001",),
).fetchone()
assert current is not None
after_current = tuple(current)
after_evidence = [
tuple(row)
for row in connection.execute(
"SELECT replaced_revision, classification_json FROM capture_label_evidence "
"WHERE capture_id = ? ORDER BY replaced_revision",
("capture-0001",),
)
]
assert after_current == before_current
assert after_evidence == before_evidence
def test_deletion_intent_recovers_after_unlink_failure(
corpus: tuple[StorageDatabase, CaptureRepository, CaptureCorpus],
monkeypatch: pytest.MonkeyPatch,