@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
@@ -2,74 +2,33 @@ using System.Security.Cryptography;
2
2
 
3
3
  namespace Morph.AiKit.Media;
4
4
 
5
- /// <summary>Que tipo de midia o pedido produz. Decide provider, tarifa e limites.</summary>
6
5
  public enum MediaKind
7
6
  {
8
- /// <summary>Imagem.</summary>
9
7
  Image = 0,
10
8
 
11
- /// <summary>Video.</summary>
12
9
  Video = 1,
13
10
  }
14
11
 
15
- /// <summary>
16
- /// Para que serve a midia pedida.
17
- /// </summary>
18
- /// <remarks>
19
- /// <b>E dado, nao convencao de prompt.</b> A finalidade decide qualidade minima, teto de
20
- /// custo, se a midia pode ser publicada e se a marcacao visivel e obrigatoria — decisoes que
21
- /// o codigo precisa tomar. Deixa-las implicitas numa frase do prompt e como uma regra vira
22
- /// folclore; as duas ultimas sao lidas por <see cref="MediaPurposeExtensions"/> e cobradas em
23
- /// <see cref="MediaJob.Publish"/>.
24
- /// </remarks>
25
12
  public enum MediaPurpose
26
13
  {
27
- /// <summary>
28
- /// Foto de catalogo. Revisao humana obrigatoria; qualidade alta na aprovada; marcacao
29
- /// visivel obrigatoria para publicar — cobrada por <see cref="MediaJob.Publish"/>.
30
- /// </summary>
31
14
  CatalogPhoto = 0,
32
15
 
33
- /// <summary>
34
- /// Peca de marketing. Revisao humana obrigatoria; marcacao visivel obrigatoria — e a
35
- /// obrigatoriedade e COBRADA por <see cref="MediaJob.Publish"/>, nao prometida aqui.
36
- /// </summary>
37
16
  Marketing = 1,
38
17
 
39
- /// <summary>
40
- /// Rascunho interno. <b>Nunca publicavel</b> — e <see cref="MediaJob.Publish"/> recusa,
41
- /// em vez de deixar a frase valendo sozinha.
42
- /// </summary>
43
18
  Draft = 2,
44
19
  }
45
20
 
46
- /// <summary>Qualidade pedida. <c>null</c> em <see cref="MediaOptions"/> = nao envia ao provider.</summary>
47
21
  public enum MediaQuality
48
22
  {
49
- /// <summary>Candidatas. A escada de custo comeca aqui, sempre.</summary>
50
23
  Low = 0,
51
24
 
52
- /// <summary>Meio-termo.</summary>
53
25
  Medium = 1,
54
26
 
55
- /// <summary>So na vencedora, depois da escolha humana.</summary>
56
27
  High = 2,
57
28
  }
58
29
 
59
- /// <summary>
60
- /// Uma imagem de referencia do pedido — a foto real do produto que a edicao preserva.
61
- /// </summary>
62
- /// <remarks>
63
- /// <see cref="Sha256Hex"/> e calculado no construtor e entra na chave de idempotencia do
64
- /// PEDIDO: trocar a foto de referencia <b>e</b> outro pedido, mesmo com o prompt identico.
65
- /// Por isso o hash de CADA referencia participa, e nao a contagem delas.
66
- /// </remarks>
67
30
  public sealed class MediaReference
68
31
  {
69
- /// <summary>Cria a referencia e ja fixa o hash do conteudo.</summary>
70
- /// <param name="name">Como o prompt se refere a ela ("image 1 = o sofa").</param>
71
- /// <param name="mediaType">MIME dos bytes.</param>
72
- /// <param name="bytes">O conteudo.</param>
73
32
  public MediaReference(string name, string mediaType, ReadOnlyMemory<byte> bytes)
74
33
  {
75
34
  ArgumentException.ThrowIfNullOrWhiteSpace(name);
@@ -81,54 +40,36 @@ public sealed class MediaReference
81
40
  Sha256Hex = Convert.ToHexStringLower(SHA256.HashData(bytes.Span));
82
41
  }
83
42
 
84
- /// <summary>Como o prompt indexa esta referencia.</summary>
85
43
  public string Name { get; }
86
44
 
87
- /// <summary>MIME dos bytes.</summary>
88
45
  public string MediaType { get; }
89
46
 
90
- /// <summary>O conteudo.</summary>
91
47
  public ReadOnlyMemory<byte> Bytes { get; }
92
48
 
93
- /// <summary>sha256 do conteudo, em hex minusculo.</summary>
94
49
  public string Sha256Hex { get; }
95
50
  }
96
51
 
97
- /// <summary>
98
- /// Opcoes do pedido. <b><c>null</c> significa "nao envia ao provider"</b> — nunca "manda o
99
- /// default do kit": inventar default aqui e afirmar sobre o provider o que o kit nao sabe.
100
- /// </summary>
52
+ // null = nao envia ao provider; inventar default aqui afirmaria sobre o provider o que o kit nao sabe.
101
53
  public sealed record MediaOptions
102
54
  {
103
- /// <summary>Nenhuma opcao declarada.</summary>
104
55
  public static readonly MediaOptions None = new();
105
56
 
106
- /// <summary>Qualidade pedida.</summary>
107
57
  public MediaQuality? Quality { get; init; }
108
58
 
109
- /// <summary>Quantas candidatas numa chamada. Cobra por unidade.</summary>
110
59
  public int? Count { get; init; }
111
60
 
112
- /// <summary>Largura em pixels.</summary>
113
61
  public int? Width { get; init; }
114
62
 
115
- /// <summary>Altura em pixels.</summary>
116
63
  public int? Height { get; init; }
117
64
 
118
- /// <summary>MIME de saida.</summary>
119
65
  public string? MediaType { get; init; }
120
66
 
121
- /// <summary>Duracao, para video. O Veo aceita 4, 6 ou 8 s — e exige 8 s com referencias.</summary>
67
+ // O Veo aceita 4, 6 ou 8 s, e exige 8 s com referencias.
122
68
  public int? DurationSeconds { get; init; }
123
69
 
124
- /// <summary>Proporcao, para video ("16:9", "9:16").</summary>
125
70
  public string? AspectRatio { get; init; }
126
71
 
127
- /// <summary>
128
- /// Forma canonica das opcoes, para a chave de idempotencia. Ordem fixa e escrita a mao
129
- /// de proposito: reflexao sobre as propriedades faria a chave mudar quando alguem
130
- /// reordenasse o tipo, e uma chave que muda sozinha refaz a chamada paga.
131
- /// </summary>
72
+ // Ordem fixa e escrita a mao: reordenar o tipo nao pode mudar a chave e refazer a chamada paga.
132
73
  public string ToCanonicalString()
133
74
  => string.Join('|',
134
75
  $"quality={Quality?.ToString() ?? "-"}",
@@ -140,27 +81,14 @@ public sealed record MediaOptions
140
81
  $"ar={AspectRatio ?? "-"}");
141
82
  }
142
83
 
143
- /// <summary>
144
- /// O pedido de midia: tudo o que decide a chamada paga, e nada mais.
145
- /// </summary>
146
- /// <param name="Alias">O apelido do <c>model-registry.v2.json</c>. O pedido nunca cita modelo.</param>
147
- /// <param name="Prompt">O prompt final, ja montado pelo template versionado.</param>
148
- /// <param name="Purpose">Para que serve. Ver <see cref="MediaPurpose"/>.</param>
149
84
  public sealed record MediaRequest(string Alias, string Prompt, MediaPurpose Purpose)
150
85
  {
151
- /// <summary>Imagem ou video.</summary>
152
86
  public MediaKind Kind { get; init; } = MediaKind.Image;
153
87
 
154
- /// <summary>As referencias de produto. Vazio = geracao do zero.</summary>
155
88
  public IReadOnlyList<MediaReference> References { get; init; } = [];
156
89
 
157
- /// <summary>As opcoes. Ver <see cref="MediaOptions"/>.</summary>
158
90
  public MediaOptions Options { get; init; } = MediaOptions.None;
159
91
 
160
- /// <summary>
161
- /// Recusa um pedido sem o minimo. Result, e nao excecao: pedido malformado vem de
162
- /// entrada de usuario, que e caminho previsto.
163
- /// </summary>
164
92
  public MediaResult<MediaRequest> Validate()
165
93
  {
166
94
  if (string.IsNullOrWhiteSpace(Alias))
@@ -1,59 +1,24 @@
1
1
  namespace Morph.AiKit.Media;
2
2
 
3
- /// <summary>
4
- /// Por que uma operacao de midia recusou. Discriminado por codigo, e nao por tipo de
5
- /// excecao, porque quem trata precisa do MOTIVO — e porque recusa aqui e caminho previsto,
6
- /// nunca fluxo de controle por excecao.
7
- /// </summary>
8
3
  public enum MediaErrorCode
9
4
  {
10
- /// <summary>O pedido nao tem o minimo para ser aceito (prompt vazio, alias vazio).</summary>
11
5
  InvalidRequest = 0,
12
6
 
13
- /// <summary>O teto de custo do tenant/dia recusaria a chamada. Recusa no ENQUEUE.</summary>
14
7
  BudgetExceeded = 1,
15
8
 
16
- /// <summary>Falha tecnica do provider (429, 5xx, timeout). Retryable.</summary>
17
9
  ProviderFailure = 2,
18
10
 
19
- /// <summary>
20
- /// Bloqueio de moderacao do provider. <b>Terminal</b>: retentar gasta para receber o
21
- /// mesmo "nao", e em provider que cobra o bloqueio gasta duas vezes.
22
- /// </summary>
23
11
  PolicyBlocked = 3,
24
12
 
25
- /// <summary>A transicao pedida nao existe a partir do estado atual do job.</summary>
26
13
  InvalidTransition = 4,
27
14
 
28
- /// <summary>O job pedido nao existe no acervo do projeto.</summary>
29
15
  NotFound = 5,
30
16
 
31
- /// <summary>Nao ha tarifa declarada para o alias — o custo nao pode ser estimado.</summary>
32
17
  PricingMissing = 6,
33
18
  }
34
19
 
35
- /// <summary>Uma recusa, com codigo e frase.</summary>
36
- /// <param name="Code">O motivo, para quem decide.</param>
37
- /// <param name="Message">A frase, para quem le o log.</param>
38
20
  public sealed record MediaError(MediaErrorCode Code, string Message);
39
21
 
40
- /// <summary>
41
- /// Result do subsistema de midia.
42
- /// </summary>
43
- /// <remarks>
44
- /// <para>
45
- /// <b>Por que <c>MediaResult</c> e nao <c>Result</c>:</b> este kit nao tem — e nao ganha
46
- /// aqui — um Result de proposito geral. Introduzir um tipo generico na raiz de
47
- /// <c>Morph.AiKit</c> e uma decisao transversal, que afeta toda superficie futura do kit e
48
- /// que uma feature de midia nao deve tomar de passagem. O escopo esta no nome.
49
- /// </para>
50
- /// <para>
51
- /// Excecao continua existindo para <b>falha de composicao</b> (argumento nulo na
52
- /// montagem), como na <c>AgentFactory</c>. O que nao existe e excecao para dizer "este job
53
- /// nao pode ser publicado neste estado": isso e informacao, e informacao viaja em Result.
54
- /// </para>
55
- /// </remarks>
56
- /// <typeparam name="T">O valor devolvido no sucesso.</typeparam>
57
22
  public sealed record MediaResult<T>
58
23
  {
59
24
  private readonly T _value;
@@ -65,28 +30,21 @@ public sealed record MediaResult<T>
65
30
  Error = error;
66
31
  }
67
32
 
68
- /// <summary>Se a operacao produziu valor.</summary>
69
33
  public bool IsSuccess { get; }
70
34
 
71
- /// <summary>A recusa, ou nulo quando <see cref="IsSuccess"/>.</summary>
72
35
  public MediaError? Error { get; }
73
36
 
74
- /// <summary>O valor. Ler no caminho de falha e erro de programacao, e grita.</summary>
75
- /// <exception cref="InvalidOperationException">Quando a operacao falhou.</exception>
76
37
  public T Value => IsSuccess
77
38
  ? _value
78
39
  : throw new InvalidOperationException(
79
40
  $"MediaResult<{typeof(T).Name}> em falha ({Error?.Code}): {Error?.Message}. " +
80
41
  "Confira IsSuccess antes de ler Value.");
81
42
 
82
- /// <summary>Sucesso com valor.</summary>
83
43
  public static MediaResult<T> Ok(T value) => new(true, value, null);
84
44
 
85
- /// <summary>Recusa nomeada.</summary>
86
45
  public static MediaResult<T> Fail(MediaErrorCode code, string message)
87
46
  => new(false, default!, new MediaError(code, message));
88
47
 
89
- /// <summary>Repassa a recusa de outro Result, preservando codigo e frase.</summary>
90
48
  public static MediaResult<T> FailLike<TOther>(MediaResult<TOther> other)
91
49
  => other.Error is { } e
92
50
  ? new MediaResult<T>(false, default!, e)
@@ -1,37 +1,12 @@
1
1
  namespace Morph.AiKit.Media;
2
2
 
3
- /// <summary>O veredito de um humano sobre uma midia gerada.</summary>
4
3
  public enum MediaReviewDecision
5
4
  {
6
- /// <summary>Aprovada. Unica decisao que leva a <see cref="MediaJobState.Approved"/>.</summary>
7
5
  Approved = 0,
8
6
 
9
- /// <summary>Recusada.</summary>
10
7
  Rejected = 1,
11
8
  }
12
9
 
13
- /// <summary>
14
- /// Quem decidiu, o que decidiu, quando, e por que.
15
- /// </summary>
16
- /// <remarks>
17
- /// <para>
18
- /// <b>"Aprovado" sem quem e sem quando nao e revisao, e um campo booleano.</b> Por isso
19
- /// <see cref="Reviewer"/> e <see cref="DecidedAt"/> sao obrigatorios na construcao, e a
20
- /// construcao recusa um revisor em branco: um carimbo anonimo nao responde a pergunta que
21
- /// alguem vai fazer seis meses depois.
22
- /// </para>
23
- /// <para>
24
- /// <b>Este tipo nao reimplementa as transicoes.</b> Elas moram no <see cref="MediaJob"/>,
25
- /// que e o dono do estado; os metodos estaticos daqui sao a forma conveniente de aplicar
26
- /// uma revisao, e delegam. Uma segunda copia da regra "so Approved publica" e exatamente
27
- /// como as duas divergem.
28
- /// </para>
29
- /// <para>
30
- /// Um juiz LLM pode preencher <see cref="Note"/> como pre-filtro, mas nao pode ser o
31
- /// <see cref="Reviewer"/> de um <c>CatalogPhoto</c>: ele economiza o tempo do humano, nao
32
- /// assume a responsabilidade por um claim visual.
33
- /// </para>
34
- /// </remarks>
35
10
  public sealed record MediaReview
36
11
  {
37
12
  private MediaReview(string reviewer, MediaReviewDecision decision, DateTimeOffset decidedAt, string? note)
@@ -42,52 +17,38 @@ public sealed record MediaReview
42
17
  Note = note;
43
18
  }
44
19
 
45
- /// <summary>Identificador do humano que decidiu.</summary>
46
20
  public string Reviewer { get; }
47
21
 
48
- /// <summary>O veredito.</summary>
49
22
  public MediaReviewDecision Decision { get; }
50
23
 
51
- /// <summary>Quando.</summary>
52
24
  public DateTimeOffset DecidedAt { get; }
53
25
 
54
- /// <summary>Observacao livre — inclusive o veredito de um juiz LLM usado como pre-filtro.</summary>
55
26
  public string? Note { get; }
56
27
 
57
- /// <summary>Uma aprovacao humana.</summary>
58
- /// <exception cref="ArgumentException">Revisor em branco.</exception>
59
28
  public static MediaReview ApprovedBy(string reviewer, DateTimeOffset decidedAt, string? note = null)
60
29
  {
61
30
  ArgumentException.ThrowIfNullOrWhiteSpace(reviewer);
62
31
  return new MediaReview(reviewer, MediaReviewDecision.Approved, decidedAt, note);
63
32
  }
64
33
 
65
- /// <summary>Uma recusa humana.</summary>
66
- /// <exception cref="ArgumentException">Revisor em branco.</exception>
67
34
  public static MediaReview RejectedBy(string reviewer, DateTimeOffset decidedAt, string? note = null)
68
35
  {
69
36
  ArgumentException.ThrowIfNullOrWhiteSpace(reviewer);
70
37
  return new MediaReview(reviewer, MediaReviewDecision.Rejected, decidedAt, note);
71
38
  }
72
39
 
73
- /// <summary>Aprova o job. Delega a <see cref="MediaJob.Approve"/> — nao reimplementa a regra.</summary>
74
40
  public static MediaResult<MediaJob> Approve(MediaJob job, string reviewer, DateTimeOffset decidedAt, string? note = null)
75
41
  {
76
42
  ArgumentNullException.ThrowIfNull(job);
77
43
  return job.Approve(ApprovedBy(reviewer, decidedAt, note));
78
44
  }
79
45
 
80
- /// <summary>Recusa o job. Delega a <see cref="MediaJob.Reject"/>.</summary>
81
46
  public static MediaResult<MediaJob> Reject(MediaJob job, string reviewer, DateTimeOffset decidedAt, string? note = null)
82
47
  {
83
48
  ArgumentNullException.ThrowIfNull(job);
84
49
  return job.Reject(RejectedBy(reviewer, decidedAt, note));
85
50
  }
86
51
 
87
- /// <summary>
88
- /// Publica o job. Delega a <see cref="MediaJob.Publish"/>, que cobra as tres portas
89
- /// (estado Approved, finalidade publicavel, marcacao quando exigida).
90
- /// </summary>
91
52
  public static MediaResult<MediaJob> Publish(
92
53
  MediaJob job,
93
54
  MediaDisclosure? disclosure,
@@ -1,18 +1,5 @@
1
1
  namespace Morph.AiKit.Media;
2
2
 
3
- /// <summary>
4
- /// A resolucao de um alias de midia: para quem ir, com que modelo, por qual tarifa.
5
- /// </summary>
6
- /// <remarks>
7
- /// O pedido cita o <b>alias</b>; a chave de idempotencia e a proveniencia citam o
8
- /// <b>modelo</b>. A diferenca importa: um alias pode ser repontado para outro modelo, e
9
- /// duas geracoes com o mesmo alias e modelos diferentes nao sao o mesmo pedido.
10
- /// </remarks>
11
- /// <param name="Alias">O apelido citado pelo pedido.</param>
12
- /// <param name="Provider">Familia do SDK.</param>
13
- /// <param name="Model">O id do modelo no provider.</param>
14
- /// <param name="Kind">Imagem ou video.</param>
15
- /// <param name="Pricing">A tarifa, na unidade certa. Ver <see cref="MediaPricing"/>.</param>
16
3
  public sealed record MediaAliasBinding(
17
4
  string Alias,
18
5
  string Provider,
@@ -20,78 +7,37 @@ public sealed record MediaAliasBinding(
20
7
  MediaKind Kind,
21
8
  MediaPricing Pricing);
22
9
 
23
- /// <summary>Resolve um alias de midia. Alias desconhecido e recusa nomeada, nunca nulo.</summary>
24
10
  public interface IMediaAliasResolver
25
11
  {
26
- /// <summary>O binding do alias.</summary>
27
12
  MediaResult<MediaAliasBinding> Resolve(string alias);
28
13
  }
29
14
 
30
- /// <summary>
31
- /// Onde os jobs ficam guardados. <b>A costura entre o kit e o banco do projeto.</b>
32
- /// </summary>
33
- /// <remarks>
34
- /// Sem tipo de infraestrutura na assinatura, pela mesma razao de
35
- /// <c>IConversationStore</c>: o kit nao conhece banco. No projeto, o handler fala com o
36
- /// <c>ApplicationDbContext</c> direto — sem camada de service, sem repository generico.
37
- /// Dentro de um job, <c>IDbContextFactory</c>, como o standard de Hangfire ja exige.
38
- /// </remarks>
39
15
  public interface IMediaJobRepository
40
16
  {
41
- /// <summary>
42
- /// O job de mesma chave de PEDIDO, se existir. <b>E a consulta que evita pagar duas
43
- /// vezes</b>, e por isso ela vem antes de qualquer chamada ao provider.
44
- /// </summary>
45
17
  ValueTask<MediaJob?> FindByIdempotencyKeyAsync(MediaIdempotencyKey key, CancellationToken cancellationToken);
46
18
 
47
- /// <summary>O job por id, ou nulo.</summary>
48
19
  ValueTask<MediaJob?> FindAsync(MediaJobId id, CancellationToken cancellationToken);
49
20
 
50
- /// <summary>Grava o estado corrente do job.</summary>
51
21
  ValueTask SaveAsync(MediaJob job, CancellationToken cancellationToken);
52
22
  }
53
23
 
54
- /// <summary>
55
- /// O teto de custo por tenant/dia.
56
- /// </summary>
57
- /// <remarks>
58
- /// <b>A reserva acontece no ENQUEUE</b>, com o custo estimado ANTES da chamada. Um alerta
59
- /// que chega depois da chamada e contabilidade, nao controle: o dinheiro ja saiu.
60
- /// </remarks>
61
24
  public interface IMediaBudget
62
25
  {
63
- /// <summary>
64
- /// Reserva o custo estimado. <c>false</c> = estourou o teto, e o enqueue recusa com
65
- /// <see cref="MediaErrorCode.BudgetExceeded"/>.
66
- /// </summary>
26
+ // Reservado no ENQUEUE, antes da chamada: alerta depois dela e contabilidade, nao controle.
67
27
  ValueTask<bool> TryReserveAsync(MediaRequest request, decimal estimatedCostUsd, CancellationToken cancellationToken);
68
28
  }
69
29
 
70
- /// <summary>
71
- /// Executa UM job: chama o provider, guarda os bytes, move o estado.
72
- /// </summary>
73
- /// <remarks>
74
- /// A interface e publica para que o <c>BackgroundService</c> do projeto (ou o job Hangfire)
75
- /// possa dispara-la; a <b>implementacao</b> e <c>internal</c>, porque e ela que segura o
76
- /// <c>IMediaProvider</c>. Um handler pode pedir "rode este job"; nao pode pedir "gere estes
77
- /// bytes agora".
78
- /// </remarks>
79
30
  public interface IMediaJobRunner
80
31
  {
81
- /// <summary>Roda o job ate o proximo estado estavel e devolve como ele ficou.</summary>
82
32
  Task<MediaResult<MediaJob>> RunAsync(MediaJobId id, CancellationToken cancellationToken = default);
83
33
  }
84
34
 
85
- /// <summary>Relogio, para que o carimbo de tempo seja testavel sem esperar o mundo girar.</summary>
86
35
  public interface IMediaClock
87
36
  {
88
- /// <summary>Agora.</summary>
89
37
  DateTimeOffset UtcNow { get; }
90
38
  }
91
39
 
92
- /// <summary>O relogio do sistema.</summary>
93
40
  public sealed class SystemMediaClock : IMediaClock
94
41
  {
95
- /// <inheritdoc />
96
42
  public DateTimeOffset UtcNow => DateTimeOffset.UtcNow;
97
43
  }
@@ -2,98 +2,43 @@ using System.Security.Cryptography;
2
2
 
3
3
  namespace Morph.AiKit.Media;
4
4
 
5
- /// <summary>
6
- /// O endereco de um conteudo: sha256 dos BYTES.
7
- /// </summary>
8
- /// <remarks>
9
- /// Responde "ja guardamos isto?" — nunca "ja pagamos por isto?", que e a pergunta do
10
- /// <see cref="MediaIdempotencyKey"/>. Confundir as duas e o defeito caro: dois pedidos
11
- /// diferentes podem produzir bytes identicos (e devem compartilhar UM arquivo), e o mesmo
12
- /// pedido nunca deve ser pago duas vezes (mesmo que os bytes saiam diferentes).
13
- /// </remarks>
14
- /// <param name="Value">sha256 em hex minusculo.</param>
5
+ // Endereca BYTES ("ja guardamos?"), nunca o pedido ("ja pagamos?"), que e o MediaIdempotencyKey.
15
6
  public readonly record struct MediaHash(string Value)
16
7
  {
17
- /// <summary>O hash do conteudo.</summary>
18
8
  public static MediaHash Of(ReadOnlySpan<byte> bytes)
19
9
  => new(Convert.ToHexStringLower(SHA256.HashData(bytes)));
20
10
 
21
- /// <summary>O hash, para log e para o caminho no blob.</summary>
22
11
  public override string ToString() => Value;
23
12
  }
24
13
 
25
- /// <summary>
26
- /// Onde os bytes ficam guardados. <b>A costura entre o kit e o blob do projeto</b> — o
27
- /// unico ponto do <see cref="MediaStore"/> que um projeto precisa implementar.
28
- /// </summary>
29
- /// <remarks>
30
- /// Deliberadamente sem tipo de infraestrutura na assinatura: nenhum <c>BlobClient</c>,
31
- /// nenhum <c>S3</c>, nenhum caminho absoluto. A implementacao de producao vive no projeto;
32
- /// a de teste vive nos fakes da suite. O kit nao conhece storage, pela mesma razao pela
33
- /// qual <c>IConversationStore</c> nao conhece banco.
34
- /// </remarks>
35
14
  public interface IMediaBlobs
36
15
  {
37
- /// <summary>Se o caminho relativo ja existe. Nunca lanca por ausencia.</summary>
38
16
  ValueTask<bool> ExistsAsync(string relativePath, CancellationToken cancellationToken);
39
17
 
40
- /// <summary>Grava o conteudo no caminho relativo.</summary>
41
18
  ValueTask WriteAsync(string relativePath, ReadOnlyMemory<byte> bytes, CancellationToken cancellationToken);
42
19
  }
43
20
 
44
- /// <summary>O que o storage devolve sobre um conteudo guardado.</summary>
45
- /// <param name="Hash">sha256 dos bytes.</param>
46
- /// <param name="RelativePath">
47
- /// O caminho RELATIVO, que e o que o banco guarda. Caminho absoluto ou URL de provider
48
- /// amarram a linha ao disco de hoje ou a uma URL que expira — o Veo apaga o arquivo em 2
49
- /// dias, e todo agregador devolve URL temporaria.
50
- /// </param>
51
- /// <param name="MediaType">MIME.</param>
52
- /// <param name="AlreadyExisted">
53
- /// Se o conteudo ja estava la. <b>Nao e enfeite:</b> e como o chamador sabe que a gravacao
54
- /// foi deduplicada em vez de repetida.
55
- /// </param>
21
+ // Caminho RELATIVO, nunca URL de provider: o Veo apaga o arquivo em 2 dias e agregadores devolvem URL temporaria.
56
22
  public sealed record MediaStoreEntry(MediaHash Hash, string RelativePath, string MediaType, bool AlreadyExisted);
57
23
 
58
- /// <summary>Storage de midia enderecado por conteudo.</summary>
59
24
  public interface IMediaStore
60
25
  {
61
- /// <summary>Guarda os bytes e devolve o endereco. Conteudo repetido NAO e regravado.</summary>
62
26
  ValueTask<MediaStoreEntry> PutAsync(
63
27
  ReadOnlyMemory<byte> bytes,
64
28
  string mediaType,
65
29
  CancellationToken cancellationToken);
66
30
  }
67
31
 
68
- /// <summary>
69
- /// Storage enderecado por conteudo: o caminho e derivado do sha256, e gravar o mesmo
70
- /// conteudo duas vezes grava uma.
71
- /// </summary>
72
- /// <remarks>
73
- /// <para>
74
- /// O caminho e <c>ab/cd/&lt;hash&gt;.&lt;ext&gt;</c>. Os dois niveis de prefixo existem
75
- /// porque um diretorio unico com centenas de milhares de arquivos degrada listagem em
76
- /// praticamente todo sistema de arquivos e em varios object stores.
77
- /// </para>
78
- /// <para>
79
- /// A extensao vem do MIME e e o unico lugar do subsistema que faz esse mapa. Um MIME
80
- /// desconhecido vira <c>.bin</c> em vez de recusa: perder a extensao e cosmetico, perder os
81
- /// bytes pelos quais se pagou nao e.
82
- /// </para>
83
- /// </remarks>
84
32
  public sealed class MediaStore : IMediaStore
85
33
  {
86
34
  private readonly IMediaBlobs _blobs;
87
35
 
88
- /// <summary>Cria o storage sobre o blob do projeto.</summary>
89
- /// <exception cref="ArgumentNullException">Blob nulo.</exception>
90
36
  public MediaStore(IMediaBlobs blobs)
91
37
  {
92
38
  ArgumentNullException.ThrowIfNull(blobs);
93
39
  _blobs = blobs;
94
40
  }
95
41
 
96
- /// <inheritdoc />
97
42
  public async ValueTask<MediaStoreEntry> PutAsync(
98
43
  ReadOnlyMemory<byte> bytes,
99
44
  string mediaType,
@@ -113,14 +58,13 @@ public sealed class MediaStore : IMediaStore
113
58
  return new MediaStoreEntry(hash, path, mediaType, AlreadyExisted: false);
114
59
  }
115
60
 
116
- /// <summary>O caminho relativo de um conteudo. Puro, para poder ser conferido em teste.</summary>
61
+ // Dois niveis de prefixo: um diretorio com centenas de milhares de arquivos degrada a listagem.
117
62
  public static string RelativePathFor(MediaHash hash, string mediaType)
118
63
  {
119
64
  var value = hash.Value;
120
65
  return $"{value[..2]}/{value[2..4]}/{value}{ExtensionFor(mediaType)}";
121
66
  }
122
67
 
123
- /// <summary>MIME para extensao de arquivo. Desconhecido vira <c>.bin</c>, nunca recusa.</summary>
124
68
  public static string ExtensionFor(string mediaType) => mediaType.ToLowerInvariant() switch
125
69
  {
126
70
  "image/png" => ".png",
@@ -1,65 +1,22 @@
1
1
  namespace Morph.AiKit.Media;
2
2
 
3
- /// <summary>
4
- /// As pecas que o projeto precisa fornecer para o subsistema de midia funcionar.
5
- /// </summary>
6
- /// <remarks>
7
- /// Nenhuma delas produz bytes. Repare no que NAO ha aqui: nao ha como o projeto injetar,
8
- /// substituir ou alcancar um <c>IMediaProvider</c> — os adaptadores sao montados dentro do
9
- /// assembly, por <see cref="MorphMedia"/>.
10
- /// </remarks>
11
3
  public sealed record MorphMediaOptions
12
4
  {
13
- /// <summary>Resolve alias para provider/modelo/tarifa. Obrigatorio.</summary>
14
5
  public required IMediaAliasResolver AliasResolver { get; init; }
15
6
 
16
- /// <summary>Onde os jobs vivem. Obrigatorio.</summary>
17
7
  public required IMediaJobRepository Jobs { get; init; }
18
8
 
19
- /// <summary>Onde os bytes vivem. Obrigatorio.</summary>
20
9
  public required IMediaStore Store { get; init; }
21
10
 
22
- /// <summary>
23
- /// Constroi os clientes de SDK. Obrigatorio — e a costura com o provider de verdade.
24
- /// </summary>
25
11
  public required IMediaClientFactory Clients { get; init; }
26
12
 
27
- /// <summary>O teto de custo. Nulo = sem teto (aceitavel so em ambiente de desenvolvimento).</summary>
28
13
  public IMediaBudget? Budget { get; init; }
29
14
 
30
- /// <summary>O relogio. Nulo = relogio do sistema.</summary>
31
15
  public IMediaClock? Clock { get; init; }
32
16
  }
33
17
 
34
- /// <summary>
35
- /// A raiz de composicao do subsistema de midia.
36
- /// </summary>
37
- /// <remarks>
38
- /// <para>
39
- /// <b>Por que uma fabrica estatica e nao um <c>AddMorphMedia(IServiceCollection)</c>:</b>
40
- /// uma extensao de DI exigiria uma <c>PackageReference</c> explicita a
41
- /// <c>Microsoft.Extensions.DependencyInjection.Abstractions</c>, e neste repositorio as
42
- /// versoes de pacote sao GERADAS de <c>framework/ai-pin.json</c>. Acrescentar um pacote e
43
- /// mexer no pin — uma decisao que se toma de proposito, com re-verificacao, e nao de
44
- /// passagem numa feature de midia. Depender do pacote de forma TRANSITIVA seria pior: uma
45
- /// dependencia escondida, que some no dia em que a arvore de dependencias mudar.
46
- /// </para>
47
- /// <para>
48
- /// No projeto consumidor, o registro e uma linha, e o kit continua sem conhecer DI:
49
- /// </para>
50
- /// <code>
51
- /// var media = MorphMedia.Create(options);
52
- /// builder.Services.AddSingleton&lt;IMediaGenerator&gt;(media.Generator);
53
- /// builder.Services.AddSingleton&lt;IMediaJobRunner&gt;(media.Runner);
54
- /// </code>
55
- /// </remarks>
56
18
  public static class MorphMedia
57
19
  {
58
- /// <summary>
59
- /// Monta o subsistema: o gerador publico e o executor de job, ambos sobre os mesmos
60
- /// adaptadores internos.
61
- /// </summary>
62
- /// <exception cref="ArgumentNullException">Opcoes nulas.</exception>
63
20
  public static MorphMediaComponents Create(MorphMediaOptions options)
64
21
  {
65
22
  ArgumentNullException.ThrowIfNull(options);
@@ -87,7 +44,4 @@ public static class MorphMedia
87
44
  }
88
45
  }
89
46
 
90
- /// <summary>O que a raiz de composicao devolve. Duas interfaces, nenhuma delas com bytes.</summary>
91
- /// <param name="Generator">O ponto de entrada do pedido.</param>
92
- /// <param name="Runner">O executor de job, para o <c>BackgroundService</c> do projeto.</param>
93
47
  public sealed record MorphMediaComponents(IMediaGenerator Generator, IMediaJobRunner Runner);