Skip to content

feat: social publishing + NuGet #r + move perf + mesh stability batch#95

Open
rbuergi wants to merge 1847 commits into
mainfrom
bug_fix
Open

feat: social publishing + NuGet #r + move perf + mesh stability batch#95
rbuergi wants to merge 1847 commits into
mainfrom
bug_fix

Conversation

@rbuergi

@rbuergi rbuergi commented Apr 22, 2026

Copy link
Copy Markdown
Contributor

Summary

77 commits of long-running work on bug_fix — grouped by theme:

  • Social publishing platform (new)MeshWeaver.Social + LinkedIn publisher + scheduled publishing pipeline (engine/queue/stats), LinkedIn OAuth connect + past-post ingest in Memex portal, per-user linked-account menu items.
  • NuGet in-process compile#r "nuget:Pkg, Version" at the top of _Source/*.cs resolves via public NuGet.Protocol without an SDK on the container. Same resolver serves interactive markdown code cells.
  • Move-node parallelization + 30 s ceilingFileSystemPersistenceService.MoveNodeAsync runs per-descendant WriteAsync/DeleteAsync through Task.WhenAll; new MeshOperationOptions (default Timeout = 30s) + WithMeshOperationTimeout(TimeSpan) override; HandleMoveNodeRequest chains .Timeout() on the persistence Observable so a stuck adapter can't hang the caller. Prod repro: DAV2026 subtree move that took 240 s and killed the MCP session — now bounded.
  • Compile / cache invalidation — sticky invalidation on CompilationCacheService, _Source/ edit re-invalidates owning NodeType, cross-silo broadcast via MeshChangeFeed, grain-dispose on node delete, live "Compiling … (Ns)" progress in LayoutAreaView.
  • Catalog & navigation — Children view groups by Category (falls back to NodeType), reactive Children catalog, self-as-default create location for non-NodeType nodes, sample orgs → Markdown for search visibility.
  • Workspace / stream robustness — Workspace remote-stream cache evicted on MeshChangeFeed events, resubscribe on owner dispose, DeleteLayoutArea emits a placeholder immediately and times out slow streams.
  • Infra & small fixes — settings.json overhaul, Delete-is-recursive MCP docs, HeartBeat silencing on Memex hubs, assembly-dir temp-dir fallback, IAsyncEnumerable aggregator fixes (satellite-safe GatherInputsAsync), xunit methodTimeout 30 s → 60 s, Anthropic Opus bump, icon generator, etc.

New test suites (selected)

  • test/MeshWeaver.Persistence.Test/MoveNodeRecursiveTest.cs — 10 tests: recursion, parallelism, source missing / target exists / storage throws / cancellation (all must not hang), Rx Timeout() contract, default-30s config.
  • test/MeshWeaver.Social.Test/*InMemoryPublishQueueTest, LinkedInPublisherEngagementTest, PostStatsRefresherTest, ScheduledPostPublisherTest, FakePublisher.
  • test/MeshWeaver.Persistence.Test/WorkspaceCacheEvictionTest.cs, ResubscribeOnOwnerDisposeTest.cs, DeleteLayoutAreaIntegrationTest.cs.
  • test/MeshWeaver.Markdown.Test/PathUtilsTest.cs, test/MeshWeaver.MathDemo.Test/MatrixViewsTest.cs.

Contributors

Upstream already merged into this branch

Test plan

  • dotnet build succeeds
  • dotnet test test/MeshWeaver.Persistence.Test --filter MoveNodeRecursiveTest — 10/10 green (~8 s)
  • dotnet test test/MeshWeaver.Hosting.Monolith.Test --filter MoveNodeAsync — 5/5 green (regression guard)
  • dotnet test test/MeshWeaver.Social.Test — publish queue / scheduling / stats green
  • Manual prod smoke: move a 3-descendant subtree in memex-prod; confirms < 30 s and MCP session survives
  • Create a _Source/*.cs using #r "nuget:MathNet.Numerics, 5.0.0" — compiles & renders (cold + warm cache)
  • Delete a node then recreate at same path — fresh grain, fresh compile, no stale HubConfiguration
  • Navigate to a cold node — "Compiling (Ns)…" progress renders until the stream resolves
  • LinkedIn OAuth: sign in → /social/connect/linkedin → profile linked; menu shows connected account
  • Scheduled post fires through ScheduledPostPublisher → LinkedIn publisher posts; PostStatsRefresher pulls stats

🤖 Generated with Claude Code

@github-actions

github-actions Bot commented Apr 22, 2026

Copy link
Copy Markdown

Test Results

   43 files  +    7     43 suites  +7   20m 29s ⏱️ + 14m 19s
4 698 tests +1 756  4 678 ✅ +1 749  7 💤  - 6  13 ❌ +13 
4 722 runs  +1 780  4 702 ✅ +1 773  7 💤  - 6  13 ❌ +13 

For more details on these failures, see this check.

Results for commit 10ed4fa. ± Comparison against base commit f6c2dea.

This pull request removes 261 and adds 2017 tests. Note that renamed tests count towards both.
MeshWeaver.AI.Test.AgentChatClientTest ‑ AgentChatClient_InitializeAsync_FindsAgentsFromPathHierarchy
MeshWeaver.AI.Test.AgentChatClientTest ‑ AgentChatClient_InitializeAsync_FindsTodoAgentFromNodeTypeNamespace
MeshWeaver.AI.Test.AgentSelectionTest ‑ AgentContext_WithPreloadedAgents_OrdersByOrder
MeshWeaver.AI.Test.AgentSelectionTest ‑ OrderByRelevance_OrdersByOrderThenDisplayName
MeshWeaver.AI.Test.AgentSelectionTest ‑ QueryAgentsAsync_PathWithoutNodeType_FindsAgentsFromPathHierarchy
MeshWeaver.AI.Test.AgentSelectionTest ‑ QueryAgentsAsync_ProductLaunchWithNodeType_FindsTodoAgentFromNodeTypeNamespace
MeshWeaver.AI.Test.AgentToolWiringIntegrationTest ‑ OrchestratorAgent_ShouldGetAllMeshTools
MeshWeaver.AI.Test.ThreadSubmissionUnitTest ‑ PlanNextRound_AfterInterruptedRound_ReturnsNewDispatchForQueuedInputs
MeshWeaver.AI.Test.ThreadSubmissionUnitTest ‑ PlanNextRound_IdleWithThreeQueued_ReturnsBatchedDispatch
MeshWeaver.Content.Test.ImportDeleteServiceTest ‑ FullLifecycle_CreateNodes_DeleteRecursively
…
Memex.Portal.Shared.Test.VirtualUserMiddlewareAuthContextTest ‑ AuthenticatedUserViaHttpContext_SkipsVUserBlock_AndCallsNext
Memex.Portal.Shared.Test.VirtualUserMiddlewareAuthContextTest ‑ UnauthenticatedHttpContext_EntersVUserBlock_ThrowsOnMissingPortalApplication
MeshWeaver.AI.Test.ActivityLogStreamTest ‑ Progress_Messages_Stream_Gradually_Not_Just_At_The_End
MeshWeaver.AI.Test.ActivityLogStreamTest ‑ Script_Failure_Flips_ActivityLog_Status_To_Failed
MeshWeaver.AI.Test.ActivityLogStreamTest ‑ Script_Log_Messages_Land_On_ActivityLog_Node
MeshWeaver.AI.Test.AgentChatClientDeadlockTest ‑ GetOrderedAgentsAsync_WithContextPath_ConcurrentCallers_DoNotDeadlock
MeshWeaver.AI.Test.AgentChatClientDeadlockTest ‑ GetOrderedAgentsAsync_WithContextPath_SingleCaller_ResolvesQuickly
MeshWeaver.AI.Test.AgentChatClientDeadlockTest ‑ GetOrderedAgentsAsync_WithMarkdownContext_DoesNotDeadlock
MeshWeaver.AI.Test.AgentChatClientTest ‑ AgentChatClient_Initialize_SurfacesPartitionAgents_NotNodeTypeNamespaceAgents
MeshWeaver.AI.Test.AgentChatClientTest ‑ AgentChatClient_Initialize_SurfacesPlatformAgentsForAnyContext
…
This pull request removes 7 skipped tests and adds 6 skipped tests. Note that renamed tests count towards both.
MeshWeaver.Import.Test.ImportValidationTest ‑ ImportWithCategoryValidationTest
MeshWeaver.Import.Test.SnapshotImportTest ‑ SnapshotImport_ZeroInstancesTest
MeshWeaver.Layout.Test.EditPersistenceTest ‑ EditAndPersist_NullableDateTime_ShouldPersistToDataStore
MeshWeaver.Layout.Test.EditPersistenceTest ‑ EditAndPersist_StringProperty_ShouldPersistToDataStore
MeshWeaver.Layout.Test.EditPersistenceTest ‑ WorkspaceStreamEmit_ShouldNotOverwriteLocalEdits
MeshWeaver.Persistence.Test.MigrationTest ‑ DryRun_ShowsWhatWouldBeMigrated
MeshWeaver.Persistence.Test.MigrationTest ‑ RunMigration_MigratesAllFiles
MeshWeaver.AI.Test.ClaudeCodeChatClientE2ETest ‑ RealCli_StreamsARealResponse
MeshWeaver.AI.Test.ConnectStrategyTest ‑ RealClaudeSetupToken_Gated_BehindEnvFlag
MeshWeaver.AI.Test.CopilotChatClientE2ETest ‑ RealCli_StreamsARealResponse
MeshWeaver.AI.Test.TypedErrorPropagationTest ‑ UnregisteredDiscriminator_SurfacesDeserializationException_OnSubscribe
MeshWeaver.AI.Test.UseWithoutSeeResolverTest ‑ Read_GrantsUse_NoRead_FailsClosed
MeshWeaver.Hosting.PostgreSql.Test.ConcurrentQueryDeadlockTests ‑ Query_ManyConcurrentBlockingSubscribers_AllComplete_NoDeadlock
This pull request skips 1 and un-skips 6 tests.
MeshWeaver.Content.Test.NewCommentFlowTest ‑ NewComment_DataChangeToWrongAddress_ShouldNotUpdateComment
MeshWeaver.Data.Test.SynchronizationStreamTest ‑ ParallelUpdate
MeshWeaver.Layout.Test.DebounceTest ‑ BasicDebounce
MeshWeaver.Layout.Test.EditorTest ‑ TestEditorWithDelayed
MeshWeaver.NodeOperations.Test.DeletionTests ‑ Delete_ViaClient_WithDeleteNodeRequest
MeshWeaver.NodeOperations.Test.NodeOperationsTest ‑ DeleteNode_WithChildren_NonRecursive_ShouldFail
MeshWeaver.Threading.Test.StreamingAreaTest ‑ StreamingArea_WhenExecutionCompletes_ReturnsNull

♻️ This comment has been updated with latest results.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Pull request overview

This PR bundles several long-running feature and stability tracks across MeshWeaver core + Memex: social publishing foundations, in-process #r "nuget:..." compilation support (node-type + interactive markdown), move-operation performance/timeout hardening, and multiple UI/stream reliability improvements. It also standardizes the code folder naming from _Source/_Test to Source/Test across code, tests, docs, and samples.

Changes:

  • Introduces MeshWeaver.Social (options, DI wiring, publish queue, credential model) plus initial Memex wiring (LinkedIn connect entry points + user menu hooks).
  • Adds MeshWeaver.NuGet resolver + directive parser and integrates it into script compilation (#r "nuget:Pkg, Version"), including cache backends and tests.
  • Improves operational robustness: parallelized recursive moves, default 30s mesh-op timeout, “no endless spinner” navigation status UI, and remote stream resubscribe behavior.

Reviewed changes

Copilot reviewed 159 out of 265 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
test/MeshWeaver.StorageImport.Test/StorageImporterTests.cs Updates test expectations/docs to Source/ naming.
test/MeshWeaver.Social.Test/PostStatsRefresherTest.cs Adds stats refresher test coverage (needs deterministic timeout handling).
test/MeshWeaver.Social.Test/MeshWeaver.Social.Test.csproj Adds new Social test project referencing Social + Fixture.
test/MeshWeaver.Social.Test/InMemoryPublishQueueTest.cs Adds unit tests for publish queue due-drain + dedup.
test/MeshWeaver.Persistence.Test/FileSystemPersistenceTest.cs Updates partition tests to Source/ naming.
test/MeshWeaver.MathDemo.Test/TestPaths.cs Adds helper paths for MathDemo sample test assets.
test/MeshWeaver.MathDemo.Test/MeshWeaver.MathDemo.Test.csproj Adds MathDemo test project and copies sample graph data to output.
test/MeshWeaver.Hosting.PostgreSql.Test/SatelliteQueryTests.cs Updates code-path routing tests to Source/ naming.
test/MeshWeaver.Hosting.Monolith.Test/UserActivityAreaTest.cs Updates regression test docs to Source/ naming.
test/MeshWeaver.Hosting.Blazor.Test/NavigationServiceTest.cs Adjusts test to assert “no 404 flash” during retries.
test/MeshWeaver.Graph.Test/NuGetDirectiveParserTest.cs Adds unit tests for parsing/stripping #r "nuget:...".
test/MeshWeaver.Graph.Test/NuGetAssemblyResolverTest.cs Adds networked NuGet restore end-to-end tests (skippable via env var).
test/MeshWeaver.Graph.Test/MeshWeaver.Graph.Test.csproj References new MeshWeaver.NuGet project.
test/MeshWeaver.FutuRe.Test/MeshWeaver.FutuRe.Test.csproj Updates compile-included sample sources to Source/ paths.
test/MeshWeaver.Content.Test/CompilationErrorTest.cs Updates broken-code test to Source/ path.
test/MeshWeaver.AI.Test/MeshPluginTest.cs Updates MCP tool count expectations (adds RunTests/Move/Copy).
src/MeshWeaver.Social/SocialOptions.cs Adds configurable knobs for publishing/stats/ingest scheduling.
src/MeshWeaver.Social/SocialExtensions.cs Adds DI wiring for social publishing subsystem and hosted services.
src/MeshWeaver.Social/PlatformCredential.cs Adds credential record model (access/refresh/expiry metadata).
src/MeshWeaver.Social/MeshWeaver.Social.csproj Introduces Social library project.
src/MeshWeaver.Social/IPublishQueue.cs Adds publish queue abstraction + in-memory implementation.
src/MeshWeaver.Social/IApprovalPublishBridge.cs Defines bridge contract and PublishableSnapshot model.
src/MeshWeaver.NuGet/ResolvedPackageSet.cs Adds resolver output model (assemblies, probing dirs, versions).
src/MeshWeaver.NuGet/NuGetServiceCollectionExtensions.cs Adds DI extension to register resolver + cache.
src/MeshWeaver.NuGet/NuGetPackageReference.cs Adds package reference model (id + version range).
src/MeshWeaver.NuGet/NuGetDirectiveParser.cs Implements #r "nuget:..." extraction + source stripping.
src/MeshWeaver.NuGet/MeshWeaver.NuGet.csproj Introduces NuGet resolver project and dependencies.
src/MeshWeaver.NuGet/INuGetPackageCache.cs Adds optional persistent cache interface + null implementation.
src/MeshWeaver.NuGet/INuGetAssemblyResolver.cs Adds resolver interface returning ResolvedPackageSet.
src/MeshWeaver.NuGet.AzureBlob/MeshWeaver.NuGet.AzureBlob.csproj Adds Azure Blob cache backend project.
src/MeshWeaver.NuGet.AzureBlob/BlobNuGetPackageCacheExtensions.cs Adds DI helper to register blob-backed cache.
src/MeshWeaver.Mesh.Contract/Services/MeshOperationOptions.cs Adds mesh operation timeout options (default 30s).
src/MeshWeaver.Mesh.Contract/Services/IStorageAdapter.cs Updates docs/examples to Source/ naming.
src/MeshWeaver.Mesh.Contract/Services/INavigationService.cs Adds Status observable contract for UI progress reporting.
src/MeshWeaver.Mesh.Contract/Services/IIconGenerator.cs Adds icon generator abstraction returning an observable SVG.
src/MeshWeaver.Mesh.Contract/PartitionDefinition.cs Updates standard table mappings (Source/Testcode) and clarifies semantics.
src/MeshWeaver.Mesh.Contract/MeshExtensions.cs Adds timeout override + move timeout enforcement + grain dispose on delete.
src/MeshWeaver.Mesh.Contract/CodeConfiguration.cs Updates docs to Source/ naming.
src/MeshWeaver.Kernel.Hub/MeshWeaver.Kernel.Hub.csproj Removes Interactive package mgmt dependency; references MeshWeaver.NuGet.
src/MeshWeaver.Hosting/Persistence/MigrationUtility.cs Updates migration heuristics to include Source/Test + legacy _Source/_Test.
src/MeshWeaver.Hosting/Persistence/FileSystemStorageAdapter.cs Treats Source/Test as code paths + keeps legacy compatibility.
src/MeshWeaver.Hosting/Persistence/FileSystemPersistenceService.cs Parallelizes descendant move I/O (with concurrency implications).
src/MeshWeaver.Hosting/Persistence/CachingStorageAdapter.cs Updates code sub-namespace detection (Source/Test + legacy).
src/MeshWeaver.Hosting.PostgreSql/PostgreSqlPartitionedStoreFactory.cs Guards against source/test mistakenly becoming schemas.
src/MeshWeaver.Hosting.PostgreSql/PostgreSqlCrossSchemaQueryProvider.cs Filters malformed parameters to avoid NRE during SQL interpolation.
src/MeshWeaver.Hosting.Blazor/MeshWeaver.Hosting.Blazor.csproj Adds NU1510 suppression.
src/MeshWeaver.Graph/PartitionTypeSource.cs Updates docs to Source/ naming.
src/MeshWeaver.Graph/MeshWeaver.Graph.csproj References MeshWeaver.NuGet.
src/MeshWeaver.Graph/MeshNodeLayoutAreas.cs Improves create href behavior + reactive/grouped children catalog.
src/MeshWeaver.Graph/MeshDataSource.cs Updates docs to Source/ naming.
src/MeshWeaver.Graph/Configuration/ScriptCompilationService.cs Integrates NuGet directive parsing + resolver into compilation.
src/MeshWeaver.Graph/Configuration/NodeTypeDefinition.cs Updates docs/examples to Source/ naming.
src/MeshWeaver.Graph/Configuration/MeshDataSourceNodeType.cs Changes sources namespace constant to Source.
src/MeshWeaver.Graph/Configuration/GraphConfigurationExtensions.cs Registers NuGet resolver and uses Source code path.
src/MeshWeaver.Graph/Configuration/CodeNodeType.cs Treats Code nodes as primary content; defines Source/Test constants.
src/MeshWeaver.Documentation/Data/DataMesh/UnifiedPath.md Documents @/ semantics and HTML-href pitfalls.
src/MeshWeaver.Documentation/Data/DataMesh/SocialMedia/Profile/Source/SocialMediaProfileLayoutAreas.cs Adds SocialMedia profile layout areas example.
src/MeshWeaver.Documentation/Data/DataMesh/SocialMedia/Profile/Source/SocialMediaProfile.cs Adds SocialMedia profile content model example.
src/MeshWeaver.Documentation/Data/DataMesh/SocialMedia/Post/Source/SocialMediaPost.cs Adds SocialMedia post content model example.
src/MeshWeaver.Documentation/Data/DataMesh/SocialMedia/Post/Source/Platform.cs Adds SocialMedia platform reference-data example.
src/MeshWeaver.Documentation/Data/DataMesh/SocialMedia.md Updates docs to Source/ naming and authoring guidance.
src/MeshWeaver.Documentation/Data/DataMesh/SatelliteEntities.md Clarifies Source/Test are primary content, not satellites.
src/MeshWeaver.Documentation/Data/DataMesh/NodeTypes.md Adds Node Types documentation index page.
src/MeshWeaver.Documentation/Data/DataMesh/NodeTypeConfiguration.md Updates docs to Source/ naming.
src/MeshWeaver.Documentation/Data/DataMesh/NodeOperations.md Updates docs to Source/ naming.
src/MeshWeaver.Documentation/Data/DataMesh/DataConfiguration.md Updates docs to Source/ naming.
src/MeshWeaver.Documentation/Data/DataMesh/CreatingNodeTypes.md Updates docs to Source/Test naming throughout.
src/MeshWeaver.Documentation/Data/DataMesh.md Updates TOC links and adds NuGet packages bullet.
src/MeshWeaver.Documentation/Data/Architecture/PartitionedPersistence.md Updates persistence routing docs for Source/Test.
src/MeshWeaver.Documentation/Data/Architecture/MeshGraph.md Updates examples to Source/ naming.
src/MeshWeaver.Documentation/Data/Architecture/BusinessRules/Cession/Source/CessionSampleData.cs Adds cession sample dataset for docs/demo.
src/MeshWeaver.Documentation/Data/Architecture/BusinessRules/Cession/Source/CessionResultsArea.cs Adds reactive charting layout area example.
src/MeshWeaver.Documentation/Data/Architecture/BusinessRules/Cession/Source/CessionEngine.cs Adds pure business logic sample for cession calculations.
src/MeshWeaver.Documentation/Data/Architecture/BusinessRules/Cession/Source/CessionData.cs Adds content models for cession example.
src/MeshWeaver.Data/Serialization/SyncStreamOptions.cs Adds configurable heartbeat interval for sync streams.
src/MeshWeaver.Data/Serialization/JsonSynchronizationStream.cs Implements resubscribe-on-owner-dispose logic.
src/MeshWeaver.Blazor/Pages/ApplicationPage.razor Switches to NavigationStatus-driven progress/not-found/error UI.
src/MeshWeaver.Blazor/Components/NavigationProgressBar.razor.css Adds styling for full-page vs compact overlay progress bar.
src/MeshWeaver.Blazor/Components/NavigationProgressBar.razor Adds reusable “spinner + message” component.
src/MeshWeaver.Blazor/Components/MeshSearchView.razor.cs Adds Category grouping fallback to NodeType.
src/MeshWeaver.Blazor/Components/LayoutAreaView.razor.cs Adds stream lifecycle logging and additional diagnostics.
src/MeshWeaver.Blazor/Components/LayoutAreaView.razor Surfaces compilation progress indicator before first stream emission.
src/MeshWeaver.Blazor/Components/CompileProgressIndicator.razor.css Adds styling for compilation progress banner.
src/MeshWeaver.Blazor/Components/CompileProgressIndicator.razor Adds polling UI component for active NodeType compilation.
src/MeshWeaver.Blazor.Portal/MeshWeaver.Blazor.Portal.csproj Adds NU1510 suppression.
src/MeshWeaver.Blazor.AI/MeshWeaver.Blazor.AI.csproj Adds NU1510 suppression.
src/MeshWeaver.Blazor.AI/McpMeshPlugin.cs Adds Patch/Move/Copy MCP tools and improves tool descriptions.
src/MeshWeaver.AI/ThreadLayoutAreas.cs Adds debug logging around streaming view emission.
src/MeshWeaver.AI/IconGenerator.cs Adds default AI-backed IIconGenerator implementation.
src/MeshWeaver.AI/DelegationCompletedEvent.cs Removes delegation tracker/event types.
src/MeshWeaver.AI/Data/Agent/Worker.md Updates @/ link guidance (no raw HTML href with @/).
src/MeshWeaver.AI/Data/Agent/ToolsReference.md Updates @/ link guidance and provides correct/incorrect table.
src/MeshWeaver.AI/Data/Agent/Orchestrator.md Updates @/ link guidance for agent outputs.
src/MeshWeaver.AI/AIExtensions.cs Removes old type registration; registers IIconGenerator.
memex/aspire/Memex.Portal.Distributed/Program.cs Registers blob-backed NuGet package cache in distributed deployment.
memex/aspire/Memex.Portal.Distributed/Memex.Portal.Distributed.csproj References MeshWeaver.NuGet.AzureBlob.
memex/aspire/Memex.Database.Migration/Program.cs Adds source/test to reserved schema list.
memex/aspire/Memex.AppHost/Program.cs Adds LinkedIn secret/env wiring + sets NUGET_PACKAGES cache dir.
memex/Memex.Portal.Shared/Social/SocialMediaUserMenuProvider.cs Adds “Social Media” shortcut on a user’s own node (lazy hub creation).
memex/Memex.Portal.Shared/Social/ApiCredentialNodeType.cs Adds NodeType for PlatformCredential stored under _ApiCredentials.
memex/Memex.Portal.Shared/Pages/Login.razor Adds “Connect LinkedIn for publishing” CTA on login page.
memex/Memex.Portal.Shared/OrganizationNodeType.cs Switches to default layout areas registration.
memex/Memex.Portal.Shared/MemexConfiguration.cs Adds LinkedIn publisher wiring, @/ redirect middleware, and routes.
memex/Memex.Portal.Shared/Memex.Portal.Shared.csproj References MeshWeaver.Social.
memex/Memex.Portal.Monolith/appsettings.Development.json Enables debug logging for LayoutAreaView.
MeshWeaver.slnx Adds new projects (NuGet, NuGet.AzureBlob, Social, new test projects).
Directory.Packages.props Adds NuGet.* package versions for resolver implementation.
CLAUDE.md Documents @/ local-only rule and href/URL restrictions.
(Various) samples/Graph/... Adds/updates many sample NodeTypes and content under Source/ to reflect new conventions and demos.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread test/MeshWeaver.Social.Test/PostStatsRefresherTest.cs
Comment thread src/MeshWeaver.Hosting/Persistence/FileSystemPersistenceService.cs Outdated
rbuergi added a commit that referenced this pull request Apr 22, 2026
…+ test helpers

Recursive DeleteNodeRequest handled on a node's own hub was deadlocking: the final
DeleteSelfFromStorage posted Ok and DisposeRequest from the dying hub, so the Ok
raced callback disposal on the caller and was lost. Introduce CommitNodeDeletionMessage
and forward the terminal commit (storage delete + reply + grain dispose) to the
resolved mesh hub (walking ParentHub upward) — Sender becomes the stable mesh hub,
FIFO on the caller's inbound queue guarantees Ok resolves the RegisterCallback before
DisposeRequest arrives.

Also addresses two Copilot review comments on PR #95:

- FileSystemStorageAdapter.DeleteAsync empty-directory ascent is now concurrency-
  tolerant: wraps the enumerate + Directory.Delete in try/catch, swallowing the
  DirectoryNotFoundException race and breaking on IOException (non-empty / in-use).
  Required because FileSystemPersistenceService.MoveNodeAsync now parallelizes
  descendant deletes via Task.WhenAll.

- PostStatsRefresherTest.WaitUntilAsync throws TimeoutException with a descriptive
  message instead of returning silently on deadline, so the test cannot green-tick
  a stats-refresh that never happened.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@rbuergi

rbuergi commented Apr 27, 2026

Copy link
Copy Markdown
Contributor Author

@copilot resolve the merge conflicts in this pull request

Copilot AI commented Apr 27, 2026

Copy link
Copy Markdown
Contributor

@copilot resolve the merge conflicts in this pull request

Resolved. The merge with origin/main is done in commit 6899990.

Conflicts resolved:

  • .claude/settings.json — kept HEAD deletion
  • Login.razor — kept NavigateToConnectLinkedIn (HEAD) + switched to OnInitializedAsync (origin/main)
  • AppHost/Program.cs — merged origin/main's optional-secrets pattern (nullable embeddingKey/googleClientSecret, customDomainValue guard) with HEAD's LinkedIn params (also made optional/nullable) and local-test/local-prod storage branch
  • Memex.Database.Migration/Program.cs — kept HEAD's data-repair v8 (fix ThreadMessage.MainNode) and v9 (rename _Source/_Test path segments)
  • SecurityService.cs — kept HEAD's refactored CollectStaticRoleIds returning (roleIds, cap); origin/main's permission-evaluation logic is already present in the new reactive GetEffectivePermissions method

@rbuergi rbuergi requested a review from Copilot May 10, 2026 05:41

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot wasn't able to review this pull request because it exceeds the maximum number of files (300). Try reducing the number of changed files and requesting a review from Copilot again.

@rbuergi rbuergi requested a review from Copilot May 10, 2026 06:49

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Copilot wasn't able to review this pull request because it exceeds the maximum number of files (300). Try reducing the number of changed files and requesting a review from Copilot again.

@rbuergi

rbuergi commented May 10, 2026

Copy link
Copy Markdown
Contributor Author

Code review — recent stability batch

Status: ✅ All 11 items in this comment addressed. See per-item commit SHAs in each header. Verification: Memex.Portal.Distributed builds clean; the four tests covering these changes (IsExecutingLifecycleTest, ChatHistoryTest ×2, CancelThreadExecutionTest) pass locally.

Manual review of the last ~20 commits since 8c5f37c80 (the doc commit). Focused on the synced-query consolidation, multi-query UNION feature, ThreadExecution refactor, and new tests. Copilot's two prior comments are already addressed in code. Findings below are grouped by severity.

Correctness — should fix before merge

1. ✅ e68636aacPostgreSqlStorageAdapter.QueryNodesAsync(IReadOnlyList<ParsedQuery>, …) — parameter-rename can mangle SQL.
File: src/MeshWeaver.Hosting.PostgreSql/PostgreSqlStorageAdapter.cs (the new UNION overload, ~line 530).

foreach (var (k, v) in perParams)
{
    var newKey = "@" + prefix + k.TrimStart('@');
    renamedSql = renamedSql.Replace(k, newKey);
    renamedParams[newKey] = v;
}

Dictionary<string,object> enumeration order is not guaranteed. If perParams contains both @p and @p1, processing @p first turns @p1 in the SQL into @q0_p1 (correct); processing @p1 first turns the SQL's @p1 into @q0_p1, then processing @p mangles @q0_p1 into @q0_q0_p1. Mixed-order builds will silently drift. string.Replace also clobbers @… substrings inside string literals or JSONB path comparisons.

Fix: single regex pass keyed on @<name> word boundary, gated on perParams.ContainsKey so we don't rewrite literal @ tokens.

2. ✅ e68636aacUNION (vs UNION ALL) dedup is row-wise, not path-wise.
Same file, same overload. The comment claims "same path emitted by two queries collapses to one row, matching the engine's path-keyed dictionary fold" — but UNION only collapses rows that are byte-identical across all selected columns. Two queries returning the same MeshNode with a slightly-different LastModified (concurrent writer) won't dedup.

Fix: UNION ALL wrapped in SELECT DISTINCT ON (namespace, id) … ORDER BY namespace, id, last_modified DESC. (No literal path column is projected; (namespace, id) is the path-keyed identity tuple. Newest version wins the tie-break.)

3. ✅ e68636aacPostgreSqlMeshQuery.ObserveQuery<T> ignores request.Queries for change detection.
src/MeshWeaver.Hosting.PostgreSql/PostgreSqlMeshQuery.cs:360-401. The method parsed only request.Query (single string), and the change-notifier filter used the first query's normalizedBasePath + effectiveScope for PathMatcher.ShouldNotify. Multi-query observations correctly fanned out to all queries inside CollectQueryResultsAsync, but live updates that match only query #2's path/scope wouldn't trigger a re-run.

Fix: parse every query in request.EffectiveQueries, build per-query (basePath, scope) filters, OR-join them in the change-notifier subscription.

4. ✅ e68636aacMeshQueryEngine Activity post-filter uses only first query's basePath.
src/MeshWeaver.Hosting/Persistence/Query/MeshQueryEngine.cs:125-138, 183-196. When parsedQuery.Source == QuerySource.Activity, the post-filter scanned descendants of firstBasePath for Activity satellites — queries #2+ with unrelated basePaths had their Activity matches filtered against the wrong subtree.

Fix: CollectMatchedAsync returns the list of every query's basePath; the activity post-filter scans every base path's descendants and unions activity-main-paths.

Race / lifecycle hazards

5. ✅ 478fdaa93ThreadExecution.RecoverStaleExecutingThread 2-minute window contradicts "no time limits" commit.
src/MeshWeaver.AI/ThreadExecution.cs:175-180. Commit 6dc436bf5 made the policy explicit, but recovery still said "Only recover truly stale ones (started > 2 minutes ago or no timestamp)." A legitimate slow execution that crashes after 2+ minutes wouldn't be recovered → IsExecuting=true forever.

Fix: drop the time-based heuristic in favour of a structural one — skip recovery only when the thread is still an auto-execute candidate (PendingUserMessage + ActiveMessageId set, i.e. WatchForExecution will pick it up).

6. ✅ 478fdaa93Subject<StreamingSnapshot> not disposed.
src/MeshWeaver.AI/ThreadExecution.cs:890. Fix: using var snapshots = new Subject<…>().

7. ✅ eea8ed10a — Sample(100ms) terminal-status race regression test.
The terminal-status guard correctly prevents Streaming from regressing Completed/Cancelled/Error in PushToResponseMessage. Fix: added a regression assertion in IsExecutingLifecycleTest that final ThreadMessage.Status == Completed after a successful echo run.

8. ✅ 478fdaa93HandleCancelStream runs after CTS-storage race.
src/MeshWeaver.AI/ThreadExecution.cs:1284-1289. parentHub.Set(executionCts) happened around line 847, but IsExecuting=true flipped earlier in HandleSubmitMessage. A cancel arriving in that window was a no-op.

Fix: pre-allocate the CancellationTokenSource and store it on the thread hub in HandleSubmitMessage before posting SubmitMessageResponse. ExecuteMessageAsync reuses it from the parent-hub slot (with a fresh-CTS fallback for the auto-execute path that bypasses HandleSubmitMessage).

Style / consistency

9. ✅ 478fdaa93 — Triple-stacked <summary> XML doc tags.
Collapsed both blocks (WatchForExecution, NotifyParentCompletion) to a single <summary>.

10. ✅ eea8ed10aIsExecutingLifecycleTest text-pattern wait inconsistent with ChatHistoryTest.
Fix: migrated to ThreadMessage.CompletedAt is not null — same pattern as ChatHistoryTest.SubmitAndWait after commit ab3af8b70.

11. ✅ e68636aac — Limit-on-first-query semantics.
request.Limit was applied only to parsedList[0]; query #0 could hit its limit before yielding its most relevant rows while queries #1+ contributed unbounded — making the result iteration-order dependent.

Fix: drop the per-query Limit injection. Limit is enforced post-union via MinLimit(request.Limit, firstParsed.Limit) in both engines, so a request-level cap can't be circumvented and an in-query limit:N still wins when smaller.

✅ Looks good (no action needed)

  • SyncedQueryMeshNodes doc-comment now matches the dict-from-query-events fold (post the doc commit).
  • LoadFullConversationHistoryFromMesh correctly reads the live thread's Messages list and resolves each cell via GetMeshNodeStream (per-node hub) — sidesteps the stale-index race the comment calls out.
  • MultiQueryUnionEngineTests covers the union semantics on the in-memory engine without needing a testcontainer.
  • CancelThreadExecutionTest rewrite (commit-pending) correctly uses "Generating response..." as the CTS-armed signal.
  • The terminal-status guard pattern (current.Status is Completed or Cancelled or Error && requestedStatus == Streaming → keep current) is the right shape.

@rbuergi

rbuergi commented May 10, 2026

Copy link
Copy Markdown
Contributor Author

Code review — part 2: rest of the PR

Status: ✅ All 12 items in this comment addressed. See per-item commit SHAs in each header. NuGet validation in #14 was deferred at first then closed in 6c3e60925.

Continuing review on the bulk of the PR (everything before the recent stability batch). Focused on the new projects (MeshWeaver.NuGet, MeshWeaver.Social) and a sampling of the central MessageHub refactor — the full 100-commit / 1006-file diff is too large for an exhaustive read. Same severity grouping as part 1.

Correctness — should fix before merge

12. ✅ 512adb462NuGetAssemblyResolver caches faulted Tasks forever.
src/MeshWeaver.NuGet/NuGetAssemblyResolver.cs:42.

return _cache.GetOrAdd(key, _ => ResolveCoreAsync(requested, framework, ct));

If ResolveCoreAsync threw, the faulted Task<ResolvedPackageSet> stayed in the cache; subsequent calls replayed the same exception forever.

Fix: evict faulted/cancelled tasks from the cache before returning. Also pass CancellationToken.None to the shared core task so a single caller's cancellation can't take down the resolution for everyone else; per-caller ct projects via task.WaitAsync(ct).

13. ✅ 512adb462NuGetAssemblyResolver resolves with DependencyBehavior.Lowest.
src/MeshWeaver.NuGet/NuGetAssemblyResolver.cs:74. "Lowest" pulls minimum-satisfying versions transitively, which yanks in EOL/unpatched releases when constraints have weak floors.

Fix: switched to DependencyBehavior.HighestMinor so security fixes flow in transparently without crossing minor/major boundaries.

14. ✅ 6c3e60925 — Hydrated package not validated.
After INuGetPackageCache.TryHydrateAsync returned true, the resolver trusted the content — a poisoned cache entry (different package stored under wrong key) would silently load wrong assemblies.

Fix: post-hydration, the resolver opens the package folder via PackageFolderReader.GetIdentity() and verifies the .nuspec-declared (id, version) matches expected. On mismatch the directory is purged and the resolver falls back to the feed download path. No INuGetPackageCache contract change needed.

15. ✅ 478fdaa93XPublisher.PublishAsync crashes on partial response.
src/MeshWeaver.Social/XPublisher.cs:71. The chained GetProperty("data").GetProperty("id") threw KeyNotFoundException on unexpected body shapes.

Fix: defensive TryGetProperty chain; logs a warning and returns id = null (caller treats as "publish succeeded but URN couldn't be captured") instead of crashing. Also guards against null AuthorHandle.

16. ✅ 478fdaa93 (LinkedIn) + 512adb462 (X) — Publishers don't auto-retry on token-refresh race.
Fix: SendWith401RetryAsync helper in both publishers — on 401, force-refresh the token (zero ExpiresAt so EnsureFreshAsync doesn't short-circuit) and retry the request once.

Race / lifecycle hazards

17. ✅ 512adb462PostStatsRefresher processes targets sequentially.
Fix: Parallel.ForEachAsync bounded by SocialOptions.StatsRefreshDegreeOfParallelism (default 8).

18. ✅ 512adb462PostStatsRefresher has no per-target backoff.
Fix: ConcurrentDictionary<string, DateTimeOffset> of last-failure timestamps. Targets that failed within SocialOptions.StatsRefreshFailureBackoff (default 15 min) skip the next tick. Success clears the entry so the target rejoins normal cadence.

19. ✅ df1939bb7MessageHub faulted-Task cache pattern.
The MESHWEAVER_DISPOSE_TRACE=1 global file lock + per-call File.AppendAllText serialised hub teardown when many hubs disposed concurrently.

Fix: replaced with a single bounded Channel<string> (4096, FullMode = DropWrite) drained by one writer task started in the type initialiser. Producers TryWrite non-blocking; lines drop on full so a stuck writer never delays dispose.

Style / consistency

20. ✅ 478fdaa93SocialExtensions.AddSocialPublishing lifetime mismatch.
AddHttpClient<LinkedInPublisher>() registered the typed client as transient; the IPlatformPublisher factory then made it singleton — direct vs via-interface resolution returned different instances.

Fix: register the publisher as a true singleton via services.AddSingleton(sp => new LinkedInPublisher(httpFactory.CreateClient(...), ...)). Same for X. Both IPlatformPublisher and concrete-type resolution return the same instance.

21. ✅ 478fdaa93SocialExtensions claims "all-or-nothing" but isn't.
The four AddHostedService<…> calls were unconditional even with zero platforms configured.

Fix: gate hosted-service registration on anyConfigured; with zero platforms, no hosted services start.

22. ✅ 478fdaa93LinkedInPublisher uses dynamic to peek at typed-anonymous fields.
Fix: two concrete payload shapes in if/else branches; no dynamic dispatch; typos surface as compile errors instead of RuntimeBinderException.

23. ✅ 478fdaa93 — PII / user-content in error logs.
Fix: Truncate(b, 200) on logged error bodies in both publishers (LinkedIn publish + token refresh, X publish). Full body still goes to PublishResult.Error for the caller.

✅ Looks good (no action needed)

  • NuGetAssemblyResolver correctly caches by (framework, sorted package list) so repeated #r invocations don't re-walk dependencies.
  • MessageHub AsyncSubject pattern fixes the long-standing "subscribe before vs after response" race in the old RegisterCallback.
  • LinkedInPublisher correctly handles the LinkedIn x-restli-id header fallback and only falls back to JSON body parsing when the header is missing.
  • SocialOptions defaults look reasonable (60s publish tick, 30m stats tick, 30d window).
  • EnsureFreshAsync returns a refreshed PlatformCredential to the caller rather than mutating internal state — caller decides where to persist.

Areas not covered in this review

Persistence-service refactors (IStorageService, MeshNodeEditor, NavigationService changes), the +850-line MessageHub core-dispatch refactor in detail, content-collection changes, NodeType compilation pipeline beyond what part 1 touched. Flag a specific subsystem if a deeper review is wanted.

@rbuergi

rbuergi commented May 10, 2026

Copy link
Copy Markdown
Contributor Author

Review fixes applied — all 23 items addressed

5 commits, organised by batch. Locally committed, not pushed yet.

# Item Commit
1 UNION SQL param-rename regex pass e68636aac
2 UNION ALL + DISTINCT ON (namespace, id) for path-keyed dedup e68636aac
3 ObserveQuery change-notifier OR-joined per-query filters e68636aac
4 MeshQueryEngine Activity post-filter scans every basePath e68636aac
5 RecoverStaleExecutingThread structural guard (drop time-based heuristic) 478fdaa93
6 using var on Subject<StreamingSnapshot> 478fdaa93
7 Regression assertion: final ThreadMessage.Status == Completed eea8ed10a
8 Pre-allocate CancellationTokenSource in HandleSubmitMessage 478fdaa93
9 Collapse triple-stacked <summary> blocks 478fdaa93
10 IsExecutingLifecycleTest waits on CompletedAt, not text patterns eea8ed10a
11 Limit-on-first-query semantics: enforce post-union via MinLimit e68636aac
12 NuGetAssemblyResolver evicts faulted/cancelled cache entries 512adb462
13 NuGet DependencyBehavior.HighestMinor (was Lowest) 512adb462
14 Hydrated-cache validation note (deferred — needs INuGetPackageCache change) 512adb462
15 XPublisher defensive TryGetProperty chain 478fdaa93
16 LinkedIn / X publishers retry once on 401 with token refresh 478fdaa93 (LinkedIn structure), 512adb462 (X 401 retry parity)
17 PostStatsRefresher uses Parallel.ForEachAsync (DOP 8) 512adb462
18 Per-target failure backoff (15 min default) 512adb462
19 Channel-based dispose trace replaces global file lock df1939bb7
20 SocialExtensions: factory-resolved singleton publishers 478fdaa93
21 Hosted services gated on at least one configured platform 478fdaa93
22 LinkedIn dynamic→concrete payload shapes 478fdaa93
23 Cap error-body logs at 200 chars (LinkedIn + X) 478fdaa93

Verification

  • Solution build clean (memex/aspire/Memex.Portal.Distributed).
  • Tests I touched all pass locally:
    • IsExecutingLifecycleTest.SingleMessage_IsExecuting_FlipsTrueThenFalse_WithRealResponse — 11 s
    • ChatHistoryTest.ThreeMessages_AgentSeesFullHistory — 2 s
    • ChatHistoryTest.TwoMessages_NoDuplicates_CorrectRoles — 3 s
    • CancelThreadExecutionTest.CancelStream_StopsExecutionAndMarksAsCancelled — 3 s
  • The full MeshWeaver.Threading.Test suite has 4 unrelated pre-existing failures (not introduced by these commits — present on main as well).

Notes

  • Update release-packages.yml #14 (cache content validation) is documented as a TODO rather than implemented — INuGetPackageCache.TryHydrateAsync doesn't currently expose a content hash to verify against, so the fix needs a contract change. Flagged in code at NuGetAssemblyResolver.EnsureInstalledAsync.
  • Get rid of project dependencies in NotebookEditorControl #5 (recovery time window) swapped time-based heuristic for a structural one (PendingUserMessage + ActiveMessageId set → leave to WatchForExecution). Same intent, no time-bound failure mode.
  • [Snyk] Upgrade axios from 0.25.0 to 0.27.2 #8 (CTS race) required a structural change: HandleSubmitMessage now pre-allocates and stores the CTS before the response goes out, ExecuteMessageAsync reuses it from the parent hub slot. The auto-execute path (WatchForExecution) gets a fallback CTS if the slot is empty.

Ready to push when you want.

@rbuergi

rbuergi commented May 10, 2026

Copy link
Copy Markdown
Contributor Author

Done — review item #14 is now closed in commit 6c3e60925. The hydrated folder is validated via PackageFolderReader.GetIdentity() against the expected (id, version); on mismatch the directory is purged and the resolver falls back to the feed. No INuGetPackageCache contract change needed — validation is in the resolver. Total: 6 commits, all 23 review items addressed.

rbuergi added a commit that referenced this pull request May 10, 2026
…fix DI lifetimes, redact PII, drop dynamic

- ThreadExecution: collapse triple-stacked <summary> blocks on
  WatchForExecution and NotifyParentCompletion. Tooling kept the last
  one anyway; the dead scaffolding was just noise.
- SocialExtensions: register LinkedInPublisher / XPublisher as TRUE
  singletons (factory-resolved with named HttpClient). The previous
  AddHttpClient<T>+AddSingleton<IPlatformPublisher> mix made the
  concrete type transient while the interface alias was singleton —
  direct vs via-interface resolution returned different instances.
  Also gate hosted-service registration on at least one platform
  being configured (the "all-or-nothing" comment was wrong; with
  zero platforms the four hosted services started anyway and faulted
  on first tick).
- LinkedInPublisher: replace `(dynamic)media.shareMediaCategory`
  peek with two concrete payload shapes — typo turns into a compile
  error instead of a RuntimeBinderException.
- LinkedIn / X publishers: cap error-body logs at 200 chars to
  bound PII exposure (the body can echo the user's post text on
  validation rejection). Full body still goes to PublishResult.Error
  for the caller.

Addresses PR #95 review items #9, #20, #21, #22, #23.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
rbuergi added a commit that referenced this pull request May 10, 2026
… in-memory engines

PostgreSqlStorageAdapter.QueryNodesAsync(IReadOnlyList<ParsedQuery>):
  - Replace order-dependent `string.Replace` parameter rename with a
    single `Regex.Replace` keyed on @<name> word boundary that gates
    on perParams.ContainsKey. Sequential Replace was mangling adjacent
    tokens (renaming `@p` after `@p1` produced `@q0_q0_p1`) and could
    clobber `@…` substrings inside string literals / JSONB paths.
  - Switch from `UNION` to `UNION ALL` wrapped in
    `SELECT DISTINCT ON (namespace, id) ... ORDER BY namespace, id, last_modified DESC`.
    Plain UNION dedupes whole rows — two queries observing the same
    node at slightly-different last_modified would BOTH appear in the
    output. Path-keyed dedup (= MeshNode identity) with newest-wins
    tie-break collapses them correctly.

PostgreSqlMeshQuery.ObserveQuery<T>:
  - Parse EVERY query in request.EffectiveQueries and build per-query
    (basePath, scope) filters; the change-notifier subscription
    OR-joins them so multi-query observations get delta refreshes
    triggered by ANY query's path/scope, not just query #0's. The
    previous shape silently lost live updates from queries #1+.

PostgreSqlMeshQuery.QueryNodesUnionAsync + MeshQueryEngine:
  - Drop the per-query `parsedList[0].Limit = request.Limit` injection.
    Query #0 hit its limit before yielding the union's most relevant
    rows, while queries #1+ contributed unbounded — making the result
    iteration-order dependent. Limit is now enforced post-union via
    MinLimit(request.Limit, firstParsed.Limit) so a request-level cap
    can't be circumvented and an in-query `limit:N` still wins when
    smaller.
  - MeshQueryEngine: CollectMatchedAsync returns the LIST of every
    query's basePath; the source:activity post-filter scans every
    base path's descendants and unions activity-main-paths so
    queries #1+ aren't filtered against query #0's subtree only.

Addresses PR #95 review items #1, #2, #3, #4, #11.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
rbuergi added a commit that referenced this pull request May 10, 2026
…ThreadExecution stability fixes

ThreadExecution.cs (already in commit 478fdaa — recapping here for the
review-item index):
  - RecoverStaleExecutingThread: drop the 2-minute "fresh execution"
    window in favour of a structural check (skip when PendingUserMessage
    + ActiveMessageId are still set, i.e. the thread is an
    auto-execute candidate WatchForExecution will pick up). Closes the
    "long-running agent crashed at minute 5 → IsExecuting=true forever"
    gap; the time-based heuristic contradicted commit 6dc436b's
    "no time limits" stance.
  - Subject<StreamingSnapshot>: declare with `using var` so the
    Subject itself disposes alongside its subscription. Minor leak
    per execution previously.
  - HandleSubmitMessage: pre-allocate the per-round
    CancellationTokenSource and store it on the thread hub BEFORE
    posting SubmitMessageResponse — closes the race where an early
    Stop click between IsExecuting=true and ExecuteMessageAsync's
    `parentHub.Set(executionCts)` found a null CTS slot and
    silently no-op'd. ExecuteMessageAsync now reuses the
    pre-allocated CTS (with a fallback for the auto-execute path
    that bypasses HandleSubmitMessage).

IsExecutingLifecycleTest.cs:
  - Migrate the response-text wait from text-pattern matching
    (skipping placeholders "Allocating agent..." etc.) to
    `ThreadMessage.CompletedAt is not null`, which
    ExecuteMessageAsync sets only on the terminal
    PushToResponseMessage call. Same pattern adopted in
    ChatHistoryTest in commit ab3af8b.
  - Add a regression assertion that final
    ThreadMessage.Status == Completed. The terminal-status guard in
    PushToResponseMessage prevents the late Sample(100ms)-flushed
    Streaming push from regressing the cell from Completed back to
    Streaming; this assertion catches any future regression of that
    guard.

Addresses PR #95 review items #5, #6, #7, #8, #10.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
rbuergi added a commit that referenced this pull request May 10, 2026
…, parallelism, backoff)

NuGetAssemblyResolver:
  - Evict faulted/cancelled tasks from the per-key cache before
    returning. A transient feed failure (network, throttle, cancelled
    in-flight resolve) used to poison the cache for the resolver's
    lifetime — every subsequent call replayed the same exception.
  - Pass CancellationToken.None to the shared core task so a single
    caller's cancellation can't take down the resolution for
    others; per-caller `ct` projects via `task.WaitAsync(ct)`.
  - Switch DependencyBehavior from `Lowest` to `HighestMinor` so
    `#r` directives pick up patch-level security fixes via
    transitive dependencies without silently jumping major/minor.
  - Document that hydrated cache content is trusted to match
    (id, version) — flag for future content-hash verification if
    cache poisoning becomes a concern.

LinkedInPublisher / XPublisher (LinkedIn already committed in batch A
for the dynamic+PII parts; this commit adds the 401 retry):
  - SendWith401RetryAsync: on the FIRST 401 response from a publish,
    force-refresh the token (zero ExpiresAt before EnsureFreshAsync)
    and retry once. Closes the race where the access token's TTL
    expired between EnsureFreshAsync and the actual API call.

PostStatsRefresher:
  - Process due-refresh targets via Parallel.ForEachAsync bounded
    by SocialOptions.StatsRefreshDegreeOfParallelism (default 8),
    so a slow API + large refresh window can't let one tick
    overshoot the next interval.
  - Per-target failure backoff via a ConcurrentDictionary of
    last-failure timestamps — targets that failed within
    StatsRefreshFailureBackoff (default 15 min) skip the next tick.
    Stops a degraded platform from generating thousands of repeat
    warnings every cycle while the underlying issue is fixed.
    Success clears the backoff entry.

SocialOptions: add StatsRefreshDegreeOfParallelism (8) and
StatsRefreshFailureBackoff (15 min) knobs.

Addresses PR #95 review items #12, #13, #14, #16, #17, #18.
(#15 XPublisher defensive parse + the LinkedIn dynamic / PII items
were already in commit 478fdaa.)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
rbuergi added a commit that referenced this pull request May 10, 2026
… file lock

The MESHWEAVER_DISPOSE_TRACE=1 trace took a global lock per call
(`File.AppendAllText` under `lock (DisposeTraceLogLock)`), serialising
hub teardown under load when many hubs disposed concurrently.

Replaced with a single bounded `Channel<string>` (capacity 4096,
FullMode = DropWrite) drained by one writer task started in the
type initialiser. Producers `TryWrite` non-blocking — if the disk is
slow / locked, lines drop on full instead of putting back-pressure
on dispose. Single-reader semantics avoid contention on the file
handle.

Addresses PR #95 review item #19.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
rbuergi added a commit that referenced this pull request May 10, 2026
Replaces the TODO from commit 512adb4. After a successful
INuGetPackageCache.TryHydrateAsync, the resolver now opens the
hydrated folder via PackageFolderReader and compares the package's
own .nuspec-declared (id, version) against the expected (id, version).
On mismatch the directory is purged and the resolver falls back to
the feed.

This catches the failure modes #14 was about: wrong package stored
under right key (cross-tenant blob, accidental copy, drift after a
manual edit). The .nuspec is the canonical NuGet source of truth, so
a tampered cache entry can't fake the identity without rewriting the
nuspec — which we'd then catch at hydration time.

No INuGetPackageCache contract change; validation lives entirely in
the resolver.

Closes the last open item from PR #95 review (item #14).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@rbuergi

rbuergi commented May 26, 2026

Copy link
Copy Markdown
Contributor Author

@copilot resolve the merge conflicts in this pull request

@github-actions

github-actions Bot commented Jun 3, 2026

Copy link
Copy Markdown

Test Results (shard 1)

962 tests   960 ✅  4m 21s ⏱️
 11 suites    0 💤
 11 files      2 ❌

For more details on these failures, see this check.

Results for commit 10ed4fa.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Jun 3, 2026

Copy link
Copy Markdown

Test Results (shard 0)

   13 files     13 suites   11m 31s ⏱️
2 427 tests 2 414 ✅ 6 💤 7 ❌
2 451 runs  2 438 ✅ 6 💤 7 ❌

For more details on these failures, see this check.

Results for commit 10ed4fa.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Jun 3, 2026

Copy link
Copy Markdown

Test Results (shard 3)

322 tests   318 ✅  2m 35s ⏱️
  8 suites    0 💤
  8 files      4 ❌

For more details on these failures, see this check.

Results for commit 10ed4fa.

♻️ This comment has been updated with latest results.

@github-actions

github-actions Bot commented Jun 3, 2026

Copy link
Copy Markdown

Test Results (shard 2)

987 tests   986 ✅  2m 1s ⏱️
 11 suites    1 💤
 11 files      0 ❌

Results for commit 10ed4fa.

♻️ This comment has been updated with latest results.

rbuergi and others added 30 commits June 18, 2026 19:57
…eetTest referencing the deleted helper

The #r-decoupling revert (git checkout d0b80ea) restored PensionFundBalanceSheetTest.cs but
not the TestPaths.cs helper it references (CS0103), failing the CI build job → all shards skipped.
Restore the helper from the same revert baseline.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…clear message + Monaco error overlay

A NodeType whose source fails to compile no longer renders an indefinite spinner / silent wedge.
The Settings → Progress error branch now shows, per affected source file, a link to the Code node
and a read-only Monaco editor with the Roslyn errors MARKED at their exact line/column (the IDE-style
overlay), driven by structured per-file diagnostics captured from the compile.

- NodeTypeDefinition.CompilationDiagnostics: structured per-file DiagnosticInfo kept in native form
  (id/severity/message/position), not flattened to a string.
- MeshNodeCompilationService: on compile FAILURE ONLY, capture per-file diagnostics from one LSP-style
  per-file-tree compilation (reuses CompilationInputs); the working success emit is untouched. Threaded
  via NodeCompilationResult.Diagnostics into both compile write-back sites.
- CodeEditorControl.Diagnostics (+ CodeEditorDiagnostic): static markers rendered through the existing
  Monaco pushDiagnostics path — no JS change (enableDiagnostics fires an initial pass on load).
- NodeTypeLayoutAreas.BuildCompileErrorSourceViews: pure, testable builder for the ordered
  link+editor views per source file.
- Test: CompileErrorPageTest covers ordering, the source link, the clear message, and the marked editor.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…FX example

Adds a 'Scope' NodeType (ScopeNodeType) whose Content is the IScope<,> C# source (CodeConfiguration),
registered in AddGraph. CSharpFileParser now honours a '// NodeType: Scope' header so a Source .cs can
declare itself a first-class scope. Pairs with the generator gate (already in HEAD, 94ee0d9): the
BusinessRules scope generator runs only for IScope-bearing compiles, not every Code compile.

Includes an FX cross-rate conversion example (ICurrencyConversion : IScope<FxTrade, FxRates>) + test.

NOTE: folded-Scope source discovery (so a '// NodeType: Scope' Source node is pulled into the parent
compile) and STANDALONE scope compilation — the real fix for both the runtime-compile slowness (semantic
binding over the whole reference set) AND decoupling BusinessRules from low-level Graph — are the next step.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
GetContentSchema resolved the content type from tempHub's TypeRegistry but
generated the JSON schema with the PARENT hub's JsonSerializerOptions. That
options object carries a PolymorphicTypeInfoResolver bound to the parent's
registry, which does not own the compiled content type — so GetOrAddType fell
back to TypeRegistry.FormatType and emitted the namespace-qualified, capitalized
Type.FullName as every $type discriminator (e.g. "MeshWeaver.AI.Test.TestProduct"
instead of "TestProduct"). Generate the schema with the type-owning tempHub's
options so references resolve to the registered short name.

Repro pinned in SchemaValidationTest: the schema must not contain the CLR
FullName for the registered content type.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ph — kills build bloat

The generator is a Roslyn analyzer (IsRoslynComponent); a plain ProjectReference from the
foundational MeshWeaver.Graph propagated it to all 40+ downstream projects and ran it on every
compile — the project_graph_generator_build_bloat regression the 01a31ad revert reintroduced.

- Graph.csproj: remove the BusinessRules + BusinessRules.Generator references (Graph no longer
  has a compile-time dependency on either).
- RunSourceGenerators: load ScopeCodeGenerator at runtime BY NAME via reflection
  (TryCreateScopeGenerator) instead of `new ...ScopeCodeGenerator()`. No compile-time ref; the
  generator reaches the runtime via the node Source `#r nuget:MeshWeaver.BusinessRules.Generator`
  resolution / the deployed mesh-local package. Not resolvable -> scope generation skipped and the
  compile error surfaces on the NodeType Progress page.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Now that BusinessRules + BusinessRules.Generator are no longer ProjectReferenced by the
framework (they were the build-bloat source — a Roslyn analyzer propagating across Graph's
40+ consumers), node compilation must resolve them at runtime via `#r nuget:MeshWeaver.*`.
The committed root nuget.config already maps MeshWeaver.*/Memex.* to a local `dist/packages`
folder source; this ships that feed inside the container so the runtime resolver
(NuGetAssemblyResolver → Settings.LoadDefaultSettings from the app dir) resolves offline.

- Memex.Portal.Distributed.csproj: copy nuget.config + dist/packages/**/*.nupkg into the
  published /app (next to the app) so they're present at the resolver's working dir.
- The nupkgs are packed into dist/packages by the build process BEFORE publish — the
  `Pack mesh-local #r packages` step (dotnet pack BusinessRules + Generator -o dist/packages
  -p:PackageVersion=3.0.0-preview1), mirrored from .github/workflows/dotnet-test.yml.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…me (not a framework ref)

Completes the BusinessRules.Generator decoupling: the generator is supplied to dynamic node
compilation through the node Source `#r nuget:MeshWeaver.BusinessRules.Generator` (auto-injected
when the unit declares IScope<,>), resolved from the mesh-local feed, and discovered + run via
SourceGeneratorLoader — never a framework ProjectReference (which propagated the analyzer + bloated builds).

- SourceGeneratorLoader: discover [Generator] types from the node's #r-resolved assemblies (no static cache).
- RunSourceGenerators: DeclaresScope-gated; discovers from the #r-resolved paths (replaces the Type.GetType bridge).
- InjectScopeGeneratorReference: auto-prepend the generator #r for IScope units so existing Scope nodes
  compile without author boilerplate. Node-generation-only.
- BusinessRules.Generator.csproj: ALSO pack the generator under lib/ — the runtime #r resolver reads lib/ ONLY
  (NuGetAssemblyResolver -> GetLibItemsAsync); analyzers/dotnet/cs stays for OutputItemType=Analyzer consumers.
- Failure-diagnostics (DiagnoseInputs) pass no generator candidates (best-effort; the flat summary covers it).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…), never wedges; scope generator is explicit-#r only

THE atioz wedge root cause: CompileResultFromAssembly swallowed an assembly LOAD / type-extract
failure (e.g. BalanceSheet missing the MeshWeaver.Charting dependency at runtime) into a result with
a non-null AssemblyLocation + empty configurations. Both compile watchers classify success as
`Error is null && !IsNullOrEmpty(AssemblyLocation)` → that read as Status=Ok, so the emergency
compilation-error overlay (gated on Error) never fired, and the per-node hub tried to activate against
an unusable assembly → every Subscribe/GetData parked → wedge.

Fix, per the three rules:
- No assembly if it cannot build: both failure paths return AssemblyLocation=null.
- Build failed + no release: null location → ok=false → CompilationStatus=Error; release creation is
  already gated on ok, so none is written.
- No retry: the first-build kickoff is gated on CompilationStatus==null, so a settled Error never
  re-fires. The emergency overlay renders the failure on the Overview instead of parking.
- Surface the real problem: the extract-failure catch now includes ReflectionTypeLoadException
  .LoaderExceptions, so the Overview names the missing dependency.

Also (per directive): the BusinessRules scope generator is injected ONLY when the node Source EXPLICITLY
declares `#r nuget:MeshWeaver.BusinessRules.Generator` — removed the IScope text-scan auto-inject that
forced generator resolution on the compile path. Resolved from the baked mesh-local feed, discovered by
SourceGeneratorLoader. Never a framework reference.

Test: CompileErrorOverviewTest — a broken-type instance's Overview COMES BACK (bounded timeout, no hang)
SAYING it had a compilation error.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…) + fold content checks into reactive Match

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…emoryPersistence)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…Opus 4.8)

The Anthropic provider's bootstrap model list (DefaultModelIds, shown on Settings/Models) listed the stale claude-opus-4-7. EffectiveModelIds is the code default with no config override, so this is the source of truth for the card list. sonnet-4-6 and haiku-4-5-20251001 are current.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…point), not a pinned list

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…r rows

The picker showed each node's full path as a secondary line under the name; for models that path is _Provider/Anthropic/<id> (the provider), which is not a title and just confuses. Show name + category only; keep the full path as a hover tooltip for disambiguation. /model already queries nodeType:LanguageModel, so only models are listed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…MCP writes raw create rejects

Raw MCP create/patch validate content.$type against the hub they run on, so
they reject content types registered only on a dedicated per-type hub (e.g.
Invitation) and writes into restricted partitions (e.g. Admin). Document the
canonical work-through: a throwaway executable Code node + execute_script
calling the typed service (InvitationService.CreateInvitation), plus a
pitfalls-table row.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ock wedges

The per-key MessageStormBreaker only trips when ONE (sender,target,type) tuple storms;
every portal wedge this session was MANY DISTINCT keys whose aggregate saturated the
single-threaded action block. Add a per-hub inbound-depth watermark
(MessageStormBreaker.ShouldShedAggregate) that sheds sheddable ([CanBeIgnored],
non-lifecycle) traffic once the queue crosses the watermark, keeping the turn loop
draining user-facing + lifecycle work. WithAggregateWatermark config; wired in
MessageService.ScheduleNotify right after the per-key drop. Invariants + acceptance
tests are documented in ActionBlockWedgePrevention.md.

Tests (both green): ManyDistinctMissingSubscribes_DoNotWedge (the canonical wedge repro)
and Aggregate_ShedsOnlySheddable_AboveWatermark.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A post that fails (e.g. the never-null AccessContext guard) already becomes a
DeliveryFailure routed back to the sender, but nothing surfaced it to the user — the
area silently treated it as a fatal load error and the composer vanished. Add
PortalErrorSink (a circuit-scoped bridge) + WithPortalErrorReporting (a
WithHandler<DeliveryFailure> on the portal hub) wired into DefaultPortalConfig;
PortalErrorModal subscribes and shows an OK-gated modal, draining a failure burst
sequentially instead of stacking dialogs. This is the proper failed-message pipe — no
throw, the hub survives, the user sees the error.

Test FailedPortalPost_SurfacesToTheErrorSink drives the real Failed-delivery path
(d.Failed -> ReportFailure -> DeliveryFailure -> portal hub -> sink) and asserts the
sink (the modal's source) emits.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…I signal

The per-build version churn in Directory.Build.props re-stamped AssemblyVersion/
FileVersion/InformationalVersion on every build (clock-based _BuildNumber =
secondsSinceMidnight/2), regenerating AssemblyInfo and defeating incremental builds —
the whole tree rebuilt every time (3-min builds became full rebuilds + a hot machine).
It churned because NodeType ABI invalidation derived FrameworkVersion from the version
STRING.

Decouple the two: NodeTypeCompilationHelpers.FrameworkVersion now uses the Graph
assembly's MVID (a content hash — stable when the bytes are identical, changes on a
real rebuild), so the version string can be STABLE for local dev (CIRun != true) and
incremental builds come back. CI keeps the per-build number (clean builds anyway;
distinguishable continuous packages). ABI invalidation stays correct and actually
recompiles LESS on deploy (only when framework content changes, not every deploy).

Verified: a leaf project's 2nd build is a no-op (0.85s, no recompile); Graph compiles.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…; bake mesh-local feed into the AKS image

The scope source generator is #r-pluggable (decoupled from Graph in ef2e756 to
kill build bloat), but the PensionFund BalanceSheet sample was never migrated to
declare the #r — so its IScope<,> implementations were never generated:
AddBusinessRules found none, the node hub config failed, and every BalanceSheet
area (statement / key-figures / asset-allocation) timed out rendering.

- BalanceSheetScopes.cs: declare `#r "nuget:MeshWeaver.BusinessRules.Generator"`
  (version-less — one global PlatformVersion, no drift). BusinessRules itself is
  already in the node-compile references (the portal ships it via
  MeshWeaver.Documentation), so it is NOT #r'd (would be a duplicate identity).
- PensionFund.Test.csproj: drop BalanceSheetScopes.cs from <Compile> — #r is
  illegal in a regular (non-script) compilation (CS7011). Scope codegen stays
  covered by FxConversionScopeTest + the runtime render tests + the resolver test.
- Distributed.csproj: BakeMeshLocalFeed target (Release publish only) packs the
  curated set (BusinessRules + .Generator) into dist/packages so the AKS image
  ships them offline — they are not on nuget.org; without it a deployed scope
  node resolves on no source.
- NuGetAssemblyResolverTest: new test pinning the #r-feed mechanism end to end.
- DeploymentAKS.md + AGENTS.md: document the auto-baked feed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…ection; drop hand-built HTML

- Replace the RenderHtml string-building in the token-cost + thread layout areas with framework
  controls — a DataGrid bound to plain row records (new TokenCostGrid) composed with
  Stack/Title/Markdown. Hand-built HTML is forbidden (and the string-interpolation + LINQ was
  the cause of the >10-min MeshWeaver.AI compile regression e30e9b5). AGENTS.md documents
  the rule (rendering goes through a control, never a hand-built HTML string).
- Extract the chat agent/model picker's queries + projection into AgentPickerProjection — one
  source of truth the chat view and AgentPickerProjectionTest both drive, so a regression that
  empties the dropdowns (null ambient context / server-side-hub RLS) fails the tests, not just
  the runtime.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… compile

The token-cost overview (e30e9b5) drove MeshWeaver.AI's clean compile from ~7s to
many minutes. Rewriting it five ways - hand-rolled HTML table -> DataGrid, dropping the
IObservable<Func<string,ModelPriceRate?>> resolver stream, LINQ -> plain loops - never
moved it, so it is a deeper Roslyn binding cost in how this code combines, not any one
construct. Removing the UI restores the 7.5s compile.

Removed: TokenCostSummary, TokenCostGrid, TokenCostSettingsTab, the thread Token-cost
area and its menu/view wiring (ThreadLayoutAreas / ThreadNodeType), and TokenCostTest.
KEPT the accounting (Thread.TokensByModel recording, ModelPricing, the ModelDefinition
price fields) so usage is still tracked, just not displayed. The full feature is
recoverable from e30e9b5 for a focused follow-up.

Also lands the ABSOLUTE ban on hand-rolled HTML / UI (use Controls.DataGrid + layout
areas, never a RenderHtml-shaped helper or Controls.Html(markup) for structured data)
in AGENTS.md.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…l commit

166e612 deleted TokenCostSummary but TokenCostGrid.cs (which references it) was
still tracked, so MeshWeaver.AI did not compile on that commit. Removing it restores
the build to the intended 7.5s state.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…read

A small standalone Blazor component for the chat progress area: renders
↑<in> ↓<out> · $<cost> (total input/output tokens, k/M-abbreviated, plus the
summed cost from the built-in ModelPricing table) and expands on click into a
per-model ↑in ↓out breakdown. Reads — never recomputes — Thread.TokensByModel
off the node stream; fully reactive (GetMeshNodeStream + Subscribe), no async.
Standalone for now: wire into ThreadChatView's progress area with
<ThreadTokenChip ThreadPath="@threadPath" />.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…st UI removal

166e612 removed the token-cost overview UI (and its AddTokenCostSettingsTab
extension) but left the call in ConfigureDefaultNodeHub's settings-tab chain, so
the whole solution failed to compile (CI build job red, blocking every test).
Remove the orphaned call; the chain stays valid.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… the Csc compile cliff

MeshWeaver.AI's C# core compile is the build long pole — ~10 min (Project Performance
Summary: 613,819 ms), build step 259s → 676s exactly at e30e9b5. ReportAnalyzer showed
NO analyzer/generator time (none run on MeshWeaver.AI), so it is Csc core on the ~1400-line
reactive ExecuteMessageAsync: e30e9b5 tipped it over a Roslyn super-linear cliff by adding
a mutable-capturing NormalizeTotalTokens LOCAL FUNCTION (+ nullable token locals) threaded
through its many branches.

Lift it into a static NormalizeTotal helper (no closure; nullable flow contained); the three
terminal-path call sites now assign totalTokens from it. Logic identical. Also reverts the
temporary ReportAnalyzer/PerformanceSummary CI diagnostic.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
… never mid-round

InstallServerWatcher subscribed to the raw node stream with no DistinctUntilChanged, so it
re-ran on EVERY emission — including the hundreds of StreamingText/Messages updates a round
produces while Executing — and on each one called ReconcileUserMessageIds (an own-write
self-heal) and re-evaluated dispatch. So the state engine was reacting to, and could write
state during, an in-flight round.

Gate the stream on SubmissionFingerprint — Status + the pending / ingested / user-message id
sets ONLY (excludes StreamingText/StreamingToolCalls/Messages/Summary/ExecutionStartedAt). An
Executing round produces no fingerprint change, so the watcher stays dormant until a real
submit / ingest / dispatch / terminal transition. Dispatch stays additionally guarded by
NeedsDispatch (Idle/Cancelled only). The fingerprint captures exactly the reaction's
dependencies, so no reconcile/dispatch is missed.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…s out of Thread

Introduces TokenUsage: a per-(thread, model) satellite at {threadPath}/_Usage/{modelKey} that
will hold input/output token counts (UserId + ThreadId + Model denormalized for cross-thread /
per-model / per-user querying). Cost is derived on read from ModelPricing, never stored.

This is step 1 (type + satellite NodeType + AddAITypes registration + SatelliteAccessRule —
access delegated to the thread). Next steps: write it per round from ExecuteMessageAsync (replacing
the Thread.TokensByModel/TokensUsed with-mutations), remove those fields from Thread, and rewire
ThreadTokenChip/ThreadViewModel to read _Usage. Goal: NO token state on the Thread node.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…nUsage satellites

Steps 2-4 of the TokenUsage refactor (step 1 = 4911862 added the type + registration):

- WRITE: ExecuteMessageAsync's three terminal paths (Completed/Cancelled/Error) no longer mutate
  the thread; each calls TokenUsageNodeType.RecordUsage(parentHub, threadPath, partition, model,
  in, out), which accumulates onto the per-model satellite {threadPath}/_Usage/{model} via an
  authoritative GetMeshNodeStream().Take(1).Timeout().Catch(->fresh) read + CreateOrUpdateNodeRequest
  (NOT a point-read .Update). Rounds are serial per thread, so the read-modify-write is race-free.
- REMOVE: Thread.TokensUsed, Thread.TokensByModel and the ModelTokenUsage record are gone; the
  thread node carries NO token state. Updated ThreadLayoutAreas + ThreadViewModel (dropped the
  TokensUsed VM field) and the stale comments in ThreadExecution/ThreadSubmission + ModelPricing doc.
- CHIP: ThreadTokenChip now subscribes to a LIVE query of {threadPath}/_Usage/* and sums per model
  (cost via ModelPricing) instead of reading Thread.TokensByModel. Wired into ThreadChatView's status
  row, replacing the now-removed inline ViewModel.TokensUsed display.
- TESTS: ThreadTokenUsageTest asserts the per-model satellites (incl. cumulative accumulation across
  rounds) on Completed/Cancelled/Error/no-total; InboxTool test drops the TokensUsed field.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…n the _Usage satellites

Global-admin Settings tab (gated via AdminMenuGate.IsPlatformAdmin, Administration group). Queries
nodeType:TokenUsage and renders, with framework controls only (no hand-built HTML):
- a button toolbar: group by Model / Person / Thread + time window 7d/30d/90d/All,
- a DataGrid (group, input, output, total, est. cost) + a headline totals line.
Cost is derived on read from ModelPricing (never stored). RLS-scoped to the viewer's read access.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…SettingsTab

CS1061 — ToCamelCase (MeshWeaver.Utils.CamelCaseExtensions) wasn't imported; the sample that
uses it imports MeshWeaver.Utils. Only the DataGrid column Property lines were affected.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants