@polymorphism-tech/morph-spec 8.38.0 → 8.40.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (227) hide show
  1. package/CLAUDE.md +19 -8
  2. package/README.md +2 -2
  3. package/bin/morph-spec.js +60 -43
  4. package/claude-plugin.json +1 -1
  5. package/docs/CHEATSHEET.md +1 -1
  6. package/docs/QUICKSTART.md +1 -1
  7. package/framework/CLAUDE.md +3 -2
  8. package/framework/MORPH.md +1 -1
  9. package/framework/agents.json +36 -25
  10. package/framework/docs/CLI.md +2 -2
  11. package/framework/evals/rubrics/code-quality.md +4 -4
  12. package/framework/evals/rubrics/condensed.md +2 -2
  13. package/framework/mcp/registry.json +33 -2
  14. package/framework/rules/infrastructure-standards.md +0 -1
  15. package/framework/rules/morph-workflow.md +9 -4
  16. package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
  17. package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
  18. package/framework/skills/core/morph-apply/SKILL.md +5 -1
  19. package/framework/skills/core/morph-brainstorming/SKILL.md +1 -1
  20. package/framework/skills/core/morph-eval/SKILL.md +1 -1
  21. package/framework/skills/core/morph-hotfix/SKILL.md +1 -1
  22. package/framework/skills/core/morph-implement/SKILL.md +6 -1
  23. package/framework/skills/core/morph-media/SKILL.md +1 -1
  24. package/framework/skills/core/morph-plan/SKILL.md +4 -3
  25. package/framework/skills/core/morph-proposal/SKILL.md +1 -1
  26. package/framework/skills/core/morph-review/SKILL.md +1 -1
  27. package/framework/skills/core/morph-stack-scan/SKILL.md +1 -1
  28. package/framework/skills/core/morph-standards/SKILL.md +1 -1
  29. package/framework/skills/core/morph-uiux/SKILL.md +6 -5
  30. package/framework/standards/STANDARDS.json +3 -3
  31. package/framework/standards/ai-agents/context-providers.md +16 -26
  32. package/framework/standards/ai-agents/durable-workflows-hitl.md +15 -21
  33. package/framework/standards/ai-agents/modalities-image-gen.md +21 -25
  34. package/framework/standards/ai-agents/modalities-speech-to-text.md +4 -15
  35. package/framework/standards/ai-agents/modalities-text-to-speech.md +2 -11
  36. package/framework/standards/ai-agents/modalities-vision.md +1 -18
  37. package/framework/standards/ai-agents/prompt-sources.md +0 -4
  38. package/framework/standards/architecture/code-comments.md +52 -16
  39. package/framework/standards/backend/integrations/neon-auth/neon-auth.md +63 -50
  40. package/framework/standards/frontend/design-system/ai-image-generation.md +5 -5
  41. package/framework/standards/integration/mcp/mcp-tools.md +48 -117
  42. package/framework/templates/code/dotnet/abstractions/result.cs +0 -2
  43. package/framework/templates/code/dotnet/ai-agents/AgentBootstrap.cs.template +26 -39
  44. package/framework/templates/code/dotnet/ai-agents/EmbeddingService.cs.template +28 -68
  45. package/framework/templates/code/dotnet/ai-agents/ModelRegistry.cs.template +99 -117
  46. package/framework/templates/code/dotnet/ai-agents/PromptRepository.cs.template +2 -44
  47. package/framework/templates/code/dotnet/ai-agents/ResilientToolMiddleware.cs.template +40 -61
  48. package/framework/templates/code/dotnet/ai-agents/VectorSearchTool.cs.template +54 -107
  49. package/framework/templates/code/dotnet/ai-agents/model-registry.json.template +53 -58
  50. package/framework/templates/code/dotnet/contracts/contracts-vsa.cs +192 -281
  51. package/framework/templates/code/dotnet/database/migration.cs +49 -83
  52. package/framework/templates/code/dotnet/database/vector-entity.cs +2 -46
  53. package/framework/templates/code/dotnet/extensions/result-extensions.cs +0 -2
  54. package/framework/templates/code/dotnet/feature/validator.cs.hbs +11 -15
  55. package/framework/templates/code/dotnet/jobs/agent.cs +3 -51
  56. package/framework/templates/code/dotnet/jobs/job.cs +2 -97
  57. package/framework/templates/code/dotnet/test.cs +2 -87
  58. package/framework/templates/code/typescript/contracts.ts +140 -167
  59. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentApi.cs +1 -48
  60. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentFactory.cs +3 -79
  61. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentFactoryOptions.cs +1 -70
  62. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentSpec.cs +1 -47
  63. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentSpecOptions.cs +2 -99
  64. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/AiKitAssemblyMarker.cs +0 -6
  65. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/ConversationStore.cs +3 -88
  66. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/IConversationStore.cs +1 -41
  67. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/InMemoryConversationStore.cs +0 -21
  68. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/CountingChatClient.cs +1 -25
  69. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalAssertion.cs +2 -63
  70. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheMissException.cs +0 -30
  71. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheOptions.cs +1 -52
  72. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheStamp.cs +3 -78
  73. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalFixture.cs +4 -109
  74. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalMode.cs +0 -22
  75. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalQualityEvaluators.cs +1 -36
  76. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalScenario.cs +2 -57
  77. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalSummary.cs +0 -75
  78. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/OfflineChatClient.cs +2 -59
  79. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/ShortPathResponseCache.cs +4 -111
  80. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/IMediaGenerator.cs +0 -34
  81. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/IMediaProvider.cs +1 -57
  82. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaArtifact.cs +0 -19
  83. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaDisclosure.cs +2 -74
  84. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaGenerator.cs +2 -33
  85. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaIdempotencyKey.cs +1 -43
  86. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJob.cs +1 -96
  87. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJobId.cs +0 -13
  88. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJobState.cs +0 -27
  89. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaPricing.cs +0 -33
  90. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaProvenance.cs +0 -31
  91. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaRequest.cs +3 -75
  92. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaResult.cs +0 -42
  93. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaReview.cs +0 -39
  94. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaSeams.cs +1 -55
  95. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaStore.cs +3 -59
  96. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MorphMedia.cs +0 -46
  97. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/GoogleImageProvider.cs +3 -28
  98. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/OpenAIImageProvider.cs +7 -61
  99. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/VeoVideoProvider.cs +2 -37
  100. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/IUsageCostSink.cs +1 -23
  101. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/UsageCostMiddleware.cs +2 -82
  102. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/UsageCostRecord.cs +1 -68
  103. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Observability/MorphOpenTelemetryExtensions.cs +1 -62
  104. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Observability/MorphTelemetryOptions.cs +2 -62
  105. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/IProviderClientFactory.cs +1 -25
  106. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/ModelAlias.cs +0 -124
  107. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/ModelRegistry.cs +1 -73
  108. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Rag/DeltaIngest.cs +1 -66
  109. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Rag/DeltaPlan.cs +0 -41
  110. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/AgentFactoryTests.cs +2 -112
  111. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/AgentSpecTests.cs +2 -43
  112. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/ConversationStoreTests.cs +5 -110
  113. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/DeltaIngestTests.cs +7 -103
  114. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalAssertionTests.cs +1 -42
  115. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalCacheMissTests.cs +1 -30
  116. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalCacheStampTests.cs +1 -19
  117. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalFailuresTests.cs +0 -48
  118. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalFixtureTests.cs +1 -29
  119. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalRefreshTests.cs +2 -38
  120. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalWorkspace.cs +1 -14
  121. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/KitEvals.cs +4 -69
  122. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/OfflineChatClientTests.cs +1 -10
  123. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeChatClient.cs +1 -57
  124. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeChatClientTests.cs +0 -26
  125. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeClock.cs +1 -19
  126. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeMediaProvider.cs +0 -32
  127. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeProviderClientFactory.cs +0 -14
  128. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeUsageCostSink.cs +0 -24
  129. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/InMemoryMediaStore.cs +0 -32
  130. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaAccessibilityTests.cs +0 -37
  131. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaAliasIsNotAnAgentTests.cs +0 -24
  132. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaDisclosureTests.cs +0 -78
  133. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaGeneratorTests.cs +0 -53
  134. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaIdempotencyKeyTests.cs +0 -44
  135. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaJobStateTests.cs +0 -44
  136. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaReviewTests.cs +0 -19
  137. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaStoreTests.cs +0 -24
  138. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/ModelRegistryTests.cs +6 -48
  139. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/SkeletonSmokeTests.cs +1 -13
  140. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/TelemetryTests.cs +2 -71
  141. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/UsageCostMiddlewareTests.cs +5 -100
  142. package/framework/templates/frontend/nextjs/Dockerfile.nextjs.hbs +39 -43
  143. package/framework/templates/frontend/nextjs/env.mjs.hbs +29 -32
  144. package/framework/templates/frontend/nextjs/page.tsx.hbs +21 -22
  145. package/framework/templates/frontend/nextjs/use-feature.ts.hbs +42 -54
  146. package/framework/templates/integrations/asaas-client.cs +332 -387
  147. package/framework/templates/integrations/asaas-webhook.cs +290 -351
  148. package/framework/templates/integrations/neon-auth-config.cs +9 -105
  149. package/framework/templates/project/validate.js +97 -120
  150. package/package.json +2 -2
  151. package/src/commands/dashboard/dashboard.js +80 -53
  152. package/src/commands/mcp/mcp-setup.js +37 -9
  153. package/src/commands/project/doctor.js +3 -5
  154. package/src/commands/project/init.js +6 -3
  155. package/src/commands/project/install-plugin-cmd.js +1 -1
  156. package/src/commands/project/update.js +5 -0
  157. package/src/dashboard/app/App.jsx +80 -16
  158. package/src/dashboard/app/components/ActivityFeed.jsx +172 -0
  159. package/src/dashboard/app/components/CommandPalette.jsx +155 -0
  160. package/src/dashboard/app/components/DetailPanel.jsx +47 -11
  161. package/src/dashboard/app/components/EntityLink.jsx +55 -0
  162. package/src/dashboard/app/components/ErrorBoundary.jsx +54 -0
  163. package/src/dashboard/app/components/FlowGraph.jsx +19 -17
  164. package/src/dashboard/app/components/KnowledgeMarkdown.jsx +53 -0
  165. package/src/dashboard/app/components/Markdown.jsx +47 -0
  166. package/src/dashboard/app/components/PhaseRail.jsx +111 -0
  167. package/src/dashboard/app/components/SearchFilter.jsx +40 -22
  168. package/src/dashboard/app/components/Sidebar.jsx +146 -37
  169. package/src/dashboard/app/hooks/useMorphQuery.js +54 -0
  170. package/src/dashboard/app/hooks/usePref.js +38 -0
  171. package/src/dashboard/app/lib/format.js +69 -0
  172. package/src/dashboard/app/lib/phases.js +56 -0
  173. package/src/dashboard/app/lib/project.js +50 -0
  174. package/src/dashboard/app/pages/Activity.jsx +24 -0
  175. package/src/dashboard/app/pages/AgentMap.jsx +371 -370
  176. package/src/dashboard/app/pages/FeatureDetail.jsx +604 -0
  177. package/src/dashboard/app/pages/Features.jsx +400 -0
  178. package/src/dashboard/app/pages/Guide.jsx +146 -0
  179. package/src/dashboard/app/pages/HookInspector.jsx +357 -322
  180. package/src/dashboard/app/pages/PhasePipeline.jsx +277 -408
  181. package/src/dashboard/app/pages/Portfolio.jsx +156 -0
  182. package/src/dashboard/app/pages/Rubrics.jsx +101 -0
  183. package/src/dashboard/app/pages/RulesMap.jsx +300 -273
  184. package/src/dashboard/app/pages/SkillsLibrary.jsx +483 -366
  185. package/src/dashboard/app/pages/Standards.jsx +407 -332
  186. package/src/dashboard/app/styles/index.css +187 -47
  187. package/src/dashboard/index.html +14 -12
  188. package/src/dashboard/package.json +32 -24
  189. package/src/dashboard/scanner/activity-scanner.js +351 -0
  190. package/src/dashboard/scanner/agents-scanner.js +59 -1
  191. package/src/dashboard/scanner/docs-scanner.js +59 -0
  192. package/src/dashboard/scanner/feature-status.js +10 -0
  193. package/src/dashboard/scanner/features-scanner.js +360 -0
  194. package/src/dashboard/scanner/index.js +14 -7
  195. package/src/dashboard/scanner/phases-scanner.js +14 -15
  196. package/src/dashboard/scanner/portfolio.js +153 -0
  197. package/src/dashboard/scanner/project-api.js +195 -0
  198. package/src/dashboard/scanner/rules-scanner.js +42 -37
  199. package/src/dashboard/scanner/skills-scanner.js +105 -43
  200. package/src/dashboard/scanner/standards-scanner.js +2 -0
  201. package/src/dashboard/vite-plugin-morph-data.js +134 -17
  202. package/src/lib/detectors/claude-config-detector.js +2 -7
  203. package/src/lib/detectors/index.js +5 -1
  204. package/src/lib/evaluator-report.js +22 -0
  205. package/src/lib/generators/settings-generator.js +208 -209
  206. package/src/lib/installers/mcp-installer.js +724 -719
  207. package/src/lib/installers/memory-installer.js +1 -2
  208. package/src/lib/projects/registry.js +154 -0
  209. package/src/lib/validators/code-budget/code-budget-validator.js +32 -4
  210. package/src/scripts/install-plugin.js +1 -1
  211. package/src/scripts/setup-infra.js +2 -4
  212. package/src/utils/agents-installer.js +57 -12
  213. package/src/utils/banner.js +1 -1
  214. package/src/commands/index.js +0 -13
  215. package/src/commands/project/checkpoint.js +0 -209
  216. package/src/core/state/phase-state-machine.js +0 -197
  217. package/src/dashboard/app/hooks/useDetailPanel.js +0 -15
  218. package/src/dashboard/app/pages/LiveState.jsx +0 -206
  219. package/src/dashboard/scanner/squads-scanner.js +0 -13
  220. package/src/dashboard/scanner/state-scanner.js +0 -85
  221. package/src/lib/generators/index.js +0 -10
  222. package/src/lib/hooks/hook-executor.js +0 -167
  223. package/src/lib/validators/checkpoint-persistence.js +0 -39
  224. package/src/lib/validators/shared/emit-validator-dispatch.js +0 -14
  225. package/src/lib/validators/shared/index.js +0 -12
  226. package/src/lib/validators/shared/issue-counter.js +0 -14
  227. package/src/lib/validators/shared/result-formatter.js +0 -124
@@ -1,55 +1,14 @@
1
1
  namespace Morph.AiKit.Rag;
2
2
 
3
- /// <summary>
4
- /// O que precisa acontecer no acervo para que ele passe a refletir a entrada — e nada mais.
5
- /// </summary>
6
- /// <remarks>
7
- /// <para>
8
- /// <b>O defeito que este tipo existe para matar</b> e o apaga-e-regrava: a base de
9
- /// conhecimento do GHLBrain <b>apaga e re-embeda o documento inteiro</b> a cada ingestao,
10
- /// mudou ou nao. O custo e pago em embedding de trecho que nao mudou e em latencia (uma
11
- /// chamada de embedding por item no reindex em massa). Um plano em quatro baldes torna
12
- /// visivel o que a ingestao inteira escondia: quase sempre <see cref="Unchanged"/> e a lista
13
- /// grande e as outras tres sao curtas.
14
- /// </para>
15
- /// <para>
16
- /// Errata (2026-09-15, Morph.GHLBrain#96): a versao anterior deste comentario citava tambem
17
- /// uma janela entre o apagar e o gravar em que a busca nao acha nada. No GHLBrain o apagar e
18
- /// o gravar ja rodam numa transacao so, com o embedding antes dela, entao essa janela nao
19
- /// existe la. Ela existe em quem apaga fora da transacao que grava.
20
- /// </para>
21
- /// <para>
22
- /// <b>E um plano, nao uma execucao.</b> Nada aqui toca banco, vetor ou arquivo: quem aplica
23
- /// e o projeto, que sabe qual e a sua transacao. E por isso que a idempotencia da ingestao
24
- /// pode ser provada sem subir infraestrutura nenhuma.
25
- /// </para>
26
- /// </remarks>
27
- /// <typeparam name="T">O item ingerido — documento, trecho, linha.</typeparam>
28
- /// <param name="Added">Itens cuja chave nao existia no acervo.</param>
29
- /// <param name="Updated">Itens cuja chave existia com <b>outro</b> conteudo.</param>
30
- /// <param name="Removed">
31
- /// Chaves que estavam no acervo e <b>nao vieram</b> na entrada. Sao chaves, e nao itens:
32
- /// o que sumiu da origem nao tem corpo para carregar.
33
- /// </param>
34
- /// <param name="Unchanged">
35
- /// Itens cuja chave existia com o <b>mesmo</b> conteudo. Nao re-embedar estes e a economia
36
- /// inteira desta feature.
37
- /// </param>
38
3
  public sealed record DeltaPlan<T>(
39
4
  IReadOnlyList<T> Added,
40
5
  IReadOnlyList<T> Updated,
41
6
  IReadOnlyList<string> Removed,
42
7
  IReadOnlyList<T> Unchanged)
43
8
  {
44
- /// <summary>Plano vazio — nem entrada, nem acervo.</summary>
45
9
  public static DeltaPlan<T> Empty { get; } = new([], [], [], []);
46
10
 
47
- /// <summary>
48
- /// Se o acervo ja reflete a entrada. <b>E a idempotencia expressa como predicado:</b>
49
- /// re-planejar a mesma entrada sobre o acervo que ela produziu tem de dar verdadeiro.
50
- /// </summary>
51
11
  public bool IsNoOp => Added.Count == 0 && Updated.Count == 0 && Removed.Count == 0;
52
12
 
53
- /// <summary>Quantos itens da entrada tocam o acervo. Zero num plano idempotente.</summary>
54
13
  public int WriteCount => Added.Count + Updated.Count + Removed.Count;
55
14
  }
@@ -8,23 +8,8 @@ using Xunit;
8
8
 
9
9
  namespace Morph.AiKit.Tests;
10
10
 
11
- /// <summary>
12
- /// Canarios da <see cref="AgentFactory"/> (T10). Duas afirmacoes sao o coracao do arquivo:
13
- /// <b>a precedencia do protocolo</b> (spec vence o alias quando presente, alias vence quando
14
- /// a spec cala) e <b>a ordem do pipeline</b>, observada na sequencia real de chamadas, nao
15
- /// na leitura do codigo.
16
- /// </summary>
17
- /// <remarks>
18
- /// Todo <c>await</c> passa <c>TestContext.Current.CancellationToken</c> — exigencia do
19
- /// analisador xUnit1051, que sob <c>TreatWarningsAsErrors</c> e build vermelho.
20
- /// </remarks>
21
11
  public sealed class AgentFactoryTests
22
12
  {
23
- // ---------------------------------------------------------------------------------
24
- // Documentos de registry usados pelos casos
25
- // ---------------------------------------------------------------------------------
26
-
27
- /// <summary>Um alias Responses e um alias Chat COM o limite do GPT-5.4 declarado.</summary>
28
13
  private const string TwoProtocolsDocument = """
29
14
  {
30
15
  "version": "2.0",
@@ -53,14 +38,6 @@ public sealed class AgentFactoryTests
53
38
  private static AgentSpec Spec(string alias = "responses-alias", AgentApi? api = null)
54
39
  => new() { Alias = alias, Instructions = "voce e um agente de teste", Api = api };
55
40
 
56
- // ---------------------------------------------------------------------------------
57
- // doneCriteria 1 — a precedencia do api:
58
- // ---------------------------------------------------------------------------------
59
-
60
- /// <summary>
61
- /// Spec calada: vale o <c>api:</c> do alias. O seletor de cliente e o observatorio —
62
- /// ele recebe o protocolo <b>efetivo</b>, ja resolvido.
63
- /// </summary>
64
41
  [Fact]
65
42
  public void TheAliasApiWinsWhenTheSpecDeclaresNone()
66
43
  {
@@ -77,11 +54,6 @@ public sealed class AgentFactoryTests
77
54
  Assert.Equal([AgentApi.Responses, AgentApi.Chat], seen);
78
55
  }
79
56
 
80
- /// <summary>
81
- /// Spec preenchida: ela vence, nos dois sentidos. A regra nao e reimplementada aqui —
82
- /// a fabrica chama <see cref="AgentSpec.ResolveApi"/> (T7), e este teste morre se
83
- /// alguem inverter a precedencia la ou copia-la para ca.
84
- /// </summary>
85
57
  [Fact]
86
58
  public void TheSpecApiWinsOverTheAliasInBothDirections()
87
59
  {
@@ -98,11 +70,6 @@ public sealed class AgentFactoryTests
98
70
  Assert.Equal([AgentApi.Chat, AgentApi.Responses], seen);
99
71
  }
100
72
 
101
- /// <summary>
102
- /// Sem seletor, "a spec vence" nao pode ser cumprido: o registry memoiza UM cliente por
103
- /// alias, construido para o protocolo do alias. A fabrica recusa nomeando os dois
104
- /// protocolos, em vez de devolver um agente que diz falar um e fala outro.
105
- /// </summary>
106
73
  [Fact]
107
74
  public void WithoutASelectorADivergentSpecApiIsRefusedNamingBothProtocols()
108
75
  {
@@ -118,10 +85,6 @@ public sealed class AgentFactoryTests
118
85
  Assert.Contains("responses-alias", error.Message, StringComparison.Ordinal);
119
86
  }
120
87
 
121
- /// <summary>
122
- /// Sem divergencia e sem seletor, o caminho normal: o cliente memoizado do alias, uma
123
- /// unica construcao por alias, por mais agentes que se criem.
124
- /// </summary>
125
88
  [Fact]
126
89
  public void WithoutASelectorEveryAgentOfAnAliasSharesTheMemoizedClient()
127
90
  {
@@ -135,20 +98,6 @@ public sealed class AgentFactoryTests
135
98
  Assert.Equal(1, providerFactory.CreateCount);
136
99
  }
137
100
 
138
- // ---------------------------------------------------------------------------------
139
- // doneCriteria 2 — a ordem do pipeline, observada nas chamadas
140
- // ---------------------------------------------------------------------------------
141
-
142
- /// <summary>
143
- /// A ordem completa, medida numa invocacao com <b>duas</b> voltas de tool calling.
144
- /// </summary>
145
- /// <remarks>
146
- /// O que a sequencia prova, e que nenhuma leitura de codigo prova: o encaixe de custo
147
- /// entra e sai <b>uma</b> vez envolvendo as duas voltas (esta ACIMA do
148
- /// <c>FunctionInvokingChatClient</c>), e o encaixe de telemetria entra e sai <b>duas</b>
149
- /// vezes, uma por chamada ao modelo (esta ABAIXO). Trocar os dois de lugar muda esta
150
- /// lista, e o teste morre.
151
- /// </remarks>
152
101
  [Fact]
153
102
  public async Task ThePipelineOrderIsUsageCostAboveFunctionInvocationAndTelemetryBelowIt()
154
103
  {
@@ -180,12 +129,7 @@ public sealed class AgentFactoryTests
180
129
  log);
181
130
  }
182
131
 
183
- /// <summary>
184
- /// A razao de o custo ficar acima do FICC, em numeros: ele ve UMA resposta com o uso
185
- /// somado das duas voltas (10+30 de entrada, 4+6 de saida). Abaixo do FICC veria duas
186
- /// respostas parciais — e leria um <c>UsageDetails</c> que o proprio FICC muta para
187
- /// acumular, o que faria a mesma conta sair diferente conforme a hora de ler.
188
- /// </summary>
132
+ // Abaixo do FunctionInvokingChatClient o custo leria o UsageDetails que ele muta para acumular entre voltas.
189
133
  [Fact]
190
134
  public async Task UsageCostAboveFunctionInvocationSeesTheUsageOfEveryToolRoundSummedOnce()
191
135
  {
@@ -209,15 +153,6 @@ public sealed class AgentFactoryTests
209
153
  Assert.Equal(10, usage.OutputTokenCount);
210
154
  }
211
155
 
212
- // ---------------------------------------------------------------------------------
213
- // As opcoes de nivel de agente (T7) chegam ao lugar que a T7 declarou
214
- // ---------------------------------------------------------------------------------
215
-
216
- /// <summary>
217
- /// <c>MaxToolIterations</c> e <c>AllowConcurrentToolInvocation</c> tem destino nomeado
218
- /// no contrato da T7. Aqui eles sao lidos <b>do objeto real</b> do pipeline, via
219
- /// <c>GetService</c> — nao de um campo que a fabrica tenha guardado.
220
- /// </summary>
221
156
  [Fact]
222
157
  public void TheAgentLevelKnobsReachTheFunctionInvokingChatClient()
223
158
  {
@@ -236,10 +171,6 @@ public sealed class AgentFactoryTests
236
171
  Assert.True(functionInvoking.AllowConcurrentInvocation);
237
172
  }
238
173
 
239
- /// <summary>
240
- /// <c>TimeoutSeconds</c> vira <see cref="CancellationToken"/> de verdade: um cliente que
241
- /// nunca responde e cancelado, em vez de pendurar a invocacao.
242
- /// </summary>
243
174
  [Fact]
244
175
  public async Task TimeoutSecondsBecomesACancellationTokenThatActuallyCancels()
245
176
  {
@@ -260,15 +191,6 @@ public sealed class AgentFactoryTests
260
191
  () => agent.RunAsync("oi", session, cancellationToken: TestContext.Current.CancellationToken));
261
192
  }
262
193
 
263
- // ---------------------------------------------------------------------------------
264
- // A spec inteira chega as ChatOptions do agente
265
- // ---------------------------------------------------------------------------------
266
-
267
- /// <summary>
268
- /// O modelo vem do alias (a spec nunca cita modelo), as instrucoes e o schema vem da
269
- /// spec, e <see cref="AgentSpecOptions.ApplyTo"/> nao atropela nem as tools nem o
270
- /// formato de resposta ja definidos.
271
- /// </summary>
272
194
  [Fact]
273
195
  public async Task TheSpecReachesTheChatOptionsWithoutClobberingToolsOrSchema()
274
196
  {
@@ -304,14 +226,6 @@ public sealed class AgentFactoryTests
304
226
  Assert.Equal("agente-de-teste", agent.Name);
305
227
  }
306
228
 
307
- // ---------------------------------------------------------------------------------
308
- // Recusas
309
- // ---------------------------------------------------------------------------------
310
-
311
- /// <summary>
312
- /// Limite de alias violado nao vira agente. A recusa carrega a procedencia escrita no
313
- /// registry — sem ela, o limite nao teria sequer carregado (T8).
314
- /// </summary>
315
229
  [Fact]
316
230
  public void AViolatedAliasConstraintRefusesTheAgentAndCarriesItsProvenance()
317
231
  {
@@ -328,11 +242,6 @@ public sealed class AgentFactoryTests
328
242
  Assert.Contains("issue #93", error.Message, StringComparison.Ordinal);
329
243
  }
330
244
 
331
- /// <summary>
332
- /// O MESMO alias, sem tools, nao viola nada: o limite tem quatro fatos, e tres nao
333
- /// bastam. O canario existe para que a recusa acima nao seja confundida com "modelo de
334
- /// raciocinio rejeita tool".
335
- /// </summary>
336
245
  [Fact]
337
246
  public void TheSameAliasWithoutToolsCreatesTheAgentNormally()
338
247
  {
@@ -347,7 +256,6 @@ public sealed class AgentFactoryTests
347
256
  Assert.NotNull(agent);
348
257
  }
349
258
 
350
- /// <summary>Alias desconhecido falha na fabrica com o mesmo erro do registry.</summary>
351
259
  [Fact]
352
260
  public void AnUnknownAliasFailsAtCreateTime()
353
261
  {
@@ -359,7 +267,6 @@ public sealed class AgentFactoryTests
359
267
  Assert.Equal(ModelRegistryError.UnknownAlias, error.Reason);
360
268
  }
361
269
 
362
- /// <summary>Seletor que devolve nulo e recusa explicita, nunca agente sem cliente.</summary>
363
270
  [Fact]
364
271
  public void ASelectorThatReturnsNullIsRefusedInsteadOfProducingAClientlessAgent()
365
272
  {
@@ -374,10 +281,6 @@ public sealed class AgentFactoryTests
374
281
  Assert.Equal(AgentFactoryError.NoChatClient, error.Reason);
375
282
  }
376
283
 
377
- /// <summary>
378
- /// Sem nenhum encaixe preenchido, o agente e valido e responde. Um agente sem custo e
379
- /// sem telemetria nao e um agente pela metade.
380
- /// </summary>
381
284
  [Fact]
382
285
  public async Task AnAgentWithNoHooksIsStillAValidAgentThatAnswers()
383
286
  {
@@ -395,15 +298,7 @@ public sealed class AgentFactoryTests
395
298
  Assert.Equal("pronto", response.Text);
396
299
  }
397
300
 
398
- // ---------------------------------------------------------------------------------
399
- // Apoio dos testes
400
- // ---------------------------------------------------------------------------------
401
-
402
- /// <summary>
403
- /// Roteiro de duas voltas: o modelo pede a tool, a tool responde, o modelo conclui. Os
404
- /// numeros de uso sao escolhidos para que a soma (40/10) seja distinguivel de qualquer
405
- /// uma das parcelas.
406
- /// </summary>
301
+ // Os usos por volta (10/4 e 30/6) diferem da soma (40/10), para a soma nao se confundir com uma parcela.
407
302
  private static FakeChatClient ScriptedToolCallThenAnswer()
408
303
  => new FakeChatClient()
409
304
  .Enqueue(new ChatResponse(new ChatMessage(
@@ -433,10 +328,6 @@ public sealed class AgentFactoryTests
433
328
  return factory.Create(spec);
434
329
  }
435
330
 
436
- /// <summary>
437
- /// Marca a entrada e a saida de uma posicao do pipeline. E o unico jeito de observar a
438
- /// ORDEM: ela nao esta em nenhuma propriedade, so na sequencia das chamadas.
439
- /// </summary>
440
331
  private sealed class ProbeChatClient(
441
332
  IChatClient inner,
442
333
  string tag,
@@ -465,7 +356,6 @@ public sealed class AgentFactoryTests
465
356
  }
466
357
  }
467
358
 
468
- /// <summary>Cliente que nunca responde — existe so para o timeout ter o que cancelar.</summary>
469
359
  private sealed class NeverAnsweringChatClient : IChatClient
470
360
  {
471
361
  public async Task<ChatResponse> GetResponseAsync(
@@ -7,24 +7,8 @@ using Xunit;
7
7
 
8
8
  namespace Morph.AiKit.Tests;
9
9
 
10
- /// <summary>
11
- /// Canarios do agente-como-dado (T7). O que estes testes protegem nao e a existencia dos
12
- /// tipos — o compilador ja faz isso — e sim a UNICA regra estrutural que
13
- /// <see cref="AgentSpecOptions"/> tem: <b>nulo nao envia nada ao provider</b>.
14
- /// </summary>
15
- /// <remarks>
16
- /// A comparacao e feita por reflexao contra um <see cref="ChatOptions"/> virgem, e nao
17
- /// contra uma lista de propriedades escrita a mao, de proposito: uma propriedade nova no
18
- /// <c>Microsoft.Extensions.AI</c> entra na comparacao sozinha. Uma lista escrita a mao
19
- /// envelheceria em silencio no proximo bump do pin — que e exatamente o defeito que a
20
- /// feature inteira existe para matar.
21
- /// </remarks>
22
10
  public sealed class AgentSpecTests
23
11
  {
24
- // ---------------------------------------------------------------------------------
25
- // A regra estrutural: nulo = nao envia
26
- // ---------------------------------------------------------------------------------
27
-
28
12
  [Fact]
29
13
  public void FreshOptionsProduceChatOptionsIndistinguishableFromPristine()
30
14
  {
@@ -68,9 +52,7 @@ public sealed class AgentSpecTests
68
52
  [Fact]
69
53
  public void AgentLevelKnobsNeverLeakIntoChatOptions()
70
54
  {
71
- // As quatro opcoes de nivel de agente. Nenhuma tem casa em ChatOptions: elas sao
72
- // lidas pela AgentFactory (T10) e pela telemetria (T12). Se alguem "resolver" isso
73
- // despejando-as em AdditionalProperties, este teste morre — que e o ponto.
55
+ // As quatro opcoes de nivel de agente nao tem casa em ChatOptions: a AgentFactory e a telemetria as leem.
74
56
  var produced = new AgentSpecOptions
75
57
  {
76
58
  MaxToolIterations = 7,
@@ -86,10 +68,6 @@ public sealed class AgentSpecTests
86
68
  $"Opcao de nivel de agente vazou para ChatOptions: {string.Join(", ", differing)}");
87
69
  }
88
70
 
89
- // ---------------------------------------------------------------------------------
90
- // Mapeamento de cada opcao de nivel de requisicao para a sua casa
91
- // ---------------------------------------------------------------------------------
92
-
93
71
  [Fact]
94
72
  public void EachRequestLevelOptionLandsInItsOwnChatOptionsHome()
95
73
  {
@@ -115,8 +93,7 @@ public sealed class AgentSpecTests
115
93
  Assert.Same(ChatToolMode.RequireAny, produced.ToolMode);
116
94
  Assert.Equal(ReasoningEffort.Medium, produced.Reasoning?.Effort);
117
95
 
118
- // service tier NAO existe em ChatOptions no Microsoft.Extensions.AI 10.9.0 (apurado
119
- // por reflexao sobre o assembly do pin) — viaja em AdditionalProperties.
96
+ // ServiceTier nao existe em ChatOptions no Microsoft.Extensions.AI 10.9.0: viaja em AdditionalProperties.
120
97
  Assert.NotNull(produced.AdditionalProperties);
121
98
  Assert.True(produced.AdditionalProperties.TryGetValue(
122
99
  AgentSpecOptions.ServiceTierPropertyName,
@@ -127,8 +104,6 @@ public sealed class AgentSpecTests
127
104
  [Fact]
128
105
  public void AnExplicitlyEmptyStopSequenceListIsSentAsWritten()
129
106
  {
130
- // "Nulo nao envia; nao-nulo envia o que voce escreveu." Tratar lista vazia como
131
- // nula seria uma excecao escondida na unica regra do tipo.
132
107
  var produced = new AgentSpecOptions { StopSequences = [] }.ToChatOptions();
133
108
 
134
109
  Assert.Equal([nameof(ChatOptions.StopSequences)], DiffAgainstPristine(produced));
@@ -139,7 +114,6 @@ public sealed class AgentSpecTests
139
114
  [Fact]
140
115
  public void ApplyToNeverClobbersWhatTheCallerAlreadySet()
141
116
  {
142
- // A AgentFactory (T10) preenche Tools e ResponseFormat; ApplyTo nao pode apaga-los.
143
117
  var destination = new ChatOptions
144
118
  {
145
119
  ResponseFormat = ChatResponseFormat.Json,
@@ -158,10 +132,6 @@ public sealed class AgentSpecTests
158
132
  public void ApplyToRejectsANullDestination()
159
133
  => Assert.Throws<ArgumentNullException>(() => new AgentSpecOptions().ApplyTo(null!));
160
134
 
161
- // ---------------------------------------------------------------------------------
162
- // AgentSpec
163
- // ---------------------------------------------------------------------------------
164
-
165
135
  [Fact]
166
136
  public void AFreshSpecCarriesNoToolsNoSchemaAndSendsNoOptions()
167
137
  {
@@ -194,11 +164,9 @@ public sealed class AgentSpecTests
194
164
  var withoutOverride = new AgentSpec { Alias = "a", Instructions = "i" };
195
165
  var withOverride = withoutOverride with { Api = AgentApi.Chat };
196
166
 
197
- // alias manda quando a spec nao opinou
198
167
  Assert.Equal(AgentApi.Responses, withoutOverride.ResolveApi(AgentApi.Responses));
199
168
  Assert.Equal(AgentApi.Chat, withoutOverride.ResolveApi(AgentApi.Chat));
200
169
 
201
- // spec manda quando opinou — inclusive contra o alias
202
170
  Assert.Equal(AgentApi.Chat, withOverride.ResolveApi(AgentApi.Responses));
203
171
  }
204
172
 
@@ -222,15 +190,6 @@ public sealed class AgentSpecTests
222
190
  Assert.Equal("object", spec.OutputSchema.Value.GetProperty("type").GetString());
223
191
  }
224
192
 
225
- // ---------------------------------------------------------------------------------
226
- // Helpers
227
- // ---------------------------------------------------------------------------------
228
-
229
- /// <summary>
230
- /// Nomes das propriedades de <see cref="ChatOptions"/> em que <paramref name="actual"/>
231
- /// difere de um <see cref="ChatOptions"/> virgem — ou seja, o conjunto exato do que
232
- /// seria enviado ao provider.
233
- /// </summary>
234
193
  private static IReadOnlyList<string> DiffAgainstPristine(ChatOptions actual)
235
194
  {
236
195
  var pristine = new ChatOptions();
@@ -7,21 +7,6 @@ using Xunit;
7
7
 
8
8
  namespace Morph.AiKit.Tests;
9
9
 
10
- /// <summary>
11
- /// Canarios do <see cref="ConversationStore"/> (T13). A afirmacao central: <b>uma unica
12
- /// instancia de provider atende todas as sessoes, e o historico de uma conversa nunca
13
- /// aparece em outra</b> — porque o estado por sessao vive na
14
- /// <see cref="AgentSession"/> (via <see cref="ProviderSessionState{TState}"/>), jamais num
15
- /// campo do provider.
16
- /// </summary>
17
- /// <remarks>
18
- /// Os testes rodam pelo caminho <b>real</b> do MAF: um <see cref="ChatClientAgent"/> de
19
- /// verdade, com <c>ChatClientAgentOptions.ChatHistoryProvider</c> apontando para o
20
- /// <see cref="ConversationStore"/>, sessoes criadas por <c>CreateSessionAsync</c> e voltas
21
- /// disparadas por <c>RunAsync</c>. O unico fake e o <see cref="FakeChatClient"/> no lugar do
22
- /// provider de modelo — e por isso "o provider e chamado" e um fato observado, e nao uma
23
- /// chamada manual ao metodo protegido fingindo ser o framework.
24
- /// </remarks>
25
10
  public sealed class ConversationStoreTests
26
11
  {
27
12
  private static ChatClientAgent Agent(FakeChatClient client, ConversationStore history)
@@ -37,15 +22,6 @@ public sealed class ConversationStoreTests
37
22
  private static string[] TextsOf(IEnumerable<ChatMessage> messages)
38
23
  => [.. messages.Select(m => m.Text)];
39
24
 
40
- // ---------------------------------------------------------------------------------
41
- // doneCriteria 2: AppendAsync recebe a volta; LoadAsync alimenta a volta seguinte
42
- // ---------------------------------------------------------------------------------
43
-
44
- /// <summary>
45
- /// Uma volta e gravada com pedido e resposta, nessa ordem — e a volta seguinte chega ao
46
- /// modelo ja com essa historia na frente. As duas metades do contrato, medidas nos dois
47
- /// lados: no acervo e no que o cliente de modelo recebeu.
48
- /// </summary>
49
25
  [Fact]
50
26
  public async Task TheRoundIsAppendedAndFeedsTheHistoryOfTheNextRound()
51
27
  {
@@ -61,7 +37,6 @@ public sealed class ConversationStoreTests
61
37
 
62
38
  await agent.RunAsync("Meu pedido 1234 esta atrasado.", session, cancellationToken: Ct);
63
39
 
64
- // Depois da 1a volta o acervo tem exatamente o pedido e a resposta daquela volta.
65
40
  var afterFirst = await store.LoadAsync("conversa-A", Ct);
66
41
  Assert.Equal(
67
42
  ["Meu pedido 1234 esta atrasado.", "Vou verificar o pedido 1234."],
@@ -69,8 +44,6 @@ public sealed class ConversationStoreTests
69
44
 
70
45
  await agent.RunAsync("E quando foi despachado?", session, cancellationToken: Ct);
71
46
 
72
- // A 2a ida ao modelo levou o historico carregado do acervo, e so uma vez cada
73
- // mensagem — o turno corrente nao vem duplicado.
74
47
  var secondCall = TextsOf(client.ReceivedMessages[1]);
75
48
  Assert.Equal(
76
49
  [
@@ -84,10 +57,6 @@ public sealed class ConversationStoreTests
84
57
  Assert.Equal(4, afterSecond.Count);
85
58
  }
86
59
 
87
- /// <summary>
88
- /// A primeira volta nao carrega historico nenhum: conversa inexistente e lista vazia, nao
89
- /// erro. E o caso mais comum de todos.
90
- /// </summary>
91
60
  [Fact]
92
61
  public async Task AConversationThatDoesNotExistYetLoadsEmptyInsteadOfFailing()
93
62
  {
@@ -106,19 +75,6 @@ public sealed class ConversationStoreTests
106
75
  Assert.Equal(["oi", "ola"], TextsOf(await store.LoadAsync("conversa-nova", Ct)));
107
76
  }
108
77
 
109
- // ---------------------------------------------------------------------------------
110
- // doneCriteria 1: DUAS sessoes concorrentes na MESMA instancia, sem vazamento
111
- // ---------------------------------------------------------------------------------
112
-
113
- /// <summary>
114
- /// <b>O canario da invariante.</b> Uma instancia de <see cref="ConversationStore"/>, duas
115
- /// sessoes, muitas voltas intercaladas em paralelo: nenhuma mensagem de uma conversa
116
- /// aparece na outra, e cada uma tem exatamente as suas.
117
- /// </summary>
118
- /// <remarks>
119
- /// Este e o teste que morre se alguem guardar "a conversa atual" num campo do provider —
120
- /// o defeito que em producao aparece como historico de um usuario na tela de outro.
121
- /// </remarks>
122
78
  [Fact]
123
79
  public async Task TwoConcurrentSessionsOnTheSameProviderNeverSeeEachOthersMessages()
124
80
  {
@@ -136,7 +92,6 @@ public sealed class ConversationStoreTests
136
92
  clientB.EnqueueText($"resposta-B-{i}");
137
93
  }
138
94
 
139
- // Duas conversas distintas, o MESMO provider de historico nos dois agentes.
140
95
  var agentA = Agent(clientA, history);
141
96
  var agentB = Agent(clientB, history);
142
97
 
@@ -145,7 +100,7 @@ public sealed class ConversationStoreTests
145
100
  history.BindConversation(sessionA, "conversa-A");
146
101
  history.BindConversation(sessionB, "conversa-B");
147
102
 
148
- // Sequencial DENTRO de cada sessao (uma conversa e uma fila), concorrente ENTRE elas.
103
+ // Sequencial dentro de cada sessao (uma conversa e uma fila), concorrente entre elas.
149
104
  var driveA = Task.Run(async () =>
150
105
  {
151
106
  for (var i = 0; i < Rounds; i++)
@@ -170,26 +125,18 @@ public sealed class ConversationStoreTests
170
125
  Assert.Equal(Rounds * 2, conversationA.Length);
171
126
  Assert.Equal(Rounds * 2, conversationB.Length);
172
127
 
173
- // Nenhum texto do outro lado atravessou.
174
128
  Assert.DoesNotContain(conversationA, t => t.Contains("-B-", StringComparison.Ordinal));
175
129
  Assert.DoesNotContain(conversationB, t => t.Contains("-A-", StringComparison.Ordinal));
176
130
 
177
- // E cada conversa tem as suas, na ordem.
178
131
  Assert.Equal(
179
132
  [.. Enumerable.Range(0, Rounds).SelectMany(i => new[] { $"pergunta-A-{i}", $"resposta-A-{i}" })],
180
133
  conversationA);
181
134
 
182
- // O que o modelo VIU tambem nao se misturou: a ultima ida de A nao carrega nada de B.
183
135
  Assert.DoesNotContain(
184
136
  TextsOf(clientA.ReceivedMessages[^1]),
185
137
  t => t.Contains("-B-", StringComparison.Ordinal));
186
138
  }
187
139
 
188
- /// <summary>
189
- /// A prova direta de que o estado por sessao <b>mora na sessao</b>: o mesmo provider
190
- /// devolve ids diferentes para sessoes diferentes, e o id de cada sessao esta gravado
191
- /// no <c>StateBag</c> dela — que e onde o MAF o serializa e o retoma.
192
- /// </summary>
193
140
  [Fact]
194
141
  public async Task TheConversationIdLivesInTheSessionStateNotInAProviderField()
195
142
  {
@@ -204,19 +151,13 @@ public sealed class ConversationStoreTests
204
151
  var secondId = history.GetConversationId(second);
205
152
 
206
153
  Assert.NotEqual(firstId, secondId);
207
- Assert.Equal(firstId, history.GetConversationId(first)); // estavel na mesma sessao
154
+ Assert.Equal(firstId, history.GetConversationId(first));
208
155
 
209
- // Esta gravado na sessao, sob a chave que o provider declara em StateKeys.
210
156
  Assert.True(first.StateBag.TryGetValue<string>(ConversationStore.ConversationIdStateKey, out var stored));
211
157
  Assert.Equal(firstId, stored);
212
158
  Assert.Contains(ConversationStore.ConversationIdStateKey, history.StateKeys);
213
159
  }
214
160
 
215
- /// <summary>
216
- /// Sem amarracao explicita o id e um <see cref="Guid.CreateVersion7"/> — ordenavel no
217
- /// tempo, ao contrario de <c>Guid.NewGuid</c>. A versao 7 e legivel no proprio valor
218
- /// (o nibble de versao), entao a regra da casa e verificavel e nao so declarada.
219
- /// </summary>
220
161
  [Fact]
221
162
  public async Task AnUnboundSessionGetsATimeOrderedIdentifier()
222
163
  {
@@ -231,11 +172,6 @@ public sealed class ConversationStoreTests
231
172
  Assert.Equal(7, parsed.ToByteArray(bigEndian: true)[6] >> 4);
232
173
  }
233
174
 
234
- /// <summary>
235
- /// O caminho do CRM: a conversa ja existe como registro, e a sessao entra nela — o
236
- /// historico anterior aparece na primeira volta desta sessao. E o que torna a decisao
237
- /// "conversa no acervo do projeto" util em vez de teorica.
238
- /// </summary>
239
175
  [Fact]
240
176
  public async Task ASessionCanJoinAConversationThatAlreadyExistsInTheProjectStore()
241
177
  {
@@ -258,21 +194,10 @@ public sealed class ConversationStoreTests
258
194
  await agent.RunAsync("Nao chegou.", session, cancellationToken: Ct);
259
195
 
260
196
  Assert.Contains("Ontem eu pedi um orcamento.", TextsOf(client.ReceivedMessages[0]));
261
- // 2 do acervo anterior + o pedido e a resposta desta volta. A volta grava SO a si
262
- // mesma: regravar o historico inteiro faria a conversa crescer em progressao
263
- // geometrica a cada turno.
197
+ // 2 do acervo + pedido e resposta desta volta: a volta grava so a si mesma.
264
198
  Assert.Equal(4, (await store.LoadAsync("crm-conversa-42", Ct)).Count);
265
199
  }
266
200
 
267
- // ---------------------------------------------------------------------------------
268
- // O acervo e plugavel: o kit nao conhece banco
269
- // ---------------------------------------------------------------------------------
270
-
271
- /// <summary>
272
- /// A costura e substituivel: um acervo de teste que apenas anota as chamadas prova que o
273
- /// provider fala com <see cref="IConversationStore"/> e com mais nada — e um projeto que
274
- /// troque memoria por <c>jsonb</c> so implementa esta interface.
275
- /// </summary>
276
201
  [Fact]
277
202
  public async Task TheProviderTalksOnlyToTheSeamSoPersistenceIsReplaceable()
278
203
  {
@@ -289,11 +214,6 @@ public sealed class ConversationStoreTests
289
214
  Assert.Equal(["load:c-1", "append:c-1"], spy.Calls);
290
215
  }
291
216
 
292
- /// <summary>
293
- /// Volta que falhou nao vira historico. Persistir o pedido sem resposta deixaria a
294
- /// conversa terminando numa pergunta nunca respondida — e a volta seguinte a repetiria,
295
- /// com o usuario se vendo duplicado.
296
- /// </summary>
297
217
  [Fact]
298
218
  public async Task AFailedRoundIsNotPersisted()
299
219
  {
@@ -311,22 +231,7 @@ public sealed class ConversationStoreTests
311
231
  Assert.Empty(await store.LoadAsync("conversa-falha", Ct));
312
232
  }
313
233
 
314
- /// <summary>
315
- /// <b>Uma volta sem sessao.</b> O MAF declara <c>InvokingContext.Session</c> como
316
- /// anulavel, e o provider recusa esse caso: inventar um id gravaria conversa orfa e usar
317
- /// uma chave fixa juntaria tenants no mesmo balde — as duas saidas silenciosas sao
318
- /// piores que a recusa. Este teste mede o que o framework realmente faz por esse
319
- /// caminho, em vez de supor: e o comportamento observado que fica pinado aqui.
320
- /// </summary>
321
- /// <remarks>
322
- /// O caminho de construir <c>InvokingContext</c> a mao — que seria a forma direta de
323
- /// exercitar a guarda — <b>nao esta disponivel</b>: no MAF 1.20.0 os construtores de
324
- /// <c>InvokingContext</c> e <c>InvokedContext</c> sao os unicos membros da area de
325
- /// <c>ChatHistoryProvider</c> marcados <c>[Experimental("MAAI001")]</c>, e com
326
- /// <c>TreatWarningsAsErrors</c> isso e erro de compilacao. Consequencia registrada: um
327
- /// provider de historico so pode ser testado <b>atraves de um agente real</b>, que e
328
- /// como esta classe inteira o testa.
329
- /// </remarks>
234
+ // No MAF 1.20.0 o construtor de InvokingContext e [Experimental("MAAI001")]: o provider so se exercita via agente real.
330
235
  [Fact]
331
236
  public async Task ARoundWithoutAnExplicitSessionStillHasAConversationOfItsOwn()
332
237
  {
@@ -337,14 +242,10 @@ public sealed class ConversationStoreTests
337
242
 
338
243
  await agent.RunAsync("sem sessao", cancellationToken: Ct);
339
244
 
340
- // O agente cria uma sessao efemera para a volta: a guarda de sessao ausente nao e
341
- // alcancada por este caminho, e a conversa nasce com id proprio em vez de cair num
342
- // balde compartilhado.
245
+ // Sem sessao, o agente cria uma efemera para a volta: a conversa nasce com id proprio.
343
246
  var conversation = Assert.Single(store.ConversationIds);
344
247
  Assert.Equal(["sem sessao", "respondido"], TextsOf(await store.LoadAsync(conversation, Ct)));
345
248
 
346
- // E a volta seguinte, tambem sem sessao, NAO herda a conversa anterior — sem sessao
347
- // nao ha continuidade, que e a semantica correta de uma chamada avulsa.
348
249
  client.EnqueueText("de novo");
349
250
  await agent.RunAsync("outra avulsa", cancellationToken: Ct);
350
251
 
@@ -352,15 +253,10 @@ public sealed class ConversationStoreTests
352
253
  Assert.DoesNotContain("sem sessao", TextsOf(client.ReceivedMessages[^1]));
353
254
  }
354
255
 
355
- /// <summary>Provider sem acervo e defeito de composicao, e falha na construcao.</summary>
356
256
  [Fact]
357
257
  public void TheProviderRefusesToBeBuiltWithoutAStore()
358
258
  => Assert.Throws<ArgumentNullException>(() => new ConversationStore(null!));
359
259
 
360
- /// <summary>
361
- /// O acervo em memoria e ele proprio seguro sob concorrencia — senao o canario das duas
362
- /// sessoes mediria a falta de trava aqui, e nao o isolamento do provider.
363
- /// </summary>
364
260
  [Fact]
365
261
  public async Task TheInMemoryStoreDoesNotLoseWritesUnderConcurrency()
366
262
  {
@@ -378,7 +274,6 @@ public sealed class ConversationStoreTests
378
274
  Assert.Empty(await store.LoadAsync("inexistente", Ct));
379
275
  }
380
276
 
381
- /// <summary>Acervo de teste que so anota o que lhe pediram, na ordem.</summary>
382
277
  private sealed class RecordingConversationStore : IConversationStore
383
278
  {
384
279
  private readonly ConcurrentQueue<string> _calls = new();