diff --git a/docs/SAFETY.md b/docs/SAFETY.md
index 4cdf0a9..b93ec2c 100644
--- a/docs/SAFETY.md
+++ b/docs/SAFETY.md
@@ -22,10 +22,16 @@ For each file EC is allowed to convert:
8. Atomically installs the verified temporary output where the platform supports it.
9. Marks the recovery record `Completed` after installation succeeds.
-If a required step fails, EC does not install converted output. Because the
-backup is created before decoding and encoding, a later conversion failure can
-leave a valid `.bak` beside an unchanged source; metadata is not written until
-both the output and backup have verified.
+If a step before installation (step 8) fails, EC does not install converted
+output and the source is unchanged. Because the backup is created before decoding
+and encoding, a later conversion failure can leave a valid `.bak` beside an
+unchanged source; metadata is not written until both the output and backup have
+verified.
+
+If step 9 fails, installation has already happened: the verified converted output
+is in place, EC reports a recovery-record error with the replacement recorded as
+committed, and the `Prepared` record keeps the hashes needed to inspect the result.
+Do not treat that file as unchanged.
## Source-encoding policy
@@ -53,6 +59,9 @@ This is intentionally conservative: most ordinary BOM-less UTF-16 files are
expected to be refused. It costs a second full read, which is deliberate because
rewriting a file must not rest on a sample-based byte-order guess.
+Choose `-From utf-16le` or `-From utf-16be` if you know the source order. That
+chooses the source interpretation only; it does not bypass any other safeguard.
+
### BOM-less UTF-32
EC never converts it automatically. It reports `Refused` with reason code
@@ -67,8 +76,10 @@ pass it through. Separately, genuine UTF-32 can be valid under both orders.
The cost is that ordinary BOM-less UTF-32 is refused too. Nothing in the bytes
separates it from the UTF-16 file above. Add a BOM, or name the source.
-Choose `-From utf-16le` or `-From utf-16be` if you know the source order. That
+Choose `-From utf-32le` or `-From utf-32be` if you know the source order. That
chooses the source interpretation only; it does not bypass any other safeguard.
+Naming a UTF-16 order for a UTF-32 file would read its bytes as UTF-16, so use
+only the UTF-32 names.
## Plans, backups, and recovery metadata
diff --git a/sources/EncodingChecker/ConversionMetadata.cs b/sources/EncodingChecker/ConversionMetadata.cs
index 868d375..c52dcdc 100644
--- a/sources/EncodingChecker/ConversionMetadata.cs
+++ b/sources/EncodingChecker/ConversionMetadata.cs
@@ -103,13 +103,12 @@ internal sealed record ConversionMetadata
public required bool SourceHasBom { get; init; }
///
- /// What detection concluded, or when detection did not run.
+ /// What detection concluded, or when it identified no codec.
///
///
- /// This is null only when detection did not run, such as a CLI -From
- /// conversion. A GUI user can explicitly choose a source after a scan; in that
- /// case the sidecar preserves both the detector's earlier conclusion and the
- /// codec the conversion actually used.
+ /// Detection runs even when the source is chosen explicitly, such as with a CLI
+ /// -From or a GUI choice after a scan, so the sidecar preserves the detector's
+ /// conclusion beside the codec the conversion actually used.
///
[JsonPropertyOrder(15)]
public int? DetectedEncodingId { get; init; }
diff --git a/sources/EncodingChecker/ConversionPolicy.cs b/sources/EncodingChecker/ConversionPolicy.cs
index 152772b..e688071 100644
--- a/sources/EncodingChecker/ConversionPolicy.cs
+++ b/sources/EncodingChecker/ConversionPolicy.cs
@@ -60,7 +60,8 @@ internal static PlannedAction Decide(
return PlannedAction.Skip;
}
- // An unchanged file is not read or rewritten, so no source choice is needed.
+ // An unchanged file is not rewritten, so no source choice is needed. It is still
+ // read and validated in full after this decision.
//
// Codec identity is the code page, not the label. "utf-16", "unicode", "ucs-2"
// and "utf-16le" all name code page 1200, so comparing the strings reported a