Skip to content

Bump pretty much ALL packages for .NET 10#2264

Merged
myieye merged 47 commits into
developfrom
chore/update-nuget-pkgs-dotnet-10
May 19, 2026
Merged

Bump pretty much ALL packages for .NET 10#2264
myieye merged 47 commits into
developfrom
chore/update-nuget-pkgs-dotnet-10

Conversation

@rmunn
Copy link
Copy Markdown
Contributor

@rmunn rmunn commented May 12, 2026

Fixes most of #2263.

Follow up PR will do:

This PR also happened to fix the IsLanguageForgeProject GQL loader which was/is currently broken in all environments.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 12, 2026

Review Change Stack

Important

Review skipped

Auto incremental reviews are disabled on this repository.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: bbbb9be8-02e0-48c0-9cdd-5829a4ef1877

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Central NuGet versioning file updates core version variables (MauiVersion to 10.0.60, HotChocolateVersion to 15.1.16) and refreshes the package version catalog with new explicit versions for Entity Framework, MongoDB, PostgreSQL, OpenTelemetry, Quartz, Polly, OpenIddict, and supporting libraries. The global Microsoft.VisualStudio.Threading.Analyzers reference is also bumped.

Changes

NuGet Package and Version Updates

Layer / File(s) Summary
Core version variables
backend/Directory.Packages.props
PropertyGroup version variables MauiVersion (9.0.50 → 10.0.60) and HotChocolateVersion (15.1.10 → 15.1.16) are updated, cascading impact on dependent packages.
Package versions and analyzer reference
backend/Directory.Packages.props
ItemGroup PackageVersion entries updated to new explicit versions across testing, data access, cloud, workflow, and security libraries; Microsoft.VisualStudio.Threading.Analyzers global reference bumped (17.11.20 → 17.14.15).

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Poem

🐰 Hop, hop—versions climb the tree,
Maui to ten-point-sixty, you see!
Hot Chocolate flows with 15.1.16,
A hundred packages, shiny and clean!
From .NET's new spring, we gather the seeds. 🌱

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 inconclusive)

Check name Status Explanation Resolution
Linked Issues check ❓ Inconclusive The PR partially addresses #2263 by bumping package versions, but does not complete all objectives: no evidence of removing obsolete packages or updating code for breaking changes. Complete the PR by removing obsolete packages and updating code to address breaking changes caused by .NET 10 and new package versions.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title accurately summarizes the main change: bumping package versions for .NET 10 compatibility.
Out of Scope Changes check ✅ Passed Changes are limited to package version bumps in Directory.Packages.props, which are in scope for the stated objective of updating NuGet packages.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Description check ✅ Passed The pull request description clearly references issue #2263 and explains the primary objective of bumping packages for .NET 10, with a follow-up PR planned for remaining work.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch chore/update-nuget-pkgs-dotnet-10

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@rmunn
Copy link
Copy Markdown
Contributor Author

rmunn commented May 12, 2026

Bumping linq2db.EntityFrameworkCore from 9.0.0 to 10.3.0 had more knock-on effects than I would like: it changed the baseline linq2db version from 5 to 6, and linq2db 6 included several breaking changes. But I suspect that dropping linq2db.EntityFrameworkCore back down to 9.0.0 would not work very well when we bump EF Core up to version 10.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 12, 2026

C# Unit Tests

165 tests  ±0   165 ✅ ±0   20s ⏱️ ±0s
 23 suites ±0     0 💤 ±0 
  1 files   ±0     0 ❌ ±0 

Results for commit 02ab435. ± Comparison against base commit 52ae636.

♻️ This comment has been updated with latest results.

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@backend/Directory.Packages.props`:
- Around line 111-114: The PackageVersion entry for SQLitePCLRaw.lib.e_sqlite3
(Version="2.1.11") is incompatible with bundle_e_sqlite3 (3.0.3); replace that
PackageVersion with SourceGear.sqlite3 at 3.50.4.2 (or later) so the
bundle_e_sqlite3 / SQLitePCLRaw.config.e_sqlite3 dependency chain is satisfied,
or alternatively downgrade bundle_e_sqlite3 to the 2.x series to match
SQLitePCLRaw.lib.e_sqlite3—update the PackageVersion symbol accordingly (remove
or replace the SQLitePCLRaw.lib.e_sqlite3 entry and add/replace
SourceGear.sqlite3 with the newer version).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 3096712b-e0c2-43f6-a100-94c0c59bf5b8

📥 Commits

Reviewing files that changed from the base of the PR and between d75763a and fd44d25.

📒 Files selected for processing (1)
  • backend/Directory.Packages.props

Comment on lines +111 to +114
<PackageVersion Include="SQLitePCLRaw.bundle_e_sqlite3" Version="3.0.3" />
<PackageVersion Include="SQLitePCLRaw.core" Version="3.0.3" />
<PackageVersion Include="SQLitePCLRaw.lib.e_sqlite3" Version="2.1.11" />
<PackageVersion Include="SQLitePCLRaw.provider.e_sqlite3" Version="2.1.11" />
<PackageVersion Include="Squidex.Assets.TusClient" Version="6.6.4" />
<PackageVersion Include="SQLitePCLRaw.provider.e_sqlite3" Version="3.0.3" />
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🔴 Critical

🧩 Analysis chain

🌐 Web query:

What is the latest version of SQLitePCLRaw.lib.e_sqlite3 and is it compatible with SQLitePCLRaw 3.0.3?

💡 Result:

Latest SQLitePCLRaw.lib.e_sqlite3 version (as of 2026-05-12): 3.50.3.[1] Compatibility with SQLitePCLRaw 3.0.3: Not enough evidence found to confirm compatibility. SQLitePCLRaw 3.x uses different packaging: the recommended “crypto-enabled SQLite build” approach is to use SQLitePCLRaw.config.e_sqlite3 together with a native SQLite library package (e.g., SourceGear.sqlite3), and the native-library package name is no longer SQLitePCLRaw.lib.e_sqlite3 (it’s SourceGear.sqlite3 in SQLitePCLRaw 3.0+). [2][3] So, while SQLitePCLRaw.lib.e_sqlite3 exists at version 3.50.3, the sources indicate that in the SQLitePCLRaw 3.0+ line you should generally use SQLitePCLRaw.config.e_sqlite3 + SourceGear.sqlite3 instead of relying on SQLitePCLRaw.lib.e_sqlite3.[2][3]

Citations:


🏁 Script executed:

# Check the Directory.Packages.props file for SQLitePCLRaw packages and SQLite-related entries
grep -n -i "sqlite\|SourceGear" backend/Directory.Packages.props

Repository: sillsdev/languageforge-lexbox

Length of output: 483


🌐 Web query:

SQLitePCLRaw.bundle_e_sqlite3 3.0.3 lib.e_sqlite3 2.1.11 compatibility

💡 Result:

SQLitePCLRaw.bundle_e_sqlite3 version 3.0.3 (or latest 3.0.x like 3.0.2) is not compatible with SQLitePCLRaw.lib.e_sqlite3 version 2.1.11. In SQLitePCLRaw 3.0, the native library package changed from SQLitePCLRaw.lib.e_sqlite3 (2.x series) to SourceGear.sqlite3 (now using SQLite version numbers like 3.50+).[1][2] The bundle_e_sqlite3 3.0.x now depends on SQLitePCLRaw.config.e_sqlite3 (>=3.0.x) and SourceGear.sqlite3, not the old lib.e_sqlite3 2.1.11.[3][4][5] Using the old lib.e_sqlite3 2.1.11 with bundle 3.0.x would cause conflicts due to different native libraries and provider expectations.[6][1] Recommendation: Update to SourceGear.sqlite3 (e.g., 3.50.4.2 as used in examples) or use bundle_e_sqlite3 2.1.11 if needing the old lib.[7][8]

Citations:


Fix SQLitePCLRaw package incompatibility — lib.e_sqlite3 2.1.11 conflicts with bundle_e_sqlite3 3.0.3.

SQLitePCLRaw 3.0 changed native library handling from SQLitePCLRaw.lib.e_sqlite3 (2.x series) to SourceGear.sqlite3. The bundle_e_sqlite3 3.0.3 depends on SourceGear.sqlite3 and SQLitePCLRaw.config.e_sqlite3, not the old 2.1.11 lib package. This mismatch will cause runtime conflicts. Replace SQLitePCLRaw.lib.e_sqlite3 2.1.11 with SourceGear.sqlite3 3.50.4.2 or later, or downgrade the bundle to 2.1.11 if needed.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/Directory.Packages.props` around lines 111 - 114, The PackageVersion
entry for SQLitePCLRaw.lib.e_sqlite3 (Version="2.1.11") is incompatible with
bundle_e_sqlite3 (3.0.3); replace that PackageVersion with SourceGear.sqlite3 at
3.50.4.2 (or later) so the bundle_e_sqlite3 / SQLitePCLRaw.config.e_sqlite3
dependency chain is satisfied, or alternatively downgrade bundle_e_sqlite3 to
the 2.x series to match SQLitePCLRaw.lib.e_sqlite3—update the PackageVersion
symbol accordingly (remove or replace the SQLitePCLRaw.lib.e_sqlite3 entry and
add/replace SourceGear.sqlite3 with the newer version).

@rmunn
Copy link
Copy Markdown
Contributor Author

rmunn commented May 13, 2026

@CodeRabbit - Several files, such as backend/FwLite/FwLiteWeb/Routes/ActivityRoutes.cs, are failing to compile with the error "The type or namespace name 'Models' does not exist in the namespace 'Microsoft.OpenApi' (are you missing an assembly reference?)". Please find out where the namespace name Models was moved to.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 13, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

@rmunn: The Microsoft.OpenApi.Models namespace was removed as part of the breaking changes introduced in Microsoft.OpenApi 2.0, which ships with .NET 10. In Swashbuckle.AspNetCore 10.x, those OpenAPI types were moved to the Microsoft.OpenApi namespace, which is why you're seeing the error that Models does not exist.

Fix

Update any using directives that reference types from the Microsoft.OpenApi.Models namespace to use the new namespace Microsoft.OpenApi.

In practice, replace:

using Microsoft.OpenApi.Models;

with:

using Microsoft.OpenApi;

Additional breaking changes in Microsoft.OpenApi 2.0 to be aware of

You may hit further compile errors beyond the namespace rename. Entities within the OpenAPI document, like operations and parameters, are now typed as interfaces, with concrete implementations existing for inlined and referenced variants. For example, an IOpenApiSchema can either be an inlined OpenApiSchema or an OpenApiSchemaReference that points to a schema defined elsewhere in the document.

  • The Nullable property has been removed from OpenApiSchema. To determine if a type is nullable, evaluate if OpenApiSchema.Type sets JsonSchemaType.Null.
  • One of the most significant changes is that the OpenApiAny class has been dropped in favor of using JsonNode directly.
  • There is no longer a reference to Microsoft.OpenApi.Models, and because of that OpenApiReference and Reference (used for security schemes) are no longer available in the old form.

Important caution: don't accidentally pull in Microsoft.OpenApi 3.x

.NET 10's OpenAPI generator was built against the 2.x OpenAPI model, so when version 3.x is present, the generator attempts to assign values to properties that are no longer writable — resulting in build failures. Make sure your Directory.Packages.props pins Microsoft.OpenApi to a 2.x version if it's being pulled in transitively.


🏁 Script executed:

#!/bin/bash
# Find the version of Microsoft.OpenApi being used
rg "Microsoft.OpenApi" backend/Directory.Packages.props

# Find all usages of Microsoft.OpenApi.Models in the codebase
rg -rn "Microsoft\.OpenApi\.Models" --type cs -l

Length of output: 363


🌐 Web query:

Microsoft.OpenApi Models namespace moved breaking change new version

💡 Result:

In newer OpenAPI.NET / ASP.NET Core OpenAPI stacks, the Microsoft.OpenApi.Models namespace is no longer where the core model types live—they were moved/flattened into Microsoft.OpenApi, which is why upgrades can break code that does using Microsoft.OpenApi.Models;.

What changed (the breaking symptom)

  • After upgrading (commonly when Microsoft.OpenApi is 2.0.0+ or when using newer ASP.NET Core / Swashbuckle combinations), code referencing Microsoft.OpenApi.Models.* can fail because that namespace (and types like OpenApiDocument) are no longer there; the types exist under Microsoft.OpenApi.* instead. [1]
  • Microsoft’s API docs for current OpenApiDocument show it in Microsoft.OpenApi. [2]

What to do

  • Replace usings like:
    • using Microsoft.OpenApi.Models;
      with:
    • using Microsoft.OpenApi;
      and update any fully-qualified type references accordingly. [1][2]

Important nuance (common in .NET 10 migrations)

  • If you mix the built-in Microsoft.AspNetCore.OpenApi pipeline with Swashbuckle, you can end up with mismatched expectations about where OpenAPI model types come from; Microsoft Q&A notes this shows up as “Models does not exist in Microsoft.OpenApi”, and the fix is to align packages (either stay on an older compatible Swashbuckle/OpenAPI.NET set, or move to the newer namespaces). [3]

Sources: [1] [2] [3]


Let me check the specific usages in the affected files to give you a more complete picture.


🏁 Script executed:

#!/bin/bash
# Show the using lines and context around OpenApi usage in each affected file
for f in \
  backend/LexBoxApi/Auth/AuthKernel.cs \
  backend/FwLite/FwLiteWeb/Routes/HistoryRoutes.cs \
  backend/FwLite/FwLiteWeb/Routes/TestRoutes.cs \
  backend/FwLite/FwLiteWeb/Routes/ImportRoutes.cs \
  backend/FwLite/FwLiteWeb/Routes/ActivityRoutes.cs \
  backend/FwLite/FwLiteWeb/Routes/MiniLcmRoutes.cs \
  backend/FwLite/FwLiteWeb/Routes/FwIntegrationRoutes.cs \
  backend/LexBoxApi/Program.cs; do
  echo "=== $f ==="
  rg -n "OpenApi" "$f"
  echo ""
done

# Also check if Microsoft.OpenApi is listed in any csproj files (direct reference)
rg -rn "Microsoft\.OpenApi" --include="*.csproj" --include="*.props"

Length of output: 2712


@rmunn: Here's the full picture across all 8 affected files. The fix is not just the using swap — two files have additional breaking changes.


All 8 files — Simple fix

Replace using Microsoft.OpenApi.Models;using Microsoft.OpenApi; in each of these files:

File Line
backend/LexBoxApi/Auth/AuthKernel.cs 19
backend/FwLite/FwLiteWeb/Routes/HistoryRoutes.cs 10
backend/FwLite/FwLiteWeb/Routes/TestRoutes.cs 6
backend/FwLite/FwLiteWeb/Routes/ImportRoutes.cs 4
backend/FwLite/FwLiteWeb/Routes/ActivityRoutes.cs 3
backend/FwLite/FwLiteWeb/Routes/FwIntegrationRoutes.cs 6
backend/FwLite/FwLiteWeb/Routes/MiniLcmRoutes.cs 5
backend/LexBoxApi/Program.cs 24

backend/FwLite/FwLiteWeb/Routes/MiniLcmRoutes.cs — Additional fix needed

Microsoft.OpenApi.Any was removed entirely in 2.0. OpenApiString and friends are replaced by System.Text.Json.Nodes.JsonNode types. OpenApiSchema.Enum is now IList<JsonNode>.

// Before
using Microsoft.OpenApi.Any;
using Microsoft.OpenApi.Models;
// ...
Schema = new OpenApiSchema()
{
    Enum =
    {
        new OpenApiString(ProjectDataFormat.FwData.ToString()),
        new OpenApiString(ProjectDataFormat.Harmony.ToString())
    }
}

// After
using Microsoft.OpenApi;
using System.Text.Json.Nodes;
// ...
Schema = new OpenApiSchema()
{
    Enum =
    {
        JsonValue.Create(ProjectDataFormat.FwData.ToString()),
        JsonValue.Create(ProjectDataFormat.Harmony.ToString())
    }
}

backend/LexBoxApi/Auth/AuthKernel.cs — Additional fix needed

OpenApiReference with ReferenceType is gone in 2.0. Security scheme references now use the dedicated OpenApiSecuritySchemeReference class.

// Before (lines ~245–252)
options.AddSecurityRequirement(new OpenApiSecurityRequirement
{
    {
        new OpenApiSecurityScheme
        {
            Reference = new OpenApiReference { Id = "Bearer", Type = ReferenceType.SecurityScheme }
        },
        []
    }
});

// After
options.AddSecurityRequirement(new OpenApiSecurityRequirement
{
    {
        new OpenApiSecuritySchemeReference("Bearer"),
        []
    }
});

@github-actions github-actions Bot added 💻 FW Lite issues related to the fw lite application, not miniLcm or crdt related 📦 Lexbox issues related to any server side code, fw-headless included labels May 13, 2026
@rmunn
Copy link
Copy Markdown
Contributor Author

rmunn commented May 13, 2026

@CodeRabbit rate limit

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 13, 2026

Review rate limit: 0/1 reviews remaining, refill in 33 minutes and 18 seconds.

@argos-ci
Copy link
Copy Markdown

argos-ci Bot commented May 13, 2026

The latest updates on your projects. Learn more about Argos notifications ↗︎

Build Status Details Updated (UTC)
default (Inspect) ✅ No changes detected - May 19, 2026, 12:11 PM

@rmunn
Copy link
Copy Markdown
Contributor Author

rmunn commented May 13, 2026

@CodeRabbit - Next question. In backend/FwLite/FwLiteWeb/Routes/MiniLcmRoutes.cs, dotnet build errors out with "Cannot create an instance of the abstract type or interface 'IOpenApiParameter'" (and several related errors) on lines 36-56. Please explain the errors and what to do about them.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 13, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

Let me read the file and check the latest Microsoft.OpenApi 2.0 docs.

I have the web search results. Let me also check the actual file content now that the script has run.

The script is still executing. Let me provide my analysis based on what I know so far while waiting for the file content.

@rmunn: I now have enough context from the web search. Let me share the findings — and the actual file content will help me give even more targeted advice.

The root cause of the error is a breaking change in Microsoft.OpenApi 2.0, which ships with .NET 10.

What changed

In .NET 10, entities within the OpenAPI document — like operations and parameters — are typed as interfaces. Concrete implementations exist for the inlined and referenced variants of an entity.

Specifically, OpenApiParameter is now an abstract class, with these concrete subclasses:

The IOpenApiParameter interface can be:

  • OpenApiParameter (further subclassed to OpenApiBodyParameter / OpenApiFormDataParameter)
  • or OpenApiParameterReference

So code like:

new OpenApiParameter { Name = "format", In = ParameterLocation.Query, ... }

…now fails because OpenApiParameter itself is abstract. You need to use the appropriate concrete subclass.

The fix

For typical query, path, header, or cookie parameters, use one of the concrete non-abstract subclasses. From the OpenAPI.NET v2 upgrade guide, the mapping is:

Parameter type (In) Concrete class to use
query, path, header, cookie OpenApiParameter subclass — likely a new non-abstract query/path variant, OR check the OpenApiQueryParameter / OpenApiPathParameter if they exist
body OpenApiBodyParameter
formData OpenApiFormDataParameter

For the common case of query/path/header parameters at lines 36–56, the fix is typically to replace new OpenApiParameter { In = ParameterLocation.Query, ... } with the appropriate concrete subclass. In v1.6.x the visitor used OpenApiParameter, while in v2.0 it uses IOpenApiParameter — meaning the collection types accept IOpenApiParameter but you must pass a concrete implementor.

I'd need to see the actual code at those lines to give you the exact class names. The script I kicked off is retrieving the file content now — once those results come back, I can provide a concrete diff. Could you share the code at lines 36–56 of MiniLcmRoutes.cs in the meantime, or wait for the script results?


🏁 Script executed:

#!/bin/bash
# Get the content of the file, focusing on the problematic area but also showing broader context
cat -n backend/FwLite/FwLiteWeb/Routes/MiniLcmRoutes.cs | head -100

Length of output: 4796


🏁 Script executed:

#!/bin/bash
cat -n backend/FwLite/FwLiteWeb/Routes/MiniLcmRoutes.cs | sed -n '1,100p'

Length of output: 4796


@rmunn: Now that I can see the code, the root cause is clear and the fix is straightforward.

Root Cause

In Microsoft.OpenApi 2.0, operation.Parameters is now typed as IList<IOpenApiParameter>. When you write new() (a C# target-typed new expression), the compiler infers the target type — which is IOpenApiParameter, an interface — and then fails because you can't instantiate an interface. This is the direct cause of the error.

There's also a secondary error lurking on line 48: Type = "string". In OpenAPI.NET 2.0, OpenApiSchema.Type changed from string to JsonSchemaType? (a flags enum), so that assignment will also fail to compile.

The Fix

Replace both new() calls with explicit new OpenApiParameter(), and fix the Type assignment:

// Lines 36-50: replace new() with new OpenApiParameter(), and fix Type
operation.Parameters?.Add(new OpenApiParameter()
{
    Name = "projectType",
    In = ParameterLocation.Path,
    Required = true,
    Schema = new OpenApiSchema()
    {
        Enum =
        [
            JsonValue.Create(ProjectDataFormat.FwData.ToString()),
            JsonValue.Create(ProjectDataFormat.Harmony.ToString())
        ],
        Type = JsonSchemaType.String   // was: Type = "string"
    },
});

// Lines 51-56: replace new() with new OpenApiParameter()
operation.Parameters?.Add(new OpenApiParameter()
{
    Name = "projectCode",
    In = ParameterLocation.Path,
    Required = true
});

OpenApiParameter is the concrete non-abstract class for path, query, header, and cookie parameters. The abstract subclasses (OpenApiBodyParameter, OpenApiFormDataParameter) only apply to OpenAPI 2.x Swagger-style body/formdata parameters — not relevant here.

JsonSchemaType is already accessible since using Microsoft.OpenApi; is on line 4, so no additional using directive is needed.

@github-actions
Copy link
Copy Markdown
Contributor

github-actions Bot commented May 13, 2026

UI unit Tests

  1 files  ±0   59 suites  ±0   30s ⏱️ -1s
176 tests ±0  176 ✅ ±0  0 💤 ±0  0 ❌ ±0 
245 runs  ±0  245 ✅ ±0  0 💤 ±0  0 ❌ ±0 

Results for commit 02ab435. ± Comparison against base commit 52ae636.

♻️ This comment has been updated with latest results.

@rmunn
Copy link
Copy Markdown
Contributor Author

rmunn commented May 13, 2026

@CodeRabbit - Next question. backend/FwHeadless/Program.cs has an error on line 48: "'EntityFrameworkInstrumentationOptions' does not contain a definition for 'SetDbStatementForText' and no accessible extension method 'SetDbStatementForText' accepting a first argument of type 'EntityFrameworkInstrumentationOptions' could be found (are you missing a using directive or an assembly reference?)". Explain the error and suggest a fix.

@rmunn
Copy link
Copy Markdown
Contributor Author

rmunn commented May 13, 2026

Good news: sillsdev/harmony#63 is finally green (after much re-running the build with no changes until a GHA runner was finally fast enough to satisfy the performance test), which would have blocked this PR from being merged. (Harmony needs to acquire a .net 10 build at the same time that FW Lite does).

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 13, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

Let me check the file and look up the breaking change.


🏁 Script executed:

#!/bin/bash
echo "=== backend/FwHeadless/Program.cs around line 48 ==="
sed -n '35,65p' backend/FwHeadless/Program.cs

echo ""
echo "=== OpenTelemetry EF package version in Directory.Packages.props ==="
rg "OpenTelemetry|EntityFramework" backend/Directory.Packages.props

Unrecoverable error: Failed to clone repository


🌐 Web query:

OpenTelemetry EntityFrameworkCore instrumentation SetDbStatementForText removed breaking change

💡 Result:

SetDbStatementForText (and SetDbStatementForStoredProcedure) was removed from OpenTelemetry.Instrumentation.EntityFrameworkCore as a breaking change starting in 1.13.0-beta.1 (released 2025-10-22); the library now behaves as if those options are always enabled. In other words, statement-related behavior that was previously gated by these flags is now unconditional. [1][2]

What to do now:

  • If you were setting SetDbStatementForText = true: just delete that code; it’s the default behavior now. [1][2]
  • If you were setting it to false to avoid emitting statement text: there is no longer a built-in switch in the EFCore instrumentation options; you’ll need to drop/clear the statement attribute(s) in your pipeline (e.g., via a custom ActivityProcessor) or avoid exporting those spans. (You can still use EFCore instrumentation options like Filter / EnrichWithIDbCommand for other customization.) [3]

Sources:
[1] EFCore instrumentation changelog entry for 1.13.0-beta.1 (breaking change + “always enabled”)
[2] PR #3072 removing the options and describing the new behavior (“behave as if it were enabled”)
[3] NuGet package docs showing supported EFCore instrumentation options (Filter, EnrichWithIDbCommand, etc.)


@rmunn: This is a straightforward breaking change in a newer version of the OpenTelemetry EFCore instrumentation package.

Root Cause

SetDbStatementForText (and SetDbStatementForStoredProcedure) was removed from OpenTelemetry.Instrumentation.EntityFrameworkCore as a breaking change starting in 1.13.0-beta.1 (released 2025-10-22). The library now behaves as if those options are always enabled — statement-related behavior that was previously gated by these flags is now unconditional.

The Fix

It depends on what the code at line 48 is doing:

Case 1 — You were enabling it (SetDbStatementForText = true):

Simply delete that line of code; it's now the default behavior.

// Before
.AddEntityFrameworkCoreInstrumentation(o =>
{
    o.SetDbStatementForText = true;  // <-- delete this line
})

// After
.AddEntityFrameworkCoreInstrumentation()

Case 2 — You were suppressing it (SetDbStatementForText = false):

There is no longer a built-in switch in the EFCore instrumentation options to suppress statement text. You'll need to drop/clear the statement attribute(s) in your pipeline via a custom ActivityProcessor, or avoid exporting those spans. You can still use EFCore instrumentation options like Filter / EnrichWithIDbCommand for other customization.

A minimal suppression processor would look like:

public class RedactDbStatementProcessor : BaseProcessor<Activity>
{
    public override void OnEnd(Activity activity)
    {
        if (activity.GetTagItem("db.statement") != null)
            activity.SetTag("db.statement", null);
    }
}

// Register it:
.AddEntityFrameworkCoreInstrumentation()
// ...
.AddProcessor(new RedactDbStatementProcessor())

Given this is a backend service (FwHeadless), Case 1 (enabling statement text for tracing) is the most likely scenario — so the fix is probably just deleting that one line.

@rmunn
Copy link
Copy Markdown
Contributor Author

rmunn commented May 13, 2026

@CodeRabbit - Next question. In backend/LexBoxApi/Services/DevGqlSchemaWriterService.cs, line 14 errors with "The type 'RequestExecutorProxy' exists in both 'HotChocolate.Execution.Abstractions, Version=16.0.0.0, Culture=neutral, PublicKeyToken=null' and 'HotChocolate.Execution, Version=15.1.16.0, Culture=neutral, PublicKeyToken=null'". The Directory.Packages.props file is requesting version 15.1.16.0 of the HotChocolate packages, so I don't know where the version 16 reference is coming from. Is it coming from a transitive NuGet dependency? If so, please identify the package that is referencing HotChocolate 16, and identify the latest version of that package that still references HotChocolate 15.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 13, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

The scripts are running to gather the codebase context. Let me also search for which specific packages are known to pull in HotChocolate 16 transitively.

I'm waiting for the script results. While the scripts are running, let me check if HC 16 is a pre-release being pulled in.

I'm still waiting for the script results. Let me check if GreenDonut is the culprit:

@rmunn: While my scripts to inspect your Directory.Packages.props didn't complete in time, I have enough from the web research to give you a thorough diagnosis and a recommended fix.


Root Cause

HotChocolate 16 is pre-release only (versions like 16.0.0-p.1.3, 16.0.0-p.10.6, etc.). This is a prerelease version of HotChocolate.Abstractions. The 16.0.0.0 assembly version you're seeing in the error is how NuGet pre-release 16.0.0-p.x packages identify themselves at the CLR assembly level.

The conflict happens because something in the build is pulling in HotChocolate.Execution.Abstractions at version 16 alongside your pinned HotChocolate.Execution at 15.1.16. These are incompatible assemblies that both define RequestExecutorProxy, hence the "type exists in both" error.

Why is HC 16 being pulled in?

The most likely cause is that one of the non-$(HotChocolateVersion)-versioned packages in Directory.Packages.props was bumped to a version that has a HC 16 pre-release as a transitive dependency. Since Central Package Management pins only the packages you explicitly list, any unlisted transitives are still resolved by NuGet's minimum-version-wins algorithm — and if a direct dependency requires HotChocolate.Execution.Abstractions >= 16.0.0-p.x, NuGet will pull that pre-release in.

The prime suspects in a typical HotChocolate-heavy backend are:

  1. HotChocolate.Data.EntityFramework or HotChocolate.Data.MongoDb — if either was bumped to a pre-release version that jumped ahead to HC 16
  2. Any third-party HC integration package (e.g. an OpenIddict/HC bridge, a tracing or telemetry package) that has since updated to target HC 16 pre-releases

How to diagnose definitively

The fastest way to find the culprit is:

This will print the full dependency chain showing exactly which package is pulling in HotChocolate.Execution.Abstractions at version 16.

Alternatively, examine the generated obj/project.assets.json file and search for HotChocolate.Execution.Abstractions — every entry will show its parent dependency:

The Fix

Once you've identified the offending package (call it SomePackage), you have two options:

Option A — Pin HC 16 transitive packages in Directory.Packages.props:
Override all the HC 16 transitives back to 15.x using VersionOverride:

<!-- Force these transitively-pulled HC 16 packages back to 15.x -->
<PackageVersion Include="HotChocolate.Execution.Abstractions" Version="$(HotChocolateVersion)" />

Option B — Downgrade SomePackage to the last version that still targeted HC 15:
Find the last version of SomePackage that doesn't drag in HC 16 and pin that in Directory.Packages.props. The dotnet nuget why output from Option A's diagnostic will tell you which version to target.

Option A is usually faster to apply but may hide compatibility issues; Option B is cleaner long-term.


🏁 Script executed:

#!/bin/bash
echo "=== HotChocolate entries in Directory.Packages.props ==="
rg -n "HotChocolate" backend/Directory.Packages.props

echo ""
echo "=== All csproj/props files referencing HotChocolate ==="
rg -rn "HotChocolate" --include="*.csproj" --include="*.props" -l

echo ""
echo "=== All PackageReference to HotChocolate in csproj files ==="
rg -rn "HotChocolate" --include="*.csproj"

echo ""
echo "=== DevGqlSchemaWriterService.cs content ==="
cat backend/LexBoxApi/Services/DevGqlSchemaWriterService.cs

Length of output: 4103


🏁 Script executed:

# In the LexBoxApi project directory:
dotnet nuget why <path-to-LexBoxApi.csproj> HotChocolate.Execution.Abstractions

Length of output: 71


🏁 Script executed:

grep -A3 "HotChocolate.Execution.Abstractions" backend/LexBoxApi/obj/project.assets.json | head -40

Length of output: 75


🏁 Script executed:

#!/bin/bash
echo "=== Find all csproj files and grep for HotChocolate ==="
fd -e csproj -x grep -ln "HotChocolate" {}

echo ""
echo "=== Show all HotChocolate PackageReference lines in all csproj files ==="
fd -e csproj -x grep -n "HotChocolate" {}

echo ""
echo "=== Show full Directory.Packages.props ==="
cat backend/Directory.Packages.props

Length of output: 11155


🌐 Web query:

HotChocolate.Execution.Abstractions NuGet package introduced version

💡 Result:

HotChocolate.Execution.Abstractions was introduced as its own NuGet package starting with version 13.0.0 (published February 7, 2023). (nuget.org)

Citations:


Now I can see the full picture. The key suspect is DataAnnotatedModelValidations at version 11.0.0 — it's a third-party HotChocolate integration package with an unusually high version number. Let me confirm its HC dependency.


🌐 Web query:

DataAnnotatedModelValidations 11.0.0 NuGet HotChocolate dependency version

💡 Result:

DataAnnotatedModelValidations 11.0.0 has a HotChocolate dependency on HotChocolate.Types (>= 16.0.0) (for net8.0, net9.0, and net10.0), i.e., it targets HotChocolate 16.0.0+. [1]

Source: [1]


Found it. Now let me find the latest version that still targets HC 15.


🌐 Web query:

DataAnnotatedModelValidations NuGet latest version HotChocolate 15 compatible

💡 Result:

  • Latest NuGet version (overall): DataAnnotatedModelValidations 11.0.0 (last updated May 10, 2026) — this targets HotChocolate 16+, not 15. [1]
  • Latest version compatible with HotChocolate 15: 10.0.0 (compatible with HotChocolate ≥ 15.1.11; updated Mar 14, 2026). [1]

@rmunn: Found it. The transitive HC 16 pull is coming from DataAnnotatedModelValidations.

Root Cause

DataAnnotatedModelValidations 11.0.0 (released May 10, 2026) depends on HotChocolate.Types >= 16.0.0, pulling in the entire HC 16 pre-release package tree, including HotChocolate.Execution.Abstractions 16.0.0.0. That assembly defines the same RequestExecutorProxy type as HotChocolate.Execution 15.1.16, causing the ambiguity error on line 14 of DevGqlSchemaWriterService.cs.

The Fix

Downgrade DataAnnotatedModelValidations from 11.0.0 to 10.0.0 in Directory.Packages.props. Version 10.0.0 (released Mar 14, 2026) is the latest version that targets HotChocolate ≥ 15.1.11 — a perfect match for your HotChocolateVersion of 15.1.16.

<!-- Before -->
<PackageVersion Include="DataAnnotatedModelValidations" Version="11.0.0" />

<!-- After -->
<PackageVersion Include="DataAnnotatedModelValidations" Version="10.0.0" />

That single change should eliminate the HotChocolate.Execution.Abstractions 16.0.0.0 reference entirely and resolve the compile error. You can revisit the bump to 11.0.0 once you're ready to move to HC 16 proper.

@rmunn
Copy link
Copy Markdown
Contributor Author

rmunn commented May 13, 2026

@CodeRabbit - Next question. Line 61 of backend/LexBoxApi/GraphQL/CustomTypes/IsLanguageForgeProjectDataLoader.cs is erroring out with "The type arguments for method 'MongoExtensions.ToAsyncEnumerable(IMongoCollection)' cannot be inferred from the usage. Try specifying the type arguments explicitly." Suggest the type arguments needed for this line.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 13, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
echo "=== IsLanguageForgeProjectDataLoader.cs full content ==="
cat -n backend/LexBoxApi/GraphQL/CustomTypes/IsLanguageForgeProjectDataLoader.cs

echo ""
echo "=== MongoExtensions ToAsyncEnumerable usages across codebase ==="
rg -n "ToAsyncEnumerable" --type cs -C3

Length of output: 19475


@rmunn: The root cause is a type inference failure in the newer MongoDB.Driver version — the compiler can no longer infer T from the chained LINQ expression (.AsQueryable().Select(...).Where(...)) because the resulting IMongoQueryable<string> is no longer directly recognized as IAsyncCursorSource<T> for inference purposes.

The Fix

Explicitly specify <string> as the type argument. Since .Select(p => p.ProjectCode) projects to string, and .Where(...) keeps it IMongoQueryable<string>, T = string:

// Before (line 61)
var actualProjects = await MongoExtensions.ToAsyncEnumerable(loader._systemDbContext.Projects.AsQueryable()
        .Select(p => p.ProjectCode)
`#pragma` warning disable MALinq2001
        .Where(projectCode => list.Contains(projectCode)))
`#pragma` warning restore MALinq2001
    .ToHashSetAsync(cancellationToken: token);

// After — add explicit <string> type argument
var actualProjects = await MongoExtensions.ToAsyncEnumerable<string>(loader._systemDbContext.Projects.AsQueryable()
        .Select(p => p.ProjectCode)
`#pragma` warning disable MALinq2001
        .Where(projectCode => list.Contains(projectCode)))
`#pragma` warning restore MALinq2001
    .ToHashSetAsync(cancellationToken: token);

This tells the compiler to use the ToAsyncEnumerable<T>(this IAsyncCursorSource<T> cursorSource) overload with T = string, which IMongoQueryable<string> satisfies. Nothing else about the logic needs to change.

@rmunn
Copy link
Copy Markdown
Contributor Author

rmunn commented May 13, 2026

@CodeRabbit - Next question. Line 94 of backend/FwLite/LcmCrdt.Tests/EntityCopyMethodTests.cs is erroring out with "'INode' does not contain a definition for 'Description' and no accessible extension method 'Description' accepting a first argument of type 'INode' could be found (are you missing a using directive or an assembly reference?)". Identify the cause and suggest a fix.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 13, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
echo "=== EntityCopyMethodTests.cs content around line 94 ==="
cat -n backend/FwLite/LcmCrdt.Tests/EntityCopyMethodTests.cs | sed -n '80,120p'

echo ""
echo "=== Full file top (usings/imports) ==="
cat -n backend/FwLite/LcmCrdt.Tests/EntityCopyMethodTests.cs | head -30

echo ""
echo "=== Search for INode in the codebase (definitions/usages) ==="
rg -n "INode" --type cs -C2 backend/FwLite/ | head -80

echo ""
echo "=== Check Directory.Packages.props for relevant packages ==="
rg -n "MiniLcm\|SIL\|Lexbox\|Node" backend/Directory.Packages.props | head -40

echo ""
echo "=== Find where INode is defined ==="
rg -rn "interface INode" --type cs -C3

Length of output: 2507


@rmunn
Copy link
Copy Markdown
Contributor Author

rmunn commented May 13, 2026

Tip

For best results, initiate chat on the files or code changes.
🧩 Analysis chain

@rmunn: The root cause is a type inference failure in the newer MongoDB.Driver version — the compiler can no longer infer T from the chained LINQ expression (.AsQueryable().Select(...).Where(...)) because the resulting IMongoQueryable<string> is no longer directly recognized as IAsyncCursorSource<T> for inference purposes.

The Fix

Explicitly specify <string> as the type argument. Since .Select(p => p.ProjectCode) projects to string, and .Where(...) keeps it IMongoQueryable<string>, T = string:

// Before (line 61)
var actualProjects = await MongoExtensions.ToAsyncEnumerable(loader._systemDbContext.Projects.AsQueryable()
        .Select(p => p.ProjectCode)
`#pragma` warning disable MALinq2001
        .Where(projectCode => list.Contains(projectCode)))
`#pragma` warning restore MALinq2001
    .ToHashSetAsync(cancellationToken: token);

// After — add explicit <string> type argument
var actualProjects = await MongoExtensions.ToAsyncEnumerable<string>(loader._systemDbContext.Projects.AsQueryable()
        .Select(p => p.ProjectCode)
`#pragma` warning disable MALinq2001
        .Where(projectCode => list.Contains(projectCode)))
`#pragma` warning restore MALinq2001
    .ToHashSetAsync(cancellationToken: token);

This tells the compiler to use the ToAsyncEnumerable<T>(this IAsyncCursorSource<T> cursorSource) overload with T = string, which IMongoQueryable<string> satisfies. Nothing else about the logic needs to change.

When I do that, the whole LINQ expression fails to compile with the error "Argument 1: cannot convert from 'System.Linq.IQueryable' to 'MongoDB.Driver.IMongoCollection'".

@rmunn
Copy link
Copy Markdown
Contributor Author

rmunn commented May 13, 2026

@CodeRabbit - Next question. Please read #2264 (comment) and suggest a fix for the "cannot convert from 'System.Linq.IQueryable' to 'MongoDB.Driver.IMongoCollection'" error.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 13, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
echo "=== IsLanguageForgeProjectDataLoader.cs lines 50-75 ==="
sed -n '50,75p' backend/LexBoxApi/GraphQL/CustomTypes/IsLanguageForgeProjectDataLoader.cs

echo ""
echo "=== MongoExtensions class - find and show ==="
fd "MongoExtensions.cs" -x cat {}

echo ""
echo "=== MongoDB.Driver version in Directory.Packages.props ==="
rg -n "MongoDB\|Mongo" backend/Directory.Packages.props

echo ""
echo "=== All ToAsyncEnumerable usages in codebase ==="
rg -n "ToAsyncEnumerable" --type cs -C3

Length of output: 16684


@rmunn: My previous suggestion was incorrect — I apologize for the detour. Now that I can see the MongoExtensions class, the root cause is clear.

Root Cause

MongoExtensions.ToAsyncEnumerable has three overloads: IMongoCollection<T>, IAsyncCursorSource<T>, and IAsyncCursor<T>. For the IAsyncCursorSource<T> overload to work, the argument must be an IMongoQueryable<T> (which implements IAsyncCursorSource<T>).

The problem is that in the newer MongoDB.Driver (3.x), chaining .Select() and .Where() on an IMongoQueryable<T> degrades the static type back to plain System.Linq.IQueryable<T>. Plain IQueryable<string> implements neither IMongoCollection<T> nor IAsyncCursorSource<T>, so all three overloads fail regardless of whether you add an explicit type argument.

The Fix

Avoid LINQ projection entirely and use the MongoDB Fluent API (Find + Project), which stays within MongoDB.Driver's type system the whole way:

private static async Task<Dictionary<string, bool>> LoadBatch(IsLanguageForgeProjectDataLoader loader,
    IReadOnlyList<string> list,
    CancellationToken token)
{
    var actualProjects = (await loader._systemDbContext.Projects
        .Find(Builders<Project>.Filter.In(p => p.ProjectCode, list))
        .Project(p => p.ProjectCode)
        .ToListAsync(token))
        .ToHashSet();
    return list.ToDictionary(pc => pc, pc => actualProjects.Contains(pc));
}

This removes the LINQ chain entirely, eliminates the #pragma warning disable MALinq2001 workaround (since Find + In is a native MongoDB filter, not a LINQ Contains), and returns a IFindFluent that goes directly to .ToListAsync() — no ToAsyncEnumerable needed at all.

You'll need to ensure Builders<Project> is accessible, which requires using MongoDB.Driver; (likely already present).

@rmunn
Copy link
Copy Markdown
Contributor Author

rmunn commented May 13, 2026

@CodeRabbit - Next question. backend/FwLite/LcmCrdt.Tests/EntityCopyMethodTests.cs fails to build. dotnet build errors out on line 94 with "'INode' does not contain a definition for 'Description' and no accessible extension method 'Description' accepting a first argument of type 'INode' could be found (are you missing a using directive or an assembly reference?)". Identify the cause and suggest a fix.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 13, 2026

Tip

For best results, initiate chat on the files or code changes.

@rmunn: This was already answered earlier in this thread — here's a recap for convenience.

Root Cause

The project is on FluentAssertions 8.9.0, which introduced a breaking change to the equivalency API in v8.0.0. In FA 7.x, INode directly exposed string Description { get; }. In FA 8.0, that property was removed from INode itself. Instead, INode now exposes two Pathway properties — Subject and Expectation — and each Pathway carries the string descriptors (Description, Path, Name, PathAndName).

The Fix

Replace context.CurrentNode.Description with context.CurrentNode.Subject.Description on line 94:

// Before (line 94)
.FailWith("Subject and Expectation for {0} should not reference the same instance in memory.", context.CurrentNode.Description);

// After
.FailWith("Subject and Expectation for {0} should not reference the same instance in memory.", context.CurrentNode.Subject.Description);

Subject.Description gives the human-readable path to the subject node being compared (e.g. "root.SomeProperty"), which is exactly what the original Description was providing. For a symmetric check like this one, Expectation.Description would yield the same result, but Subject.Description is the more natural choice.

@rmunn
Copy link
Copy Markdown
Contributor Author

rmunn commented May 13, 2026

@CodeRabbit - Next question. backend/Testing/SyncReverseProxy/LegacyProjectApiTests.cs has two lines, 105 and 121, which fail to compile. Both are the same: responseObject.Should().ContainKey("error"); (where responseObject is a JsonObject instance). The errors are the same, too: "'JsonNodeAssertions' does not contain a definition for 'ContainKey' and no accessible extension method 'ContainKey' accepting a first argument of type 'JsonNodeAssertions' could be found (are you missing a using directive or an assembly reference?)". This is likely caused by a breaking change in FluentAssertions, which has probably moved the ContainKey extension method to a different namespace. Please explain the error and suggest a fix.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 13, 2026

Tip

For best results, initiate chat on the files or code changes.

🧩 Analysis chain

🌐 Web query:

FluentAssertions 8 JsonNodeAssertions ContainKey JsonObject breaking change

💡 Result:

In FluentAssertions v8.7.0 the new System.Text.Json JsonNode / JsonArray assertion API uses property-based assertions (e.g., HaveProperty, NotHaveProperty)—there is no JsonNodeAssertions.ContainKey / JsonObjectAssertions.ContainKey in that API. The JSON feature was introduced in 8.7.0 and documented with HaveProperty(...) as the “key exists” equivalent. [1] [2] [3]

Migration:

JsonNode node = JsonNode.Parse(@"{ ""name"": ""John"" }");

// old idea (dictionary-style): ContainKey("name")  // not available on JsonNodeAssertions

node.Should().HaveProperty("name");
node.Should().NotHaveProperty("code");

If you truly want dictionary assertions, you’d need to assert on a dictionary-like view/type (since ContainKey is a dictionary assertion), but FluentAssertions’ intended API for JsonNode is HaveProperty. [2] [3]

Sources
[1] FluentAssertions release notes (8.7.0 adds JsonNode/JsonArray assertions) https://fluentassertions.com/releases/
[2] FluentAssertions JSON (System.Text.Json) docs (HaveProperty, NotHaveProperty) https://fluentassertions.com/json/
[3] Xceed blog announcement showing HaveProperty usage for JsonNode assertions https://xceed.com/blog/all/fluent-assertions-8-7-is-live/


🏁 Script executed:

#!/bin/bash
echo "=== LegacyProjectApiTests.cs around lines 95-130 ==="
sed -n '95,130p' backend/Testing/SyncReverseProxy/LegacyProjectApiTests.cs

echo ""
echo "=== Top of file (usings) ==="
head -20 backend/Testing/SyncReverseProxy/LegacyProjectApiTests.cs

echo ""
echo "=== FluentAssertions version ==="
rg -n "FluentAssertions" backend/Directory.Packages.props

Length of output: 2625


@rmunn: Confirmed — this is a FluentAssertions 8.7.0 breaking change, not a namespace issue.

Root Cause

In FA 8.7.0, a new dedicated JsonNode/JsonArray assertion API was introduced. When you call .Should() on a JsonObject, FA 8.7+ returns JsonNodeAssertions<JsonObject> — a JSON-specific assertions type that uses property-based assertion methods, not dictionary-style ones. ContainKey belongs to the dictionary assertion API (IDictionary<K,V>) and is not exposed on JsonNodeAssertions<T>. The intended equivalent in the JSON assertion API is HaveProperty.

The Fix

Replace .ContainKey("error") with .HaveProperty("error") at both lines 105 and 121:

// Before (lines 105 and 121)
responseObject.Should().ContainKey("error");

// After
responseObject.Should().HaveProperty("error");

No using directive changes are needed — HaveProperty is already accessible through the existing using FluentAssertions; on line 5.

The rest of the assertion chain (responseObject["error"]!.GetValue<string>().Should().Be(...)) is unaffected and does not need to change.

myieye and others added 19 commits May 19, 2026 14:07
The .NET 10 / Linq2Db v6 fixes turned many fast-failing tests into
slow-passing tests (they now actually run to completion). On Windows CI
the LcmCrdt + FwLiteProjectSync test step needs more than 40 min.
Adds LINQ2DB-V6-NOTES.md next to the affected code with: the two v6
behavior changes that triggered the issues, the matrix of workarounds
we tried, the trade-off the current code picks, links to the migration
wiki / related issues / in-flight PR, and a clear revert checklist for
when upstream lands a fix.

Drops TODO pointers to that doc in the two source locations holding
the workaround (Json.QueryInternal bodies and the SemanticDomains /
PublishIn ExpressionMethodAttribute expressions in LcmCrdtKernel).
linq2db v6 introduced regressions around [ExpressionMethod]
materialization that this branch had been working around with growing
hacks (.ToList wraps, raw-SQL test inserts, JSON path-builder Alias
unwrap, etc.) — see backup branch wip/linq2db-v6-attempts and tag
pre-linq2db-downgrade for the in-progress work.

Decouple linq2db from .NET 10: keep the framework + ASP.NET / EF.Sqlite
on 10.x, but pin

  linq2db.AspNet              5.4.1   (renamed from .Extensions in v6)
  linq2db.EntityFrameworkCore 9.0.0
  Microsoft.EntityFrameworkCore* 9.0.16
  Npgsql / .EntityFrameworkCore.PostgreSQL 9.0.5 / 9.0.4
  Gridify(.EntityFramework)   2.17.0  (last without explicit net10.0 TFM)
  NeinLinq(.EntityFrameworkCore) 7.3.2
  OpenIddict.*                7.1.0   (same — net10 TFM forces EF 10)

The downgrade is mirrored in the harmony submodule
(chore/linq2db-v5-on-net10). Files that picked up v6-only API surface
(LinqToDB.Internal.*, .Extensions.Logging, UseSQLiteMicrosoft,
SqlExpression(new DbDataType ...), etc.) are restored to develop's
v5-shaped form. The v6 docs (LINQ2DB-V6-NOTES.md) and regression-test
file are removed; both live on the backup branch.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
After the linq2db v5 / EF Core 9 pin, several earlier branch fixes were
either no-op churn or workarounds for problems that no longer apply.
Clean them up so the diff vs develop reflects what's actually changing:

- CI: drop the fw-lite job's 40→60 timeout bump. The "tests run to
  completion now instead of fast-failing" justification was a v6 perf
  hit; gone with the downgrade.
- FwHeadless: restore `c.SetDbStatementForText = true`. The line was
  dropped because OpenTelemetry.Instrumentation.EntityFrameworkCore
  1.13+ removed the property (now always-on). Pin to 1.12.0-beta.2 so
  the explicit opt-in stays in source.
- SystemTextJsonPatch: pin to 3.3.0 and revert the JsonElement-aware
  RichMultiString.IDictionary.Add. v5's PocoAdapter change forced the
  workaround; 3.x doesn't.
- EmailService: drop the `if (admin.Email is not null)` skip-on-null
  guard. An admin without an email is a programming error — fail
  loudly. MailKit 4.16's tightened nullability means we need an
  explicit throw rather than the bare ctor call develop has.
- EF snapshots and the Quartz migration: regenerated on EF Core 9
  (dotnet-ef tool pinned to 9.0.16). Removes the EF10 syntactic noise
  (DiscriminatorProperty drop, ToTable null cast, ComplexProperty
  signature change, lost Optional/Required annotations on nav props,
  ProductVersion bump) so the snapshot diff is exactly the two real
  schema changes the migration applies: Quartz misfire_orig_fire_time
  and OpenIddictTokens.Type widened 50→150.
- LexData: revert no-op linq2db/AspNet using/PackageReference re-order
  back to develop's order.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
OpenTelemetry.Instrumentation.EntityFrameworkCore: back to 1.15.1-beta.1.
SetDbStatementForText was the default starting 1.13, so the line that
explicitly set it true is now redundant — drop it; behaviour preserved.

SystemTextJsonPatch: back to 5.0.0. The reason the v5 bump needed the
JsonElement workaround on RichMultiString.IDictionary.Add is that STJP's
PocoAdapter routes non-generic-IDictionary targets through
DictionaryPropertyProxy, whose PropertyType returns typeof(object) for
keys that don't exist yet — so ConversionResultProvider.TryConvertTo
doesn't deserialize, and our explicit IDictionary.Add receives the raw
JsonElement.

Cleaner fix: make RichMultiString implement IDictionary<WritingSystemId,
RichString>. STJP then picks DictionaryTypedPropertyProxy<,> instead,
which knows the value type and deserialises to RichString before
assigning. Our IDictionary.Add(object, object) reverts to the simple
`value as RichString ?? throw` cast — same as develop — because the
JsonElement case no longer reaches it.

Only addition needed for the new interface was CopyTo(KeyValuePair<,>[],
int); everything else (Add, Remove, indexer, ContainsKey, TryGetValue,
Keys, Values, Count, IsReadOnly) was already present.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The IDictionary<,> implementation on RichMultiString alone wasn't enough
to route through DictionaryTypedPropertyProxy<,>: STJP also requires the
key type's TypeConverter to support string conversion. Without it, STJP
falls back to the non-generic DictionaryPropertyProxy, whose PropertyType
returns typeof(object) for new keys — meaning no deserialization happens
and our explicit IDictionary.Add gets a raw JsonElement.

Add a TypeConverter that does string ↔ WritingSystemId via the existing
Parse method (and ToString back). WritingSystemId already has implicit
string conversion at the C# level, ISpanParsable<T>, and a JsonConverter
— making it TypeDescriptor-visible too is the missing piece and the
right shape for ASP.NET route/config binders as well.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The model snapshot was restored from develop (which captured EF 9.0.6).
We're running 9.0.16 now, so the VerifyDbModel test was comparing a
"9.0.16" model against a "9.0.6" verified file and failing on that one
line. Patch the version in both the migration snapshot and the verified
file.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
STJP's DictionaryTypedPropertyProxy casts to IDictionary<TKey, TValue?>
and passes null when an "add /xxx" patch value deserialises to a null
RichString (e.g. for an empty-string write that the converter folds to
null). The non-generic indexer already handled that by removing the
key; the typed one was calling value.EnsureWs(key) on null and throwing
NRE. Mirror the same remove-on-null behaviour.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The recent attempt to revert to 40 was based on the assumption that the
slowdown was specific to linq2db v6, but the latest CI run (linq2db v5 +
EF Core 9 + .NET 10) confirms the test step still exceeds 40 minutes —
the slowdown is from .NET 10 itself (some tests that used to fast-fail
now run to completion). Document that and restore 60 until we
investigate.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The Dotnet test step on this branch hangs (>40 min) where develop
finishes in ~15 min. xUnit's default progress only prints failures and
skips, so we have no visibility into which test is stuck after the
last reported event.

- `--blame-hang-timeout 5m` causes the runner to capture a dump if any
  single test runs longer than 5 minutes, identifying the culprit.
- `--blame-hang-dump-type full` gets a full process dump for postmortem.
- `console;verbosity=normal` adds per-test "Passed" lines so we can see
  exactly when (and where) the run stalls.

This is a temporary diagnostic step; revert once the slowdown is found.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The CI --blame-hang dump fingered LcmCrdt.Tests.SnapshotAtCommitServiceTests.
PreserveAllFieldWorksCommits_IncludesFieldWorksChangesInSnapshot as the
stuck test. That test exercises SQLite's BackupDatabase API to fork a
shared-cache in-memory DB. Locally it finishes in 14s, but on the
windows-latest CI runner it idles for >5 minutes — symptomatic of a
lock/IO wait in the bundled SQLite library.

SQLitePCLRaw 3.0 bumped the bundled SQLite version, which is the most
plausible Windows-only behaviour change between develop (2.1.11, test
finishes) and this branch (3.0.3, test hangs).

Keeping --blame-hang-timeout / --blame-hang-dump-type / console verbose
for one more run to confirm the test passes after the downgrade and to
catch any remaining slow tests.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Neither downgrade fixed the hang on PreserveAllFieldWorksCommits, so reset
those packages. Bump blame-hang-timeout 5m -> 15m: maybe the test is
genuinely just slow rather than deadlocked (develop ran the whole class
in 14m36s on the same Windows runner). Diagnostic flags stay on so we
can confirm pass-vs-hang.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The --blame-hang dump from the windows-latest CI runner showed
SnapshotAtCommitServiceTests.PreserveAllFieldWorksCommits_Includes…
stuck in FluentAssertions:

  snapshotDefault.Should().NotBeEquivalentTo(snapshotPreserved)
    → LooselyOrderedEquivalencyStrategy<Guid>.FindClosestMismatches
    → AssertionChain.FailWith → Regex.Replace
    → (recursive, ~depth 20+ from the parallel-stack output)

FA 8.x's loose-ordering deep equivalency check is exponential in the
size of nested collections. Snapshots include the 30+ predefined morph
types plus writing systems / parts-of-speech / publications, so the
NotBeEquivalentTo blew up to >5 minutes on Windows CI (develop, on FA
7.0.0-alpha.5, ran the same test as part of the 14m36s LcmCrdt.Tests
batch with no hang). Locally on this branch the same test took 14s on
FA 8 and now 5s on FA 7 — so going back to develop's 7.0.0-alpha.5
makes the symptom disappear cleanly.

Revert the three "FluentAssertions breaking change" commits' source
edits since FA 7 doesn't need them — except where they actually fixed
FluentValidation 12 (ShouldHaveAnyValidationError → ShouldHaveValidationErrors)
or Squidex.Assets-TusClient using/namespace splits, both of which stay.

Also restore the fw-lite job to timeout-minutes: 40 and drop the
--blame-hang / dump-upload diagnostics that were added to find this.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
MAUI 10's MSIX packaging targets no longer honour --artifacts-path; they
write AppPackages relative to the FwLiteMaui project directory instead
of under artifacts/bin/. The cp glob was still pointing at the old
artifacts/bin/.../AppPackages path, so MakeAppx received an empty
directory and failed with "bundle must contain at least one app package
targeting a known processor architecture".

Source from AppPackages/*/*.msix (relative to the working-directory,
which is backend/FwLite/FwLiteMaui).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Setting -p:AppxPackageDir=../artifacts/AppPackages/ is the MSIX-targets
equivalent of --artifacts-path; without it MAUI 10 writes AppPackages to
the project directory regardless of --artifacts-path. With both set
everything lives under a single artifacts/ tree which:
- matches the layout we had pre-bump (msix output reachable under
  artifacts/...)
- removes the implicit "but what if Debug" hypothetical from the cp glob
  by forcing the output to a single known location we control
- doesn't change any signed/zipped/uploaded artifact (the msixbundle is
  built from the same .msix contents and uploaded as a single file)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
The DataLoader's constructor used [Service(ResiliencePolicyName)] to inject
the typed Polly pipeline, but that attribute is Hot Chocolate's resolver-
pipeline marker — ActivatorUtilities (which constructs DataLoaders) doesn't
honour it. The bare ResiliencePipeline<T> lookup then failed because
AddResiliencePipeline<string, T> only registers a ResiliencePipelineProvider
<string>, not a keyed pipeline.

This path was never test-covered, so the bug had been silently present on
develop too — the LF-project detection always threw at construction time.
Surfaced today by querying isLanguageForgeProject on the deployed branch.

Inject the provider and resolve the pipeline by name in the constructor —
the canonical Polly 8.x pattern.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@myieye myieye force-pushed the chore/update-nuget-pkgs-dotnet-10 branch from db4a2ca to 02ab435 Compare May 19, 2026 12:08
@myieye myieye marked this pull request as ready for review May 19, 2026 12:44
@myieye myieye merged commit b1ab821 into develop May 19, 2026
43 checks passed
@myieye myieye deleted the chore/update-nuget-pkgs-dotnet-10 branch May 19, 2026 12:45
myieye added a commit that referenced this pull request May 20, 2026
linq2db.AspNet+linq2db.EntityFrameworkCore 9.0.0 (v5 API) → linq2db.Extensions
6.2.1 + linq2db.EntityFrameworkCore 10.3.0 (v6 API). EF Core 9.0.x → 10.0.7,
Npgsql 9→10, NeinLinq 7.3→7.4, OpenIddict 7.1→7.5, Gridify 2.17→2.19. Robin's
PR #2264 handled everything else in the .NET 10 bump; this PR covers only the
linq2db/EF Core upgrade and its specific workarounds.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
myieye added a commit that referenced this pull request May 20, 2026
Advances from 50b0e2da (Robin's PR #2264 base) to 96a75b26 which adds the
HasCommit TOCTOU fix: the commit-presence check now happens inside the lock
in DataModel.Add, preventing a race where two concurrent callers could both
pass the check and then collide on insert.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

💻 FW Lite issues related to the fw lite application, not miniLcm or crdt related 📦 Lexbox issues related to any server side code, fw-headless included

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants