{"record":{"id":"3b774b5d230b96ff","repo":"Humanizr/Humanizer","slug":"release-duration-case-locales-must-use-the-explici","errorCode":null,"errorMessage":"Release duration-case locales must use the explicit inventory, reason, sources, and provenance contract: {string.Join(\", \", legacyCatalogs)}.","messagePattern":"Release duration-case locales must use the explicit inventory, reason, sources, and provenance contract: (.+?)\\.","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"src/Humanizer.SourceGenerators/Common/DurationCaseModels.cs","lineNumber":168,"sourceCode":"                            DurationCaseClassification.NotApplicable => \"not-applicable\",\n                            _ => \"unsupported\"\n                        }\n                        : locale.VariantOf is not null\n                            ? \"same-language-inherited\"\n                            : throw new InvalidOperationException(\n                                $\"Locale '{locale.LocaleCode}' has no authored or same-language inherited durationCases classification.\"),\n                    locale.VariantOf));\n            }\n\n            var immutableCatalogs = catalogs.MoveToImmutable();\n            var legacyCatalogs = immutableCatalogs\n                .Where(static catalog => catalog.Sources.IsEmpty)\n                .Select(static catalog => catalog.LocaleCode)\n                .OrderBy(static locale => locale, StringComparer.Ordinal)\n                .ToArray();\n            if (legacyCatalogs.Length > 0)\n            {\n                throw new InvalidOperationException(\n                    $\"Release duration-case locales must use the explicit inventory, reason, sources, and provenance contract: {string.Join(\", \", legacyCatalogs)}.\");\n            }\n\n            var unsupportedPathBuilder = ImmutableArray.CreateBuilder<string>();\n            foreach (var catalog in immutableCatalogs)\n            {\n                unsupportedPathBuilder.AddRange(GetUnsupportedPaths(catalog));\n            }\n\n            var unsupportedPaths = unsupportedPathBuilder\n                .OrderBy(static path => path, StringComparer.Ordinal)\n                .ToArray();\n            if (unsupportedPaths.Length > 0)\n            {\n                throw new InvalidOperationException(\n                    $\"Release duration-case completeness requires zero unsupported dispositions; found {unsupportedPaths.Length}: {string.Join(\", \", unsupportedPaths)}.\");\n            }\n","sourceCodeStart":150,"sourceCodeEnd":186,"githubUrl":"https://github.com/Humanizr/Humanizer/blob/ffc2b77c0f30d2fb176875841424379319d0ae9b/src/Humanizer.SourceGenerators/Common/DurationCaseModels.cs#L150-L186","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","solutions":["Convert the locale's durationCases block to the explicit format with inventory, sources, reason, and provenance keys.","For not-applicable classifications, provide a reason and provenance block with at least one source.","Run the locale through ParseForTests locally to verify it routes to ParseExplicit (not ParseLegacyForTests) by including inventory or sources keys."],"exampleFix":"# before (legacy format, no sources)\ndurationCases:\n  classification: distinct\n  cases:\n    genitive: { ... }\n# after (explicit format with provenance)\ndurationCases:\n  classification: distinct\n  citationCase: nominative\n  inventory: [nominative, genitive]\n  sources:\n    - citation: 'Reference Grammar'\n      locator: 'p.123'\n  reason: 'Russian inflects duration units.'\n  provenance:\n    revision: '1'\n    locator: 'src/...'\n    credit: 'Author'\n  cases:\n    genitive: { ... }","handlingStrategy":"validation","validationCode":"// Verify all release catalogs have non-empty sources before the batch check\nstatic bool AllCatalogsHaveSources(ImmutableArray<DurationCaseCatalog> catalogs) =>\n    catalogs.All(c => !c.Sources.IsEmpty);","typeGuard":null,"tryCatchPattern":null,"preventionTips":["Release locales must use the explicit durationCases format with sources/provenance.","Never ship a locale parsed via the legacy path — it produces empty sources.","Run DurationCaseCoverageInput.Create in CI to catch missing provenance before release."],"tags":["source-generator","locale","duration-cases","release-gate","provenance"],"backgroundTag":null,"analyzedSha":"ffc2b77c0f30d2fb176875841424379319d0ae9b","analyzedAt":"2026-08-13T21:42:34.584Z","schemaVersion":2},"datasetVersion":"2026-08-14T00:17:13.853Z"}