Skip to content

MAINT Extract scenario progress read model from ScenarioRunService #2627

Description

@romanlutz

Is your feature request related to a problem? Please describe.

ScenarioRunService currently owns both scenario lifecycle orchestration and a substantial progress-reporting subsystem. In particular:

  • _get_progress_snapshot manages the per-run LRU cache, keyset paging, cursor advancement, stale identifier-enrichment detection, legacy-plan synthesis, persistence-to-API mapping, and summary invalidation;
  • _build_progress_summary derives overall, technique, display-group, seed-group, and atomic-group rollups, including statuses, retry/error counts, scorer metadata, and unattributed attempts;
  • the snapshot boundary is an unnamed tuple of deltas, mapped results, summary, and effective plan, while cache invalidation state is also represented with positional tuples.

This makes scenario lifecycle code harder to reason about and couples independently testable read-model behavior to the service's run creation, execution, cancellation, and history responsibilities. The concern is responsibility concentration and implicit state invariants, not method size alone.

Describe the solution you'd like

Extract an internal, typed scenario-progress read-model component. It should own:

  • incremental hydration of lightweight persisted attack-result deltas;
  • keyset cursor advancement and bounded per-run caching;
  • cache invalidation when plan, terminal/live state, or persisted identifier enrichment changes;
  • legacy effective-plan synthesis and plan lookup used for result mapping;
  • mapping persisted deltas into ScenarioProgressResult values;
  • canonical construction of ScenarioProgressSummary.

Replace positional snapshot/state tuples with explicit internal dataclasses or equivalent typed models. ScenarioRunService should retain lifecycle orchestration: capture active-run state, load and validate the run header and persisted plan, request the progress snapshot, apply client-facing pagination, and construct the existing ScenarioRunProgress response.

The extraction must preserve the current REST schema, storage schema, cursor format, sorting, status calculation, retry/error accounting, scorer projection, legacy-plan behavior, and public APIs.

Describe alternatives you've considered, if relevant

Splitting _get_progress_snapshot and _build_progress_summary into more private methods on ScenarioRunService would reduce local method size but leave lifecycle and read-model responsibilities coupled.

A broader simultaneous split of MemoryInterface history/aggregation SQL was also considered. That has higher query and migration risk and should be handled as a separate follow-up after this boundary is established, rather than expanding this issue into a backend redesign.

Additional context

This was identified independently in the September 10 and September 11, 2026 repository complexity audits. No exact active issue, pull request, or audit session was found.

Suggested regression coverage should preserve and extend the existing scenario progress tests for:

  • incremental polling where only newly persisted rows are mapped;
  • multi-page storage hydration and deterministic keyset ordering;
  • a persisted row whose attack-technique identifier is enriched after its initial insertion;
  • plan changes and invalid or unavailable persisted plans;
  • live, completed, failed, and cancelled status transitions;
  • retry, error, success, and unattributed-attempt rollups;
  • technique, display-group, seed-group, and atomic-group projections;
  • objective scorer identity and official metric projection;
  • client since cursors, page limits, has_more, and first-page-only plan delivery;
  • cache eviction and concurrent reads of different runs.

One important edge case is an existing cached first page containing an unenriched result, followed by in-place enrichment of that result and the append of a later page. A subsequent poll should refresh the enriched row, include the appended row exactly once, preserve cursor order, and avoid summary double-counting.

Definition of done:

  • progress hydration/projection has a clear typed boundary outside ScenarioRunService;
  • duplicated or superseded progress branches are removed rather than wrapped in pass-through helpers;
  • current API and persistence behavior remain unchanged;
  • focused backend service and route tests, Ruff, typing, and git diff --check pass.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions