Humanizr/Humanizer · error · InvalidOperationException

Release duration-case locales must use the explicit inventor

Error message

Release duration-case locales must use the explicit inventory, reason, sources, and provenance contract: {string.Join(", ", legacyCatalogs)}.

What it means

Thrown by DurationCaseCoverageInput.Create when one or more release locales have a DurationCaseCatalog with empty Sources. Release locales must include the explicit provenance contract — inventory, reason, sources, and provenance — so that duration-case data is traceable. The error message lists every offending locale code.

Source

Thrown at src/Humanizer.SourceGenerators/Common/DurationCaseModels.cs:168

                            DurationCaseClassification.NotApplicable => "not-applicable",
                            _ => "unsupported"
                        }
                        : locale.VariantOf is not null
                            ? "same-language-inherited"
                            : throw new InvalidOperationException(
                                $"Locale '{locale.LocaleCode}' has no authored or same-language inherited durationCases classification."),
                    locale.VariantOf));
            }

            var immutableCatalogs = catalogs.MoveToImmutable();
            var legacyCatalogs = immutableCatalogs
                .Where(static catalog => catalog.Sources.IsEmpty)
                .Select(static catalog => catalog.LocaleCode)
                .OrderBy(static locale => locale, StringComparer.Ordinal)
                .ToArray();
            if (legacyCatalogs.Length > 0)
            {
                throw new InvalidOperationException(
                    $"Release duration-case locales must use the explicit inventory, reason, sources, and provenance contract: {string.Join(", ", legacyCatalogs)}.");
            }

            var unsupportedPathBuilder = ImmutableArray.CreateBuilder<string>();
            foreach (var catalog in immutableCatalogs)
            {
                unsupportedPathBuilder.AddRange(GetUnsupportedPaths(catalog));
            }

            var unsupportedPaths = unsupportedPathBuilder
                .OrderBy(static path => path, StringComparer.Ordinal)
                .ToArray();
            if (unsupportedPaths.Length > 0)
            {
                throw new InvalidOperationException(
                    $"Release duration-case completeness requires zero unsupported dispositions; found {unsupportedPaths.Length}: {string.Join(", ", unsupportedPaths)}.");
            }

View on GitHub (pinned to ffc2b77c0f)

Solutions

  1. Convert the locale's durationCases block to the explicit format with inventory, sources, reason, and provenance keys.
  2. For not-applicable classifications, provide a reason and provenance block with at least one source.
  3. Run the locale through ParseForTests locally to verify it routes to ParseExplicit (not ParseLegacyForTests) by including inventory or sources keys.

Example fix

# before (legacy format, no sources)
durationCases:
  classification: distinct
  cases:
    genitive: { ... }
# after (explicit format with provenance)
durationCases:
  classification: distinct
  citationCase: nominative
  inventory: [nominative, genitive]
  sources:
    - citation: 'Reference Grammar'
      locator: 'p.123'
  reason: 'Russian inflects duration units.'
  provenance:
    revision: '1'
    locator: 'src/...'
    credit: 'Author'
  cases:
    genitive: { ... }
Defensive patterns

Strategy: validation

Validate before calling

// Verify all release catalogs have non-empty sources before the batch check
static bool AllCatalogsHaveSources(ImmutableArray<DurationCaseCatalog> catalogs) =>
    catalogs.All(c => !c.Sources.IsEmpty);

Prevention

When it happens

Trigger: After parsing all locale catalogs, Create filters for catalogs where catalog.Sources.IsEmpty, collects their LocaleCodes, and throws if any exist. This fires when a locale's durationCases block was parsed via the legacy path (which produces empty sources) instead of the explicit path.

Common situations: A locale's durationCases YAML uses the legacy format (classification + cases without inventory/sources/provenance) in a release build. The legacy format is only valid for tests, not for checked-in release locales. Also occurs when a contributor adds a new locale and copies a test fixture's durationCases block.

Related errors


AI-assisted analysis of Humanizr/Humanizer@ffc2b77c0f (2026-08-13). Data as JSON: /api/errors/3b774b5d230b96ff. Report an issue: GitHub.