Renaming the wire without breaking it

Renaming a framework is easy until you reach the wire — the HMAC-signed webhook headers a receiver verifies by name, the resource URIs an agent has already cached, the env vars in someone's CI. Forge became Smeldr at the wire level in v1.32.0 without breaking a single integration, by dual-emitting the new and legacy identifiers with identical values for a deprecation window. Here is the asymmetric-risk reasoning behind it.

Renaming the wire without breaking it

The problem

Renaming a project is mostly find-and-replace: the package, the import paths, the docs. We had already done that. What was left was the part that find-and-replace can't safely touch — the identifiers that leave the process and that someone else's code depends on by their exact spelling:

  • the HMAC-signed webhook headers a receiver verifies *by name*,
  • the MCP resource URIs an agent may have already listed and cached,
  • the environment variables sitting in someone's CI config.

Rename any of these naively and you don't get a compile error. You get a silent breakage in production, in code you don't own. So the rule for this slice was: the new identifier is generated and preferred; the legacy one is still accepted and emitted alongside it; nothing breaks. Removal comes later, deliberately (tracked as T87), once integrations have migrated.

But "dual-emit everything" is the lazy answer. The three families carry very different risk, and that shaped how much each one was worth.

The high-risk one: signed webhook headers

A webhook receiver verifies authenticity by computing an HMAC over the payload and comparing it to the signature header. Crucially, it looks the signature up *by header name*. Rename X-Forge-Signature to X-Smeldr-Signature and every existing receiver fails verification — not with an error you'll see, but by silently rejecting (or worse, silently accepting) deliveries.

So the signature is computed once and sent under both names, with identical values:

// outbound.go — dual-emitted during the T86 deprecation window
req.Header.Set("X-Smeldr-Signature", sig)
req.Header.Set("X-Smeldr-Timestamp", strconv.FormatInt(ts, 10))
req.Header.Set("X-Smeldr-Event", job.Event)
req.Header.Set("X-Smeldr-Delivery", job.ID)

req.Header.Set("X-Forge-Signature", sig) // same value
req.Header.Set("X-Forge-Timestamp", strconv.FormatInt(ts, 10))
req.Header.Set("X-Forge-Event", job.Event)
req.Header.Set("X-Forge-Delivery", job.ID)

sig is the same sha256=<hex> string in both. An existing receiver keeps verifying X-Forge-Signature and never notices; a new one verifies X-Smeldr-Signature. This is the low-visibility, high-breakage corner — nobody sees these headers in a UI, but get it wrong and you break auth. It is the part of the sweep that was pure cost, done for completeness, not for value.

The low-risk one: MCP resource URIs

The opposite case. Every AI agent that browses resources sees the URI scheme, so it is highly visible — but it is also transient: an agent lists resources and reads one in the same breath, so a cached forge:// URI is rare and short-lived.

The new scheme is generated:

smeldr://posts/{slug}

and the parser accepts both, so any agent still holding a forge:// URI resolves fine:

// resource.go
// parseResourceURI resolves a smeldr:// or forge:// URI to its module and slug.
// Accepts the new smeldr:// scheme (preferred) and the legacy forge:// scheme
// (still accepted during the deprecation window — T87 removes it).

High visibility, low breakage: generate the new, accept both, move on. The MCP server also now identifies itself as smeldr-mcp in serverInfo — checked first that nothing keyed on the old name.

The boring one: environment variables

The CLI now prefers SMELDR_URL / SMELDR_TOKEN / SMELDR_MCP_URL and falls back to the FORGE_* names if only those are set; init writes a .smeldr-cli.env. Anyone's existing CI keeps working untouched; new setups use the Smeldr names.

The principle

Three families, one rule, calibrated by risk:

IdentifierVisibilityBreakage riskTreatment
Webhook HMAC headersLowHigh (verified by name)dual-emit, identical values
MCP resource URIsHighLow (transient)generate new, accept both
CLI env varsLowMediumprefer new, fall back to legacy

In every case: the new identifier is generated and preferred, the legacy one is accepted/emitted alongside, and nothing breaks. The one thing that is *not* in this release is the removal — dropping forge://, ceasing to emit X-Forge-*, dropping the FORGE_* fallback. That is a deliberate, separately-communicated breaking change for later (T87), once the telemetry says integrations have moved. Renaming the wire is safe precisely because the rename and the removal are two different events, with a deprecation window in between.