Appendix B - 4. Mediator & single-writer contracts
The Structure of Mediator & single-writer contracts — its shape at a glance:
Two registries declare the contracts. A coverage lint reads them, scans the code for the contracted call classes, and names any call site that is not wired to its mediator.
flowchart LR
MR[(Mediator registry)] --> Cov{{Coverage lint}}
SW[(Single-writer registry)] --> Cov
Code[(Call sites)] --> Cov
Cov -->|uncovered call| Finding([build blocked])
Accessible description: a mediator registry and a single-writer registry both feed a coverage lint, which also scans the code's call sites. When a contracted call class appears at a site not wired to its mediator, the lint reports it and blocks the build.
Projected from the catalogue entry system-models / Mediator & single-writer contracts.
On this page: Intent · Motivation · Applicability · Structure · Sample Code · Consequences · Example use within DocAble · Related Patterns
Intent
Intent — Typed registries of the system's concurrency contracts: which subprocess invocations are serialized by a mediator, and which state-mutation functions are single-writer / monopoly. "Who may run this, and how many at once" becomes declared and enforceable.
Motivation
Concurrent agent worktrees share a host and shared state. Two contract classes keep them from trampling each other: mediators (a subprocess like dotnet test must run through the serializer, not raw) and single-writer contracts (a state-mutation function must have exactly one writer, no concurrent mutation). Undeclared, both silently break. A raw dotnet test slips the serializer; a second writer corrupts state; and the breakage is a race, discovered late and hard.
Applicability
- A mediator registry (subprocess → serializer, cap, bypass) and a single-writer registry (function → monopoly contract).
- Enforcers keyed on the registry so an unmediated call is refused.
- Coverage lints so a new subprocess/mutator that should be contracted is flagged.
Structure
The Structure diagram appears at the top of this page.
Sample Code
The registry declares which subprocess each mediator serializes. A coverage lint walks the code for calls to those subprocesses and fails on any that isn't routed through the declared mediator — closing the gap where an enforcer can't guard a call nobody routed to it.
import ast, sys
# Declared contracts: subprocess name -> the mediator that must wrap it.
MEDIATED = {"run_tests": "test-serializer", "build_image": "build-semaphore"}
MEDIATOR_MODULE = "mediators" # the file allowed to invoke the raw subprocess
def lint(path: str, source: str) -> list[str]:
if path.endswith(f"{MEDIATOR_MODULE}.py"):
return [] # the mediator is the sanctioned caller
findings = []
for node in ast.walk(ast.parse(source)):
if isinstance(node, ast.Call) and isinstance(node.func, ast.Name):
if node.func.id in MEDIATED:
need = MEDIATED[node.func.id]
findings.append(f"{path}:{node.lineno}: '{node.func.id}' must route through {need}")
return findings
if __name__ == "__main__":
hits = [f for p in sys.argv[1:] for f in lint(p, open(p).read())]
print("\n".join(hits))
sys.exit(1 if hits else 0)
Consequences
- New mediated subprocess / new mutator ⇒ a registry entry or a coverage-lint failure.
- The contract is only as good as the enforcer. A declared-but-unenforced contract is documentation.
Example use within DocAble
- The mediator-registry: the dev-mediator (subprocess-serializer) registry for the host's concurrency locks.
- The single-writer-registry: single-writer / monopoly contracts.
- The mediator enforcers (test-serializer et al.).
Related Patterns
- Bridge — the agent mediators & resource locks family enforces these contracts (agent side) ◀──▶ the model declares the concurrency the codebase must honour (product side).
- See also — synchronization-model: the OS-lock layer beneath these higher-level contracts.
- Counterpart — drift-parity-gates: the coverage lints keeping the registries complete.