Skip to content

View decomposition — Slice 8: extract ProcessRunner (the PythonKit call boundary) #110

Description

@LegalMarc

Design doc (read in full first): docs/design/view_controller_decomposition.md — §2.1 ProcessRunner, §3.3 items 4 and 5, §4 slice 8. The spike's line numbers are stale (it measured the view model at 2,926 lines; it is 3,720 today). Line references in this issue are current as of 2026-09-10.

Goal

Single-document execution moves out of DocumentRedactionViewModel into a ProcessRunner. This is the direct PythonKit call boundary and the single highest-consequence piece in either file: a regression here changes what actually gets redacted. It lands after every other service extraction and gets its own review pass.

Dependencies

Scope

Move: processDocument(_:destination:) (1175), logAdvancedSettingsSnapshot (1285), applyAdvancedSettingsEnvironment() (1298), applyMetadataSettingsEnvironment(_:context:) (1309), processDocumentWithPythonKit(...) (1384), awaitPythonOutcome(_:runner:) (1634), and the metadata counterpart scrubDocumentMetadataOnly(_:destination:) (972) and generateMetadataReport(for:runner:destination:) (658). The runner takes a DocumentItem, destination, RedactionSettings, the runner provider and the preflight closures, and returns a result (outcome + output paths) that the view model applies to item status; it does not mutate published state.

The environment-variable order must be preserved exactly and is pinned by the goldens: clearCancellationRequestMARCUT_LOG_PATHMARCUT_ADVANCED_*MARCUT_METADATA_* → model readiness (LLM only) → MARCUT_SCRUB_REPORT_PATH → runner; and for scrub: MARCUT_METADATA_*MARCUT_METADATA_ONLY → runner, METADATA_ONLY unset on return. The defer { unsetenv("MARCUT_SCRUB_REPORT_PATH") } inside the detached completion task (1530) must keep the same lifetime.

Extraction rules (design doc §3.3)

  • Move code only. No logic edits beyond what Swift access rules mechanically require (a method becoming internal on the new type instead of private on the old one) and call-site updates to go through the collaborator.
  • Reviewer diffs each moved body against its old body with git diff --color-moved to confirm nothing rode along.
  • Characterization tests run unmodified except for setup/construction. An assertion that needs to change to keep passing means the extraction changed behaviour and blocks the PR.
  • If a bug is found mid-extraction, land the fix as a separate PR before or after. Never combine.

Out of scope

Automation

  • afk — rung 0. The design doc's "manual end-to-end smoke run" (§3.3 item 5) is replaced here by the bridge-call goldens: the marcut CLI does not go through the view model, so a CLI run proves nothing about this slice, while the goldens pin every argument and env var the view model hands to Python. State this in the PR.

Acceptance criteria

  • Golden harness diffs to zero across all scenarios, before and after.
  • The Python suite passes (several env vars this code sets are read by pipeline.py).
  • git diff --color-moved shows moves only; the reviewer confirms the env-var ordering line by line.
  • Both suites pass; SwiftFormat lint clean.

Required verification

  • swift build --package-path src/swift/MarcutApp
  • swift test --package-path src/swift/MarcutApp
  • source .venv/bin/activate && PYTHONPATH=src/python python3 -m pytest -q
  • swiftformat --lint src/swift/MarcutApp/Sources src/swift/MarcutApp/Tests
  • swift test --package-path src/swift/MarcutApp --filter RedactionCharacterizationTests run before and after the change: zero golden diffs, no golden file modified in the PR.

Notes


Land as its own PR; do not combine with other issues in this set. Do not reopen design-spike #24. Before committing, update the affected docs/INDEX.md lines and the design doc's status in the same commit (repo docs-sync rule).

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

    Labels

    afkAutonomous workflow-loop eligibledecomp-c1Scoped batch: C1 view controller decomposition (prereqs + 9 slices)

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions