Provider Request Record/Replay Entry Implementation Plan#
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: Add a secure, deterministic CLI/config entry point that records provider traffic and replays it without credentials or network fallback.
Architecture: Keep the version-1 JSONL format and public provider classes, but move file coordination into a shared RecordingSink, install one registry-level provider transform, and select the transformed provider before credential resolution. Keep managed-recording lifecycle functions in recording.py; cli.py only declares arguments and delegates commands.
Tech Stack: Python 3.11+, argparse, JSONL, os.open/os.write/fsync, _compat.flock, pytest, Ruff.
Global Constraints#
- Start from
origin/maincommit8f62da5a; use only branchcodex/record-replay-entry-20260811in/private/tmp/openprogram-record-replay-entry-20260811. - Do not modify JSON Schema, MCP, or resource-governance files.
- Keep changes to
openprogram/cli.pyandopenprogram/config_schema.pylocal to record/replay declarations and dispatch. record_replay.modeisoff,record, orreplay; changes apply on the next process start.- Managed directories and lock files use mode
0700and0600; POSIX permission verification is fail-closed. - Redaction is unconditional and has no disabling configuration.
- Replay must parse the whole file before serving calls and must not resolve credentials, retain an original provider, or make network calls.
- Preserve version-1 constructors, properties,
call_count,RecordingFileError,ReplayMismatch, and structurally valid legacy recordings. - Use test-driven development for every production change and commit each task independently.
Task 1: Strict recording format, redaction, and private paths#
Files:
- Modify:
openprogram/paths.py - Modify:
openprogram/providers/recording.py - Modify:
openprogram/providers/replay.py - Modify:
tests/providers/test_record_replay.py
Interfaces:
-
Produces:
get_recordings_dir() -> Path,restrict_recording_file(path: Path) -> None,read_recording_file(path: str | Path) -> list[RecordedCall]. -
Preserves:
RecordingProvider(provider, path),ReplayProvider(path),ReplayProvider.call_count,RecordingFileError, andReplayMismatch. -
Step 1: Add failing parser, redaction, and permission tests
@pytest.mark.parametrize("mutation", ["event_after_end", "index_gap", "unknown_event"])
def test_strict_parser_rejects_invalid_structure(tmp_path, mutation):
path = write_mutated_recording(tmp_path, mutation)
with pytest.raises(RecordingFileError):
ReplayProvider(path)
def test_redaction_covers_basic_auth_url_userinfo_and_query_secrets(tmp_path):
path = record_one_request(tmp_path, "Basic dXNlcjpwYXNz https://u:p@host/?api_key=secret")
assert "dXNlcjpwYXNz" not in path.read_text()
assert "u:p" not in path.read_text()
assert "secret" not in path.read_text()
def test_recording_file_and_directory_are_private(tmp_path, monkeypatch):
monkeypatch.setattr(paths, "get_state_dir", lambda: tmp_path)
recording = create_managed_recording("private")
assert stat.S_IMODE(paths.get_recordings_dir().stat().st_mode) == 0o700
assert stat.S_IMODE(recording.path.stat().st_mode) == 0o600
Construct exact JSONL fixtures for duplicate headers, orphan events, missing call_end, mismatched event counts, non-contiguous indexes, and unknown event classes. Assert RecordingFileError is raised during ReplayProvider(path), before iteration.
- Step 2: Run the new tests and verify the intended failures
Run: pytest -q tests/providers/test_record_replay.py
Expected: FAIL because the current parser ignores call_end, unknown rows, index gaps, and permission requirements.
- Step 3: Implement the minimum strict parser and path protection
def get_recordings_dir() -> Path:
path = get_state_dir() / "recordings"
path.mkdir(parents=True, exist_ok=True, mode=0o700)
_restrict_to_owner(path, 0o700)
return path
class RecordingFileError(RuntimeError):
def __init__(self, path, reason, *, line_number=None, call_index=None):
self.path = Path(path)
self.reason = reason
self.line_number = line_number
self.call_index = call_index
def read_recording_file(path):
rows = load_jsonl_rows(path)
validate_header(rows[0])
return assemble_and_validate_contiguous_calls(rows[1:])
Implement URL-userinfo, query-parameter, authorization-scheme, secret-field, and known-token-prefix replacement with the existing <REDACTED> placeholder. Add no redaction toggle and no entropy heuristic. Verify regular-file status, symlink safety, and exact POSIX 0600 before reading or appending.
- Step 4: Run focused tests and compatibility tests
Run: pytest -q tests/providers/test_record_replay.py
Expected: PASS, including existing multi-turn tool-call replay and mismatch tests.
- Step 5: Commit Task 1
git add openprogram/paths.py openprogram/providers/recording.py openprogram/providers/replay.py tests/providers/test_record_replay.py
git commit -m "feat(providers): harden recording file format"
Task 2: Concurrent sink and registry-wide transform#
Files:
- Modify:
openprogram/providers/recording.py - Modify:
openprogram/providers/api_registry.py - Modify:
openprogram/providers/__init__.py - Modify:
tests/providers/test_record_replay.py - Create:
tests/providers/test_record_replay_registry.py
Interfaces:
-
Consumes: strict format helpers and private-path checks from Task 1.
-
Produces:
RecordingSink(path),configure_provider_transform(transform), andactivate_record_replay_from_config(). -
RecordingProvider(provider, path_or_sink)accepts a path or sharedRecordingSinkwithout changing existing path-based callers. -
Step 1: Add failing concurrency and registry tests
def test_two_recording_providers_share_contiguous_call_indexes(tmp_path):
sink = RecordingSink(tmp_path / "calls.jsonl")
drain(RecordingProvider(provider_a, sink).stream("k", request_a))
drain(RecordingProvider(provider_b, sink).stream("k", request_b))
assert [call.call_index for call in read_recording_file(sink.path)] == [0, 1]
def test_transform_wraps_existing_and_future_registry_entries(monkeypatch):
reset_test_registry(monkeypatch, {"a": provider_a})
configure_provider_transform(wrap)
register_api_provider("b", provider_b)
assert get_api_provider("a").inner is provider_a
assert get_api_provider("b").inner is provider_b
Use two provider instances and separate processes against one file; assert unique contiguous call indexes and a strict-reader success. Save and restore registry module dictionaries/globals in fixtures so the one-time transform does not leak across tests.
- Step 2: Run the focused tests and verify failures
Run: pytest -q tests/providers/test_record_replay.py tests/providers/test_record_replay_registry.py
Expected: FAIL because call indexes are currently per wrapper and the registry has no transform hook.
- Step 3: Implement
RecordingSinkand one-time transform
class RecordingSink:
def begin_call(self, request: dict[str, object]) -> int:
return self._append_request_with_next_index(request)
def append_event(self, call_index: int, event_index: int, event: dict[str, object]) -> None:
self._append_row({"type": "event", "call_index": call_index, "event_index": event_index, "event": event})
def end_call(self, call_index: int, event_count: int) -> None:
self._append_row({"type": "call_end", "call_index": call_index, "event_count": event_count}, fsync=True)
def configure_provider_transform(transform: Callable[[str, APIProvider], APIProvider]) -> None:
install_once_and_rewrap_registered_providers(transform)
For each append, open the data file with O_WRONLY|O_CREAT|O_APPEND and mode 0600, hold an exclusive sibling lock-file flock, loop until os.write consumes the complete encoded JSON line, and fsync on call_end. Allocate a call index while holding the lock by scanning request rows. Store original providers separately; transform all existing providers atomically, transform future registrations, no-op only when passed the identical callable object, and reject a different second transform.
- Step 4: Implement startup activation without per-provider configuration
def activate_record_replay_from_config() -> None:
setting = read_record_replay_setting()
if setting.mode == "record":
install_shared_recording_sink(setting.file)
elif setting.mode == "replay":
install_shared_replay_provider(setting.file)
Call activation once from providers/__init__.py after built-in providers register. Keep mode off behavior identical and avoid importing CLI modules.
- Step 5: Run focused tests and commit Task 2
Run: pytest -q tests/providers/test_record_replay.py tests/providers/test_record_replay_registry.py
Expected: PASS.
git add openprogram/providers/recording.py openprogram/providers/api_registry.py openprogram/providers/__init__.py tests/providers/test_record_replay.py tests/providers/test_record_replay_registry.py
git commit -m "feat(providers): install concurrent record replay transform"
Task 3: Strict offline stream selection#
Files:
- Modify:
openprogram/providers/stream.py - Modify:
openprogram/providers/api_registry.py - Modify:
tests/unit/test_usage_stream_chokepoint.py - Modify:
tests/providers/test_record_replay_registry.py
Interfaces:
-
Consumes: transformed providers from Task 2.
-
Produces:
APIProvider.requires_credentials: bool, defaulting toTrue;ReplayProvider.requires_credentialsreturnsFalse. -
Step 1: Add failing credential-order and no-fallback tests
def test_replay_stream_skips_credential_resolution(monkeypatch):
monkeypatch.setattr(stream_module, "resolve_api_key", fail_if_called)
monkeypatch.setattr(stream_module, "get_api_provider", lambda api: replay)
assert list(stream_simple(request)) == expected_events
def test_replay_provider_has_no_original_provider_or_network_fallback(tmp_path):
replay = ReplayProvider(valid_recording(tmp_path))
assert not hasattr(replay, "provider")
with pytest.raises(ReplayMismatch):
list(replay.stream("", mismatched_request))
Patch the credential resolver to raise if called in replay mode; invoke both stream_simple and stream. Assert malformed recordings fail during provider construction and mismatch/exhaustion never consult another provider.
- Step 2: Run the focused tests and verify failure
Run: pytest -q tests/unit/test_usage_stream_chokepoint.py tests/providers/test_record_replay_registry.py
Expected: FAIL because both stream entry points currently resolve credentials before selecting the provider.
- Step 3: Select provider before credentials and expose capability
@property
def requires_credentials(self) -> bool:
return True
provider = get_api_provider(api)
api_key = resolve_api_key(api) if provider.requires_credentials else ""
Apply the order in both stream entry points. Add ReplayProvider.assert_consumed() and keep the replay object free of any original-provider reference.
- Step 4: Run stream and provider tests and commit Task 3
Run: pytest -q tests/unit/test_usage_stream_chokepoint.py tests/providers/test_record_replay.py tests/providers/test_record_replay_registry.py
Expected: PASS.
git add openprogram/providers/stream.py openprogram/providers/api_registry.py openprogram/providers/replay.py tests/unit/test_usage_stream_chokepoint.py tests/providers/test_record_replay_registry.py
git commit -m "feat(providers): make replay strictly offline"
Task 4: Config and recording-management CLI#
Files:
- Modify:
openprogram/config_schema.py - Modify:
openprogram/cli.py - Modify:
openprogram/providers/recording.py - Create:
tests/providers/test_record_replay_cli.py
Interfaces:
-
Produces:
record_replay.mode,record_replay.file,create_managed_recording(name=None),resolve_recording_selector(selector),list_recordings(),show_recording(selector, include_content=False),delete_recording(recording_id), andprune_recordings(older_than_days, dry_run=False). -
CLI produces:
recordings status|record|replay|off|list|show|delete|prunewith exit codes0success,1runtime/validation failure, and2usage/confirmation failure. -
Step 1: Add failing configuration and CLI tests
def test_record_command_precreates_private_header_then_updates_config(cli_env):
assert main(["recordings", "record", "--name", "fixture"]) == 0
config = setup._read_config()["record_replay"]
assert config["mode"] == "record"
assert managed_path(config["file"]).read_text().startswith('{"type":"header"')
def test_replay_command_prevalidates_before_updating_config(cli_env):
invalid = cli_env.root / "invalid.jsonl"
invalid.write_text("not-json\n")
with pytest.raises(RecordingFileError):
set_replay_mode(str(invalid))
assert "record_replay" not in setup._read_config()
def test_delete_rejects_active_recording(cli_env):
active = set_record_mode("active")
with pytest.raises(RecordingManagementError):
delete_recording(active.recording_id)
Drive main(argv) or subprocess parsing as appropriate. Patch state/config paths only, keep real filesystem modes, and verify no config mutation on every failure path.
- Step 2: Run CLI tests and verify failure
Run: pytest -q tests/providers/test_record_replay_cli.py
Expected: FAIL because the command group and config keys do not exist.
- Step 3: Implement managed lifecycle and transactional mode changes
def create_managed_recording(name: str | None = None) -> RecordingInfo:
return create_exclusive_private_header_file(validated_recording_id(name))
def set_record_mode(name: str | None = None) -> RecordingInfo:
return transactionally_create_and_select_recording(name)
def set_replay_mode(selector: str) -> RecordingInfo:
return validate_then_transactionally_select_replay(selector)
def set_record_replay_off() -> None:
setup.update_config(clear_record_replay_selection)
Treat selectors containing a path separator or absolute syntax as explicit paths for replay/show only; otherwise resolve IDs under the managed root. Reject traversal and symlinks. record creates a header-only 0600 file before one setup.update_config call and removes only that newly created file if configuration fails. replay strictly parses before configuration update. Delete/prune only managed files, never delete the active selector, and skip a non-blocking locked recording during prune.
- Step 4: Add the two config settings and thin argparse dispatch
SettingSpec("record_replay.mode", choices=("off", "record", "replay"), default="off", apply="next_start")
SettingSpec("record_replay.file", value_type="string", default=None, apply="next_start")
Add one recordings parser with the eight verbs and set_defaults(_cmd_parser=recordings_parser); delegate runtime behavior to recording.py. JSON modes use json.dumps, destructive commands require --yes or an exact-ID interactive confirmation, and non-TTY refusal returns 2.
- Step 5: Run CLI/config tests and commit Task 4
Run: pytest -q tests/providers/test_record_replay_cli.py tests/unit/test_config_schema.py tests/unit/test_config_write_safety.py tests/unit/test_cli_subcommand_dispatch.py
Expected: all feature/config tests PASS; if the isolated subprocess baseline still lacks typing_extensions, record that pre-existing environment failure separately and rerun all in-process command cases.
git add openprogram/config_schema.py openprogram/cli.py openprogram/providers/recording.py tests/providers/test_record_replay_cli.py
git commit -m "feat(cli): manage provider recordings"
Task 5: Documentation status and final verification#
Files:
- Modify:
docs/reference/design/providers/record-replay.html
Interfaces:
-
Consumes: completed behavior and test evidence from Tasks 1–4.
-
Produces: an evidence-separated implementation/verification status section without changing the approved conceptual design.
-
Step 1: Update only the implementation status and evidence table
Record exact production files, compatibility boundary, test files, and fresh verification commands. Mark only implemented behavior as complete; retain limitations such as provider-layer-only offline scope and scheduling-order nondeterminism.
- Step 2: Run the complete relevant test matrix
Run: pytest -q tests/providers/test_record_replay.py tests/providers/test_record_replay_registry.py tests/providers/test_record_replay_cli.py tests/providers/test_scripted_provider.py tests/unit/test_usage_stream_chokepoint.py tests/unit/test_config_schema.py tests/unit/test_config_write_safety.py tests/unit/test_cli_subcommand_dispatch.py
Expected: PASS except any reproduced baseline-only missing-dependency subprocess case, which must be reported exactly and not attributed to this feature.
- Step 3: Run lint, docs, and diff checks
Run: ruff check openprogram/paths.py openprogram/config_schema.py openprogram/cli.py openprogram/providers tests/providers/test_record_replay.py tests/providers/test_record_replay_registry.py tests/providers/test_record_replay_cli.py tests/unit/test_usage_stream_chokepoint.py
Run the repository-provided HTML/docs check discovered from package scripts or CI configuration; then run git diff --check 8f62da5a HEAD and git status --short.
Expected: zero Ruff, docs, and whitespace errors; only intended files changed.
- Step 4: Commit Task 5
git add docs/reference/design/providers/record-replay.html
git commit -m "docs: record request replay implementation evidence"
- Step 5: Review the branch without merging
Run: git log --oneline 8f62da5a..HEAD and git diff --stat 8f62da5a HEAD.
Expected: one plan commit plus independently reviewable task commits, no JSON Schema/MCP/resource-governance file changes, and no merge into main.