@polymorphism-tech/morph-spec 8.38.0 → 8.41.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 (242) hide show
  1. package/CLAUDE.md +20 -9
  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 +4 -3
  8. package/framework/MORPH.md +10 -9
  9. package/framework/agents.json +43 -26
  10. package/framework/docs/CLI.md +7 -5
  11. package/framework/evals/rubrics/code-quality.md +4 -4
  12. package/framework/evals/rubrics/condensed.md +2 -2
  13. package/framework/hooks/README.md +1 -1
  14. package/framework/hooks/claude-code/core/telemetry-log.js +39 -8
  15. package/framework/hooks/claude-code/core/worktree-create.js +22 -6
  16. package/framework/hooks/shared/git-paths.js +38 -5
  17. package/framework/mcp/registry.json +58 -66
  18. package/framework/rules/infrastructure-standards.md +0 -1
  19. package/framework/rules/morph-workflow.md +9 -4
  20. package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
  21. package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
  22. package/framework/skills/core/morph-apply/SKILL.md +59 -25
  23. package/framework/skills/core/morph-brainstorming/SKILL.md +1 -1
  24. package/framework/skills/core/morph-eval/SKILL.md +1 -1
  25. package/framework/skills/core/morph-hotfix/SKILL.md +2 -2
  26. package/framework/skills/core/morph-implement/SKILL.md +48 -24
  27. package/framework/skills/core/morph-media/SKILL.md +2 -2
  28. package/framework/skills/core/morph-plan/SKILL.md +10 -3
  29. package/framework/skills/core/morph-proposal/SKILL.md +1 -1
  30. package/framework/skills/core/morph-review/SKILL.md +1 -1
  31. package/framework/skills/core/morph-stack-scan/SKILL.md +1 -1
  32. package/framework/skills/core/morph-standards/SKILL.md +1 -1
  33. package/framework/skills/core/morph-uiux/SKILL.md +7 -6
  34. package/framework/standards/PROVENANCE.json +4 -4
  35. package/framework/standards/STANDARDS.json +9 -9
  36. package/framework/standards/ai-agents/context-providers.md +16 -26
  37. package/framework/standards/ai-agents/durable-workflows-hitl.md +15 -21
  38. package/framework/standards/ai-agents/modalities-image-gen.md +21 -25
  39. package/framework/standards/ai-agents/modalities-speech-to-text.md +4 -15
  40. package/framework/standards/ai-agents/modalities-text-to-speech.md +2 -11
  41. package/framework/standards/ai-agents/modalities-vision.md +1 -18
  42. package/framework/standards/ai-agents/prompt-sources.md +0 -4
  43. package/framework/standards/architecture/code-comments.md +52 -16
  44. package/framework/standards/backend/dotnet/testing.md +4 -2
  45. package/framework/standards/backend/integrations/neon-auth/neon-auth.md +63 -50
  46. package/framework/standards/frontend/design-system/ai-image-generation.md +42 -28
  47. package/framework/standards/frontend/design-system/ai-video-generation.md +24 -9
  48. package/framework/standards/frontend/nextjs/testing.md +4 -2
  49. package/framework/standards/integration/mcp/mcp-tools.md +64 -126
  50. package/framework/templates/code/dotnet/abstractions/result.cs +0 -2
  51. package/framework/templates/code/dotnet/ai-agents/AgentBootstrap.cs.template +26 -39
  52. package/framework/templates/code/dotnet/ai-agents/EmbeddingService.cs.template +28 -68
  53. package/framework/templates/code/dotnet/ai-agents/ModelRegistry.cs.template +99 -117
  54. package/framework/templates/code/dotnet/ai-agents/PromptRepository.cs.template +2 -44
  55. package/framework/templates/code/dotnet/ai-agents/ResilientToolMiddleware.cs.template +40 -61
  56. package/framework/templates/code/dotnet/ai-agents/VectorSearchTool.cs.template +54 -107
  57. package/framework/templates/code/dotnet/ai-agents/model-registry.json.template +53 -58
  58. package/framework/templates/code/dotnet/contracts/contracts-vsa.cs +192 -281
  59. package/framework/templates/code/dotnet/database/migration.cs +49 -83
  60. package/framework/templates/code/dotnet/database/vector-entity.cs +2 -46
  61. package/framework/templates/code/dotnet/extensions/result-extensions.cs +0 -2
  62. package/framework/templates/code/dotnet/feature/validator.cs.hbs +11 -15
  63. package/framework/templates/code/dotnet/jobs/agent.cs +3 -51
  64. package/framework/templates/code/dotnet/jobs/job.cs +2 -97
  65. package/framework/templates/code/dotnet/test.cs +2 -87
  66. package/framework/templates/code/typescript/contracts.ts +140 -167
  67. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentApi.cs +1 -48
  68. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentFactory.cs +3 -79
  69. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentFactoryOptions.cs +1 -70
  70. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentSpec.cs +1 -47
  71. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentSpecOptions.cs +2 -99
  72. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/AiKitAssemblyMarker.cs +0 -6
  73. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/ConversationStore.cs +3 -88
  74. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/IConversationStore.cs +1 -41
  75. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/InMemoryConversationStore.cs +0 -21
  76. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/CountingChatClient.cs +1 -25
  77. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalAssertion.cs +2 -63
  78. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheMissException.cs +0 -30
  79. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheOptions.cs +1 -52
  80. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheStamp.cs +3 -78
  81. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalFixture.cs +4 -109
  82. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalMode.cs +0 -22
  83. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalQualityEvaluators.cs +1 -36
  84. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalScenario.cs +2 -57
  85. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalSummary.cs +0 -75
  86. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/OfflineChatClient.cs +2 -59
  87. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/ShortPathResponseCache.cs +4 -111
  88. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/IMediaGenerator.cs +0 -34
  89. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/IMediaProvider.cs +1 -57
  90. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaArtifact.cs +0 -19
  91. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaDisclosure.cs +2 -74
  92. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaGenerator.cs +2 -33
  93. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaIdempotencyKey.cs +1 -43
  94. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJob.cs +1 -96
  95. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJobId.cs +0 -13
  96. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJobState.cs +0 -27
  97. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaPricing.cs +0 -33
  98. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaProvenance.cs +0 -31
  99. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaRequest.cs +3 -75
  100. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaResult.cs +0 -42
  101. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaReview.cs +0 -39
  102. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaSeams.cs +1 -55
  103. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaStore.cs +3 -59
  104. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MorphMedia.cs +0 -46
  105. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/GoogleImageProvider.cs +3 -28
  106. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/OpenAIImageProvider.cs +7 -61
  107. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/VeoVideoProvider.cs +2 -37
  108. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/IUsageCostSink.cs +1 -23
  109. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/UsageCostMiddleware.cs +2 -82
  110. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/UsageCostRecord.cs +1 -68
  111. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Observability/MorphOpenTelemetryExtensions.cs +1 -62
  112. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Observability/MorphTelemetryOptions.cs +2 -62
  113. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/IProviderClientFactory.cs +1 -25
  114. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/ModelAlias.cs +0 -124
  115. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/ModelRegistry.cs +1 -73
  116. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Rag/DeltaIngest.cs +1 -66
  117. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Rag/DeltaPlan.cs +0 -41
  118. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/AgentFactoryTests.cs +2 -112
  119. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/AgentSpecTests.cs +2 -43
  120. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/ConversationStoreTests.cs +5 -110
  121. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/DeltaIngestTests.cs +7 -103
  122. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalAssertionTests.cs +1 -42
  123. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalCacheMissTests.cs +1 -30
  124. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalCacheStampTests.cs +1 -19
  125. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalFailuresTests.cs +0 -48
  126. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalFixtureTests.cs +1 -29
  127. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalRefreshTests.cs +2 -38
  128. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalWorkspace.cs +1 -14
  129. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/KitEvals.cs +4 -69
  130. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/OfflineChatClientTests.cs +1 -10
  131. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeChatClient.cs +1 -57
  132. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeChatClientTests.cs +0 -26
  133. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeClock.cs +1 -19
  134. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeMediaProvider.cs +0 -32
  135. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeProviderClientFactory.cs +0 -14
  136. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeUsageCostSink.cs +0 -24
  137. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/InMemoryMediaStore.cs +0 -32
  138. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaAccessibilityTests.cs +0 -37
  139. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaAliasIsNotAnAgentTests.cs +0 -24
  140. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaDisclosureTests.cs +0 -78
  141. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaGeneratorTests.cs +0 -53
  142. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaIdempotencyKeyTests.cs +0 -44
  143. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaJobStateTests.cs +0 -44
  144. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaReviewTests.cs +0 -19
  145. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaStoreTests.cs +0 -24
  146. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/ModelRegistryTests.cs +6 -48
  147. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/SkeletonSmokeTests.cs +1 -13
  148. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/TelemetryTests.cs +2 -71
  149. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/UsageCostMiddlewareTests.cs +5 -100
  150. package/framework/templates/frontend/nextjs/Dockerfile.nextjs.hbs +39 -43
  151. package/framework/templates/frontend/nextjs/env.mjs.hbs +29 -32
  152. package/framework/templates/frontend/nextjs/page.tsx.hbs +21 -22
  153. package/framework/templates/frontend/nextjs/use-feature.ts.hbs +42 -54
  154. package/framework/templates/integrations/asaas-client.cs +332 -387
  155. package/framework/templates/integrations/asaas-webhook.cs +290 -351
  156. package/framework/templates/integrations/neon-auth-config.cs +9 -105
  157. package/framework/templates/project/validate.js +97 -120
  158. package/package.json +2 -2
  159. package/src/commands/dashboard/dashboard.js +80 -53
  160. package/src/commands/mcp/mcp-setup.js +37 -9
  161. package/src/commands/project/doctor.js +12 -6
  162. package/src/commands/project/init.js +6 -3
  163. package/src/commands/project/install-plugin-cmd.js +1 -1
  164. package/src/commands/project/update.js +5 -0
  165. package/src/commands/project/worktree.js +60 -4
  166. package/src/commands/state/gate-check.js +5 -0
  167. package/src/commands/tasks/dag.js +27 -4
  168. package/src/commands/validation/verify.js +22 -38
  169. package/src/dashboard/app/App.jsx +80 -16
  170. package/src/dashboard/app/components/ActivityFeed.jsx +172 -0
  171. package/src/dashboard/app/components/CommandPalette.jsx +155 -0
  172. package/src/dashboard/app/components/DetailPanel.jsx +47 -11
  173. package/src/dashboard/app/components/EntityLink.jsx +55 -0
  174. package/src/dashboard/app/components/ErrorBoundary.jsx +54 -0
  175. package/src/dashboard/app/components/FlowGraph.jsx +19 -17
  176. package/src/dashboard/app/components/KnowledgeMarkdown.jsx +53 -0
  177. package/src/dashboard/app/components/Markdown.jsx +47 -0
  178. package/src/dashboard/app/components/PhaseRail.jsx +111 -0
  179. package/src/dashboard/app/components/SearchFilter.jsx +40 -22
  180. package/src/dashboard/app/components/Sidebar.jsx +146 -37
  181. package/src/dashboard/app/hooks/useMorphQuery.js +54 -0
  182. package/src/dashboard/app/hooks/usePref.js +38 -0
  183. package/src/dashboard/app/lib/format.js +69 -0
  184. package/src/dashboard/app/lib/phases.js +56 -0
  185. package/src/dashboard/app/lib/project.js +50 -0
  186. package/src/dashboard/app/pages/Activity.jsx +24 -0
  187. package/src/dashboard/app/pages/AgentMap.jsx +371 -370
  188. package/src/dashboard/app/pages/FeatureDetail.jsx +604 -0
  189. package/src/dashboard/app/pages/Features.jsx +400 -0
  190. package/src/dashboard/app/pages/Guide.jsx +146 -0
  191. package/src/dashboard/app/pages/HookInspector.jsx +357 -322
  192. package/src/dashboard/app/pages/PhasePipeline.jsx +277 -408
  193. package/src/dashboard/app/pages/Portfolio.jsx +156 -0
  194. package/src/dashboard/app/pages/Rubrics.jsx +101 -0
  195. package/src/dashboard/app/pages/RulesMap.jsx +300 -273
  196. package/src/dashboard/app/pages/SkillsLibrary.jsx +483 -366
  197. package/src/dashboard/app/pages/Standards.jsx +407 -332
  198. package/src/dashboard/app/styles/index.css +187 -47
  199. package/src/dashboard/index.html +14 -12
  200. package/src/dashboard/package.json +32 -24
  201. package/src/dashboard/scanner/activity-scanner.js +351 -0
  202. package/src/dashboard/scanner/agents-scanner.js +59 -1
  203. package/src/dashboard/scanner/docs-scanner.js +59 -0
  204. package/src/dashboard/scanner/feature-status.js +10 -0
  205. package/src/dashboard/scanner/features-scanner.js +360 -0
  206. package/src/dashboard/scanner/index.js +14 -7
  207. package/src/dashboard/scanner/phases-scanner.js +14 -15
  208. package/src/dashboard/scanner/portfolio.js +153 -0
  209. package/src/dashboard/scanner/project-api.js +195 -0
  210. package/src/dashboard/scanner/rules-scanner.js +42 -37
  211. package/src/dashboard/scanner/skills-scanner.js +105 -43
  212. package/src/dashboard/scanner/standards-scanner.js +2 -0
  213. package/src/dashboard/vite-plugin-morph-data.js +134 -17
  214. package/src/lib/detectors/claude-config-detector.js +2 -7
  215. package/src/lib/detectors/index.js +5 -1
  216. package/src/lib/evaluator-report.js +22 -0
  217. package/src/lib/generators/settings-generator.js +208 -209
  218. package/src/lib/installers/mcp-installer.js +724 -719
  219. package/src/lib/installers/memory-installer.js +1 -2
  220. package/src/lib/projects/registry.js +154 -0
  221. package/src/lib/risk-detector.js +13 -0
  222. package/src/lib/tasks/tasks-json-parser.js +21 -0
  223. package/src/lib/validators/code-budget/code-budget-validator.js +32 -4
  224. package/src/lib/worktree/env-allocator.js +9 -3
  225. package/src/scripts/install-plugin.js +1 -1
  226. package/src/scripts/setup-infra.js +2 -4
  227. package/src/utils/agents-installer.js +108 -13
  228. package/src/utils/banner.js +1 -1
  229. package/src/commands/index.js +0 -13
  230. package/src/commands/project/checkpoint.js +0 -209
  231. package/src/core/state/phase-state-machine.js +0 -197
  232. package/src/dashboard/app/hooks/useDetailPanel.js +0 -15
  233. package/src/dashboard/app/pages/LiveState.jsx +0 -206
  234. package/src/dashboard/scanner/squads-scanner.js +0 -13
  235. package/src/dashboard/scanner/state-scanner.js +0 -85
  236. package/src/lib/generators/index.js +0 -10
  237. package/src/lib/hooks/hook-executor.js +0 -167
  238. package/src/lib/validators/checkpoint-persistence.js +0 -39
  239. package/src/lib/validators/shared/emit-validator-dispatch.js +0 -14
  240. package/src/lib/validators/shared/index.js +0 -12
  241. package/src/lib/validators/shared/issue-counter.js +0 -14
  242. package/src/lib/validators/shared/result-formatter.js +0 -124
@@ -1,31 +1,10 @@
1
1
  namespace Morph.AiKit.Observability;
2
2
 
3
- /// <summary>
4
- /// O que o kit precisa saber para emitir telemetria GenAI: sob que <c>ActivitySource</c>,
5
- /// com ou sem conteudo sensivel, e — a decisao que este tipo existe para carregar — se a
6
- /// instrumentacao do chat client deve ser aplicada.
7
- /// </summary>
8
- /// <remarks>
9
- /// <para>
10
- /// <b>Por que <see cref="SourceName"/> e obrigatorio.</b> O standard
11
- /// <c>ai-agents-observability-patterns</c> manda usar
12
- /// <c>AgentOpenTelemetryConsts.DefaultSourceName</c>. No <c>Microsoft.Agents.AI</c> 1.20.0
13
- /// esse tipo <b>nao e publico</b> (apurado por reflexao sobre o assembly do pin: a classe
14
- /// se chama <c>OpenTelemetryConsts</c> e e <c>internal</c>). Um kit que quisesse "usar a
15
- /// constante do MAF" so poderia repetir o texto dela — e um literal copiado envelhece em
16
- /// silencio na primeira vez que o pacote o mudar. Entao o nome e do projeto: e a MESMA
17
- /// string que ele passa para <c>AddSource(...)</c> na configuracao do OpenTelemetry, e o
18
- /// span do agente e o span de chat nascem sob ela, juntos.
19
- /// </para>
20
- /// </remarks>
21
3
  public sealed record MorphTelemetryOptions
22
4
  {
23
5
  private readonly string _sourceName = string.Empty;
24
6
 
25
- /// <summary>
26
- /// Nome do <c>ActivitySource</c> dos spans deste kit. Branco e rejeitado na construcao:
27
- /// um source sem nome e telemetria que ninguem consegue registrar nem coletar.
28
- /// </summary>
7
+ // AgentOpenTelemetryConsts e internal no Microsoft.Agents.AI 1.20.0, entao o nome e do projeto.
29
8
  public required string SourceName
30
9
  {
31
10
  get => _sourceName;
@@ -36,47 +15,8 @@ public sealed record MorphTelemetryOptions
36
15
  }
37
16
  }
38
17
 
39
- /// <summary>
40
- /// Se prompt, resposta, argumentos e resultados de tool entram nos spans. <c>false</c>
41
- /// por padrao — e o standard exige <c>false</c> fora de dev/staging, porque esse
42
- /// conteudo e PII do cliente.
43
- /// </summary>
44
18
  public bool EnableSensitiveData { get; init; }
45
19
 
46
- /// <summary>
47
- /// Se o pipeline de chat deve receber instrumentacao propria. <b>Tres valores, e o
48
- /// default e o guard.</b>
49
- /// </summary>
50
- /// <remarks>
51
- /// <list type="table">
52
- /// <listheader><term>Valor</term><description>O que faz</description></listheader>
53
- /// <item>
54
- /// <term><c>null</c> (default)</term>
55
- /// <description>
56
- /// <b>Instrumenta somente se ainda nao houver instrumentacao no pipeline.</b> A
57
- /// deteccao e <c>GetService(typeof(OpenTelemetryChatClient))</c>, que atravessa a
58
- /// cadeia de delegates (medido). E este ramo, e so ele, que impede o span de chat
59
- /// duplicado.
60
- /// </description>
61
- /// </item>
62
- /// <item>
63
- /// <term><c>true</c></term>
64
- /// <description>
65
- /// Instrumenta sempre. Sobre um pipeline ja instrumentado, produz <b>dois</b>
66
- /// spans <c>chat</c> por invocacao — medido, e pinado por teste. Existe para
67
- /// documentar o risco, nao para ser ligado.
68
- /// </description>
69
- /// </item>
70
- /// <item>
71
- /// <term><c>false</c></term>
72
- /// <description>
73
- /// Nunca instrumenta. Com a <c>AgentFactory</c>, que monta o proprio pipeline
74
- /// (<c>UseProvidedChatClientAsIs = true</c>), isso significa <b>nenhum</b> span de
75
- /// <c>chat</c>: o slot que o MAF reservaria e ativaria sozinho nao existe nesse
76
- /// arranjo. Escolha consciente, nao default.
77
- /// </description>
78
- /// </item>
79
- /// </list>
80
- /// </remarks>
20
+ // null instrumenta so se o pipeline ainda nao tem OpenTelemetryChatClient; true duplica o span de chat.
81
21
  public bool? InstrumentChatClient { get; init; }
82
22
  }
@@ -2,32 +2,8 @@ using Microsoft.Extensions.AI;
2
2
 
3
3
  namespace Morph.AiKit.Providers;
4
4
 
5
- /// <summary>
6
- /// Constroi o <see cref="IChatClient"/> de um alias. E <b>a</b> costura do kit: o unico
7
- /// ponto em que o codigo toca um provider de verdade.
8
- /// </summary>
9
- /// <remarks>
10
- /// <para>
11
- /// O <see cref="ModelRegistry"/> nao sabe construir cliente de provider nenhum — de
12
- /// proposito. Quem sabe e uma implementacao desta interface, e e por isso que a suite do
13
- /// kit roda sem rede e sem chave de API: em teste, a fabrica devolve
14
- /// <c>FakeChatClient</c>. Num projeto, ela e o <c>switch</c> por
15
- /// <see cref="ModelAlias.Provider"/> descrito no standard
16
- /// <c>ai-agents-providers-model-registry</c> (<c>openai</c> e <c>ollama</c> de saida,
17
- /// os demais como branch nova).
18
- /// </para>
19
- /// <para>
20
- /// <b>Quem descarta o cliente:</b> o <see cref="ModelRegistry"/>. A fabrica cria; o
21
- /// registry memoiza por alias e descarta no proprio <c>Dispose</c>. Uma fabrica que
22
- /// guardasse e reaproveitasse clientes por fora criaria uma segunda cache, e duas caches
23
- /// da mesma coisa e como um cliente vazado sobrevive a um teste.
24
- /// </para>
25
- /// </remarks>
5
+ // O ModelRegistry memoiza e descarta o que a fabrica cria; a fabrica nao guarda cliente.
26
6
  public interface IProviderClientFactory
27
7
  {
28
- /// <summary>
29
- /// Cria o cliente do alias dado. Chamado <b>uma vez por alias</b> pelo
30
- /// <see cref="ModelRegistry"/>; o resultado e memoizado.
31
- /// </summary>
32
8
  IChatClient CreateChatClient(ModelAlias alias);
33
9
  }
@@ -3,31 +3,6 @@ using Morph.AiKit.Agents;
3
3
 
4
4
  namespace Morph.AiKit.Providers;
5
5
 
6
- /// <summary>
7
- /// Um alias do <c>model-registry.v2.json</c>: o apelido estavel que os agentes citam, com
8
- /// tudo o que e preciso saber para alcancar o modelo e para contabilizar o que ele custa.
9
- /// </summary>
10
- /// <remarks>
11
- /// <para>
12
- /// Estende a forma de <c>templates/code/dotnet/ai-agents/ModelRegistry.cs.template</c>
13
- /// (provider + model + options) com os dois campos que a issue #92 exige e que o template
14
- /// v1 nao tinha: <see cref="Api"/> — o protocolo, por alias — e a decisao de tarifa
15
- /// (<see cref="Pricing"/> ou <see cref="PricingWaiver"/>).
16
- /// </para>
17
- /// <para>
18
- /// <b>Invariante do tipo:</b> ou existe <see cref="Pricing"/>, ou existe
19
- /// <see cref="PricingWaiver"/> — nunca os dois, nunca nenhum. E o que impede
20
- /// <c>cost_usd</c> de sair nulo la na frente: a ausencia de tarifa passa a ser uma decisao
21
- /// escrita por alguem, com motivo, e nao um campo que ninguem preencheu.
22
- /// <see cref="ValidatePricingDecision"/> e o guardiao dessa invariante e e chamado pelo
23
- /// <see cref="ModelRegistry"/> em toda carga.
24
- /// </para>
25
- /// </remarks>
26
- /// <param name="Provider">Familia de SDK: <c>openai</c>, <c>google</c>, <c>ollama</c>...</param>
27
- /// <param name="Model">Id do modelo no provider.</param>
28
- /// <param name="Api">Protocolo deste alias. Ver <see cref="AgentApi"/>.</param>
29
- /// <param name="Pricing">Tarifa por 1M de tokens, ou nulo se houver <see cref="PricingWaiver"/>.</param>
30
- /// <param name="Endpoint">Endpoint alternativo (Ollama, gateway). Nulo = o padrao do SDK.</param>
31
6
  public sealed record ModelAlias(
32
7
  string Provider,
33
8
  string Model,
@@ -35,35 +10,17 @@ public sealed record ModelAlias(
35
10
  ModelPricing? Pricing,
36
11
  string? Endpoint)
37
12
  {
38
- /// <summary>A chave do alias no documento — <c>text-default</c>, <c>reasoning</c>...</summary>
39
13
  public required string Name { get; init; }
40
14
 
41
- /// <summary>
42
- /// Motivo, por extenso, de este alias nao ter tarifa. Preenchido <b>somente</b> quando
43
- /// <see cref="Pricing"/> e nulo. Exemplo real: modelo local, onde a conta e a maquina e
44
- /// nao o token.
45
- /// </summary>
46
15
  public string? PricingWaiver { get; init; }
47
16
 
48
- /// <summary>Limites conhecidos deste alias. Ver <see cref="ModelAliasConstraints"/>.</summary>
49
17
  public ModelAliasConstraints Constraints { get; init; } = ModelAliasConstraints.None;
50
18
 
51
- /// <summary>Se ha tarifa. Quando falso, <see cref="PricingWaiver"/> explica por que.</summary>
52
19
  public bool HasPricing => Pricing is not null;
53
20
 
54
- /// <summary>
55
- /// A tarifa, ou uma falha que diz o nome do alias e o motivo declarado da ausencia.
56
- /// Use quando o custo e obrigatorio; use <see cref="HasPricing"/> quando ausencia de
57
- /// tarifa e um caminho previsto (e o caso do <c>UsageCostMiddleware</c>, que registra
58
- /// tokens com custo zero e sinaliza a ausencia — nunca inventa tarifa).
59
- /// </summary>
60
21
  public ModelPricing RequirePricing()
61
22
  => Pricing ?? throw ModelRegistryException.MissingPricing(Name, PricingWaiver);
62
23
 
63
- /// <summary>
64
- /// Cobra a invariante "tarifa OU dispensa, nunca ambas, nunca nenhuma".
65
- /// </summary>
66
- /// <exception cref="ModelRegistryException">Quando a decisao de tarifa nao foi tomada.</exception>
67
24
  public static void ValidatePricingDecision(ModelAlias alias)
68
25
  {
69
26
  ArgumentNullException.ThrowIfNull(alias);
@@ -87,21 +44,6 @@ public sealed record ModelAlias(
87
44
  }
88
45
  }
89
46
 
90
- /// <summary>
91
- /// Confronta uma requisicao com os limites declarados deste alias, <b>sem lancar</b> —
92
- /// a decisao de abortar, avisar ou seguir e de quem chama (a <c>AgentFactory</c>).
93
- /// </summary>
94
- /// <param name="effectiveApi">
95
- /// O protocolo que vai valer de fato: <c>spec.Api ?? alias.Api</c>. Veja
96
- /// <see cref="AgentSpec.ResolveApi"/> — a precedencia mora la, nao aqui.
97
- /// </param>
98
- /// <param name="hasTools">Se a requisicao leva tools.</param>
99
- /// <param name="reasoningEffort">
100
- /// O esforco que sera enviado, ou nulo quando nada sera enviado. <b>Nulo nao e
101
- /// violacao</b>: nesse caso vale o default do provider, e qual e ele o kit nao afirma
102
- /// saber — modelar o contrario seria trocar um fato por folclore.
103
- /// </param>
104
- /// <param name="temperatureIsSet">Se a requisicao envia temperatura.</param>
105
47
  public AliasRequestCheck CheckRequest(
106
48
  AgentApi effectiveApi,
107
49
  bool hasTools,
@@ -135,38 +77,13 @@ public sealed record ModelAlias(
135
77
  }
136
78
  }
137
79
 
138
- /// <summary>
139
- /// Tarifa de um alias, em USD por 1 milhao de tokens. Vive junto do alias porque a conta
140
- /// so faz sentido com a tarifa daquele modelo — e porque a matematica do custo, escrita
141
- /// uma vez aqui, nao pode divergir entre o middleware (T11) e um relatorio qualquer.
142
- /// </summary>
143
- /// <param name="InputPerMTok">USD por 1M de tokens de entrada.</param>
144
- /// <param name="OutputPerMTok">USD por 1M de tokens de saida.</param>
145
- /// <param name="CachedInputPerMTok">
146
- /// USD por 1M de tokens de entrada servidos do cache do provider. Nulo = <b>nao ha desconto
147
- /// declarado</b>, e entao token cacheado e cobrado como entrada normal. Nulo NUNCA significa
148
- /// "de graca": inventar desconto e tao errado quanto inventar tarifa.
149
- /// </param>
150
80
  public sealed record ModelPricing(
151
81
  decimal InputPerMTok,
152
82
  decimal OutputPerMTok,
153
83
  decimal? CachedInputPerMTok)
154
84
  {
155
- /// <summary>Tokens a que a tarifa se refere: 1 milhao.</summary>
156
85
  public const decimal TokensPerTariffUnit = 1_000_000m;
157
86
 
158
- /// <summary>
159
- /// Custo em USD de uma volta, a partir das contagens de token da resposta.
160
- /// </summary>
161
- /// <remarks>
162
- /// <paramref name="cachedInputTokens"/> e um <b>subconjunto</b> de
163
- /// <paramref name="inputTokens"/> — e assim que o provider reporta e assim que
164
- /// <c>UsageDetails.CachedInputTokenCount</c> espelha. A parte cacheada so e cobrada a
165
- /// parte quando <see cref="CachedInputPerMTok"/> existe; sem tarifa de cache, toda a
166
- /// entrada e cobrada pela tarifa de entrada.
167
- /// </remarks>
168
- /// <exception cref="ArgumentOutOfRangeException">Contagem negativa.</exception>
169
- /// <exception cref="ArgumentException">Tokens cacheados maiores que os de entrada.</exception>
170
87
  public decimal ComputeCostUsd(long inputTokens, long outputTokens, long? cachedInputTokens = null)
171
88
  {
172
89
  ArgumentOutOfRangeException.ThrowIfNegative(inputTokens);
@@ -196,52 +113,18 @@ public sealed record ModelPricing(
196
113
  }
197
114
  }
198
115
 
199
- /// <summary>
200
- /// Limites conhecidos de um alias. <b>Por alias, com procedencia escrita</b> — nunca uma
201
- /// regra universal chumbada no codigo.
202
- /// </summary>
203
- /// <remarks>
204
- /// A diferenca importa: "modelo de raciocinio rejeita tool" e folclore; o que a
205
- /// documentacao diz e mais estreito e cabe num campo. Um limite sem
206
- /// <see cref="Provenance"/> e recusado na carga do registry, justamente para que ninguem
207
- /// consiga gravar um "todo mundo sabe que" sem dizer de onde tirou.
208
- /// </remarks>
209
116
  public sealed record ModelAliasConstraints
210
117
  {
211
- /// <summary>Nenhum limite declarado.</summary>
212
118
  public static readonly ModelAliasConstraints None = new();
213
119
 
214
- /// <summary>
215
- /// Na <b>Chat Completions</b>, este modelo so aceita tool calling com reasoning effort
216
- /// <c>none</c>. E o limite exato descrito pela documentacao — mais estreito que
217
- /// "modelo de raciocinio nao aceita tool" — e por isso e verificado com os quatro fatos
218
- /// juntos em <see cref="ModelAlias.CheckRequest"/>: protocolo Chat, tools presentes,
219
- /// effort enviado, e effort diferente de <c>none</c>.
220
- /// </summary>
221
120
  public bool ToolCallingRequiresReasoningEffortNone { get; init; }
222
121
 
223
- /// <summary>
224
- /// Este modelo nao aceita <c>temperature</c>.
225
- /// <b>Existe como flag por alias exatamente porque a afirmacao generica
226
- /// ("temperatura e obsoleta para modelos de raciocinio") NAO foi confirmada em fonte
227
- /// primaria.</b> Quem ligar isto assume a afirmacao para um modelo especifico e escreve
228
- /// de onde tirou em <see cref="Provenance"/>.
229
- /// </summary>
230
122
  public bool TemperatureUnsupported { get; init; }
231
123
 
232
- /// <summary>
233
- /// De onde veio o limite: documento, data, citacao. Obrigatorio quando qualquer flag
234
- /// esta ligada.
235
- /// </summary>
236
124
  public string? Provenance { get; init; }
237
125
 
238
- /// <summary>Se ha qualquer limite declarado.</summary>
239
126
  public bool AnyFlagSet => ToolCallingRequiresReasoningEffortNone || TemperatureUnsupported;
240
127
 
241
- /// <summary>
242
- /// Cobra "limite ligado exige procedencia".
243
- /// </summary>
244
- /// <exception cref="ModelRegistryException">Flag ligada sem procedencia.</exception>
245
128
  public void ValidateProvenance(string aliasName)
246
129
  {
247
130
  if (AnyFlagSet && string.IsNullOrWhiteSpace(Provenance))
@@ -254,14 +137,7 @@ public sealed record ModelAliasConstraints
254
137
  }
255
138
  }
256
139
 
257
- /// <summary>
258
- /// Resultado de <see cref="ModelAlias.CheckRequest"/>. Result, nao excecao: um limite
259
- /// violado e informacao para quem chama decidir, nao fluxo de controle.
260
- /// </summary>
261
- /// <param name="IsSatisfied">Se a requisicao respeita todos os limites do alias.</param>
262
- /// <param name="Violations">Uma frase por limite violado, ja com a procedencia.</param>
263
140
  public sealed record AliasRequestCheck(bool IsSatisfied, IReadOnlyList<string> Violations)
264
141
  {
265
- /// <summary>Nenhuma violacao.</summary>
266
142
  public static readonly AliasRequestCheck Satisfied = new(true, []);
267
143
  }
@@ -4,28 +4,6 @@ using Morph.AiKit.Agents;
4
4
 
5
5
  namespace Morph.AiKit.Providers;
6
6
 
7
- /// <summary>
8
- /// Fonte unica de "que modelo e esse apelido": le o documento
9
- /// <c>model-registry.v2.json</c>, resolve alias e memoiza um <see cref="IChatClient"/> por
10
- /// alias.
11
- /// </summary>
12
- /// <remarks>
13
- /// <para>
14
- /// Estende a forma de <c>templates/code/dotnet/ai-agents/ModelRegistry.cs.template</c> —
15
- /// documento JSON como fonte unica, cache de cliente por alias — e acrescenta as duas
16
- /// coisas que a issue #92 cobra: <b><c>api:</c> por alias</b> e <b>tarifa por alias</b>.
17
- /// A construcao do cliente saiu de dentro da classe e virou
18
- /// <see cref="IProviderClientFactory"/>: e essa mudanca que torna o kit testavel em CI sem
19
- /// rede e sem chave de API.
20
- /// </para>
21
- /// <para>
22
- /// <b>Toda falha aqui e falha de carga, nao fluxo de controle.</b> Documento invalido,
23
- /// alias sem decisao de tarifa, limite sem procedencia: tudo estoura em
24
- /// <see cref="FromJson"/>, na composicao, com o nome do alias e o motivo — nunca vira um
25
- /// nulo que atravessa tres camadas e aparece como custo zerado num relatorio. Para o
26
- /// caminho quente ha <see cref="TryResolve"/>, que nao lanca.
27
- /// </para>
28
- /// </remarks>
29
7
  public sealed class ModelRegistry : IDisposable
30
8
  {
31
9
  private const string SupportedDocumentVersion = "2.0";
@@ -48,6 +26,7 @@ public sealed class ModelRegistry : IDisposable
48
26
  };
49
27
 
50
28
  private readonly Dictionary<string, ModelAlias> _aliases;
29
+ // Um cliente por alias: cliente de provider carrega pool de conexao, e recria-lo por chamada vaza socket.
51
30
  private readonly Dictionary<string, IChatClient> _clients = new(StringComparer.Ordinal);
52
31
  private readonly IProviderClientFactory _factory;
53
32
  private readonly Lock _gate = new();
@@ -63,21 +42,10 @@ public sealed class ModelRegistry : IDisposable
63
42
  DefaultAlias = defaultAlias;
64
43
  }
65
44
 
66
- /// <summary>Alias usado quando quem chama nao escolhe um. Nulo se o documento nao declara.</summary>
67
45
  public string? DefaultAlias { get; }
68
46
 
69
- /// <summary>Os aliases do documento, na ordem em que foram declarados.</summary>
70
47
  public IReadOnlyCollection<string> Aliases => _aliases.Keys;
71
48
 
72
- /// <summary>
73
- /// Le o documento e valida tudo o que pode ser validado antes de a primeira requisicao
74
- /// existir.
75
- /// </summary>
76
- /// <exception cref="ModelRegistryException">
77
- /// JSON invalido, campo obrigatorio faltando, <c>api</c> desconhecida, chave nao
78
- /// reconhecida, tarifa negativa, alias sem decisao de tarifa, ou limite sem procedencia.
79
- /// A mensagem sempre nomeia o alias e o campo.
80
- /// </exception>
81
49
  public static ModelRegistry FromJson(string json, IProviderClientFactory factory)
82
50
  {
83
51
  ArgumentException.ThrowIfNullOrWhiteSpace(json);
@@ -155,12 +123,6 @@ public sealed class ModelRegistry : IDisposable
155
123
  }
156
124
  }
157
125
 
158
- /// <summary>
159
- /// O alias, ou falha nomeando os aliases que existem.
160
- /// <b>Nunca devolve nulo</b> — alias desconhecido e erro de configuracao, e um nulo
161
- /// silencioso aqui vira um <c>NullReferenceException</c> em outro arquivo.
162
- /// </summary>
163
- /// <exception cref="ModelRegistryException">Alias desconhecido.</exception>
164
126
  public ModelAlias Resolve(string alias)
165
127
  {
166
128
  ArgumentException.ThrowIfNullOrWhiteSpace(alias);
@@ -170,7 +132,6 @@ public sealed class ModelRegistry : IDisposable
170
132
  : throw ModelRegistryException.UnknownAlias(alias, _aliases.Keys);
171
133
  }
172
134
 
173
- /// <summary>Versao sem excecao de <see cref="Resolve"/>, para caminho quente.</summary>
174
135
  public bool TryResolve(string alias, out ModelAlias? resolved)
175
136
  {
176
137
  ArgumentException.ThrowIfNullOrWhiteSpace(alias);
@@ -178,14 +139,6 @@ public sealed class ModelRegistry : IDisposable
178
139
  return _aliases.TryGetValue(alias, out resolved);
179
140
  }
180
141
 
181
- /// <summary>
182
- /// O <see cref="IChatClient"/> do alias, <b>memoizado</b>: a fabrica e chamada uma unica
183
- /// vez por alias, e as chamadas seguintes devolvem a mesma instancia. Cliente de provider
184
- /// carrega pool de conexao e handler; recria-lo por chamada e como o vazamento de socket
185
- /// costuma nascer.
186
- /// </summary>
187
- /// <exception cref="ModelRegistryException">Alias desconhecido, ou fabrica devolvendo nulo.</exception>
188
- /// <exception cref="ObjectDisposedException">O registry ja foi descartado.</exception>
189
142
  public IChatClient GetChatClient(string alias)
190
143
  {
191
144
  var resolved = Resolve(alias);
@@ -209,10 +162,6 @@ public sealed class ModelRegistry : IDisposable
209
162
  }
210
163
  }
211
164
 
212
- /// <summary>
213
- /// Descarta os clientes memoizados. O registry e dono do que a fabrica criou — ver a
214
- /// nota de ownership em <see cref="IProviderClientFactory"/>.
215
- /// </summary>
216
165
  public void Dispose()
217
166
  {
218
167
  List<IChatClient> toDispose;
@@ -261,8 +210,6 @@ public sealed class ModelRegistry : IDisposable
261
210
  Constraints = ReadConstraints(name, element),
262
211
  };
263
212
 
264
- // As duas invariantes que fazem este registry valer a pena: a decisao de tarifa e
265
- // explicita, e todo limite declarado diz de onde veio.
266
213
  ModelAlias.ValidatePricingDecision(alias);
267
214
  alias.Constraints.ValidateProvenance(name);
268
215
 
@@ -273,8 +220,6 @@ public sealed class ModelRegistry : IDisposable
273
220
  {
274
221
  if (!element.TryGetProperty("api", out var api))
275
222
  {
276
- // Omitir 'api' e cair na Responses API — a decisao da casa. Chat Completions
277
- // exige alguem escrever "chat", ou seja, exige assumir a escolha.
278
223
  return AgentApi.Responses;
279
224
  }
280
225
 
@@ -284,9 +229,6 @@ public sealed class ModelRegistry : IDisposable
284
229
  {
285
230
  "responses" => AgentApi.Responses,
286
231
  "chat" => AgentApi.Chat,
287
- // Midia entra no MESMO registry: um vocabulario so de alias, um lugar so onde
288
- // se pergunta "que modelos este projeto usa". Um alias assim nao vira agente —
289
- // a AgentFactory o recusa com erro nomeado.
290
232
  "images" => AgentApi.Images,
291
233
  "videos" => AgentApi.Videos,
292
234
  _ => throw ModelRegistryException.InvalidDocument(
@@ -416,55 +358,41 @@ public sealed class ModelRegistry : IDisposable
416
358
  }
417
359
  }
418
360
 
419
- /// <summary>Motivo de uma falha do <see cref="ModelRegistry"/>. Discrimina sem exigir hierarquia de excecao.</summary>
420
361
  public enum ModelRegistryError
421
362
  {
422
- /// <summary>Documento invalido: JSON quebrado, campo faltando, valor fora do contrato.</summary>
423
363
  InvalidDocument = 0,
424
364
 
425
- /// <summary>Alias inexistente no documento carregado.</summary>
426
365
  UnknownAlias = 1,
427
366
 
428
- /// <summary>Alias sem tarifa, com o motivo da ausencia declarado no <c>pricingWaiver</c>.</summary>
429
367
  MissingPricing = 2,
430
368
  }
431
369
 
432
- /// <summary>
433
- /// Falha de configuracao do registry. <c>sealed</c> com um <see cref="Reason"/> em vez de
434
- /// uma arvore de subclasses: quem trata precisa discriminar o motivo, nao herdar dele.
435
- /// </summary>
436
370
  public sealed class ModelRegistryException : InvalidOperationException
437
371
  {
438
372
  private ModelRegistryException(ModelRegistryError reason, string message, Exception? inner = null)
439
373
  : base(message, inner)
440
374
  => Reason = reason;
441
375
 
442
- /// <summary>Cria a excecao sem motivo especifico. Prefira as fabricas nomeadas.</summary>
443
376
  public ModelRegistryException(string message)
444
377
  : this(ModelRegistryError.InvalidDocument, message)
445
378
  {
446
379
  }
447
380
 
448
- /// <summary>Cria a excecao encadeando a causa.</summary>
449
381
  public ModelRegistryException(string message, Exception innerException)
450
382
  : this(ModelRegistryError.InvalidDocument, message, innerException)
451
383
  {
452
384
  }
453
385
 
454
- /// <summary>Por que falhou.</summary>
455
386
  public ModelRegistryError Reason { get; }
456
387
 
457
- /// <summary>Documento fora do contrato.</summary>
458
388
  public static ModelRegistryException InvalidDocument(string message, Exception? inner = null)
459
389
  => new(ModelRegistryError.InvalidDocument, message, inner);
460
390
 
461
- /// <summary>Alias desconhecido — a mensagem lista os que existem.</summary>
462
391
  public static ModelRegistryException UnknownAlias(string alias, IEnumerable<string> known)
463
392
  => new(
464
393
  ModelRegistryError.UnknownAlias,
465
394
  $"alias desconhecido: '{alias}'. Declarados no model-registry: {string.Join(", ", known)}.");
466
395
 
467
- /// <summary>Tarifa exigida de um alias que declarou dispensa.</summary>
468
396
  public static ModelRegistryException MissingPricing(string alias, string? waiver)
469
397
  => new(
470
398
  ModelRegistryError.MissingPricing,
@@ -1,22 +1,5 @@
1
1
  namespace Morph.AiKit.Rag;
2
2
 
3
- /// <summary>
4
- /// Erro de <b>entrada</b> do <see cref="DeltaIngest"/>: a mesma lookup-key apareceu duas
5
- /// vezes na mesma remessa.
6
- /// </summary>
7
- /// <remarks>
8
- /// <para>
9
- /// Nao e fluxo de controle e nao e recuperavel no lugar: com a chave repetida, "o que esta
10
- /// no acervo" deixa de ter resposta unica e o plano vira nao-deterministico — qual dos dois
11
- /// itens venceu passa a depender da ordem de enumeracao. Silenciar (ficar com o ultimo)
12
- /// gravaria um dos dois ao acaso; deduplicar por conta propria esconderia um defeito da
13
- /// origem, que e onde ele tem conserto.
14
- /// </para>
15
- /// <para>
16
- /// Os dois indices vao na excecao porque "chave X duplicada" manda procurar num corpus de
17
- /// milhares; "posicoes 12 e 4.871" manda olhar duas linhas.
18
- /// </para>
19
- /// </remarks>
20
3
  public sealed class DuplicateLookupKeyException : ArgumentException
21
4
  {
22
5
  internal DuplicateLookupKeyException(string lookupKey, int firstIndex, int duplicateIndex, string paramName)
@@ -32,58 +15,16 @@ public sealed class DuplicateLookupKeyException : ArgumentException
32
15
  DuplicateIndex = duplicateIndex;
33
16
  }
34
17
 
35
- /// <summary>A chave repetida.</summary>
36
18
  public string LookupKey { get; }
37
19
 
38
- /// <summary>Onde ela apareceu pela primeira vez.</summary>
39
20
  public int FirstIndex { get; }
40
21
 
41
- /// <summary>Onde ela reapareceu.</summary>
42
22
  public int DuplicateIndex { get; }
43
23
  }
44
24
 
45
- /// <summary>
46
- /// Planejamento de ingestao <b>idempotente por lookup-key</b>: dada uma remessa e o que o
47
- /// acervo ja tem, diz o que entrou, o que mudou, o que sumiu e o que ficou igual.
48
- /// </summary>
49
- /// <remarks>
50
- /// <para>
51
- /// <b>Funcao pura, sem I/O.</b> Nao abre conexao, nao chama embedding, nao mede tempo — o
52
- /// que a torna testavel sem banco e, mais importante, torna a idempotencia <b>demonstravel</b>
53
- /// em vez de prometida: aplicar o plano e replanejar a mesma entrada tem de devolver
54
- /// <see cref="DeltaPlan{T}.IsNoOp"/> verdadeiro.
55
- /// </para>
56
- /// <para>
57
- /// <b>A chave e a identidade; o hash e a versao.</b> Sao duas perguntas diferentes e por isso
58
- /// dois delegados: <c>lookupKey</c> responde "e o mesmo documento?" e <c>contentHash</c>
59
- /// responde "mudou?". Colapsar as duas num campo so — usar o hash como chave — faz cada
60
- /// edicao parecer um documento novo e o original parecer removido, que e a ingestao
61
- /// apaga-tudo com outro nome.
62
- /// </para>
63
- /// </remarks>
64
25
  public static class DeltaIngest
65
26
  {
66
- /// <summary>
67
- /// Compara a remessa com o acervo e devolve o plano.
68
- /// </summary>
69
- /// <typeparam name="T">O item ingerido.</typeparam>
70
- /// <param name="incoming">A remessa. Enumerada uma unica vez.</param>
71
- /// <param name="lookupKey">
72
- /// A identidade estavel do item — o que faz duas versoes do mesmo documento serem o
73
- /// mesmo documento. Nula ou em branco e recusada: uma chave vazia colapsaria itens
74
- /// distintos num so.
75
- /// </param>
76
- /// <param name="contentHash">
77
- /// A versao do conteudo. Igual significa "nao mudou"; diferente significa "mudou". Nulo e
78
- /// recusado — sem hash nao ha como distinguir <c>Unchanged</c> de <c>Updated</c>, e
79
- /// tratar a ausencia como "mudou" reintroduziria o re-embedding de tudo.
80
- /// </param>
81
- /// <param name="existingHashes">
82
- /// O que o acervo tem hoje: lookup-key para hash de conteudo. As chaves daqui que nao
83
- /// aparecerem na remessa saem em <see cref="DeltaPlan{T}.Removed"/>.
84
- /// </param>
85
- /// <exception cref="DuplicateLookupKeyException">A mesma chave veio duas vezes.</exception>
86
- /// <exception cref="ArgumentException">Um item produziu chave ou hash nulo/em branco.</exception>
27
+ // A chave e a identidade e o hash e a versao: usar o hash como chave faz cada edicao virar remocao + insercao.
87
28
  public static DeltaPlan<T> Plan<T>(
88
29
  IEnumerable<T> incoming,
89
30
  Func<T, string> lookupKey,
@@ -99,9 +40,6 @@ public static class DeltaIngest
99
40
  List<T> updated = [];
100
41
  List<T> unchanged = [];
101
42
 
102
- // Comparacao ordinal, explicita: a chave e um identificador, nao texto de usuario.
103
- // Deixar a cultura decidir faria o mesmo corpus produzir planos diferentes conforme a
104
- // maquina que roda a ingestao.
105
43
  var seen = new Dictionary<string, int>(StringComparer.Ordinal);
106
44
  var index = 0;
107
45
 
@@ -151,9 +89,6 @@ public static class DeltaIngest
151
89
  index++;
152
90
  }
153
91
 
154
- // O que o acervo tem e a remessa nao trouxe. Note que 'seen' e consultado pela MESMA
155
- // chave e com o MESMO comparador usados acima — uma segunda nocao de igualdade aqui
156
- // faria itens presentes serem reportados como removidos.
157
92
  List<string> removed = [];
158
93
 
159
94
  foreach (var key in existingHashes.Keys)