docs: clarify store transaction boundaries

This commit is contained in:
lda
2026-06-09 20:22:15 +07:00 Verified
parent d0c2153258
commit efe80effbc
7 changed files with 152 additions and 5 deletions
+3 -2
View File
@@ -58,8 +58,9 @@ Remaining hardening should focus on correctness under real server use.
- Completed: same-process `resume_run` calls are serialized per run id.
Implementation:
[`resume run concurrency guard`](historical/superpowers/plans/2026-06-09-resume-run-concurrency-guard.md).
- Clarify store-level locking/transaction expectations for future filesystem
and transactional stores.
- Completed: store-level locking/transaction expectations are documented for
current file stores and future transactional stores:
[`store transaction boundary`](superpowers/specs/2026-06-09-store-transaction-boundary.md).
- Preserve existing semantics: broken pinned dependencies return blocked
readiness and diagnostics; ordinary live tool/source failures are failed runs,
not implicit pauses.
@@ -7,6 +7,7 @@ Status: contract clarification; current V1 mostly implemented
Related:
- [Durable workflow runs and resume design](./2026-05-26-durable-workflow-runs-and-resume-design.md)
- [Store transaction and locking boundary](./2026-06-09-store-transaction-boundary.md)
- [Durable run operations](../../durable_run_operations.md)
- [WorkflowOperationContext audit](../research/2026-06-03-workflow-operation-context-audit.md)
@@ -0,0 +1,119 @@
# Store Transaction And Locking Boundary
Date: 2026-06-09
Status: current contract clarification
Related:
- [Persisted run/resume contract](./2026-06-03-persisted-run-resume-contract.md)
- [Workflow config targets and sources](./2026-06-03-workflow-config-targets-and-sources.md)
- [Store-backed source registry](./2026-06-03-store-backed-source-registry-design.md)
## Purpose
This spec defines what the current file-backed stores guarantee and what they
intentionally do not guarantee. The product path is now a long-lived
`wf-rpc-server`, so future agents must not assume that JSON files plus
process-local locks provide cloud-grade transaction semantics.
The short rule:
> File stores are local/dev/single-process stores. Multi-process or cloud use
> needs a transactional backend before claiming strong concurrent mutation
> safety.
## Current Store Classes
| Store | Current role | Current concurrency guarantee |
| --- | --- | --- |
| `FileWorkflowArtifactStore` | Immutable artifact versions and mutable deployments | Path validation only; simple JSON writes/deletes; no multi-operation transaction |
| `FileDraftWorkspaceStore` | Mutable draft workspaces | Per-process `RLock`; optimistic revision checks are safe inside one process only |
| `FileRunStore` | Durable stopped-run summaries and checkpoints | Per-process `RLock` around individual writes; `WorkflowRunApi.resume_run()` adds a per-run async critical section in one API process |
| `AtomicJsonRegistryStore` / `FileSourceRegistryStore` | Desired source registry document | Whole-file temp-write + replace; no compare-and-swap revision; concurrent writers are last-writer-wins |
| `FileAuthStore` | Local/dev MCP auth JSON records | Simple JSON writes/deletes; payload values are write-only through admin surfaces, but storage is plaintext local JSON |
| `FileCatalogStore` | MCP catalog snapshots | Simple JSON writes; snapshot cache, not an authoritative source of truth |
| `FileStore` | Compatibility wrapper for MCP auth + catalog stores | Delegates to `FileAuthStore` and `FileCatalogStore`; no extra locking |
## Guarantees Today
### Path Safety
All durable file stores validate ids before constructing filesystem paths. A
rejected id must not escape the configured store root.
### Single-Process Mutation Safety
Some stores protect multi-step operations inside one process:
- `FileDraftWorkspaceStore.create_workspace()` and `.replace_workspace()` keep
duplicate/revision checks and writes under one process-local lock.
- `WorkflowRunApi.resume_run()` serializes the restore, pinned dependency
validation, runtime resume, and checkpoint write sequence per `run_id` in one
API/server process.
These are process-local guards. They do not coordinate with another Python
process that points at the same root.
### Atomic Single-File Replacement
Some writes use temp files followed by `Path.replace()`:
- draft workspace writes
- run checkpoint/run summary writes
- source registry whole-file writes
This reduces partially-written file risk for one file. It does not make a
multi-file operation transactional.
## Non-Guarantees Today
Current file stores do not guarantee:
- Cross-process locks.
- Compare-and-swap updates.
- Serializable transactions.
- Crash recovery across a multi-file mutation.
- Rollback when one file write succeeds and a later related write fails.
- Secret encryption at rest.
- Multi-writer safety for registry/auth/admin mutation endpoints.
## API-Layer Policies
Some correctness rules intentionally live above stores:
- `WorkflowArtifactApi.delete_artifact()` checks referencing deployments before
deleting an artifact version. `FileWorkflowArtifactStore.delete_artifact()`
only removes the file.
- `WorkflowRunApi.resume_run()` owns same-process resume serialization.
`FileRunStore` only saves and loads run records/checkpoints.
- Source registry config ownership rules are enforced by the registry admin and
connection services. `FileSourceRegistryStore` only loads/saves the desired
registry document.
This is acceptable for current local/dev use, but a transactional backend should
move the relevant compare-and-swap guarantees into the store layer.
## Future Transactional Store Requirements
A SQL or equivalent transactional backend should provide:
- Atomic artifact/deployment mutation policies where needed.
- Draft workspace compare-and-swap by revision.
- Per-run transaction or lock for resume state transitions.
- Monotonic checkpoint sequence allocation per run.
- Source registry revision or compare-and-swap writes.
- Auth record storage suitable for the deployment environment, preferably via a
secret manager or encrypted-at-rest store.
- Clear behavior for multiple API workers.
## Implementation Guidance
- Do not add ad hoc filesystem lock files unless a real cross-process locking
design is specified and tested on Windows.
- Do not claim a file-backed deployment is cloud-safe just because focused tests
pass.
- Prefer keeping local file stores simple and adding a dedicated transactional
store implementation when the product needs multi-worker durability.
- Keep error messages explicit when an operation is blocked by policy rather
than by missing files.
+5 -1
View File
@@ -58,7 +58,11 @@ class SourceRegistryStore(Protocol[RegistryT]):
class AtomicJsonRegistryStore(Generic[RegistryT]):
"""Filesystem implementation for small desired-registry documents."""
"""Filesystem implementation for small desired-registry documents.
Saves replace one whole JSON file atomically, but there is no revision check
or cross-process compare-and-swap. Concurrent writers are last-writer-wins.
"""
def __init__(
self,
+6 -1
View File
@@ -30,7 +30,12 @@ class RunStore:
class FileRunStore(RunStore):
"""JSON file-backed stopped-run store for local development and tests."""
"""JSON file-backed stopped-run store for local development and tests.
The internal lock protects individual writes inside one process only.
`WorkflowRunApi.resume_run()` owns the same-process read/execute/write
critical section; multi-worker deployments need a transactional store.
"""
def __init__(self, root: Path) -> None:
self.root = root
+6 -1
View File
@@ -56,7 +56,12 @@ class WorkflowArtifactStore:
class FileWorkflowArtifactStore(WorkflowArtifactStore):
"""JSON file-backed artifact store for local development and tests."""
"""JSON file-backed artifact store for local development and tests.
This store validates paths and performs simple file writes/deletes. API
services own multi-step policies such as "do not delete referenced
artifacts"; use a transactional store for multi-process safety.
"""
def __init__(self, root: Path) -> None:
self.root = root
+12
View File
@@ -54,6 +54,12 @@ class Store(AuthStore, CatalogStore):
class FileAuthStore(AuthStore):
"""Local plaintext JSON auth store for development and test deployments.
Admin surfaces keep payload values write-only, but this filesystem backend
does not encrypt secrets or coordinate cross-process writes.
"""
def __init__(self, root: Path) -> None:
self.root = root
self.root.mkdir(parents=True, exist_ok=True)
@@ -115,6 +121,12 @@ class FileAuthStore(AuthStore):
class FileCatalogStore(CatalogStore):
"""Local JSON cache for MCP catalog snapshots.
Catalog snapshots are a convenience cache, not authoritative desired state,
and writes are not cross-process transactional.
"""
def __init__(self, root: Path) -> None:
self.root = root
self.root.mkdir(parents=True, exist_ok=True)