Files
lda-wf/docs/superpowers/research/2026-06-03-workflow-operation-context-audit.md
T
2026-07-30 01:18:53 +07:00

179 lines
7.5 KiB
Markdown

# WorkflowOperationContext Shape Audit
Date: 2026-06-03
## Summary
`WorkflowOperationContext` is the right seam for protocol-neutral workflow APIs.
After the recent cleanup it no longer depends on MCP transport classes or the old
artifact-cataloger hop. The remaining shape is usable, but it still mixes three
different concepts:
- persistence stores (`artifact_store`, `draft_workspace_store`, `run_store`)
- capability/source lookup (`capability_sources`, `specs`)
- operational side effects (`events`, `runtime`, `live_sources`)
This is acceptable for the current MCP/CLI path. Before building a durable HTTP
API or persisted resume workflow, the context should become a little more explicit
so future frontends do not inherit accidental MCP-era seams.
## Current Shape
```python
@dataclass(frozen=True, slots=True)
class WorkflowOperationContext:
artifact_store: WorkflowArtifactStore | None
draft_workspace_store: DraftWorkspaceStore | None
run_store: RunStore | None
capability_sources: Mapping[str, CapabilitySource]
events: WorkflowEventRecorder
specs: WorkflowSpecProvider
runtime: WorkflowRuntimeRunner
live_sources: WorkflowLiveSourceChecker | None = None
```
## Field Audit
| Field | Used by | Classification | Recommendation |
| --- | --- | --- | --- |
| `artifact_store` | artifacts, deployments, capabilities, runs | Real dependency | Keep, but consider grouping all stores under a `WorkflowStores` field so store availability is one explicit concern. |
| `draft_workspace_store` | drafts, artifacts | Real dependency | Keep short-term; same grouping recommendation as `artifact_store`. |
| `run_store` | runs | Real dependency | Keep; persisted resume will need this and probably stronger checkpoint APIs. |
| `capability_sources` | capabilities, deployments, runs, capability requirements | Duplicated dependency | Prefer moving reads through `specs.capability_sources`; keeping both gives two paths to the same source map. |
| `events` | artifacts, deployments, operation-context tests | Real dependency | Keep; it is protocol-neutral enough because workflow APIs call `record_workflow_event()`. |
| `specs` | drafts, capabilities | Real dependency | Keep; rename to `capabilities` or `source_index` later if it grows beyond spec lookup. |
| `runtime` | capabilities, runs | Real dependency | Keep; this is the important seam for durable runtime backends. |
| `live_sources` | deployments | Optional adapter hook | Keep optional; only live validation should touch external sources. |
## Main Issue
`capability_sources` and `specs.capability_sources` are the same concept exposed
twice. Today this is harmless because `context_from_service()` passes:
```python
specs = WfMcpWorkflowSpecProvider(service)
capability_sources = specs.capability_sources
specs = specs
```
But it creates a future maintenance trap: one API may iterate
`context.capability_sources` while another iterates
`context.specs.capability_sources`. A future backend could accidentally make those
two source maps disagree. `context.specs.get_qualified_spec()` is a related lookup
operation, but it is not the duplicate inventory path.
Recommendation: remove the top-level `capability_sources` field in a small future
slice and update consumers to use `context.specs.capability_sources`.
## Store Shape Issue
The context currently stores three optional stores directly. Each domain API has
to repeat availability checks:
- `WorkflowArtifactApi._artifact_store()`
- `WorkflowDeploymentApi._artifact_store()`
- `WorkflowDraftApi._draft_store()`
- `WorkflowRunApi._run_store()`
This is fine for MCP where stores may be disabled in test/config paths. For a
durable API, store availability is not optional: no run store means no persisted
resume.
Recommendation:
- Keep optional stores for current MCP compatibility.
- For durable API work, introduce a required `WorkflowStores` bundle at the API
construction boundary, or a `require_stores()` helper that produces a stricter
context for durable surfaces.
## Runtime Shape
`WorkflowRuntimeRunner` is a good seam:
- `run_workflow_from_plan()` already receives deployment, artifact, and saved
subgraph tree.
- `resume_workflow_from_plan()` already receives pinned artifact/deployment context.
For persisted resume, this seam should stay. The durable run design should focus
on:
- how `RunStore` checkpoints are loaded
- how pinned deployment/artifact/source diagnostics are validated
- how interrupted runs are resumed across process restarts
It should not reintroduce direct MCP service access.
## Completed Context Simplification
Completed before the persisted-run spec:
1. Removed top-level `WorkflowOperationContext.capability_sources`.
2. Updated `wf_api` consumers to use `context.specs.capability_sources`.
3. Updated tests that asserted `context.capability_sources`.
4. Kept `WorkflowSpecProvider.capability_sources` as the single source inventory
path.
Why this was first:
- It is low-risk and mechanical.
- It removes duplicate source inventory paths before durable resume depends on
source diagnostics.
- It clarifies that source/capability lookup is a single domain dependency.
### Inline Cleanup Result
This cleanup was small enough to do inline without a separate agent plan.
1. Updated `src/wf_api/operation_context.py`.
- Removed `capability_sources: Mapping[str, CapabilitySource]` from
`WorkflowOperationContext`.
- Kept `WorkflowSpecProvider.capability_sources`.
2. Updated `src/wf_mcp/broker/service/workflow_operation_context.py`.
- Stopped passing `capability_sources=specs.capability_sources` to
`WorkflowOperationContext`.
3. Updated `wf_api` consumers.
- In `capabilities.py`, replaced `self.context.capability_sources` with
`self.context.specs.capability_sources`.
- In `deployments.py`, replaced `self.context.capability_sources` with
`self.context.specs.capability_sources`.
- In `runs.py`, replaced `self.context.capability_sources` with
`self.context.specs.capability_sources`.
- In `capability_requirements.py`, replaced `context.capability_sources` with
`context.specs.capability_sources`.
4. Updated tests.
- In `tests/wf_api/test_operation_context.py`, replaced assertions on
`context.capability_sources` with assertions on
`context.specs.capability_sources`.
5. Verification target.
- `uv run pytest tests/wf_api -q`
- `uv run pytest tests/wf_mcp/service/test_workflow_runtime.py tests/wf_mcp/workflow_surface -q`
- `uv run ruff check src/wf_api src/wf_mcp/broker/service/workflow_operation_context.py tests/wf_api`
- `uv run ruff format --check src/wf_api src/wf_mcp/broker/service/workflow_operation_context.py tests/wf_api`
- `uv run basedpyright --level error`
## Follow-Up For Persisted Runs
After the context simplification, write the persisted run/resume spec around these
requirements:
- Run records must store enough pinned environment data to resume after process
restart.
- Resume must validate run status/readiness before executing.
- Resume must validate pinned deployment/artifacts/source capabilities before
executing.
- Dead external sources should not pause runs; they should produce diagnostics or
runtime failure. Only workflow interrupts pause/resume.
- Trace output should stay paged/ranged; full trace remains opt-in.
## Non-Goals
- Do not move stores out of `wf_artifacts` in this pass.
- Do not make `live_sources` required; live checks are optional and expensive.
- Do not make `WorkflowOperationContext` depend on `wf_mcp`.
- Do not redesign `WorkflowRuntimeRunner` until the persisted-run spec needs a
concrete change.