Skip to content

Record what a conversion actually did - #49

Merged
amrali-eg merged 2 commits into
masterfrom
feat/conversion-journal
Aug 27, 2026
Merged

Record what a conversion actually did#49
amrali-eg merged 2 commits into
masterfrom
feat/conversion-journal

Conversation

@amrali-eg

Copy link
Copy Markdown
Owner

Phase F — the conversion journal

The per-file .ecmeta.json sidecar answers "how do I put this one back?" and is written only where a backup exists. Nothing answered "why was this file not converted?" once the console output scrolled away — which is the question people actually ask, and the one the ambiguity refusal creates more of.

-Journal <path> records the run whole. In the GUI it goes through Export report as a second format alongside the CSV.

{
  "RelativePath": "notes.txt",
  "Sha256Before": "f19e0e0c…",
  "Sha256After": null,
  "DetectionMode": "Detected",
  "DetectedEncoding": "iso-8859-1",
  "SourceEncoding": "iso-8859-1",
  "Ambiguity": "TextChanging",
  "AmbiguityReason": "MultipleCodecsDifferentText",
  "DetectionCandidates": ["cp866", "ibm852", "ibm855", ""],
  "PlannedAction": "Refuse",
  "Status": "Refused",
  "Reason": "The encoding could not be determined uniquely…"
}

Every field you listed is there, plus Semantics/SemanticsVersion, AppliedPlan, Surface, and a Summary whose counts sum to the run.

It records the decision executed, not the detector's output

SourceEncoding is what the conversion actually read the file as; DetectedEncoding keeps the detector's answer beside it. Those differ whenever somebody named the source encoding, and that difference is the whole of who was responsible for the reading.

Getting this right needed a new field, and a smoke test caught it. A completed conversion re-labels its entry to the target so a second pass reads the new bytes correctly — so by journal time the effective label describes what the file now is. The first journal I built reported a Shift_JIS file as having been read as utf-8. The label is now captured when it is true rather than derived afterwards.

It does not claim more than happened

Sha256After is present only where a file was actually rewritten, so the record can be checked against the disk rather than taken on trust.

A second honesty bug, also caught before merge: a -WhatIf run reports its rows as "would be converted", and the journal was copying that through as Status: Converted for a directory nothing had touched. The hashes would have given it away — before and after would match — but a record should not need to be caught out to be read correctly. A preview journal is now marked "Preview": true and records those files as NotAttempted, i.e. decided but deliberately not carried out.

Cost

One extra read per file, so it is opt-in and rejected with -DetectOnly/-Validate (use -Report for those). The plan-bound paths already carry the approved hash and pay nothing.

10 new tests, 432 passing

Believed/decided/written recorded together; the read-as encoding rather than the became encoding; refusals with their competing encodings and hashes proving the file is untouched; explicit vs detected distinguished; every file accounted for; semantics recorded; -Apply naming the plan it carried out; a failed conversion recorded as Failed, not Refused — both leave the file alone, and the difference is the whole of what a reader needs.


Release checklist

RELEASE-CHECKLIST.md carries the GUI smoke-test matrix — your ten steps, plus the .bak-is-a-directory case and a journal-export check — and the record template.

The evidence for every destructive case is the source file's SHA-256 before and after, not the status message.

It also says why the test stays manual: automating it would mean weakening the architecture to make Windows Forms drivable, which trades a real safety property for a test. And why it is not optional: "genuinely just UI" was the last description of this codebase that turned out to be wrong.

🤖 Generated with Claude Code

The per-file sidecar answers "how do I put this one back?" and is written only
where a backup exists. Nothing answered "why was this file not converted?" once
the console output had scrolled away - which is the question people actually ask,
and the one the ambiguity refusal creates more of.

-Journal writes the run whole: every file EC looked at, what its encoding was
detected or declared to be, whether the bytes identified it, which encodings
competed, what EC decided, what it did, and the file's SHA-256 before and after.
Refused and skipped files are in it, because they are the interesting ones. In
the GUI it goes through Export report, as a second format alongside the CSV.

Three things it does on purpose.

It records the encoding the conversion read, not the detector's raw output, and
keeps the detector's answer beside it. Those differ whenever somebody named the
source encoding, and that difference is the whole of who was responsible for the
reading. Getting this right needed a new field: a completed conversion re-labels
its entry to the target so a second pass reads the new bytes correctly, so by
journal time the effective label describes what the file now is. The first
journal built reported a Shift_JIS file as having been read as UTF-8. The label
is now captured when it is true rather than derived afterwards.

It does not claim more than happened. Sha256After is present only where a file
was actually rewritten, so the record can be checked against the disk rather than
taken on trust. A -WhatIf run reports its rows as "would be converted"; a journal
of one is marked a preview and records those files as decided, not converted. The
hashes would have given that away - before and after would match - but a record
should not need to be caught out to be read correctly.

It costs an extra read per file, so it is opt-in. The plan-bound paths already
carry the approved hash and pay nothing.

Also adds the release checklist, with the GUI smoke-test matrix and a record
template. The evidence for every destructive case is the source file's SHA-256
before and after, not the status message. That test stays manual because
automating it would mean weakening the architecture to make Windows Forms
drivable - but it is not optional, because "genuinely just UI" was the last
description of this codebase that turned out to be wrong.

432 passing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@amrali-eg
amrali-eg force-pushed the feat/conversion-journal branch from 573f50c to f53d60a Compare August 27, 2026 03:51
The manual matrix needs an instrument, and the first one was wrong in a way worth
writing down. It used a single folder and one final check for the whole sequence.
That cannot work: the stale-plan case stops the entire run, so every "must have
converted" expectation after it is unreachable by construction - and the state it
leaves is byte-identical to "the tester cancelled everything", so the result
cannot say which protection fired. Its first real run reported FAIL, and only
reading the bytes by hand showed the product had been correct throughout.

So the harness is four independent phases, each its own folder, click sequence
and check, each proving one property. Every phase verifier was reproduced through
the CLI before being handed over, and each was also checked to fail when it
should - a verifier that cannot fail is the same defect as a test that silently
asserts nothing. Phase C refuses to pass at all when the edit that makes the plan
stale was never made, which is the failure mode a manual matrix is most prone to.

Records the 2026-08-27 run on a201a08: all four phases pass.

Two notes kept with it because they qualify what the phases establish. Phase B's
French sample decodes identically under windows-1252 and iso-8859-1, so its text
assertion cannot show which codec was used; what settles it is the recovery
record's DetectedCodePage of 1252. And TextEquivalent is nearly unreachable -
eight ASCII shapes all classify as StructurallyDetermined, and only a one-byte
file reaches it - so the corpus has to be contrived to exercise that class at all.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@amrali-eg
amrali-eg merged commit 3072682 into master Aug 27, 2026
1 check passed
@amrali-eg
amrali-eg deleted the feat/conversion-journal branch August 27, 2026 03:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant