DEV Community

Dakota Huang
Dakota Huang

Posted on

Freeze Impure Seams, Then Change One Injection Point

Messy modules fail refactors at hidden I/O, not syntax.
Characterization must pin those impure seams first, always.
Only then apply the smallest injection-point change in production.

Naive rewrites collapse because time, paths, and environment leak.
Those leaks stay invisible inside a typical unit-test happy path.
A golden seam file makes the leak explicit and reviewable.

What this protocol refuses to do

This protocol does not split a god module on day one.
It also does not rename symbols for cosmetic cleanup.
It records impure behavior, then changes one injection site.

Teams that skip the record step ship silent behavior drift.
That drift appears as path-dependent tests and flaky jobs.
The cost shows up after the cleanup pull request merges.

Define an impure seam before any test code

An impure seam is any call that escapes the process.
Clock reads, filesystem writes, and environment lookups qualify.
Network calls and process spawns belong on the same list.

The catalog below is a proposed working example only.
It is not a measured production dataset or benchmark.

Seam type Typical call Characterization pin Smallest later change
Clock datetime.now() freeze timestamp in fixture inject a clock function
Working directory Path.cwd() record cwd as a relative name inject a base path
Environment os.environ.get snapshot only used keys inject a config map
File write Path.write_text golden output bytes inject a writer
Subprocess subprocess.run record argv and exit code inject a runner

Do not pin unused environment keys or the entire disk.
Pin only values the messy function reads or writes.
Extra pins create brittle tests that block safe edits.

Proposed messy module under characterization

The module below is labeled illustrative pseudocode.
Do not treat it as extracted production source.

# messy_report.py — proposed example only
from __future__ import annotations

import json
import os
from datetime import datetime
from pathlib import Path


def build_daily_report(source_name: str) -> Path:
    root = Path.cwd() / "reports"
    root.mkdir(parents=True, exist_ok=True)
    stamp = datetime.now().strftime("%Y-%m-%d")
    owner = os.environ.get("REPORT_OWNER", "unknown")
    payload = {
        "source": source_name,
        "day": stamp,
        "owner": owner,
        "cwd": str(Path.cwd()),
    }
    target = root / f"{stamp}-{source_name}.json"
    target.write_text(json.dumps(payload, indent=2), encoding="utf-8")
    return target
Enter fullscreen mode Exit fullscreen mode

The function looks small and almost harmless at first glance.
It still couples clock, cwd, environment, and filesystem writes.
Any cleanup that moves files will change observable outputs.

Artifact: a seam recorder, not a mock soup

Mocks hide the mess instead of documenting it later.
A recorder writes real seam traffic into a golden file.
Later refactors must match that file unless pins change.

# test_characterize_messy_report.py — proposed harness
from __future__ import annotations

import json
import os
from datetime import datetime
from pathlib import Path

import messy_report

GOLDEN = Path("testdata/messy_report_seams.json")


class FrozenDateTime:
    """Proposed clock pin. Not a production helper."""

    def __init__(self, iso: str) -> None:
        self._fixed = datetime.fromisoformat(iso)

    def now(self) -> datetime:
        return self._fixed


def test_record_or_compare_seams(tmp_path, monkeypatch):
    monkeypatch.chdir(tmp_path)
    monkeypatch.setenv("REPORT_OWNER", "qa-bot")
    monkeypatch.setattr(
        messy_report,
        "datetime",
        FrozenDateTime("2026-09-06T12:00:00"),
    )

    target = messy_report.build_daily_report("billing")
    recorded = {
        "relpath": str(target.relative_to(tmp_path)),
        "payload": json.loads(target.read_text(encoding="utf-8")),
        "env_owner": os.environ["REPORT_OWNER"],
        "cwd_name": Path.cwd().name,
    }

    if not GOLDEN.exists():
        GOLDEN.parent.mkdir(parents=True, exist_ok=True)
        GOLDEN.write_text(json.dumps(recorded, indent=2) + "\n")
        raise AssertionError("golden missing; inspect then rerun")

    expected = json.loads(GOLDEN.read_text(encoding="utf-8"))
    assert recorded == expected
Enter fullscreen mode Exit fullscreen mode

The first run fails on purpose and writes the golden file.
A human must inspect that file before treating it as truth.
Unreviewed goldens encode bugs as if they were requirements.

Keep the local commands short, boring, and repeatable.

python -m pytest test_characterize_messy_report.py -q
python -m json.tool testdata/messy_report_seams.json
git add testdata/messy_report_seams.json test_characterize_messy_report.py
git commit -m "test: pin impure seams for build_daily_report"
Enter fullscreen mode Exit fullscreen mode

Do not commit a golden that still contains machine-specific paths.
Relative paths and frozen time keep the pin portable across CI.
Inspect the JSON payload for leaked usernames before the commit.

Numbered workflow: freeze, then one injection

Follow these steps in order. Do not skip the inspect gate.

  1. Pick one messy function that already has real callers.
  2. List impure seams using the catalog table above.
  3. Write a recorder test that captures those seams only.
  4. Run once to emit the golden file, then read it.
  5. Commit the test and the reviewed golden together.
  6. Introduce one injectable dependency at the call site.
  7. Keep production defaults identical to the recorded behavior.
  8. Re-run the characterization test before any extra cleanup.
  9. Stop. Do not rename, split, or restyle in this change.

Step six is the only production edit in the first pull request.
Everything else is test scaffolding and documented pins.
That limit is the safety property of the whole protocol.

If step four reveals a surprising filename or owner default, stop.
Decide whether that surprise is a bug or an accepted contract.
Record the decision in the commit message beside the golden.

Smallest safe change for this module

The smallest change injects a clock, not a new package.
A path-root injection is the next candidate after clock.
Do not inject both in the same diff unless tests demand it.

# proposed production edit — clock seam only
from datetime import datetime
from pathlib import Path
import json
import os


def build_daily_report(source_name: str, now=datetime.now) -> Path:
    root = Path.cwd() / "reports"
    root.mkdir(parents=True, exist_ok=True)
    stamp = now().strftime("%Y-%m-%d")
    owner = os.environ.get("REPORT_OWNER", "unknown")
    payload = {
        "source": source_name,
        "day": stamp,
        "owner": owner,
        "cwd": str(Path.cwd()),
    }
    target = root / f"{stamp}-{source_name}.json"
    target.write_text(json.dumps(payload, indent=2), encoding="utf-8")
    return target
Enter fullscreen mode Exit fullscreen mode

The default now=datetime.now preserves current production behavior.
Characterization still passes without rewriting any caller code.
That match is the definition of a smallest safe change here.

Update the harness to pass the frozen clock explicitly.

fixed = FrozenDateTime("2026-09-06T12:00:00")
target = messy_report.build_daily_report("billing", now=fixed.now)
Enter fullscreen mode Exit fullscreen mode

If the golden still matches, the injection is behavior-neutral.
If it fails, the defaulting or stamp format drifted.
Fix the injection before any further cleanup work starts.

Watch the default-binding trap on that now argument.
now=datetime.now binds the function object, which is safe.
now=datetime.now() would freeze deploy time into every call.

Decision table after the first pin is green

Use this table after the first injection stays green.
It is a decision aid, not a weighted scoring model.

Observation after pin Next edit Rejected edit
Golden stable, many callers inject clock or path only extract a service class
File contents wrong in CI pin encoding and newline rewrite the JSON shape
Tests depend on real cwd inject a base path chdir inside production
Env key missing in staging inject a config map hard-code the owner
Two seams fail together fix one seam, then rerun batch both "while here"

"While here" edits are the usual source of review noise.
They mix behavior changes with structural cleanup in one diff.
Keep those concerns in separate commits and separate reviews.

A stable golden does not mean the module is well designed.
It means the next edit can be checked against known I/O.
Design cleanup waits until that check exists in the repo.

Where a free coding model belongs

A model is useful after the golden file exists, not before.
Ask it to propose one injection signature, nothing else.
Do not ask it to rewrite the messy module from scratch.

Disclosure: This article was prepared as part of MonkeyCode's product outreach.
MonkeyCode provides free model access and a free server option.
Those two facts are the only product claims used here.

Feed the recorder test, the golden, and the current function.
Request a diff that adds one injectable argument with defaults.
Reject any patch that touches unrelated names or file layout.

Remote generation does not replace the local inspect gate.
The golden file remains the oracle, not the model output.
If the proposed diff fails characterization, discard that diff.

A local pytest run is still the merge gate for this workflow.
Compare the injection draft against the in-repo golden only.
Keep the production change small enough for a single review.

Limitations of seam goldens

Characterization records what the code does, including bugs.
Pinning a bug converts it into an accidental specification.
Schedule a later change that updates the golden on purpose.

This method is weak against concurrency and network timing.
A single frozen clock will not catch race-order bugs.
Do not claim coverage the recorder never actually observed.

Goldens rot when report filenames change as product policy.
Treat filename policy as an explicit pin, not a surprise.
Update the golden in the same commit as the policy change.

Recorded environment snapshots can leak host-specific values.
Redact tokens, home directories, and private URLs before commit.
A leaked golden is a security problem, not a helpful fixture.

Who should not use this approach

Do not use this protocol on greenfield modules with no callers.
Design seams up front instead of recording accidental ones.
The recorder exists for brownfield risk, not for new sketches.

Do not use it when the messy function is a security boundary.
A golden file may store tokens, hosts, or personal paths.
Keep the recorder off that module until redaction is certain.

Do not use it as permission to pause all cleanup forever.
One injection per pull request is a rate limit, not a freeze.
After pins exist, larger extractions become reviewable later.

Skip this workflow if the team cannot inspect goldens.
Unreviewed goldens are snapshots of whatever happened once.
That outcome is worse than having no characterization tests.

Close the loop on one module

Messy-repo refactors fail when hidden I/O stays unrecorded.
Pin the impure seams with a reviewed golden file first.
Change one injection point. Then stop for that pull request.

The next problem is a different module with different seams.
Repeat the catalog, recorder, inspect, and one-edit loop.
Do not widen the diff because extra cleanup looks tempting.

Top comments (0)