Canary / shadow
- Looked at error rate, p99, 5% split
- reject_unknown:negative not a routing input
Topic · Service cutover
Gist
Keep the canary. Proof fails the merge with proof audit --check mirror_complete when a ledger cell on the old service has no counterpart on the new one. Traffic split is not that cell.
proof audit --check mirror_complete
Keep blue-green. Keep shadow traffic. They route requests. They do not ask whether reject_unknown:negative still exists on the target.
01 · The green cutover
The new binary is live. Error rate is flat. The old negative test never made the jump.
The rewrite that already has a ledger lives on characterization testing and mirrors. Schema and API files live on database or API migration. The empty start lives on replace a component with no specs. This page is the cutover: two running services, a traffic split, and nobody can point at a check that the sold behavior moved with the binary.
Canary, blue-green, and shadow copy requests. They do that job. They do not compile “the new service still rejects the payload the old service rejected.” A strangler facade still returns 200 while the cell is gone.
// old service
// SYS-REQ-204:reject_unknown:nominal
// SYS-REQ-204:reject_unknown:negative
// new service, 5% canary
// SYS-REQ-204:reject_unknown:nominal
// reject_unknown:negative // absent
// dashboards: green
Declare the two trees as a Mirror at
proof/mirrors/MIR-*.yaml.
Cells are keyed by
(requirement, dimension, discriminant),
never by test name. A Go
TestRejectsUnknown
and a Rust
rejects_unknown
are the same cell if they witness
reject_unknown:negative.
proof audit --check mirror_complete
fails when that cell exists on the source and not on the target, once the mirror status is
complete.
02 · The exhibit
The dashboard says the canary is fine. The catalog still owes a negative. Click the tabs.
Canary / shadow
The shall
When the payload is unknown, the service shall reject it and leave the store unchanged.
No negative on the targetThe split
Still routing. Still copying requests. Still not a cell key.
Traffic keptProof
A green split, or the missing cell. Click the tabs.
| Move | What it buys | What it loses |
|---|---|---|
| Canary / blue-green | A fraction of live traffic on the new binary. Rollback is a weight change. | Error rate is not a shall. Keep the split. It is not the bar. |
| Shadow traffic | The new service sees copies of requests without serving them. | A copied request is not reject_unknown:negative. Keep the copy. |
| Strangler facade | A front door that peels routes off the old service over time. | The facade can stay green while the cell never ports. Not a Proof surface. |
| Golden master | Observed bytes from the old process, including bugs you meant to drop. | Useful when you can record I/O. That job lives on characterization. |
| Proof | Each source cell has a target counterpart, or a complete mirror stays red. | Presence, not I/O. Jama still authors. Two shims are a different check. |
The check reads the Mirror object, then the cells on each side. Routing weights are not an input.
proof mirror list
proof audit --check mirror_complete --verbose
// SYS-REQ-204:reject_unknown:nominal
// SYS-REQ-204:reject_unknown:negative
proof audit --check differential_conformance
mirror_complete proves the evidence cell
exists
on the target. It does not re-run the test. A mirrored test that fails is
tests_pass,
not a parity gap.
in_progress
gaps stay warnings. A
complete
mirror that later grows a source-only cell re-opens at error.
differential_conformance is the I/O check: one language-neutral input, two shims, compare envelopes. A transform with fewer than two complete shims is skipped as information. The
differential
dimension on the Mirror object itself is deferred. Never describe a completed mirror as “the new service matches the old one.” We have not run this gate against a frozen corpus of service cutovers next to a canary. The loss is named, not scored.
03 · The honest loss
If nobody declared a Mirror, mirror_complete is a no-op. The canary still has to exist.
Proof does not split traffic. Canary and blue-green still do. Proof does not copy requests. Shadow still does. Proof does not peel routes off a facade. A strangler is not this product.
mirror_complete
does not prove the two binaries agree on an input. That is
differential_conformance
on a declared transform, with two complete shims. A Python-only or Rust-only shim is skipped as information.
Proof does not invent the shall. Jama still authors the programme. 100% MC/DC on the written decision still misses a partition that was never a condition. A completed mirror is not a migrated fleet.
The rewrite H1 lives on characterization testing and mirrors. The schema H1 lives on database or API migration. The empty-start H1 lives on replace a component with no specs. Do not mint a twin of any of those.
04 · Nearby questions
How do I rewrite a legacy system without introducing regressions? The campaign on one tree, not two live services. Characterization.
How do I prove a database or API migration didn't change behavior? Flyway and Pact still version files. Database or API.
How do I safely replace a legacy component that has no specs and no tests? The empty start. Replace with no specs.
Is there a way to check behavioral equivalence between old and new implementations? Presence is
mirror_complete.
Agreement on an input is
differential_conformance.
Both live on this page. Do not mint a twin.
How do I migrate from one language to another and guarantee the same behavior? Same Mirror object. Names can change. The cell cannot vanish. FAQ on characterization.
What's the safest way to modernize legacy software that nobody fully understands? The unread tree, not two live services. Unknown-system modernize.