From 59e46fa069dcd770c66015f72df7ebefda62e05b Mon Sep 17 00:00:00 2001 From: Michael Cahill Date: Wed, 11 Mar 2026 21:50:25 -0700 Subject: [PATCH] M10: ProcessingRunner skeleton - Add modules/runtime/runner.py with ProcessingRunner and ProcessingRequest - Wire process_images to delegate through runner (internal only) - Add test/quality/test_processing_runner.py contract test Behavior-preserving. Zero blast radius. All callers unchanged. Made-with: Cursor --- docs/milestones/M10/M10_plan.md | 125 +++++++++++++++++++++++++ docs/milestones/M10/M10_toolcalls.md | 11 +++ modules/processing.py | 5 +- modules/runtime/__init__.py | 4 + modules/runtime/runner.py | 22 +++++ test/quality/test_processing_runner.py | 26 +++++ 6 files changed, 192 insertions(+), 1 deletion(-) create mode 100644 docs/milestones/M10/M10_plan.md create mode 100644 docs/milestones/M10/M10_toolcalls.md create mode 100644 modules/runtime/__init__.py create mode 100644 modules/runtime/runner.py create mode 100644 test/quality/test_processing_runner.py diff --git a/docs/milestones/M10/M10_plan.md b/docs/milestones/M10/M10_plan.md new file mode 100644 index 000000000..7551cbb56 --- /dev/null +++ b/docs/milestones/M10/M10_plan.md @@ -0,0 +1,125 @@ +# M10 — ProcessingRunner Skeleton + +Phase: **Phase III — Runner & Service Boundary** +Status: Planned + +--- + +# 1. Intent / Target + +Introduce the **ProcessingRunner** abstraction that will become the unified execution surface for Serena. + +Currently the pipeline is invoked directly through internal orchestration code. +This milestone introduces a **runner boundary** that: + +- encapsulates pipeline execution +- standardizes runtime entrypoints +- prepares Serena for CLI / API / service mode + +The runner initially acts as a **thin adapter** around existing behavior. + +No behavior changes are permitted. + +--- + +# 2. Scope Boundaries + +### In scope + +- Create `ProcessingRunner` skeleton at `modules/runtime/runner.py` +- Define `ProcessingRequest` wrapping `StableDiffusionProcessing` +- Wire `process_images` to delegate through runner (internal only) +- Maintain existing CLI/UI/API/scripts behavior +- Add minimal contract test at `test/quality/test_processing_runner.py` + +### Out of scope + +- No runtime behavior changes +- No async processing yet +- No service layer +- No multiprocessing +- No new configuration surfaces +- No performance changes +- No RuntimeContext in runner (M10) + +--- + +# 3. Clarifications (Authoritative) + +| Decision | Choice | +|----------|--------| +| Module path | `modules/runtime/runner.py` | +| ProcessingRequest | Wraps `p`: `ProcessingRequest(processing=p)` | +| RuntimeContext | Not passed to runner; omit from constructor | +| Wiring | Inside `process_images` only; all callers unchanged | +| Test location | `test/quality/test_processing_runner.py` | + +--- + +# 4. Invariants + +| Surface | Invariant | Verification | +|---------|-----------|--------------| +| CLI behavior | Identical outputs and execution path | smoke tests | +| API responses | No schema changes | tests | +| Processing results | Byte-identical outputs | golden comparison | +| Runtime state | No side effects introduced | test suite | +| CI coverage | ≥ 40% | CI gate | + +--- + +# 5. Implementation Steps + +## Step 1 — Create runner module + +Create `modules/runtime/runner.py`: + +```python +class ProcessingRequest: + def __init__(self, processing): + self.processing = processing + +class ProcessingRunner: + def run(self, request): + from modules.processing import process_images_inner + return process_images_inner(request.processing) +``` + +## Step 2 — Wire process_images + +Inside `process_images`, replace: + +```python +res = process_images_inner(p) +``` + +With: + +```python +from modules.runtime.runner import ProcessingRunner, ProcessingRequest +runner = ProcessingRunner() +request = ProcessingRequest(p) +res = runner.run(request) +``` + +## Step 3 — Add contract test + +Add `test/quality/test_processing_runner.py` with delegation test. + +--- + +# 6. Risk & Rollback Plan + +Risk level: **Low** + +Mechanical refactor. Rollback: revert wiring commit. + +--- + +# 7. Deliverables + +Code: `modules/runtime/runner.py`, wiring in `process_images` +Tests: `test/quality/test_processing_runner.py` +Docs: M10_toolcalls.md, M10_run1.md, M10_summary.md, M10_audit.md +Ledger: Update `docs/serena.md` +Tag: `v0.0.10-m10` diff --git a/docs/milestones/M10/M10_toolcalls.md b/docs/milestones/M10/M10_toolcalls.md new file mode 100644 index 000000000..a75cc61e7 --- /dev/null +++ b/docs/milestones/M10/M10_toolcalls.md @@ -0,0 +1,11 @@ +# M10 Toolcalls — ProcessingRunner Skeleton + +Implementation toolcalls for Cursor execution. + +| Timestamp | Tool | Purpose | Files/Target | Status | +|-----------|------|---------|--------------|--------| +| 2026-03-11 | write | Create M10_plan.md, M10_toolcalls.md | docs/milestones/M10/ | done | +| 2026-03-11 | write | Create modules/runtime/runner.py | modules/runtime/ | done | +| 2026-03-11 | search_replace | Wire process_images to delegate through runner | modules/processing.py | done | +| 2026-03-11 | write | Add contract test for ProcessingRunner | test/quality/test_processing_runner.py | done | +| 2026-03-11 | run | Create branch m10-processing-runner | git | in_progress | diff --git a/modules/processing.py b/modules/processing.py index 6f3d87b30..b8feb730a 100644 --- a/modules/processing.py +++ b/modules/processing.py @@ -844,7 +844,10 @@ def process_images(p: StableDiffusionProcessing) -> Processed: sd_samplers.fix_p_invalid_sampler_and_scheduler(p) with profiling.Profiler(): - res = process_images_inner(p) + from modules.runtime.runner import ProcessingRunner, ProcessingRequest + runner = ProcessingRunner() + request = ProcessingRequest(p) + res = runner.run(request) finally: sd_models.apply_token_merging(p.sd_model, 0) diff --git a/modules/runtime/__init__.py b/modules/runtime/__init__.py new file mode 100644 index 000000000..590d3ad53 --- /dev/null +++ b/modules/runtime/__init__.py @@ -0,0 +1,4 @@ +"""Runtime execution boundary for Serena. + +M10: ProcessingRunner skeleton. Thin adapter around process_images_inner. +""" diff --git a/modules/runtime/runner.py b/modules/runtime/runner.py new file mode 100644 index 000000000..96c38b53e --- /dev/null +++ b/modules/runtime/runner.py @@ -0,0 +1,22 @@ +"""ProcessingRunner — unified execution entrypoint for Serena pipeline. + +M10: Thin adapter around process_images_inner. No behavior changes. +""" + + +class ProcessingRequest: + """Wraps StableDiffusionProcessing for runner boundary.""" + + def __init__(self, processing): + self.processing = processing + + +class ProcessingRunner: + """ + Unified execution entrypoint for Serena processing pipeline. + """ + + def run(self, request): + """Execute processing pipeline.""" + from modules.processing import process_images_inner + return process_images_inner(request.processing) diff --git a/test/quality/test_processing_runner.py b/test/quality/test_processing_runner.py new file mode 100644 index 000000000..3aae3f0ff --- /dev/null +++ b/test/quality/test_processing_runner.py @@ -0,0 +1,26 @@ +"""Contract tests for ProcessingRunner (M10 runner skeleton).""" +import modules.processing +from modules.runtime.runner import ProcessingRunner, ProcessingRequest + + +def test_processing_runner_delegates(monkeypatch): + """ProcessingRunner.run delegates to process_images_inner.""" + called = {} + + def fake_process_images_inner(p): + called["ok"] = True + return "result" + + monkeypatch.setattr( + modules.processing, + "process_images_inner", + fake_process_images_inner, + ) + + runner = ProcessingRunner() + request = ProcessingRequest(processing="dummy") + + result = runner.run(request) + + assert called["ok"] + assert result == "result"