Skip to content

feat(l10n): localize PLC I/O errors by type (#120) - #165

Merged
mrcsin merged 1 commit into
masterfrom
plc-errors-typed
Jul 31, 2026
Merged

mrcsin merged 1 commit into
masterfrom
plc-errors-typed

Conversation

@mrcsin

@mrcsin mrcsin commented Jul 31, 2026

Copy link
Copy Markdown
Member

Problem

Slice 6a typed only the connection-lost fault. The rest of the PLC producer surface stayed English:

  • NotConnectedError / ProtocolVersionMismatchError were internal — invisible to ReasonLocalizer.
  • The recipe-active, write-verification and not-committed faults were free-text new Error(...) / Result.Fail(string).
  • PlcSyncExecutor laundered typed inners — it re-wrapped a typed inner's .Message into a fresh untyped Error before emitting the fault, discarding the type so the localizing seam could not reach it.

On the diagnostics side, PlcTransactionExecutor's five catch { return Result.Fail(ex.Message); } sites kept only the message string — the exception type and stack were gone before any log sink, and nothing walks Result.Reasons. This is the second half of #120 ("exception type/stack lost at Result.Fail(ex.Message)").

What changed

Type the PLC producer surface, un-launder the fault forwarding, and land the exception in a log.

Six typed faults localize by type (2 made public, 4 new), each with a ReasonLocalizer arm, en/ru resx, Designer accessor, and a coverage sample:

Class en note
NotConnectedError Not connected to PLC made public + fieldless
ProtocolVersionMismatchError PLC protocol version {0} does not match expected {1} made public + Expected/Actual props
RecipeActiveError Recipe is being executed on PLC new leaf
WriteVerificationFailedError Recipe write verification failed after {0} attempts new leaf (Attempts)
RecipeNotCommittedError Recipe not committed on PLC new leaf
PlcCommandFailedError PLC command failed new Rule-B envelope (CausedBy(ex))

Un-laundering — PlcSyncExecutor now forwards the typed inner directly (reportFault(Errors[0]), return activeResult.ToResult()) so it localizes through the 6a ReportFailure(IError) seam; the recipe-active and exception-stringify sites are typed.

Diagnostics (#120, second half) — the five PlcTransactionExecutor catches return PlcCommandFailedError().CausedBy(ex) and log the exception once (writes at Error, reads at Warning). The PlcExecutionMonitor poll-error line is guarded to skip PlcCommandFailedError (already logged at the catch) while still surfacing the decode-Result.Fail path — exactly-once logging, no silenced failure.

Behavior-preserving for English except PlcCommandFailedError, which replaces the raw ex.Message headline with a localized "PLC command failed" and keeps the exception on CausedBy plus the log. HasError<T> type assertions survive the make-public.

Deferred to 6c (documented in error-reporting.md): the #120 cancellation threading (part 1), the short protocol-version-read diagnostic, the enable catch, and the codec decode-Result.Fail forwarding — all left English by design.

Verification

  • dotnet build SemiStep.slnx — 0 warnings.
  • dotnet test SemiStep/SemiStep.Tests/SemiStep.Tests.csproj — 1661 passed, 0 failed.
  • ReasonLocalizerTests pins the ru render of all six types (e.g. ProtocolVersionMismatchError(2,1) → "Версия протокола ПЛК 1 не соответствует ожидаемой 2") + en Localize == .Message; CoreErrorLocalizationCoverageTests auto-enrolls the public types; ResourceSyncTests enforces en/ru/Designer key + placeholder parity.
  • ResultReportingExtensionsTests drives NotConnectedError/RecipeActiveError/PlcCommandFailedError through the seam and asserts Russian in the operation slot. PlcSyncCoordinatorTests pins the RecipeActiveError production path and PlcCommandFailedError carrying the exception on .Reasons.
  • Review chain: comprehensive (5 agents) → critical re-check → smells → comment audit → critical, all clean after fixes. The reviews caught a real diagnostics hole (deleting the monitor poll-error line silenced the decode-Result.Fail path — restored guarded) and a symmetric write-path double-log (guarded).
  • Manual test (operator): passed.

Addresses #120 (second half; part 1 — cancellation threading — deferred to 6c).

Per-task commits before collapse
69a01fa docs(plan): record exec branch and verify steps for 6b
f2c6fd5 style: separate ProtocolVersionMismatchError properties with blank line
3efbf7a fix: skip redundant write-failure log for typed command failures
7d577a3 fix: address review findings (monitor log, RecipeActive test, doc counts)
4036bb7 test(l10n): cover PLC error localization end-to-end
10f9e9c feat(l10n): type PLC command failures and log the exception
1c802ea feat(l10n): type PLC producer errors and un-launder faults

🤖 Generated with Claude Code

The PLC producer surface stayed English after slice 6a typed only the
connection-lost fault. NotConnectedError and ProtocolVersionMismatchError
were internal (invisible to the localizer); the recipe-active,
write-verification and not-committed faults were free-text
new Error(...) / Result.Fail(string); and PlcSyncExecutor laundered typed
inners by re-wrapping their .Message into a fresh untyped Error before
emitting the fault, discarding the type so the localizing seam could not
reach it. On the diagnostics side, PlcTransactionExecutor's five
catch { return Result.Fail(ex.Message); } sites kept only the message
string -- the exception type and stack were gone before any log sink
(issue #120, second half).

Type the PLC producer surface, un-launder the fault forwarding, and land
the exception in a log:

- Make NotConnectedError (fieldless) and ProtocolVersionMismatchError
  (with Expected/Actual) public; add the leaves RecipeActiveError,
  WriteVerificationFailedError(attempts), RecipeNotCommittedError, and the
  Rule-B envelope PlcCommandFailedError -- each with a ReasonLocalizer arm,
  en/ru resx, and a coverage sample. Six typed faults now localize.
- Un-launder PlcSyncExecutor: forward the typed inner directly
  (reportFault(Errors[0]), return activeResult.ToResult()) so it localizes
  through the 6a seam; type the recipe-active and exception-stringify sites.
- Diagnostics: the five PlcTransactionExecutor catches now return
  PlcCommandFailedError().CausedBy(ex) and log the exception once -- writes
  at Error, reads at Warning. The redundant PlcExecutionMonitor poll-error
  line is guarded to skip PlcCommandFailedError (already logged at the
  catch) while still surfacing the decode-Fail path, keeping exactly-once
  logging with no silenced failure.

English is behavior-preserving except PlcCommandFailedError, which replaces
the raw ex.Message headline with a localized "PLC command failed" and keeps
the exception on CausedBy plus the log. Cancellation threading (issue #120,
first half) and the two remaining English producers (short protocol-version
read; enable catch) are deferred to the next slice.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@mrcsin
mrcsin marked this pull request as ready for review July 31, 2026 13:51
@mrcsin
mrcsin merged commit fd16bd1 into master Jul 31, 2026
1 check passed
@mrcsin
mrcsin deleted the plc-errors-typed branch July 31, 2026 13:53
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