DEV Community

Finley Sun
Finley Sun

Posted on

Seal the Fixture Before the Agent Patch

A billing patch went green at two in the morning. The agent rewrote one tax fixture so the new total matched. Production still charged the older rate on the next invoice.

The suite never crashed, and the diff looked almost tidy. Only the fixture file had changed its witness without a review note. A green bar then proved the edited story, not the live behavior.

This note proposes an input custody gate for agent patches. It reports no measured outage and no private production metric. The snippets below are unexecuted examples for a throwaway branch.

An agent may edit the production module under review. It should not edit the bytes that define the case itself. Those bytes work like a sealed sample bag on a lab bench.

You may read the bag and argue about the code beside it. You may not swap the sample and keep the old case name. A renamed experiment needs a new seal, not a quiet overwrite.

Wax before the green bar

Three gates run in a fixed order on every patch. A hash ledger seals each fixture before tests even start. Property checks then reject a sealed file with a hollow shape.

A flake freeze file stands as the last gate in line. It blocks attempts to hide a wobbling test inside that same change. Code may move, but the case memory may not move with it.

The ledger stores only a relative path and a sha256 digest. A reviewer updates that file in a separate custody commit. If the agent patch and the ledger move together, the gate fails.

Think of the ledger as the wax on that evidence bag. The patch can shake the table, but it cannot reseal the wax. A fresh seal is a human act, recorded where git history can show it.

# Proposal only. This example has not been executed here.
import hashlib
import json
import pathlib
import sys

ROOT = pathlib.Path('tests/fixtures')
LEDGER = pathlib.Path('tests/fixture_ledger.json')

def digest(path: pathlib.Path) -> str:
    blob = hashlib.sha256()
    blob.update(path.read_bytes())
    return blob.hexdigest()

def scan() -> dict[str, str]:
    files = sorted(p for p in ROOT.rglob('*') if p.is_file())
    return {str(p.relative_to(ROOT)): digest(p) for p in files}

def main() -> int:
    current = scan()
    sealed = json.loads(LEDGER.read_text(encoding='utf-8'))
    drifted = sorted(k for k, v in sealed.items() if current.get(k) != v)
    extra = sorted(k for k in current if k not in sealed)
    missing = sorted(k for k in sealed if k not in current)
    if drifted or extra or missing:
        print('fixture seal broken')
        for name in drifted + extra + missing:
            print(name)
        return 1
    print('fixture seal holds:', len(current))
    return 0

if __name__ == '__main__':
    sys.exit(main())
Enter fullscreen mode Exit fullscreen mode

Save the checker as a script named seal_fixtures under tools. Build the first ledger by hand from the script scan function. Review that JSON, commit it, and treat later edits as custody events.

The check command below is a merge gate, not a friendly hint. Run it on the throwaway branch before you trust any green bar. A nonzero exit means the case memory moved, so stop the review there.

python tools/seal_fixtures.py
Enter fullscreen mode Exit fullscreen mode

A matching hash still cannot rescue a poorly built case. An empty cart can be sealed and still teach the suite nothing. A tax rate from nowhere can also sit quietly under a valid digest.

Shape checks read the fixture before any assertion runs. They refuse missing lines, unknown currencies, and broken totals. Keep each bound dull, local, and easy for a reviewer to reject.

# Proposal only. Bounds below are examples, not production policy.
import json
import pathlib

def check_invoice(row: dict) -> None:
    assert row['currency'] in {'USD', 'EUR'}
    assert 0 <= row['tax_rate'] <= 0.25
    assert row['lines'], 'empty lines make the total look proven'
    net = sum(item['qty'] * item['price'] for item in row['lines'])
    assert abs(row['net'] - net) < 0.01

def main() -> None:
    folder = pathlib.Path('tests/fixtures/invoices')
    for path in sorted(folder.glob('*.json')):
        check_invoice(json.loads(path.read_text(encoding='utf-8')))
        print('shape ok', path.name)

if __name__ == '__main__':
    main()
Enter fullscreen mode Exit fullscreen mode

Wide bounds recreate a green bar that barely says anything. Bounds copied from one odd customer will wobble across runs. That wobble belongs in the freeze file, not in a regenerated fixture.

The freeze file is a plain list of stable test node ids. A test enters after two disagreements on the same commit range. It leaves only in a commit that avoids fixtures and production paths.

The agent may not delete a frozen id to purchase a green bar. The agent may not mark that id as an expected failure either. Both moves hide the wobble instead of isolating it for a human.

Run the seal, the shape check, and the invoice tests in order. Then write the changed paths and invoke the flake gate script. Stop at the first nonzero exit and keep the printed names.

python tools/seal_fixtures.py
python tools/check_fixture_shape.py
python -m pytest tests/invoices -q --maxfail=1
git diff --name-only origin/main...HEAD > /tmp/changed.txt
python tools/flake_gate.py tests/flake_freeze.txt /tmp/changed.txt
Enter fullscreen mode Exit fullscreen mode
# Proposal only. Unexecuted. This half checks co-change, not missing ids.
import pathlib
import sys

PROD_PREFIXES = ('src/', 'app/', 'lib/')
FIXTURE_PREFIX = 'tests/fixtures/'

def main() -> int:
    freeze = pathlib.Path(sys.argv[1])
    changed = pathlib.Path(sys.argv[2]).read_text(encoding='utf-8').splitlines()
    names = {freeze.as_posix(), str(freeze)}
    freeze_changed = any(path in names for path in changed)
    hot = [
        path for path in changed
        if path.startswith(PROD_PREFIXES) or path.startswith(FIXTURE_PREFIX)
    ]
    if freeze_changed and hot:
        print('freeze file moved beside code or fixtures')
        return 1
    print('freeze boundary holds')
    return 0

if __name__ == '__main__':
    sys.exit(main())
Enter fullscreen mode Exit fullscreen mode

The sample only checks that co-change boundary, and it is unexecuted. A missing-id check needs a baseline copy of the freeze file from main. Keep that half beside the sample, and do not pretend the sample does it.

The flake gate script should stay boring and small on purpose. If the freeze file changes beside production code, it exits nonzero. If the freeze file changes beside a fixture, it exits nonzero too.

A missing frozen id is the same class of failure. Print the id and leave the file untouched for the reviewer. Automatic cleanup here would hand custody back to the patch author.

That split is the strategy, stated without extra ceremony. Production code may change inside the agent commit. Fixture bytes and freeze lines need their own reviewed commit.

One clean run

Disclosure: This article was prepared as part of MonkeyCode's product outreach. The operator reports free model access and a free server option. This draft states no token quota, hardware size, or rental duration.

Those terms change, so confirm them on a current primary page. Those options earn one seat in this workflow, and only one. Use a clean server so a dirty local fixture cannot leak in.

Use model help to draft shape predicates, then freeze the files. Reject any draft that edits fixtures or the flake freeze list. A person still writes the ledger commit after reading the diff.

A local virtualenv can run the same gate if access stays closed. Do not let the model update the ledger in the same turn. A writer that both swaps the sample and blesses the hash is untrustworthy.

Generation and custody belong on opposite sides of human review. A model draft can suggest a bound, but it cannot bless the wax. The reviewer remains the only one who may close the bag.

Leave it on the shelf

Some teams should leave this gate on the shelf. Skip it when fixtures are rebuilt on every run by design. Snapshot shops that expect churn will only harvest noisy failures.

Skip it when nobody owns the freeze list from week to week. An unowned list turns into a graveyard and blocks useful repairs. Skip it in labs that must mutate fixtures as the exercise itself.

Skip it on a prototype whose cases still change every day. The seal will shout, and that shout will be pure noise. Adopt the gate when cases are stable enough to deserve a name.

The limits are ordinary, and they should stay visible in review. A correct hash can seal a wrong fixture after a careless first read. Property bounds can stay too wide and still return a pass.

A freeze list that nobody triages will block work you still need. Byte seals also flag harmless formatting, so canonicalize JSON early. These gates never watch live traffic or prove production health.

They only stop the suite from lying about the case you named. A green bar after a fixture rewrite is a different experiment. Call it a new case, or revert the bytes and keep the old name.

Check the current free server terms if local capacity is tight. Then keep the ledger in git, where a person can still refuse. Review the seal before you review the prose of the patch.

Top comments (0)