A desktop rewrite had screens for shipments, reports, settings, and logs. A feature checklist could have marked those areas complete.
The old application also had behavior no screen list would reveal:
- a failed temporary-file cleanup must not turn a submitted print into a failure, or the next run may print the label again;
- the first run treats older unprinted records differently from later runs;
- setup unlocks when a credential setting exists, not when its value is non-empty; and
- a visible report search box does nothing.
Some of those rules protect operators. One is an awkward contract. One is probably a dead feature. A rewrite cannot treat them all as “parity.”
It needs a behavior ledger: a finite record of what must survive, what will change deliberately, what may be dropped, and what evidence closes each decision.
A feature name hides the dangerous part
“Printing” is too broad to migrate. It says nothing about the point at which a print becomes successful.
The retained source submits a document to the operating system, records the successful activity, and then attempts to remove a temporary file. If cleanup fails, the failure is reported, but it does not reverse the successful submission.
That order is the behavior:
submit document
-> record successful submission
-> attempt temporary-file cleanup
-> report cleanup failure without making the job printable againA rewrite can have a print button, send bytes to a printer, and still get this wrong. Marking “printing” complete would conceal a duplicate-label risk.
The same problem appears in first-run scheduling. The useful contract is not “there is a scheduler.” It is the combination of:
- whether an initial run has been recorded;
- whether old labels are allowed on that run;
- which creation time defines “old”;
- what later runs do; and
- how an interrupted run changes the next decision.
Feature inventories are navigation aids. Behavior ledgers carry migration risk.
Give every row a disposition
“Match the old application” sounds safe until the old application contradicts the product the rewrite is meant to become. Every observed behavior needs an explicit disposition:
| Disposition | Meaning | Required proof |
|---|---|---|
| preserve | callers and operators receive the same outcome | source observation and target contract test |
| equivalent | implementation or presentation changes while the owned outcome remains | invariant tested on both sides |
| change | the product will behave differently on purpose | approved target rule and migration or communication plan |
| discard | no supported consumer or outcome needs the behavior | consumer search and removal rationale |
| unresolved | evidence or authority is still missing | named blocker; release remains open |
This avoids two opposite mistakes.
The first is copying everything. A dead search field does not become valuable because it survived long enough to be discovered.
The second is calling every inconvenience an implementation detail. First-run state and post-submission cleanup may look like framework mechanics. Their consequences—printing old documents or printing the same document twice—belong to the product.
The ledger forces the argument into one row rather than letting it hide inside the word “parity.”
Source evidence and target evidence are different
The old code is evidence, not automatically the specification.
For each row, retain both sides:
behavior:
successful submission survives cleanup failure
source evidence:
source job ordering
source test or observed trace
disposition:
preserve
target evidence:
shared runtime test
platform adapter test
remaining proof:
physical printer observation on each supported hostSource tests can establish the old rule. Source code can reveal branches which tests missed. Production traces or operator reports can show what actually happens. A product decision may still replace the observed behavior.
Target evidence answers a separate question: does the new implementation deliver the chosen outcome? Copying an old test name into a new suite proves nothing unless the new assertion crosses the same observable boundary.
This distinction also keeps provenance honest. In the retained rewrite, repository tests cover shared runtime logic and platform command planning. Physical printing, installation, background lifecycle, signing, and launch on real hosts remain separate evidence. A green local suite does not turn those external checks into completed work.
Equivalent outcomes permit a different architecture
The rewrite did not aim for line-by-line or pixel parity. It introduced a shared core, a graphical shell, and a headless process so unattended printing could run without an open window.
That is an architectural change with an outcome constraint:
graphical refresh ─┐
├── shared polling and print runtime
headless schedule ─┘The ledger should preserve:
- ingestion rules;
- idempotency;
- scheduling decisions;
- print-state transitions;
- settings semantics; and
- operator-visible status.
It should not require the new process graph to resemble the old one.
Presentation can change under the same rule. The retained ledger accepts inline activity details in place of a modal while preserving the underlying properties and enabled/disabled behavior. It also promotes useful runtime health information which had been trapped behind a dead operator path.
Those are not parity failures when the disposition says equivalent or
change and names the target outcome. They would be silent scope changes if
the ledger merely said “activity page: done.”
Preserve quirks only through a decision
Source archaeology uncovers behavior nobody would design today.
One setup guard in the retained application checks whether a credential key exists. A blank stored value therefore satisfies that guard even though later validation may reject the credential. The rewrite initially had to decide whether setup parity meant preserving key existence or requiring a usable value.
There is no mechanical answer. Preserving the rule may be necessary for existing state to migrate without locking users out. Changing it may make the product more coherent. The ledger should show the choice and the consequence:
observed:
key existence unlocks setup
possible dispositions:
preserve for migrated state
migrate blank values, then require non-empty
reject blank state immediately
decision evidence:
stored-state inventory
recovery path
product authorityCHG-03 owns this compatibility decision in depth. The rewrite ledger makes sure the decision exists before a new implementation accidentally chooses through different control flow.
Keep the ledger finite
A rewrite can become permanent archaeology. Every newly noticed class, translation key, or visual difference spawns another checklist item. The old application continues changing while the target never reaches the finish line.
The retained project eventually separated three gates:
- repository quality;
- behavior parity; and
- real-host platform proof.
It also closed the release list: new detail had to map to an existing operator-facing requirement unless the source product genuinely gained a new one. That distinction prevents proof work from expanding merely because a narrow check exposed another implementation detail.
A useful row should name:
owned outcome
source evidence
disposition
target evidence
status
remaining external proofIt should not become a diary of every file touched while implementing the row. The retained parity document is extensive and sometimes records that history inside individual items. That detail helps forensic review, but it also makes the actual release decision harder to scan. Move long investigations into linked evidence and keep the ledger as the index.
A rewrite closes one row at a time
Refactoring keeps the running implementation in place while structure changes around it. A rewrite creates a second implementation and removes that immediate feedback. The behavior ledger supplies the missing connection.
For each slice:
- state the operator or caller outcome;
- reproduce it at the old boundary;
- classify it as preserve, equivalent, change, discard, or unresolved;
- implement the target behavior;
- prove it at the new boundary;
- run any migration needed for state or users;
- retain external proof as open when local automation cannot provide it; and
- remove the old slice only when no unresolved row depends on it.
The synthetic reproduction shows why a feature list is insufficient. A mutated replacement still exposes setup, printing, and reports, so the broad list passes. It mishandles cleanup after a successful print. The behavior ledger rejects that mutation, permits one explicit presentation change, drops one dead convenience, and keeps release blocked on a physical output check which the fixture cannot perform.
A rewrite is not complete when the new application contains the old feature names. It is complete when every behavior with a consumer has a deliberate destination, every deliberate change has authority, every discarded behavior has no required consumer, and every remaining unknown is still visible.