Filed per the recommendation on #113, which noted this and deliberately left it alone:
The single-character looseness noted and deliberately left alone (meta_x_field → x → next) is the same class of bug as the empty-string one, one step less severe. Worth its own issue rather than leaving it in a comment — it is main's behaviour today and will outlive this thread.
What's wrong
PropertyNameNormalizer.Normalize strips one decorative prefix and one decorative suffix, so a key like meta_x_field normalizes to the single character x. Both matching call sites then test containment in both directions:
NameStandardizer.FindStandardPropertyMatch — !normalizedKey.Contains(normalizedStandard) && !normalizedStandard.Contains(normalizedKey)
PropertyMerger.FindBasicCanonicalName — the same pair, against the other keys present
PropertyMerger.CalculateWordMatchScore — word1.Contains(word2) || word2.Contains(word1), scoring +1
A one-character fragment is contained in any candidate that happens to use that letter, so it is admitted on an incidental letter rather than a shared word. Both classes already guard the degenerate Length == 0 case, with a comment explaining exactly this failure mode; a length of 1 falls through that guard.
Measured on main (6ed61c5), .NET SDK 10.0.401, Linux
NameStandardizer.StandardizePropertyNames silently renames the key:
| key |
normalizes to |
result |
meta_x_field |
x |
next |
page_a_value |
a |
area |
meta_e_field |
e |
editor |
custom_s_data |
s |
slug |
page_x_text |
x |
next |
page_t_data |
t |
toc |
meta_z_info |
z |
preserved — no standard property contains "z" |
PropertyMerger.MergeSimilarProperties with FrontmatterMergeStrategy.Maximum goes further and drops a property entirely:
IN [meta_x_field=A, next=B, text=C]
OUT [meta_x_field=A, next=B] // text=C is gone
The merger also cross-references mutually, because containment is bidirectional: meta_x_field → next (as "next" contains "x") while next → meta_x_field at the same time.
Why it matters
This is the failure mode #113 and #124 were explicitly trying to close — a value silently attributed to an unrelated property, in a library whose job is round-tripping frontmatter — reached by a fragment of length 1 instead of length 0. The merger case is worse than reattribution: the value is not moved, it is lost.
Suggested fix
Extend the existing degenerate-fragment rule from "empty" to "shorter than two characters", for the containment paths only. Exact matching should keep working, so a genuine one-character key still merges with another key that normalizes to the same thing.
The floor is safe at 2: the shortest names anywhere in StandardOrder.PropertyNames or PropertyMappings are by, tag and url, and no standard property or mapping name normalizes to fewer than two characters — so nothing legitimate is matched by containment on a single character today.
Acceptance criteria
- A key whose normalized form is a single character is preserved by
StandardizePropertyNames rather than renamed to an unrelated standard property.
MergeSimilarProperties neither merges such a key into an unrelated one nor drops any other property because of it.
- Exact matching on a one-character normalized form still works.
Filed per the recommendation on #113, which noted this and deliberately left it alone:
What's wrong
PropertyNameNormalizer.Normalizestrips one decorative prefix and one decorative suffix, so a key likemeta_x_fieldnormalizes to the single characterx. Both matching call sites then test containment in both directions:NameStandardizer.FindStandardPropertyMatch—!normalizedKey.Contains(normalizedStandard) && !normalizedStandard.Contains(normalizedKey)PropertyMerger.FindBasicCanonicalName— the same pair, against the other keys presentPropertyMerger.CalculateWordMatchScore—word1.Contains(word2) || word2.Contains(word1), scoring+1A one-character fragment is contained in any candidate that happens to use that letter, so it is admitted on an incidental letter rather than a shared word. Both classes already guard the degenerate
Length == 0case, with a comment explaining exactly this failure mode; a length of 1 falls through that guard.Measured on
main(6ed61c5), .NET SDK 10.0.401, LinuxNameStandardizer.StandardizePropertyNamessilently renames the key:meta_x_fieldxnextpage_a_valueaareameta_e_fieldeeditorcustom_s_datasslugpage_x_textxnextpage_t_datattocmeta_z_infozPropertyMerger.MergeSimilarPropertieswithFrontmatterMergeStrategy.Maximumgoes further and drops a property entirely:The merger also cross-references mutually, because containment is bidirectional:
meta_x_field→next(as"next"contains"x") whilenext→meta_x_fieldat the same time.Why it matters
This is the failure mode #113 and #124 were explicitly trying to close — a value silently attributed to an unrelated property, in a library whose job is round-tripping frontmatter — reached by a fragment of length 1 instead of length 0. The merger case is worse than reattribution: the value is not moved, it is lost.
Suggested fix
Extend the existing degenerate-fragment rule from "empty" to "shorter than two characters", for the containment paths only. Exact matching should keep working, so a genuine one-character key still merges with another key that normalizes to the same thing.
The floor is safe at 2: the shortest names anywhere in
StandardOrder.PropertyNamesorPropertyMappingsareby,tagandurl, and no standard property or mapping name normalizes to fewer than two characters — so nothing legitimate is matched by containment on a single character today.Acceptance criteria
StandardizePropertyNamesrather than renamed to an unrelated standard property.MergeSimilarPropertiesneither merges such a key into an unrelated one nor drops any other property because of it.