Stop a single-character key being containment-matched onto unrelated properties - #129
Merged
Merged
Conversation
…related properties
A key like meta_x_field normalizes to the single character x, and the
containment tests in NameStandardizer and PropertyMerger are bidirectional,
so x is admitted against any candidate that happens to use that letter:
"next" contains "x". The key is then renamed, or merged, onto an unrelated
property.
Both classes already guarded the empty normalized form, with comments
describing exactly this failure. A length of 1 fell through that guard.
Measured on main: StandardizePropertyNames turned meta_x_field into next,
page_a_value into area, meta_e_field into editor, custom_s_data into slug
and page_t_data into toc. MergeSimilarProperties was worse than
reattribution -- {meta_x_field, next, text} came back as
{meta_x_field, next}, dropping text entirely, because containment matched
mutually in both directions.
The rule now lives once, as PropertyNameNormalizer.MayMatchByContainment,
and applies to the three containment sites: the standardizer's partial-match
loop, the merger's partial-match loop, and CalculateWordMatchScore's
containment branch, which could otherwise re-admit the same key through the
word-score path.
Containment only. Exact matching on a one-character normalized form is
untouched, and the floor is two characters rather than more, so the shortest
real names anywhere in StandardOrder.PropertyNames or PropertyMappings --
by, tag, url -- still match as before.
Fixes #128
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_013qMsnuVCPrfVynzgpbKQTD
|
This was referenced Sep 28, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.



Fixes #128
Filed and fixed per the recommendation left on #113: "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 ismain's behaviour today and will outlive this thread."The defect
PropertyNameNormalizer.Normalizestrips one decorative prefix and one decorative suffix, someta_x_fieldnormalizes to the single characterx. The containment tests in both matching classes are bidirectional, so a one-character fragment is contained in any candidate that happens to use that letter —"next"contains"x"— and the key is admitted on an incidental letter rather than a shared word.Both classes already guarded the degenerate
Length == 0case, each with a comment describing exactly this failure mode. A length of 1 fell through that guard.Measured at
main(6ed61c5), .NET SDK 10.0.401, LinuxNameStandardizer.StandardizePropertyNamessilently renames the key:meta_x_fieldxnextmeta_x_fieldpage_a_valueaareapage_a_valuemeta_e_fieldeeditormeta_e_fieldcustom_s_datasslugcustom_s_datapage_x_textxnextpage_x_textpage_t_datattocpage_t_datameta_z_infozmeta_z_infometa_z_infometa_z_infowas already preserved, and only because no standard property happens to contain the letter "z" — which is the clearest statement of how incidental the old match was.PropertyMerger.MergeSimilarPropertiesunderFrontmatterMergeStrategy.Maximumis worse than reattribution — it drops a property outright:The cause is the same bidirectionality:
meta_x_fieldresolved tonext(as"next"contains"x") whilenextresolved back tometa_x_fieldat the same time, and the third key that also contained the letter was lost in the crossfire.This is the failure mode #113 and #124 set out 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 rather than length 0.
The change
The rule lives in one place, as
PropertyNameNormalizer.MayMatchByContainment, following the consolidation #124 established rather than adding a third copy of the same reasoning. It is applied at the three containment sites:NameStandardizer.FindStandardPropertyMatch— the partial-match loopPropertyMerger.FindBasicCanonicalName— the partial-match loopPropertyMerger.CalculateWordMatchScore— the containment branch scoring+1The third one matters and is easy to miss: without it,
FindSemanticCanonicalName's word-score path re-admits the same key after the other two are guarded, since a one-character key word scores+1against any word containing it and.Where(match => match.Score > 0)then accepts it.Containment only. Exact matching on a one-character normalized form is untouched, so the fix is not "ignore short keys".
The floor is two, not more. The shortest names anywhere in
StandardOrder.PropertyNamesorPropertyMappingsareby,tagandurl, and nothing in either normalizes to fewer than two characters — so no legitimate name was being matched by containment on a single character, and none stops matching now.Tests
Four added in
SingleCharacterKeyTests, alongside the existingDecorationOnlyKeyTestsit parallels.StandardizePropertyNames_PreservesKeysNormalizingToOneCharacterMergeSimilarProperties_DoesNotMergeOrDropOnASingleCharacterFragmentMayMatchByContainment_RejectsAOneCharacterNameOnEitherSideStandardizePropertyNames_StillMatchesATwoCharacterFragmentagis intags,stage,image)Proved failing without the fix. Reverting only the two call-site files and keeping the new helper — so it still compiles — the two behavioural tests fail and the other two pass. That is the intended split: the rule test exercises the helper, and the floor test is a no-regression test that should pass both before and after.
Verification
dotnet build Frontmatter.sln -c Release— succeeded, 0 warnings (this repo runs analyzers as errors)dotnet test Frontmatter.sln -c Release— 145 total, 145 passed, 0 failedNot in this change
The
ktsu.FuzzySearchadoption inPropertyMergerthat #113 still carries is untouched — that one waits on the ranking decision recorded there, and this is an independent bug fix.The adjacent looseness one step further out is also left alone: containment on a two-character fragment is admitted, so
meta_ag_fieldstill lands on a standard property viatags/stage/image. That ismain's behaviour, it is pinned here as a no-regression test rather than changed, and narrowing it further is a behaviour judgement that wants its own issue — the same reasoning by which the single-character case was split out of #113 into #128.🤖 Generated with Claude Code
https://claude.ai/code/session_013qMsnuVCPrfVynzgpbKQTD
Generated by Claude Code