Umbrella architecture

Aftermarket is one Go program, aftermarket, with a subcommand for each tool. Every tool lives in its own package and has its own tests. They share code only where two real tools need the same thing, such as the rule matcher or the audit log. If a tool ever needs to run on its own, it can be built as a separate binary from the same repository.

Where it sits

CLIENTS AND INTEGRATIONS (unchanged)
          |
          v
   aftermarket
   patch  | fix requests and responses
   adapt  | translate old APIs to new ones
   record | capture traffic as test fixtures
   replay | compare two versions of a service
   shadow | serve from one version, compare a copy with the next
          |
          v
APPLICATION / API / SERVICE (unchanged)

patch shipped in 0.1.0, adapt in 0.2.0, record and replay in 0.3.0, config in 0.4.0 (with TOML added in 0.7.0), hook in 0.5.0, guard in 0.6.0, schema in 0.8.0, bridge in 0.9.0, and shadow, the live mode of replay, in 0.10.0. shadow sits in the traffic like record: it passes everything through unchanged and sends a copy of each finished request to a second service on the side, never in the client’s way. guard sits in the traffic like patch, but decides instead of repairing: it lets a request through unchanged or turns it away. bridge is a server of its own: it answers requests from snapshots of an app’s files, with no upstream behind it. Three tools don’t sit in the traffic: config works on the vendor’s config file before the app reads it, schema reads two versions of a SQLite database and writes views for the new one, and hook watches files, folders, endpoints and command output from the side and calls a webhook when they change. They share the plumbing for bodies, timeouts, health checks, fixtures and the audit log, and the same when: block picks traffic for patch and record. The other commands are planned in the order shown on the roadmap.

AfterPatch, the first tool

AfterPatch is a small reverse proxy. It matches HTTP traffic against explicit conditions and applies limited changes to the request or the response. Rules are YAML, short enough to read in a code review. A rule looks like this, and the reference has the full format:

rules:
  - id: restore-customer-id
    when:
      method: GET
      path: /v2/customers/*
      content_type: application/json
    response:
      json:
        - copy: {from: customer.id, to: customer_id}

Every request goes through the same steps: receive, match, change, forward, record. In dry-run mode the proxy forwards traffic untouched and only reports what it would have changed. Rules always apply in the same order, so the same input gives the same output.

Some limits are part of the design from day one:

  • a body size cap, above which traffic passes through unchanged
  • correct handling of chunked and compressed bodies
  • timeouts on the upstream service
  • secrets removed from the audit log
  • a health check endpoint

Traffic that no rule matches is passed on byte for byte. The test suite checks that on every run.

What stays out

No scripting language inside the rules. No central control plane, no plugin marketplace, no cluster mode. A rule should do one visible thing, and a person should be able to see from the audit log exactly what it did.