Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion planfile/api/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -509,9 +509,12 @@ def _bounded_stale_index_response(
offset: int,
limit: int | None,
view: Literal["full", "operational", "summary"],
allow_unbounded_summary: bool = False,
) -> Response | None:
"""Serve a recent, coherent projection while its source index is repaired."""
if sprint != "all" or limit is None or view == "full":
archive_queue = sprint == "all" and limit is not None
legacy_summary = allow_unbounded_summary and view == "summary"
if view == "full" or not (archive_queue or legacy_summary):
return None
index = pf.store._sqlite_ticket_index()
if not index.has_fresh_snapshot(_INDEX_REPAIR_STALE_WINDOW_SECONDS):
Expand Down Expand Up @@ -557,6 +560,7 @@ def _ticket_list_response(
limit: int | None,
view: Literal["full", "operational", "summary"] = "full",
allow_stale: bool = False,
allow_index_stale: bool = False,
) -> Response:
# FastAPI runs this sync endpoint in a worker pool. Serialize cache misses so
# a burst of websocket-driven dashboard refreshes builds one 5+ MB response,
Expand Down Expand Up @@ -670,6 +674,7 @@ def _ticket_list_response(
offset=offset,
limit=limit,
view=view,
allow_unbounded_summary=allow_stale or allow_index_stale,
)
if stale_index_response is not None:
return stale_index_response
Expand Down Expand Up @@ -759,6 +764,7 @@ def list_tickets(
limit=limit,
view=effective_view,
allow_stale=browser_client,
allow_index_stale=legacy_unbounded_request,
)


Expand Down
32 changes: 32 additions & 0 deletions tests/test_sqlite_ticket_index.py
Original file line number Diff line number Diff line change
Expand Up @@ -405,6 +405,38 @@ def test_stale_archive_queue_reads_use_recent_index_without_query_cache(
assert pf.store.ticket_index_status()["current"] is False


def test_legacy_archive_dashboard_uses_recent_current_index_without_cache(
tmp_path, monkeypatch
):
pf = Planfile(str(tmp_path))
_disable_archive(pf)
ticket = pf.create_ticket(name="Last known good dashboard item")
pf.store.configure_ticket_index(True)
sprint_file = pf.store._sprint_file("current")
data = yaml.safe_load(sprint_file.read_text(encoding="utf-8"))
data["sprint"]["tickets"][ticket.id]["name"] = "New durable value"
sprint_file.write_text(yaml.safe_dump(data, sort_keys=False), encoding="utf-8")
mirror_path(sprint_file).unlink(missing_ok=True)
monkeypatch.setattr(server, "get_planfile", lambda: pf)
monkeypatch.setattr(
pf,
"list_tickets",
lambda **_kwargs: (_ for _ in ()).throw(
AssertionError("legacy request parsed durable sprint")
),
)
server._TICKET_LIST_RESPONSE_CACHE.clear()
server._TICKET_LIST_LATEST.clear()
client = TestClient(server.app)

response = client.get("/tickets?sprint=all")

assert response.status_code == 200
assert response.json()[0]["name"] == "Last known good dashboard item"
assert response.headers["X-Planfile-View"] == "summary"
assert response.headers["X-Planfile-Index-State"] == "stale"


def test_expired_stale_archive_index_remains_fail_closed(tmp_path, monkeypatch):
pf = Planfile(str(tmp_path))
_disable_archive(pf)
Expand Down
Loading