{"record":{"id":"706ae770cfa3f049","repo":"louthy/language-ext","slug":"diff-broken-vectorclock-orda-numb-a-b","errorCode":null,"errorMessage":"diff broken","messagePattern":"diff broken","errorType":"exception","errorClass":"InvalidOperationException","httpStatus":null,"severity":"error","filePath":"LanguageExt.Core/Concurrency/VectorClock/VectorClock.OrdA.NumB.A.B.cs","lineNumber":331,"sourceCode":"        \n    /// <summary>\n    /// If vc2 causes vc1, compute the smallest vc3\n    /// </summary>\n    /// <remarks>Note that the /first/ parameter is the newer vector clock.</remarks>\n    public static Option<VectorClock<OrdA, NumB, A, B>> diff(VectorClock<OrdA, NumB, A, B> vc1, VectorClock<OrdA, NumB, A, B> vc2)\n    {\n        return vc1 == vc2\n                   ? Some(Empty)\n                   : causes(vc2, vc1)\n                       ? Some(combine(diffOne, vc1, vc2))\n                       : None;\n\n        static Option<B> diffOne(A _, Option<B> ox, Option<B> oy) =>\n            (ox.Case, oy.Case) switch\n            {\n                (null, null) => None,\n                (B x, null)  => Some(x),\n                (null, B)    => throw new InvalidOperationException(\"diff broken\"),\n                (B x, B y)   => equals<NumB, B>(x, y) ? None : Some(x),\n                _            => None\n            };\n    }\n\n    bool? valid;\n    public bool Valid \n    { \n        get\n        {\n            if (valid.HasValue) return valid.Value;\n            var keys     = Entries.Map(e => e.Item1);\n            var sorted   = keys.Sort<OrdA, A>()                                         == keys;\n            var distinct = Entries.Distinct<EqTuple2<OrdA, NumB, A, B>, (A, B)>().Count == Entries.Count;\n            valid = sorted && distinct;\n            return valid.Value;\n        }\n    }","sourceCodeStart":313,"sourceCodeEnd":349,"githubUrl":"https://github.com/louthy/language-ext/blob/2f0e3628242889774d4141960a35671a0280051f/LanguageExt.Core/Concurrency/VectorClock/VectorClock.OrdA.NumB.A.B.cs#L313-L349","documentation":"Same `diff broken` invariant check, in the `VectorClock<OrdA, NumB, A, B>` variant's `diffOne`: a (None, Some) entry pair or unrecognized case when diffing the B-typed entries throws `InvalidOperationException(\"diff broken\")`. The B-clock diff can never legitimately see a key absent in the receiver but present in the argument.","triggerScenarios":"Calling `Diff` on a two-type-parameter VectorClock where the argument clock contains a key the receiver lacks, or an option holds a non-B/non-null case — only possible with inconsistently built clocks.","commonSituations":"Mixing clocks from different merge lineages; custom merge/serialization code that mutates one clock but not the other; bugs in user code that manipulates the underlying map via reflection or unsafe casts.","solutions":["Only obtain clocks through public constructors and `Merge`/`Next`; never mutate internals.","Ensure both clocks passed to Diff share the same key domain (same origin/lineage).","If reachable via valid public API, file a bug with LanguageExt including repro steps."],"exampleFix":null,"handlingStrategy":"try-catch","validationCode":"if (!clockA.Valid || !clockB.Valid) throw new InvalidOperationException(\"invalid vector clocks\");","typeGuard":null,"tryCatchPattern":"try { var d = a.Diff(b); }\ncatch (InvalidOperationException ex) when (ex.Message == \"diff broken\") { /* rebuild from common ancestor */ }","preventionTips":["Keep both clocks in the same merge lineage (same key domain).","Do not mix clocks from independent origins in one Diff call.","Rely only on public Merge/Next/constructors."],"tags":["vector-clock","invariant","concurrency","csharp"],"backgroundTag":"internal-invariant-violation","analyzedSha":"2f0e3628242889774d4141960a35671a0280051f","analyzedAt":"2026-09-15T03:31:55.716Z","contentChangedAt":"2026-09-15T03:31:55.716Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}