ADR 0028: Project Fuzzy as Opt-In Review State
Accepted architecture decision record: coverage, audit, and review classify fuzzy entries without exposing arbitrary gettext flags or slowing the default catalog projection.
- Status: Accepted
- Date: 2026-07-30
- Partially supersedes: ADR 0023, ADR 0027
- Relates to: ADR 0014, ADR 0017
Context
ADR 0023 removed gettext flags from the public catalog message model. ADR 0027
later preserved those flags internally as opaque round-trip state, while still
prohibiting any semantic interpretation. That combination protected catalog
updates from data loss, but left coverage unable to distinguish a verified
translation from an active #, fuzzy entry.
Gettext defines fuzzy as a review-needed marker. Counting such an entry as
translated overstates completion. The gap also forced Palamedes to parse every
PO target twice: once through the format-neutral catalog API and again through
the low-level PO API to join fuzzy flags back onto message identities. Its
manual coverage loop then risked diverging from Ferrocat's plural completeness
rule.
Restoring every flag to CatalogMessage would solve the visibility problem at
the wrong layer. Most callers do not request reports, arbitrary flags such as
c-format have no ICU meaning, and the public high-throughput projection should
not allocate metadata it immediately discards.
Decision
Add a dedicated review-aware parse projection:
parse_catalog_for_reviewparses PO or FCL directly into a normalized catalog and retains only the identities carrying an exactfuzzyflag.- The ordinary
parse_catalogpath continues to discard translator comments and flags from its public projection. It does not allocate or expose review state. NormalizedParsedCatalogrecords whether review state is available. Coverage, audit completeness/fuzzy checks, and current-state review reject a metadata-discarding projection instead of silently counting an unknown fuzzy entry as translated.- Programmatically constructed
ParsedCatalogvalues can opt into reporting withinto_normalized_view_assuming_no_fuzzy; becauseCatalogMessagehas no flag field, this explicitly declares that the constructed messages are non-fuzzy. CatalogMessageStatus::Fuzzy,CatalogLocaleCoverage::fuzzy(),CatalogAuditChecks::fuzzy_flags, andcatalog.fuzzy_flagare restored.
The shared classifier applies one precedence order to an expected target identity:
- absent is
missing; - obsolete is
obsolete; - an empty singular or any empty required plural form is
empty; - a non-empty active entry with the review marker is
fuzzy; - otherwise it is
translated.
An active target identity outside the active source set remains extra,
regardless of its flag. These states are mutually exclusive. Only translated
contributes to completion.
This is a narrow exception to ADR 0027's "flags carry no semantics" rule.
Ferrocat still does not infer fuzzy matches, move translations by similarity,
or expose arbitrary flags. It recognizes only the exact fuzzy marker when a
caller explicitly requests report state.
Consequences
Positive:
- coverage no longer treats review-needed translations as complete
- PO
#, fuzzyand FCLf=fuzzyhave identical report behavior - coverage, audit, and review use one classification rule, including partial gettext plurals
- Palamedes can remove its second PO parse, manual identity join, and local coverage classifier
- ordinary parse callers keep the allocation-light projection introduced after ADR 0027
Negative:
- report callers must use
parse_catalog_for_review; passing a normalized view derived from ordinaryparse_catalognow returns an argument error - coverage details can carry the
fuzzystatus, and the per-localefuzzy()accessor derives its count without adding a field to the externally constructible report struct - Ferrocat now interprets one member of an otherwise opaque flag list, so future review markers need another explicit architecture decision rather than being inferred from arbitrary metadata
Alternatives Considered
Expose All Flags on CatalogMessage
Rejected because it would reverse the useful public-model boundary from ADR 0023, increase the default projection cost, and encourage callers to build new semantics on unstructured gettext metadata.
Keep Counting Fuzzy as Translated
Rejected because it contradicts the review-needed meaning of fuzzy, the
original coverage contract, and the completion rule downstream hosts need.
Add a File-Only Coverage API
Rejected because audit and review need the same state, callers may already hold normalized catalogs, and a separate content API would duplicate locale-set validation and report classification.
Silently Treat Missing Review State as Non-Fuzzy
Rejected because a fast parse projection cannot distinguish "no fuzzy flags" from "flags were discarded." A clear argument error prevents an incorrect completion result.