Skip to content

Commit 542a59c

Browse files
committed
test: prove portable evidence plan branch ownership
1 parent 82b8055 commit 542a59c

1 file changed

Lines changed: 286 additions & 16 deletions

File tree

tests/agent_kernel/evidence/test_service.py

Lines changed: 286 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,21 @@
3131
"SCAN stream",
3232
"MATERIALIZE model_calls_visible",
3333
"AUTOMATIC COVERING INDEX",
34-
"USE TEMP B-TREE",
34+
)
35+
_TEMP_SORT_MARKER = "USE TEMP B-TREE FOR ORDER BY"
36+
_PORTABLE_SESSION_SORT_VIEWS = frozenset({"timeline", "allowance_interval"})
37+
_SESSION_LOOKUP = "SEARCH s USING PRIMARY KEY (session_id=?)"
38+
_OCCURRENCE_LOOKUP = "SEARCH o USING PRIMARY KEY (occurrence_id=?) LEFT-JOIN"
39+
_MANIFESTATION_LOOKUP = (
40+
"SEARCH sm USING INDEX source_manifestations_by_occurrence_key "
41+
"(manifestation_key=?) LEFT-JOIN"
42+
)
43+
_LIFECYCLE_LOOKUP = (
44+
"SEARCH lt USING INDEX evidence_lifecycle_by_session_order (session_id=?)"
45+
)
46+
_BOUND_HEAD_LOOKUP = "SEARCH bound_head USING PRIMARY KEY (singleton=?)"
47+
_BOUND_HEAD_EXISTS_LOOKUP = (
48+
"SEARCH bound_head EXISTS USING PRIMARY KEY (singleton=?)"
3549
)
3650

3751

@@ -310,13 +324,13 @@ def _add_unrelated_lifecycle_history(
310324
connection.execute("PRAGMA query_only = ON")
311325

312326

313-
def _explain_details(
327+
def _explain_rows(
314328
connection: sqlite3.Connection,
315329
view: str,
316330
direction: str,
317331
scope: Mapping[str, Any],
318332
cursor_order: tuple[Any, ...] | None,
319-
) -> tuple[str, ...]:
333+
) -> tuple[tuple[int, int, int, str], ...]:
320334
sql, parameters = evidence_service._page_statement(
321335
view,
322336
direction,
@@ -326,11 +340,170 @@ def _explain_details(
326340
7,
327341
)
328342
return tuple(
329-
str(row[-1])
343+
(int(row[0]), int(row[1]), int(row[2]), str(row[3]))
330344
for row in connection.execute("EXPLAIN QUERY PLAN " + sql, parameters)
331345
)
332346

333347

348+
def _assert_unique_manifestation_lookup(connection: sqlite3.Connection) -> None:
349+
indexes = connection.execute("PRAGMA index_list(source_manifestations)").fetchall()
350+
matching = [
351+
row
352+
for row in indexes
353+
if str(row[1]) == "source_manifestations_by_occurrence_key"
354+
]
355+
assert len(matching) == 1
356+
assert int(matching[0][2]) == 1
357+
columns = connection.execute(
358+
"PRAGMA index_info(source_manifestations_by_occurrence_key)"
359+
).fetchall()
360+
assert [str(row[2]) for row in columns] == ["manifestation_key"]
361+
362+
363+
def _is_session_branch_metadata(detail: str) -> bool:
364+
return detail == _BOUND_HEAD_EXISTS_LOOKUP or (
365+
detail.startswith("SCALAR SUBQUERY ")
366+
and detail.removeprefix("SCALAR SUBQUERY ").isdigit()
367+
)
368+
369+
370+
def _descendants(
371+
rows: tuple[tuple[int, int, int, str], ...],
372+
node_id: int,
373+
) -> tuple[tuple[int, int, int, str], ...]:
374+
children_by_parent: dict[int, list[tuple[int, int, int, str]]] = {}
375+
for row in rows:
376+
children_by_parent.setdefault(row[1], []).append(row)
377+
descendants: list[tuple[int, int, int, str]] = []
378+
pending = [node_id]
379+
while pending:
380+
parent = pending.pop()
381+
children = sorted(children_by_parent.get(parent, ()), key=lambda row: row[0])
382+
descendants.extend(children)
383+
pending.extend(row[0] for row in children)
384+
return tuple(descendants)
385+
386+
387+
def _leftmost_branch(
388+
rows: tuple[tuple[int, int, int, str], ...],
389+
node_id: int,
390+
) -> bool:
391+
rows_by_id = {row[0]: row for row in rows}
392+
children_by_parent: dict[int, list[tuple[int, int, int, str]]] = {}
393+
for row in rows:
394+
children_by_parent.setdefault(row[1], []).append(row)
395+
current = node_id
396+
while current in rows_by_id:
397+
parent = rows_by_id[current][1]
398+
siblings = sorted(children_by_parent.get(parent, ()), key=lambda row: row[0])
399+
if not siblings or siblings[0][0] != current:
400+
return False
401+
current = parent
402+
return True
403+
404+
405+
def _session_branch_parent_candidates(
406+
rows: tuple[tuple[int, int, int, str], ...],
407+
) -> tuple[int, ...]:
408+
children_by_parent: dict[int, list[tuple[int, int, int, str]]] = {}
409+
for row in rows:
410+
children_by_parent.setdefault(row[1], []).append(row)
411+
candidates: list[int] = []
412+
for parent, children in children_by_parent.items():
413+
ordered = sorted(children, key=lambda row: row[0])
414+
details = tuple(row[3] for row in ordered)
415+
required = (
416+
_SESSION_LOOKUP,
417+
_OCCURRENCE_LOOKUP,
418+
_MANIFESTATION_LOOKUP,
419+
)
420+
if any(details.count(detail) != 1 for detail in required):
421+
continue
422+
if details.count(_TEMP_SORT_MARKER) > 1:
423+
continue
424+
if any(
425+
detail not in required
426+
and detail != _TEMP_SORT_MARKER
427+
and not _is_session_branch_metadata(detail)
428+
for detail in details
429+
):
430+
continue
431+
positions = {detail: details.index(detail) for detail in required}
432+
if not (
433+
positions[_SESSION_LOOKUP]
434+
< positions[_OCCURRENCE_LOOKUP]
435+
< positions[_MANIFESTATION_LOOKUP]
436+
):
437+
continue
438+
marker_position = (
439+
details.index(_TEMP_SORT_MARKER)
440+
if _TEMP_SORT_MARKER in details
441+
else None
442+
)
443+
if marker_position is not None and marker_position <= positions[
444+
_MANIFESTATION_LOOKUP
445+
]:
446+
continue
447+
metadata = [
448+
row
449+
for row in ordered
450+
if _is_session_branch_metadata(row[3])
451+
]
452+
if len(metadata) > 1:
453+
continue
454+
for row in metadata:
455+
if row[3].startswith("SCALAR SUBQUERY "):
456+
descendants = _descendants(rows, row[0])
457+
assert len(descendants) == 1
458+
assert descendants[0][3] == _BOUND_HEAD_LOOKUP
459+
candidates.append(parent)
460+
return tuple(candidates)
461+
462+
463+
def _assert_session_marker_branch_ownership(
464+
rows: tuple[tuple[int, int, int, str], ...],
465+
) -> None:
466+
candidates = _session_branch_parent_candidates(rows)
467+
assert len(candidates) == 1, (candidates, rows)
468+
session_parent = candidates[0]
469+
assert _leftmost_branch(rows, session_parent), (session_parent, rows)
470+
markers = [row for row in rows if row[3] == _TEMP_SORT_MARKER]
471+
if markers:
472+
assert markers[0][1] == session_parent, (session_parent, markers, rows)
473+
474+
475+
def _assert_page_plan_contract(
476+
connection: sqlite3.Connection,
477+
rows: tuple[tuple[int, int, int, str], ...],
478+
*,
479+
view: str,
480+
cursor_order: tuple[Any, ...] | None,
481+
) -> None:
482+
details = tuple(row[3] for row in rows)
483+
assert not any(
484+
marker in "\n".join(details) for marker in _FORBIDDEN_PLAN_MARKERS
485+
), details
486+
temp_markers = [detail for detail in details if "USE TEMP B-TREE" in detail]
487+
markers = [row for row in rows if row[3] == _TEMP_SORT_MARKER]
488+
allows_session_sort = (
489+
cursor_order is not None and view in _PORTABLE_SESSION_SORT_VIEWS
490+
)
491+
if view in _PORTABLE_SESSION_SORT_VIEWS:
492+
assert _LIFECYCLE_LOOKUP in details, details
493+
if not allows_session_sort:
494+
assert temp_markers == [], (view, cursor_order, details)
495+
return
496+
497+
assert temp_markers in ([], [_TEMP_SORT_MARKER]), (view, cursor_order, details)
498+
assert len(markers) <= 1, (view, cursor_order, details)
499+
if not markers:
500+
_assert_session_marker_branch_ownership(rows)
501+
return
502+
503+
_assert_session_marker_branch_ownership(rows)
504+
_assert_unique_manifestation_lookup(connection)
505+
506+
334507
def test_evidence_contract_is_closed_and_bounded() -> None:
335508
with pytest.raises(EvidenceContractError, match="exactly one"):
336509
EvidenceRequest()
@@ -595,13 +768,103 @@ def test_first_and_deep_physical_plans_prune_unbounded_shapes(
595768
):
596769
for direction in ("forward", "backward"):
597770
for cursor_order in (None, deep_order):
598-
details = _explain_details(connection, view, direction, scope, cursor_order)
599-
assert not any(
600-
marker in "\n".join(details) for marker in _FORBIDDEN_PLAN_MARKERS
601-
), (view, direction, cursor_order, details)
771+
rows = _explain_rows(connection, view, direction, scope, cursor_order)
772+
_assert_page_plan_contract(
773+
connection,
774+
rows,
775+
view=view,
776+
cursor_order=cursor_order,
777+
)
602778
connection.close()
603779

604780

781+
def test_session_marker_branch_ownership_rejects_structural_mutations(
782+
tmp_path: Path,
783+
) -> None:
784+
connection, _case, selector = _published(tmp_path)
785+
try:
786+
scope = {
787+
"kind": "session",
788+
"logical_id": selector.partition(":")[2],
789+
"start_us": None,
790+
"end_us": None,
791+
}
792+
deep_order = (0, 0, 0, 0, 0, "activity:physical:00000", 0)
793+
rows = _explain_rows(
794+
connection,
795+
"timeline",
796+
"forward",
797+
scope,
798+
deep_order,
799+
)
800+
markers = [row for row in rows if row[3] == _TEMP_SORT_MARKER]
801+
if not markers:
802+
pytest.skip("SQLite build emitted the portable plan marker-free")
803+
marker = markers[0]
804+
805+
def assert_rejected(
806+
mutated: tuple[tuple[int, int, int, str], ...],
807+
) -> None:
808+
with pytest.raises(AssertionError):
809+
_assert_page_plan_contract(
810+
connection,
811+
mutated,
812+
view="timeline",
813+
cursor_order=deep_order,
814+
)
815+
816+
calls_parent = next(
817+
row[1]
818+
for row in rows
819+
if row[3].startswith("SEARCH mc USING INDEX")
820+
)
821+
tools_parent = next(
822+
row[1]
823+
for row in rows
824+
if row[3].startswith("SEARCH ti USING INDEX")
825+
)
826+
lifecycle_parent = next(
827+
row[1] for row in rows if row[3] == _LIFECYCLE_LOOKUP
828+
)
829+
for foreign_parent in (calls_parent, tools_parent, lifecycle_parent, 0):
830+
assert_rejected(
831+
tuple(
832+
(row[0], foreign_parent, row[2], row[3])
833+
if row[0] == marker[0]
834+
else row
835+
for row in rows
836+
)
837+
)
838+
839+
assert_rejected(
840+
rows
841+
+ ((max(row[0] for row in rows) + 1, marker[1], 0, _TEMP_SORT_MARKER),)
842+
)
843+
assert_rejected(
844+
tuple(
845+
(row[0], row[1], row[2], "SCAN o")
846+
if row[3] == _OCCURRENCE_LOOKUP
847+
else row
848+
for row in rows
849+
)
850+
)
851+
ambiguous_rows = tuple(
852+
row
853+
for row in rows
854+
if row[1] != calls_parent
855+
or row[3]
856+
in {
857+
_SESSION_LOOKUP,
858+
_OCCURRENCE_LOOKUP,
859+
_MANIFESTATION_LOOKUP,
860+
}
861+
or _is_session_branch_metadata(row[3])
862+
)
863+
assert_rejected(ambiguous_rows)
864+
finally:
865+
connection.close()
866+
867+
605868
def test_activity_scale_keeps_decode_and_work_bounded(
606869
tmp_path: Path,
607870
monkeypatch: pytest.MonkeyPatch,
@@ -618,7 +881,7 @@ def count_decodes(row: Mapping[str, Any]) -> dict[str, Any]:
618881
for count in (2_000, 10_000):
619882
connection, case, selector = _published(tmp_path / f"activities-{count}")
620883
_add_synthetic_activities(connection, selector, count)
621-
details = _explain_details(
884+
rows = _explain_rows(
622885
connection,
623886
"timeline",
624887
"forward",
@@ -630,7 +893,12 @@ def count_decodes(row: Mapping[str, Any]) -> dict[str, Any]:
630893
},
631894
None,
632895
)
633-
assert not any(marker in "\n".join(details) for marker in _FORBIDDEN_PLAN_MARKERS)
896+
_assert_page_plan_contract(
897+
connection,
898+
rows,
899+
view="timeline",
900+
cursor_order=None,
901+
)
634902
callback_count = 0
635903

636904
def progress() -> int:
@@ -675,16 +943,19 @@ def test_foreign_lifecycle_history_does_not_change_session_page_work(
675943
}
676944
for direction in ("forward", "backward"):
677945
for cursor_order in (None, (0, 0, 0, 0, 0, "activity:physical:00000", 0)):
678-
details = _explain_details(
946+
rows = _explain_rows(
679947
connection,
680948
"timeline",
681949
direction,
682950
scope,
683951
cursor_order,
684952
)
685-
assert not any(
686-
marker in "\n".join(details) for marker in _FORBIDDEN_PLAN_MARKERS
687-
), (count, direction, cursor_order, details)
953+
_assert_page_plan_contract(
954+
connection,
955+
rows,
956+
view="timeline",
957+
cursor_order=cursor_order,
958+
)
688959
callback_count = 0
689960

690961
def progress() -> int:
@@ -709,8 +980,7 @@ def progress() -> int:
709980
connection.close()
710981

711982
assert callback_counts[0] > 0
712-
assert callback_counts[1] <= callback_counts[0] * 2
713-
assert callback_counts[2] <= callback_counts[0] * 2
983+
assert callback_counts[1:] == [callback_counts[0], callback_counts[0]]
714984

715985

716986
def test_rate_card_pages_remain_valid_empty_and_query_only(

0 commit comments

Comments
 (0)