@polymorphism-tech/morph-spec 8.34.2 → 8.35.1

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 (328) hide show
  1. package/CLAUDE.md +22 -5
  2. package/README.md +2 -2
  3. package/bin/morph-spec.js +18 -4
  4. package/bin/validate.js +379 -379
  5. package/claude-plugin.json +1 -1
  6. package/docs/CHEATSHEET.md +5 -4
  7. package/docs/QUICKSTART.md +1 -1
  8. package/framework/MORPH.md +16 -17
  9. package/framework/agents.json +197 -44
  10. package/framework/ai-pin.json +22 -0
  11. package/framework/commands/morph-media.md +45 -0
  12. package/framework/docs/CLI.md +236 -11
  13. package/framework/evals/proof-rubrics/README.md +76 -0
  14. package/framework/evals/proof-rubrics/eval-de-modelo.md +64 -0
  15. package/framework/evals/proof-rubrics/fiacao.md +54 -0
  16. package/framework/evals/proof-rubrics/guard-cego.md +52 -0
  17. package/framework/evals/proof-rubrics/media-claims.md +109 -0
  18. package/framework/evals/proof-rubrics/mutation-proof.md +77 -0
  19. package/framework/evals/proof-rubrics/observabilidade-genai.md +119 -0
  20. package/framework/evals/proof-rubrics/verde-nao-e-coberto.md +48 -0
  21. package/framework/evals/rubrics/architecture.md +30 -0
  22. package/framework/evals/rubrics/code-quality.md +26 -0
  23. package/framework/evals/rubrics/test-coverage.md +20 -0
  24. package/framework/hooks/claude-code/core/protect-spec-files.js +41 -4
  25. package/framework/hooks/claude-code/core/worktree-env-inject.js +43 -0
  26. package/framework/hooks/claude-code/statusline.sh +11 -11
  27. package/framework/hooks/git/commit-msg/conventional-commits.sh +33 -33
  28. package/framework/hooks/git/pre-commit/agents.sh +25 -25
  29. package/framework/hooks/git/pre-commit/orchestrator.sh +64 -64
  30. package/framework/hooks/git/pre-commit/specs.sh +50 -50
  31. package/framework/hooks/git/pre-push/run-tests.sh +44 -44
  32. package/framework/hooks/shared/e2e-check.js +135 -22
  33. package/framework/hooks/shared/payload-utils.js +15 -2
  34. package/framework/hooks/shared/stdin-reader.js +26 -26
  35. package/framework/rules/ai-agents-standards.md +14 -2
  36. package/framework/schemas/ai-pin.schema.json +97 -0
  37. package/framework/schemas/config.schema.json +107 -0
  38. package/framework/schemas/feature.schema.json +23 -4
  39. package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
  40. package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
  41. package/framework/skills/core/morph-apply/SKILL.md +26 -5
  42. package/framework/skills/core/morph-brainstorming/SKILL.md +1 -1
  43. package/framework/skills/core/morph-eval/SKILL.md +1 -1
  44. package/framework/skills/core/morph-hotfix/SKILL.md +2 -2
  45. package/framework/skills/core/morph-implement/SKILL.md +18 -2
  46. package/framework/skills/core/morph-media/SKILL.md +215 -0
  47. package/framework/skills/core/morph-plan/SKILL.md +33 -2
  48. package/framework/skills/core/morph-proposal/SKILL.md +1 -1
  49. package/framework/skills/core/morph-review/SKILL.md +166 -9
  50. package/framework/skills/core/morph-stack-scan/SKILL.md +1 -1
  51. package/framework/skills/core/morph-standards/SKILL.md +1 -1
  52. package/framework/skills/core/morph-uiux/SKILL.md +1 -1
  53. package/framework/standards/PROVENANCE.json +81 -3
  54. package/framework/standards/STANDARDS.json +1483 -839
  55. package/framework/standards/ai-agents/agent-archetypes.md +160 -0
  56. package/framework/standards/ai-agents/agent-session.md +164 -68
  57. package/framework/standards/ai-agents/agent-spec.md +196 -0
  58. package/framework/standards/ai-agents/batching.md +61 -37
  59. package/framework/standards/ai-agents/context-providers.md +97 -24
  60. package/framework/standards/ai-agents/conversational-agent-with-phases.md +239 -0
  61. package/framework/standards/ai-agents/cost-and-budget.md +256 -0
  62. package/framework/standards/ai-agents/durable-workflows-hitl.md +98 -5
  63. package/framework/standards/ai-agents/evals-with-cache.md +289 -0
  64. package/framework/standards/ai-agents/guardrails.md +222 -0
  65. package/framework/standards/ai-agents/llm-runtime-defaults.md +226 -0
  66. package/framework/standards/ai-agents/mcp-server.md +119 -18
  67. package/framework/standards/ai-agents/mcp-tools.md +85 -32
  68. package/framework/standards/ai-agents/media-pipeline.md +352 -0
  69. package/framework/standards/ai-agents/media-video.md +267 -0
  70. package/framework/standards/ai-agents/middleware-patterns.md +39 -5
  71. package/framework/standards/ai-agents/modalities-image-gen.md +354 -105
  72. package/framework/standards/ai-agents/modalities-speech-to-text.md +8 -2
  73. package/framework/standards/ai-agents/modalities-text-to-speech.md +10 -2
  74. package/framework/standards/ai-agents/modalities-vision.md +6 -2
  75. package/framework/standards/ai-agents/multi-agent-patterns.md +61 -5
  76. package/framework/standards/ai-agents/observability-patterns.md +227 -17
  77. package/framework/standards/ai-agents/production.md +38 -9
  78. package/framework/standards/ai-agents/prompt-sources.md +389 -0
  79. package/framework/standards/ai-agents/providers/model-registry.md +174 -39
  80. package/framework/standards/ai-agents/rag-custom-pgvector.md +69 -4
  81. package/framework/standards/ai-agents/service-tier-flex.md +27 -19
  82. package/framework/standards/ai-agents/setup.md +166 -50
  83. package/framework/standards/ai-agents/structured-output.md +34 -3
  84. package/framework/standards/ai-agents/sweet-spot.md +23 -3
  85. package/framework/standards/ai-agents/testing-ai.md +236 -0
  86. package/framework/standards/ai-agents/vector-data-extensions.md +50 -20
  87. package/framework/standards/ai-agents/workflows.md +27 -7
  88. package/framework/standards/frontend/design-system/ai-image-generation.md +12 -0
  89. package/framework/standards/frontend/design-system/ai-video-generation.md +13 -0
  90. package/framework/standards/infrastructure/docker/local-compose-isolation.md +3 -1
  91. package/framework/templates/.idea/morph-templates.xml +92 -0
  92. package/framework/templates/.vscode/morph-templates.code-snippets +186 -0
  93. package/framework/templates/IDE-SNIPPETS.md +262 -262
  94. package/framework/templates/REGISTRY.json +13 -0
  95. package/framework/templates/code/dotnet/abstractions/api-endpoint.cs +7 -7
  96. package/framework/templates/code/dotnet/ai-agents/AgentBootstrap.cs.template +39 -39
  97. package/framework/templates/code/dotnet/ai-agents/EmbeddingService.cs.template +68 -68
  98. package/framework/templates/code/dotnet/ai-agents/ModelRegistry.cs.template +117 -117
  99. package/framework/templates/code/dotnet/ai-agents/PromptRepository.cs.template +1 -1
  100. package/framework/templates/code/dotnet/ai-agents/ResilientToolMiddleware.cs.template +61 -61
  101. package/framework/templates/code/dotnet/ai-agents/VectorSearchTool.cs.template +107 -107
  102. package/framework/templates/code/dotnet/ai-agents/model-registry.json.template +58 -58
  103. package/framework/templates/code/dotnet/contracts/contracts-vsa.cs +281 -281
  104. package/framework/templates/code/dotnet/database/migration.cs +83 -83
  105. package/framework/templates/code/dotnet/feature/endpoint.cs.hbs +21 -21
  106. package/framework/templates/code/dotnet/feature/request.cs.hbs +4 -4
  107. package/framework/templates/code/dotnet/feature/response.cs.hbs +4 -4
  108. package/framework/templates/code/dotnet/feature/validator.cs.hbs +15 -15
  109. package/framework/templates/code/sql/ai-agents/agent-prompts-migration.sql.template +1 -1
  110. package/framework/templates/code/sql/ai-agents/pgvector-embeddings-migration.sql.template +91 -91
  111. package/framework/templates/code/typescript/contracts.ts +167 -167
  112. package/framework/templates/docs/clarifications.md +253 -253
  113. package/framework/templates/docs/schema-analysis.md +119 -119
  114. package/framework/templates/docs/ui-components.md +52 -52
  115. package/framework/templates/docs/ui-design-system.md +76 -76
  116. package/framework/templates/docs/ui-flows.md +167 -167
  117. package/framework/templates/docs/ui-mockups.md +98 -98
  118. package/framework/templates/dotnet/ai-kit/Directory.Build.props +25 -0
  119. package/framework/templates/dotnet/ai-kit/Directory.Packages.props +26 -0
  120. package/framework/templates/dotnet/ai-kit/Morph.AiKit.sln +56 -0
  121. package/framework/templates/dotnet/ai-kit/NuGet.config +12 -0
  122. package/framework/templates/dotnet/ai-kit/README.md +128 -0
  123. package/framework/templates/dotnet/ai-kit/global.json +9 -0
  124. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentApi.cs +65 -0
  125. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentFactory.cs +289 -0
  126. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentFactoryOptions.cs +91 -0
  127. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentSpec.cs +91 -0
  128. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentSpecOptions.cs +181 -0
  129. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/AiKitAssemblyMarker.cs +14 -0
  130. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/ConversationStore.cs +174 -0
  131. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/IConversationStore.cs +54 -0
  132. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/InMemoryConversationStore.cs +75 -0
  133. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/CountingChatClient.cs +71 -0
  134. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalAssertion.cs +246 -0
  135. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheMissException.cs +57 -0
  136. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheOptions.cs +78 -0
  137. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheStamp.cs +225 -0
  138. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalFixture.cs +463 -0
  139. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalMode.cs +32 -0
  140. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalQualityEvaluators.cs +49 -0
  141. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalScenario.cs +214 -0
  142. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalSummary.cs +168 -0
  143. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/OfflineChatClient.cs +142 -0
  144. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/ShortPathResponseCache.cs +349 -0
  145. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/IMediaGenerator.cs +42 -0
  146. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/IMediaProvider.cs +77 -0
  147. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaArtifact.cs +28 -0
  148. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaDisclosure.cs +107 -0
  149. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaGenerator.cs +253 -0
  150. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaIdempotencyKey.cs +103 -0
  151. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJob.cs +259 -0
  152. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJobId.cs +21 -0
  153. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJobState.cs +47 -0
  154. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaPricing.cs +102 -0
  155. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaProvenance.cs +44 -0
  156. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaRequest.cs +192 -0
  157. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaResult.cs +94 -0
  158. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaReview.cs +99 -0
  159. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaSeams.cs +97 -0
  160. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaStore.cs +133 -0
  161. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MorphMedia.cs +93 -0
  162. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/GoogleImageProvider.cs +112 -0
  163. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/OpenAIImageProvider.cs +288 -0
  164. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/VeoVideoProvider.cs +158 -0
  165. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/IUsageCostSink.cs +29 -0
  166. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/UsageCostMiddleware.cs +208 -0
  167. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/UsageCostRecord.cs +95 -0
  168. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Morph.AiKit.csproj +38 -0
  169. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Observability/MorphOpenTelemetryExtensions.cs +119 -0
  170. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Observability/MorphTelemetryOptions.cs +82 -0
  171. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/IProviderClientFactory.cs +33 -0
  172. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/ModelAlias.cs +267 -0
  173. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/ModelRegistry.cs +474 -0
  174. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/model-registry.v2.json +87 -0
  175. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Rag/DeltaIngest.cs +169 -0
  176. package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Rag/DeltaPlan.cs +49 -0
  177. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/AgentFactoryTests.cs +495 -0
  178. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/AgentSpecTests.cs +260 -0
  179. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/ConversationStoreTests.cs +405 -0
  180. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/DeltaIngestTests.cs +379 -0
  181. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalAssertionTests.cs +197 -0
  182. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalCacheMissTests.cs +139 -0
  183. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalCacheStampTests.cs +157 -0
  184. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalFailuresTests.cs +178 -0
  185. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalFixtureTests.cs +153 -0
  186. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalRefreshTests.cs +194 -0
  187. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalWorkspace.cs +86 -0
  188. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/KitEvals.cs +213 -0
  189. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/OfflineChatClientTests.cs +95 -0
  190. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeChatClient.cs +225 -0
  191. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeChatClientTests.cs +215 -0
  192. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeClock.cs +33 -0
  193. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeMediaProvider.cs +89 -0
  194. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeProviderClientFactory.cs +55 -0
  195. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeUsageCostSink.cs +60 -0
  196. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/InMemoryMediaStore.cs +131 -0
  197. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaAccessibilityTests.cs +133 -0
  198. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaAliasIsNotAnAgentTests.cs +127 -0
  199. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaDisclosureTests.cs +216 -0
  200. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaGeneratorTests.cs +264 -0
  201. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaIdempotencyKeyTests.cs +148 -0
  202. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaJobStateTests.cs +184 -0
  203. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaReviewTests.cs +89 -0
  204. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaStoreTests.cs +98 -0
  205. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/ModelRegistryTests.cs +451 -0
  206. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Morph.AiKit.Tests.csproj +76 -0
  207. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/SkeletonSmokeTests.cs +33 -0
  208. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/TelemetryTests.cs +261 -0
  209. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/UsageCostMiddlewareTests.cs +374 -0
  210. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/.morph-eval-cache.json +12 -0
  211. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/orcamento.acme.a-resposta-estruturada/1/CF70A7CE27BBD66A/contents.data +13 -0
  212. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/orcamento.acme.a-resposta-estruturada/1/CF70A7CE27BBD66A/entry.json +7 -0
  213. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/triagem.acme.a-saudacao/1/19F631228C6E00BA/contents.data +13 -0
  214. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/triagem.acme.a-saudacao/1/19F631228C6E00BA/entry.json +7 -0
  215. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/triagem.acme.b-escalonamento/1/A24A7C9BDD8239D8/contents.data +13 -0
  216. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/triagem.acme.b-escalonamento/1/A24A7C9BDD8239D8/entry.json +7 -0
  217. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/triagem.globex.a-saudacao/1/E38CD37DF003EDB7/contents.data +13 -0
  218. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/triagem.globex.a-saudacao/1/E38CD37DF003EDB7/entry.json +7 -0
  219. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-datasets/orcamento/acme/a-resposta-estruturada.json +24 -0
  220. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-datasets/triagem/acme/a-saudacao.json +23 -0
  221. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-datasets/triagem/acme/b-escalonamento.json +24 -0
  222. package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-datasets/triagem/globex/a-saudacao.json +23 -0
  223. package/framework/templates/feature/decisions.md +122 -122
  224. package/framework/templates/frontend/nextjs/Dockerfile.nextjs.hbs +43 -43
  225. package/framework/templates/frontend/nextjs/client-component.tsx.hbs +26 -26
  226. package/framework/templates/frontend/nextjs/env.mjs.hbs +32 -32
  227. package/framework/templates/frontend/nextjs/feature-form.tsx.hbs +56 -56
  228. package/framework/templates/frontend/nextjs/page.tsx.hbs +22 -22
  229. package/framework/templates/frontend/nextjs/tsconfig.json.hbs +26 -26
  230. package/framework/templates/frontend/nextjs/use-feature.ts.hbs +54 -54
  231. package/framework/templates/infrastructure/docker/Dockerfile.template +58 -58
  232. package/framework/templates/infrastructure/docker/dockerfile-api.dockerfile +38 -38
  233. package/framework/templates/infrastructure/docker/dockerfile-web.dockerfile +48 -48
  234. package/framework/templates/infrastructure/github/README.md +56 -56
  235. package/framework/templates/infrastructure/github/actions/docker-build-push/action.yml.hbs +45 -45
  236. package/framework/templates/infrastructure/github/actions/health-check/action.yml.hbs +27 -27
  237. package/framework/templates/infrastructure/github/workflows/docker-build-push.yml.hbs +59 -59
  238. package/framework/templates/infrastructure/github/workflows/dotnet-build.yml.hbs +39 -39
  239. package/framework/templates/integrations/asaas-client.cs +387 -387
  240. package/framework/templates/integrations/asaas-webhook.cs +351 -351
  241. package/framework/templates/meta-prompts/fusion/fusion-agent.md +76 -76
  242. package/framework/templates/meta-prompts/fusion/fusion-aggregator.md +100 -100
  243. package/framework/templates/meta-prompts/validators/pre-commit-validator.md +95 -95
  244. package/framework/templates/project/validate.js +120 -120
  245. package/framework/templates/state.template.json +17 -17
  246. package/framework/templates/ui/design-system.css +226 -226
  247. package/package.json +3 -3
  248. package/src/commands/dashboard/dashboard.js +53 -53
  249. package/src/commands/e2e/e2e.js +15 -2
  250. package/src/commands/index.js +13 -13
  251. package/src/commands/project/checkpoint.js +209 -209
  252. package/src/commands/project/doctor.js +148 -6
  253. package/src/commands/project/update.js +469 -153
  254. package/src/commands/project/worktree.js +76 -4
  255. package/src/commands/state/advance.js +24 -2
  256. package/src/commands/state/approve.js +33 -1
  257. package/src/commands/state/create.js +72 -11
  258. package/src/commands/state/finish.js +379 -24
  259. package/src/commands/state/gate-check.js +190 -6
  260. package/src/commands/state/score.js +36 -1
  261. package/src/commands/validation/verify.js +134 -25
  262. package/src/core/state/phase-state-machine.js +197 -197
  263. package/src/core/state/state-manager.js +9 -1
  264. package/src/core/templates/template-renderer.js +477 -477
  265. package/src/dashboard/app/components/DetailPanel.jsx +11 -11
  266. package/src/dashboard/app/components/FlowGraph.jsx +17 -17
  267. package/src/dashboard/app/components/SearchFilter.jsx +22 -22
  268. package/src/dashboard/app/hooks/useDetailPanel.js +15 -15
  269. package/src/dashboard/app/main.jsx +13 -13
  270. package/src/dashboard/app/pages/AgentMap.jsx +370 -370
  271. package/src/dashboard/app/pages/PhasePipeline.jsx +408 -408
  272. package/src/dashboard/app/pages/RulesMap.jsx +273 -273
  273. package/src/dashboard/app/pages/SkillsLibrary.jsx +366 -366
  274. package/src/dashboard/app/pages/Standards.jsx +332 -332
  275. package/src/dashboard/app/styles/index.css +47 -47
  276. package/src/dashboard/index.html +12 -12
  277. package/src/dashboard/package.json +24 -24
  278. package/src/dashboard/scanner/phases-scanner.js +15 -15
  279. package/src/dashboard/scanner/rubrics-scanner.js +12 -12
  280. package/src/dashboard/scanner/rules-scanner.js +37 -37
  281. package/src/dashboard/scanner/skills-scanner.js +43 -43
  282. package/src/dashboard/scanner/squads-scanner.js +13 -13
  283. package/src/dashboard/vite-plugin-morph-data.js +17 -17
  284. package/src/dashboard/vite.config.js +24 -24
  285. package/src/lib/ai/ai-checks.js +182 -0
  286. package/src/lib/ai/ai-project-scan.js +347 -0
  287. package/src/lib/ai/code-audit.js +181 -0
  288. package/src/lib/ai/comment-stripper.js +184 -0
  289. package/src/lib/ai/pin-drift.js +138 -0
  290. package/src/lib/ai/registry-audit.js +285 -0
  291. package/src/lib/detectors/design-system-detector.js +188 -188
  292. package/src/lib/detectors/index.js +217 -217
  293. package/src/lib/e2e/compose-env.js +50 -4
  294. package/src/lib/evaluator-report.js +269 -15
  295. package/src/lib/generators/design-system-generator.js +508 -508
  296. package/src/lib/generators/settings-generator.js +209 -209
  297. package/src/lib/git/default-branch.js +92 -4
  298. package/src/lib/git/pr-files.js +56 -0
  299. package/src/lib/hooks/hook-executor.js +167 -167
  300. package/src/lib/installers/effects.js +193 -0
  301. package/src/lib/installers/mcp-installer.js +719 -719
  302. package/src/lib/installers/templates-reconcile.js +3 -3
  303. package/src/lib/risk-detector.js +284 -0
  304. package/src/lib/standards/verified-against.js +276 -0
  305. package/src/lib/tasks/eval-node.js +485 -0
  306. package/src/lib/tasks/linux-runner.js +297 -0
  307. package/src/lib/tasks/task-classifier.js +30 -30
  308. package/src/lib/tasks/task-parser.js +209 -209
  309. package/src/lib/tasks/test-nodes.js +486 -0
  310. package/src/lib/tasks/test-runner.js +410 -33
  311. package/src/lib/validators/checkpoint-persistence.js +39 -39
  312. package/src/lib/validators/css/css-validator.js +340 -340
  313. package/src/lib/validators/shared/emit-validator-dispatch.js +14 -14
  314. package/src/lib/validators/shared/issue-counter.js +14 -14
  315. package/src/lib/validators/verify-runner.js +583 -28
  316. package/src/lib/worktree/docker-audit.js +287 -0
  317. package/src/lib/worktree/env-allocator.js +24 -0
  318. package/src/lib/worktree/worktree-audit.js +30 -1
  319. package/src/scripts/setup-infra.js +24 -11
  320. package/src/utils/agent-id-mapper.js +11 -11
  321. package/src/utils/agents-installer.js +18 -8
  322. package/src/utils/claude-md-injector.js +10 -5
  323. package/src/utils/claude-settings-manager.js +300 -300
  324. package/src/utils/file-copier.js +11 -4
  325. package/src/utils/hooks-installer.js +29 -26
  326. package/src/utils/skills-installer.js +36 -29
  327. package/src/utils/version-checker.js +68 -3
  328. package/framework/standards/ai-agents/prompts-in-db.md +0 -214
@@ -0,0 +1,131 @@
1
+ using System.Collections.Concurrent;
2
+ using Morph.AiKit.Media;
3
+
4
+ namespace Morph.AiKit.Tests.Fakes;
5
+
6
+ /// <summary>
7
+ /// Blob em memoria. <b>Conta as gravacoes</b> — e a contagem e o que prova que o
8
+ /// <see cref="MediaStore"/> deduplicou em vez de regravar.
9
+ /// </summary>
10
+ /// <remarks>
11
+ /// Um teste que so confira "o hash e o mesmo" nao distingue dedupe de regravacao: os dois
12
+ /// produzem o mesmo hash. Por isso <see cref="Writes"/> existe.
13
+ /// </remarks>
14
+ public sealed class InMemoryMediaBlobs : IMediaBlobs
15
+ {
16
+ private readonly ConcurrentDictionary<string, byte[]> _files = new(StringComparer.Ordinal);
17
+
18
+ /// <summary>Quantas gravacoes de verdade aconteceram.</summary>
19
+ public int Writes { get; private set; }
20
+
21
+ /// <summary>Os caminhos gravados.</summary>
22
+ public IReadOnlyCollection<string> Paths => _files.Keys.ToArray();
23
+
24
+ /// <inheritdoc />
25
+ public ValueTask<bool> ExistsAsync(string relativePath, CancellationToken cancellationToken)
26
+ => ValueTask.FromResult(_files.ContainsKey(relativePath));
27
+
28
+ /// <inheritdoc />
29
+ public ValueTask WriteAsync(string relativePath, ReadOnlyMemory<byte> bytes, CancellationToken cancellationToken)
30
+ {
31
+ Writes++;
32
+ _files[relativePath] = bytes.ToArray();
33
+ return ValueTask.CompletedTask;
34
+ }
35
+ }
36
+
37
+ /// <summary>
38
+ /// <see cref="IMediaStore"/> em memoria, para os testes que precisam de UM storage e nao
39
+ /// estao medindo o storage.
40
+ /// </summary>
41
+ public sealed class InMemoryMediaStore : IMediaStore
42
+ {
43
+ private readonly MediaStore _inner;
44
+
45
+ /// <summary>Cria o store sobre um blob em memoria proprio.</summary>
46
+ public InMemoryMediaStore()
47
+ {
48
+ Blobs = new InMemoryMediaBlobs();
49
+ _inner = new MediaStore(Blobs);
50
+ }
51
+
52
+ /// <summary>O blob por baixo, para conferir gravacoes.</summary>
53
+ public InMemoryMediaBlobs Blobs { get; }
54
+
55
+ /// <inheritdoc />
56
+ public ValueTask<MediaStoreEntry> PutAsync(
57
+ ReadOnlyMemory<byte> bytes,
58
+ string mediaType,
59
+ CancellationToken cancellationToken)
60
+ => _inner.PutAsync(bytes, mediaType, cancellationToken);
61
+ }
62
+
63
+ /// <summary>Acervo de jobs em memoria — a costura com o banco do projeto, em teste.</summary>
64
+ public sealed class InMemoryMediaJobs : IMediaJobRepository
65
+ {
66
+ private readonly ConcurrentDictionary<Guid, MediaJob> _byId = new();
67
+
68
+ /// <summary>Quantas gravacoes aconteceram. Prova que o worker persistiu cada passo.</summary>
69
+ public int Saves { get; private set; }
70
+
71
+ /// <inheritdoc />
72
+ public ValueTask<MediaJob?> FindByIdempotencyKeyAsync(
73
+ MediaIdempotencyKey key,
74
+ CancellationToken cancellationToken)
75
+ => ValueTask.FromResult(_byId.Values.FirstOrDefault(j => j.IdempotencyKey == key));
76
+
77
+ /// <inheritdoc />
78
+ public ValueTask<MediaJob?> FindAsync(MediaJobId id, CancellationToken cancellationToken)
79
+ => ValueTask.FromResult(_byId.TryGetValue(id.Value, out var job) ? job : null);
80
+
81
+ /// <inheritdoc />
82
+ public ValueTask SaveAsync(MediaJob job, CancellationToken cancellationToken)
83
+ {
84
+ Saves++;
85
+ _byId[job.Id.Value] = job;
86
+ return ValueTask.CompletedTask;
87
+ }
88
+ }
89
+
90
+ /// <summary>Resolvedor de alias de teste: um alias, um binding.</summary>
91
+ public sealed class StubAliasResolver : IMediaAliasResolver
92
+ {
93
+ private readonly Dictionary<string, MediaAliasBinding> _bindings = new(StringComparer.Ordinal);
94
+
95
+ /// <summary>Declara um alias.</summary>
96
+ public StubAliasResolver With(MediaAliasBinding binding)
97
+ {
98
+ _bindings[binding.Alias] = binding;
99
+ return this;
100
+ }
101
+
102
+ /// <inheritdoc />
103
+ public MediaResult<MediaAliasBinding> Resolve(string alias)
104
+ => _bindings.TryGetValue(alias, out var binding)
105
+ ? MediaResult<MediaAliasBinding>.Ok(binding)
106
+ : MediaResult<MediaAliasBinding>.Fail(MediaErrorCode.InvalidRequest, $"alias '{alias}' nao existe.");
107
+ }
108
+
109
+ /// <summary>Teto de custo de teste: aceita ate um limite, e registra o que foi pedido.</summary>
110
+ public sealed class CappedBudget(decimal cap) : IMediaBudget
111
+ {
112
+ /// <summary>Os custos estimados que chegaram, na ordem.</summary>
113
+ public List<decimal> Seen { get; } = [];
114
+
115
+ /// <inheritdoc />
116
+ public ValueTask<bool> TryReserveAsync(
117
+ MediaRequest request,
118
+ decimal estimatedCostUsd,
119
+ CancellationToken cancellationToken)
120
+ {
121
+ Seen.Add(estimatedCostUsd);
122
+ return ValueTask.FromResult(estimatedCostUsd <= cap);
123
+ }
124
+ }
125
+
126
+ /// <summary>Relogio parado, para que o carimbo de tempo seja um fato do teste.</summary>
127
+ public sealed class FixedMediaClock(DateTimeOffset now) : IMediaClock
128
+ {
129
+ /// <inheritdoc />
130
+ public DateTimeOffset UtcNow { get; } = now;
131
+ }
@@ -0,0 +1,133 @@
1
+ using System.Reflection;
2
+ using Morph.AiKit.Media;
3
+ using Xunit;
4
+
5
+ namespace Morph.AiKit.Tests;
6
+
7
+ /// <summary>
8
+ /// O canario da REGRA DURA de <c>ai-agents/media-pipeline.md</c>:
9
+ /// <b>nunca gere midia dentro de um turno de conversa</b>.
10
+ /// </summary>
11
+ /// <remarks>
12
+ /// <para>
13
+ /// A regra nao e presa por prosa — e presa por ACESSIBILIDADE. O tipo que produz bytes e
14
+ /// <c>internal</c> ao assembly; um handler de mensagem no projeto consumidor nao consegue
15
+ /// nomea-lo, e o codigo nao compila. Este arquivo e o que mantem essa afirmacao verdadeira
16
+ /// depois de qualquer refatoracao.
17
+ /// </para>
18
+ /// <para>
19
+ /// <b>A mutacao foi rodada</b> (T10): tornar <c>IMediaProvider</c> publico derruba
20
+ /// <see cref="TheTypeThatProducesBytesIsNotPublic"/> nomeando o defeito. Um canario que
21
+ /// ninguem viu morder pode ser o canario errado.
22
+ /// </para>
23
+ /// <para>
24
+ /// A leitura e por <b>reflexao sobre a assembly</b>, e nao por <c>grep</c> no fonte: o
25
+ /// modificador que importa e o que sobreviveu a compilacao. Um <c>grep</c> por
26
+ /// <c>"internal interface"</c> mediria o texto, e o texto e uma copia do objeto.
27
+ /// </para>
28
+ /// </remarks>
29
+ public sealed class MediaAccessibilityTests
30
+ {
31
+ private static readonly Assembly Kit = typeof(MediaJob).Assembly;
32
+
33
+ /// <summary>Os nomes que produzem, ou orquestram a producao de, bytes pagos.</summary>
34
+ public static TheoryData<string> ByteProducingTypes() =>
35
+ [
36
+ "Morph.AiKit.Media.IMediaProvider",
37
+ "Morph.AiKit.Media.Providers.OpenAIImageProvider",
38
+ "Morph.AiKit.Media.Providers.OpenAIImageGenerator",
39
+ "Morph.AiKit.Media.Providers.GoogleImageProvider",
40
+ "Morph.AiKit.Media.Providers.VeoVideoProvider",
41
+ "Morph.AiKit.Media.MediaWorker",
42
+ "Morph.AiKit.Media.MediaGenerator",
43
+ "Morph.AiKit.Media.MediaProviderCatalog",
44
+ "Morph.AiKit.Media.MediaArtifact",
45
+ ];
46
+
47
+ /// <summary>
48
+ /// A asserção literal do plano:
49
+ /// <c>typeof(MediaJob).Assembly.GetType("Morph.AiKit.Media.IMediaProvider")!.IsPublic == false</c>.
50
+ /// </summary>
51
+ [Fact]
52
+ public void TheTypeThatProducesBytesIsNotPublic()
53
+ {
54
+ var provider = Kit.GetType("Morph.AiKit.Media.IMediaProvider");
55
+
56
+ Assert.NotNull(provider);
57
+ Assert.False(
58
+ provider.IsPublic,
59
+ "IMediaProvider virou publico: um handler de mensagem passa a conseguir nomear o tipo que " +
60
+ "produz bytes, e a regra dura de ai-agents/media-pipeline.md deixa de ser presa pelo " +
61
+ "compilador. E prosa outra vez.");
62
+ }
63
+
64
+ /// <summary>Uma rodada por tipo: nenhum deles pode vazar para fora do assembly.</summary>
65
+ [Theory]
66
+ [MemberData(nameof(ByteProducingTypes))]
67
+ public void NoByteProducingTypeIsVisibleOutsideTheAssembly(string fullName)
68
+ {
69
+ var type = Kit.GetType(fullName);
70
+
71
+ Assert.NotNull(type);
72
+ Assert.False(type.IsPublic, $"{fullName} nao pode ser publico.");
73
+ Assert.False(type.IsNestedPublic, $"{fullName} nao pode ser aninhado publico.");
74
+ }
75
+
76
+ /// <summary>
77
+ /// O outro lado da mesma afirmacao: a superficie publica EXISTE, e ela nunca devolve
78
+ /// bytes. Sem esta metade, tornar tudo internal passaria — e um subsistema inalcancavel
79
+ /// tambem esta errado.
80
+ /// </summary>
81
+ [Fact]
82
+ public void ThePublicEntryPointExistsAndNeverReturnsBytes()
83
+ {
84
+ var generator = Kit.GetType("Morph.AiKit.Media.IMediaGenerator");
85
+
86
+ Assert.NotNull(generator);
87
+ Assert.True(generator.IsPublic, "IMediaGenerator precisa ser publico — e a unica porta de entrada.");
88
+
89
+ foreach (var method in generator.GetMethods())
90
+ {
91
+ var returned = method.ReturnType.IsGenericType
92
+ ? method.ReturnType.GetGenericArguments()[0]
93
+ : method.ReturnType;
94
+
95
+ var payload = returned.IsGenericType ? returned.GetGenericArguments()[0] : returned;
96
+
97
+ Assert.True(
98
+ payload == typeof(MediaJobId) || payload == typeof(MediaJob),
99
+ $"IMediaGenerator.{method.Name} devolve '{payload.Name}'. A superficie publica devolve id " +
100
+ "de job ou o job — nunca bytes, stream, BinaryData ou URL de provider.");
101
+ }
102
+ }
103
+
104
+ /// <summary>
105
+ /// <see cref="MediaArtifact"/> — o registro que CARREGA os bytes — nao pode aparecer em
106
+ /// nenhuma assinatura publica do subsistema. Ele existe entre dois tipos internal.
107
+ /// </summary>
108
+ [Fact]
109
+ public void TheArtifactNeverAppearsInAPublicSignature()
110
+ {
111
+ var artifact = Kit.GetType("Morph.AiKit.Media.MediaArtifact");
112
+ Assert.NotNull(artifact);
113
+
114
+ var leaks = new List<string>();
115
+
116
+ foreach (var type in Kit.GetExportedTypes().Where(t => t.Namespace?.StartsWith("Morph.AiKit.Media", StringComparison.Ordinal) == true))
117
+ {
118
+ foreach (var method in type.GetMethods(BindingFlags.Public | BindingFlags.Instance | BindingFlags.Static | BindingFlags.DeclaredOnly))
119
+ {
120
+ if (Mentions(method.ReturnType, artifact) || method.GetParameters().Any(p => Mentions(p.ParameterType, artifact)))
121
+ {
122
+ leaks.Add($"{type.FullName}.{method.Name}");
123
+ }
124
+ }
125
+ }
126
+
127
+ Assert.Empty(leaks);
128
+ }
129
+
130
+ private static bool Mentions(Type candidate, Type target)
131
+ => candidate == target
132
+ || (candidate.IsGenericType && candidate.GetGenericArguments().Any(a => Mentions(a, target)));
133
+ }
@@ -0,0 +1,127 @@
1
+ using Morph.AiKit.Agents;
2
+ using Morph.AiKit.Providers;
3
+ using Morph.AiKit.Tests.Fakes;
4
+ using Xunit;
5
+
6
+ namespace Morph.AiKit.Tests;
7
+
8
+ /// <summary>
9
+ /// A companheira obrigatoria de <c>AgentApi.Images</c>/<c>AgentApi.Videos</c> existirem:
10
+ /// <b>um alias de midia nao vira agente</b>.
11
+ /// </summary>
12
+ /// <remarks>
13
+ /// <para>
14
+ /// Um registry so e um vocabulario so de alias e a decisao (<c>decisions.md</c> D3) — e
15
+ /// justamente por isso a recusa precisa existir. Sem ela, um alias de imagem passado por
16
+ /// engano viraria um agente de chat apontando para um modelo que nao fala chat: a falha
17
+ /// apareceria no primeiro turno, com uma mensagem do provider que nao diz o que aconteceu.
18
+ /// </para>
19
+ /// <para>
20
+ /// Enum novo sem recusa e uma armadilha nova. Este arquivo e o que impede que ela volte.
21
+ /// </para>
22
+ /// </remarks>
23
+ public sealed class MediaAliasIsNotAnAgentTests
24
+ {
25
+ private const string MediaAliasDocument = """
26
+ {
27
+ "version": "2.0",
28
+ "defaultAlias": "texto",
29
+ "aliases": {
30
+ "texto": {
31
+ "provider": "openai",
32
+ "model": "gpt-5.4",
33
+ "api": "responses",
34
+ "pricing": { "inputPerMTok": 1.25, "outputPerMTok": 10.0 }
35
+ },
36
+ "imagem": {
37
+ "provider": "openai",
38
+ "model": "gpt-image-2",
39
+ "api": "images",
40
+ "pricingWaiver": "tarifa de imagem cobra por imagem/qualidade, nao por token: ver MediaPricing."
41
+ },
42
+ "video": {
43
+ "provider": "google",
44
+ "model": "veo-3.1-fast-generate-preview",
45
+ "api": "videos",
46
+ "pricingWaiver": "tarifa de video cobra por segundo de saida: ver MediaPricing."
47
+ }
48
+ }
49
+ }
50
+ """;
51
+
52
+ private static AgentSpec Spec(string alias, AgentApi? api = null)
53
+ => new() { Alias = alias, Instructions = "voce e um agente de teste", Api = api };
54
+
55
+ /// <summary>O registry ACEITA <c>images</c> e <c>videos</c> — um vocabulario so de alias.</summary>
56
+ [Theory]
57
+ [InlineData("imagem", AgentApi.Images)]
58
+ [InlineData("video", AgentApi.Videos)]
59
+ public void TheRegistryUnderstandsTheMediaProtocols(string alias, AgentApi expected)
60
+ {
61
+ using var registry = ModelRegistry.FromJson(MediaAliasDocument, new FakeProviderClientFactory());
62
+
63
+ Assert.Equal(expected, registry.Resolve(alias).Api);
64
+ }
65
+
66
+ /// <summary>E a fabrica RECUSA, na construcao, com erro nomeado.</summary>
67
+ [Theory]
68
+ [InlineData("imagem")]
69
+ [InlineData("video")]
70
+ public void TheFactoryRefusesAMediaAliasByName(string alias)
71
+ {
72
+ using var registry = ModelRegistry.FromJson(MediaAliasDocument, new FakeProviderClientFactory());
73
+ var factory = new AgentFactory(registry);
74
+
75
+ var ex = Assert.Throws<AgentFactoryException>(() => factory.Create(Spec(alias)));
76
+
77
+ Assert.Equal(AgentFactoryError.MediaAliasIsNotAnAgent, ex.Reason);
78
+ Assert.Contains("EnqueueAsync", ex.Message, StringComparison.Ordinal);
79
+ }
80
+
81
+ /// <summary>
82
+ /// A recusa tambem cobre o caminho pela SPEC: um alias de conversa com
83
+ /// <c>Api = Images</c> na spec e a mesma confusao, pela outra porta.
84
+ /// </summary>
85
+ [Fact]
86
+ public void TheFactoryRefusesAMediaProtocolDeclaredOnTheSpec()
87
+ {
88
+ using var registry = ModelRegistry.FromJson(MediaAliasDocument, new FakeProviderClientFactory());
89
+ var factory = new AgentFactory(registry);
90
+
91
+ var ex = Assert.Throws<AgentFactoryException>(() => factory.Create(Spec("texto", AgentApi.Images)));
92
+
93
+ Assert.Equal(AgentFactoryError.MediaAliasIsNotAnAgent, ex.Reason);
94
+ }
95
+
96
+ /// <summary>Alias de conversa continua passando — a recusa nova nao pode ser um bloqueio geral.</summary>
97
+ [Fact]
98
+ public void AChatAliasStillBuildsAnAgent()
99
+ {
100
+ using var registry = ModelRegistry.FromJson(MediaAliasDocument, new FakeProviderClientFactory());
101
+ var factory = new AgentFactory(registry);
102
+
103
+ Assert.NotNull(factory.Create(Spec("texto")));
104
+ }
105
+
106
+ /// <summary>O predicado mora num lugar so — duas copias e como as duas discordam.</summary>
107
+ [Theory]
108
+ [InlineData(AgentApi.Responses, false)]
109
+ [InlineData(AgentApi.Chat, false)]
110
+ [InlineData(AgentApi.Images, true)]
111
+ [InlineData(AgentApi.Videos, true)]
112
+ public void IsMediaAnswersForEveryProtocol(AgentApi api, bool expected)
113
+ => Assert.Equal(expected, api.IsMedia());
114
+
115
+ /// <summary>Um <c>api</c> inventado continua sendo recusado na carga do documento.</summary>
116
+ [Fact]
117
+ public void AnUnknownApiIsStillRefusedWithTheFullListOfValidValues()
118
+ {
119
+ var document = MediaAliasDocument.Replace("\"api\": \"images\"", "\"api\": \"holograma\"", StringComparison.Ordinal);
120
+
121
+ var ex = Assert.Throws<ModelRegistryException>(
122
+ () => ModelRegistry.FromJson(document, new FakeProviderClientFactory()));
123
+
124
+ Assert.Contains("images", ex.Message, StringComparison.Ordinal);
125
+ Assert.Contains("videos", ex.Message, StringComparison.Ordinal);
126
+ }
127
+ }
@@ -0,0 +1,216 @@
1
+ using System.Reflection;
2
+ using Morph.AiKit.Media;
3
+ using Xunit;
4
+
5
+ namespace Morph.AiKit.Tests;
6
+
7
+ /// <summary>
8
+ /// Canários da MARCAÇÃO — a quarta afirmação da rubrica `media-claims`, e a última que este
9
+ /// kit ainda prendia só por prosa.
10
+ /// </summary>
11
+ /// <remarks>
12
+ /// <para>
13
+ /// <b>A assimetria que estes testes fecham, medida:</b> a regra do turno de conversa estava
14
+ /// presa por TIPO (`internal` + id de job em vez de bytes) e provada por mutação; custo e
15
+ /// revisão humana, por ESTADO. A marcação — que é a obrigação legal, o art. 50 do AI Act que
16
+ /// motivou a issue — estava escrita em `MediaJobState.cs` ("publicado, com a marcacao
17
+ /// aplicada") e em `MediaRequest.cs` ("marcacao visivel obrigatoria"), e um `grep` por label,
18
+ /// watermark ou disclosure no kit inteiro voltava <b>vazio</b>. Zero mecanismo.
19
+ /// </para>
20
+ /// <para>
21
+ /// Uma rubrica que cobra de terceiros uma prova que o próprio exemplar não dá é o defeito que
22
+ /// esta epic catalogou. Estes testes são o inverso literal dele.
23
+ /// </para>
24
+ /// <para>
25
+ /// <b>Fronteira honesta:</b> nada aqui prova que o rótulo chegou aos olhos de alguém — a
26
+ /// renderização é do aplicativo consumidor, e nenhum tipo em C# alcança um pixel. O que se
27
+ /// prova é que a publicação <b>não acontece</b> sem a decisão registrada, com texto e
28
+ /// superfície nomeados.
29
+ /// </para>
30
+ /// </remarks>
31
+ public sealed class MediaDisclosureTests
32
+ {
33
+ private static readonly DateTimeOffset T0 = new(2026, 9, 8, 12, 0, 0, TimeSpan.Zero);
34
+
35
+ private static MediaDisclosure Label => MediaDisclosure.Create("Imagem gerada por IA", "legenda", T0);
36
+
37
+ /// <summary>Um job aprovado, na finalidade pedida.</summary>
38
+ private static MediaJob Approved(MediaPurpose purpose)
39
+ {
40
+ var job = MediaJob.Create(
41
+ new MediaRequest("catalogo", "um sofa verde", purpose),
42
+ new MediaIdempotencyKey("chave"),
43
+ T0).StartGenerating().Value;
44
+
45
+ job = job.MarkGenerated(
46
+ new MediaHash("abc"),
47
+ "ab/c/abc.png",
48
+ new MediaProvenance("openai", "gpt-image-2", "p", null, [], "-", null, 0m, T0, true)).Value;
49
+
50
+ return job.Approve(MediaReview.ApprovedBy("lucas", T0)).Value;
51
+ }
52
+
53
+ // ---------------------------------------------------------------------------------
54
+ // A política, por finalidade — dado, não comentário
55
+ // ---------------------------------------------------------------------------------
56
+
57
+ /// <summary>Catálogo e marketing exigem marcação; rascunho não chega a publicar.</summary>
58
+ [Theory]
59
+ [InlineData(MediaPurpose.CatalogPhoto, true)]
60
+ [InlineData(MediaPurpose.Marketing, true)]
61
+ [InlineData(MediaPurpose.Draft, false)]
62
+ public void RequiresDisclosureIsDeclaredPerPurpose(MediaPurpose purpose, bool expected)
63
+ => Assert.Equal(expected, purpose.RequiresDisclosure());
64
+
65
+ /// <summary>Rascunho é a única finalidade não publicável.</summary>
66
+ [Theory]
67
+ [InlineData(MediaPurpose.CatalogPhoto, true)]
68
+ [InlineData(MediaPurpose.Marketing, true)]
69
+ [InlineData(MediaPurpose.Draft, false)]
70
+ public void OnlyDraftIsUnpublishable(MediaPurpose purpose, bool expected)
71
+ => Assert.Equal(expected, purpose.IsPublishable());
72
+
73
+ // ---------------------------------------------------------------------------------
74
+ // A porta: publicar sem marcação NÃO é um caminho
75
+ // ---------------------------------------------------------------------------------
76
+
77
+ /// <summary>
78
+ /// O caso central. Um job aprovado, de catálogo, sem marcação: <b>não publica</b>, e a
79
+ /// razão nomeia o que falta.
80
+ /// </summary>
81
+ [Theory]
82
+ [InlineData(MediaPurpose.CatalogPhoto)]
83
+ [InlineData(MediaPurpose.Marketing)]
84
+ public void PublishFailsWithoutADisclosureWhenThePurposeDemandsOne(MediaPurpose purpose)
85
+ {
86
+ var result = Approved(purpose).Publish(null, T0);
87
+
88
+ Assert.False(
89
+ result.IsSuccess,
90
+ "publicar sem marcacao e o caminho que a obrigacao legal existe para fechar; " +
91
+ "se ele existe, a afirmacao da rubrica media-claims esta presa so por prosa");
92
+ Assert.Equal(MediaErrorCode.InvalidTransition, result.Error!.Code);
93
+ Assert.Contains("marcacao", result.Error.Message, StringComparison.OrdinalIgnoreCase);
94
+ }
95
+
96
+ /// <summary>Com a marcação, publica — e ela fica GRAVADA no job, não descartada.</summary>
97
+ [Fact]
98
+ public void PublishSucceedsWithADisclosureAndRecordsIt()
99
+ {
100
+ var published = Approved(MediaPurpose.CatalogPhoto).Publish(Label, T0);
101
+
102
+ Assert.True(published.IsSuccess);
103
+ Assert.Equal(MediaJobState.Published, published.Value.State);
104
+ Assert.Equal("Imagem gerada por IA", published.Value.Disclosure!.Text);
105
+ Assert.Equal("legenda", published.Value.Disclosure.Surface);
106
+ Assert.Equal(T0, published.Value.Disclosure.AppliedAt);
107
+ }
108
+
109
+ /// <summary>
110
+ /// Rascunho não publica, <b>nem com marcação e nem estando aprovado</b>. Era uma frase
111
+ /// num comentário XML; agora é uma recusa.
112
+ /// </summary>
113
+ [Fact]
114
+ public void ADraftNeverPublishesEvenApprovedAndLabelled()
115
+ {
116
+ var result = Approved(MediaPurpose.Draft).Publish(Label, T0);
117
+
118
+ Assert.False(result.IsSuccess);
119
+ Assert.Contains("nao publica", result.Error!.Message, StringComparison.OrdinalIgnoreCase);
120
+ }
121
+
122
+ /// <summary>
123
+ /// A ordem das portas importa: o estado vem antes da marcação. Um job NÃO aprovado, sem
124
+ /// marcação, tem de reclamar da revisão humana — mandar consertar a marcação de algo que
125
+ /// nem foi revisado manda consertar a coisa errada.
126
+ /// </summary>
127
+ [Fact]
128
+ public void TheStateGateIsReportedBeforeTheDisclosureGate()
129
+ {
130
+ var generated = MediaJob.Create(
131
+ new MediaRequest("catalogo", "p", MediaPurpose.CatalogPhoto),
132
+ new MediaIdempotencyKey("k"),
133
+ T0).StartGenerating().Value
134
+ .MarkGenerated(
135
+ new MediaHash("abc"), "ab/c/abc.png",
136
+ new MediaProvenance("openai", "m", "p", null, [], "-", null, 0m, T0, true)).Value;
137
+
138
+ var result = generated.Publish(null, T0);
139
+
140
+ Assert.False(result.IsSuccess);
141
+ Assert.Contains("Approved", result.Error!.Message, StringComparison.Ordinal);
142
+ Assert.DoesNotContain("marcacao", result.Error.Message, StringComparison.OrdinalIgnoreCase);
143
+ }
144
+
145
+ // ---------------------------------------------------------------------------------
146
+ // A marcação vazia é o campo preenchido "para passar"
147
+ // ---------------------------------------------------------------------------------
148
+
149
+ /// <summary>
150
+ /// <b>A construção não tem outra porta.</b> Este é o teste que o nome do anterior prometia
151
+ /// e não entregava.
152
+ /// </summary>
153
+ /// <remarks>
154
+ /// <para>
155
+ /// A primeira versão de <see cref="MediaDisclosure"/> era um record POSICIONAL, e record
156
+ /// posicional gera construtor <b>público</b>. A validação vivia só em
157
+ /// <see cref="MediaDisclosure.Create"/>, então um consumidor externo fazia
158
+ /// <c>new MediaDisclosure("", "", at)</c> e publicava com rótulo vazio — medido:
159
+ /// <c>IsSuccess=True State=Published</c>. O canário de então exercitava a FÁBRICA e passava
160
+ /// verde o tempo todo: media a copia, nao o objeto.
161
+ /// </para>
162
+ /// <para>
163
+ /// Por isso a asserção é por REFLEXÃO sobre o tipo. Ela não pode ser escrita como
164
+ /// <c>new MediaDisclosure(...)</c> — hoje isso nem compila, e é esse o ponto: uma asserção
165
+ /// que só existe enquanto o defeito existe não é canário.
166
+ /// </para>
167
+ /// </remarks>
168
+ [Fact]
169
+ public void TheTypeExposesNoPublicConstructor()
170
+ {
171
+ var publicCtors = typeof(MediaDisclosure)
172
+ .GetConstructors(BindingFlags.Public | BindingFlags.Instance);
173
+
174
+ Assert.True(
175
+ publicCtors.Length == 0,
176
+ "MediaDisclosure nao pode ter construtor publico: ele contorna Create e deixa " +
177
+ "passar marcacao em branco — o campo preenchido 'para passar' que este tipo existe " +
178
+ "para impedir. Record POSICIONAL gera um; use record com construtor privado, como " +
179
+ "MediaReview. Encontrados: " + string.Join(" | ", publicCtors.Select(c => c.ToString())));
180
+ }
181
+
182
+ /// <summary>Texto ou superfície em branco é recusado pela única porta que existe.</summary>
183
+ [Theory]
184
+ [InlineData("", "legenda")]
185
+ [InlineData(" ", "legenda")]
186
+ [InlineData("Imagem gerada por IA", "")]
187
+ [InlineData("Imagem gerada por IA", " ")]
188
+ public void ABlankDisclosureIsRefusedByTheOnlyFactory(string text, string surface)
189
+ => Assert.Throws<ArgumentException>(() => MediaDisclosure.Create(text, surface, T0));
190
+
191
+ /// <summary>
192
+ /// A superfície é obrigatória junto do texto: "marcado" sem dizer ONDE não responde se
193
+ /// alguém consegue ver.
194
+ /// </summary>
195
+ [Fact]
196
+ public void TheDisclosureCarriesBothTheTextAndWhereItAppears()
197
+ {
198
+ var disclosure = MediaDisclosure.Create("Gerado por IA", "sobreposicao", T0);
199
+
200
+ Assert.Equal("Gerado por IA", disclosure.Text);
201
+ Assert.Equal("sobreposicao", disclosure.Surface);
202
+ }
203
+
204
+ /// <summary>Uma publicação recusada não muda o job — o registro continua imutável.</summary>
205
+ [Fact]
206
+ public void ARefusedPublishLeavesTheJobUntouched()
207
+ {
208
+ var job = Approved(MediaPurpose.Marketing);
209
+
210
+ _ = job.Publish(null, T0);
211
+
212
+ Assert.Equal(MediaJobState.Approved, job.State);
213
+ Assert.Null(job.Disclosure);
214
+ Assert.Null(job.PublishedAt);
215
+ }
216
+ }