@polymorphism-tech/morph-spec 8.34.2 → 8.35.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.
- package/CLAUDE.md +20 -3
- package/README.md +2 -2
- package/bin/morph-spec.js +18 -4
- package/bin/validate.js +379 -379
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +5 -4
- package/docs/QUICKSTART.md +1 -1
- package/framework/MORPH.md +16 -17
- package/framework/agents.json +197 -44
- package/framework/ai-pin.json +22 -0
- package/framework/commands/morph-media.md +45 -0
- package/framework/docs/CLI.md +234 -9
- package/framework/evals/proof-rubrics/README.md +76 -0
- package/framework/evals/proof-rubrics/eval-de-modelo.md +64 -0
- package/framework/evals/proof-rubrics/fiacao.md +54 -0
- package/framework/evals/proof-rubrics/guard-cego.md +52 -0
- package/framework/evals/proof-rubrics/media-claims.md +109 -0
- package/framework/evals/proof-rubrics/mutation-proof.md +77 -0
- package/framework/evals/proof-rubrics/observabilidade-genai.md +119 -0
- package/framework/evals/proof-rubrics/verde-nao-e-coberto.md +48 -0
- package/framework/evals/rubrics/architecture.md +30 -0
- package/framework/evals/rubrics/code-quality.md +26 -0
- package/framework/evals/rubrics/test-coverage.md +20 -0
- package/framework/hooks/claude-code/core/protect-spec-files.js +41 -4
- package/framework/hooks/claude-code/statusline.sh +11 -11
- package/framework/hooks/git/commit-msg/conventional-commits.sh +33 -33
- package/framework/hooks/git/pre-commit/agents.sh +25 -25
- package/framework/hooks/git/pre-commit/orchestrator.sh +64 -64
- package/framework/hooks/git/pre-commit/specs.sh +50 -50
- package/framework/hooks/git/pre-push/run-tests.sh +44 -44
- package/framework/hooks/shared/e2e-check.js +135 -22
- package/framework/hooks/shared/stdin-reader.js +26 -26
- package/framework/rules/ai-agents-standards.md +14 -2
- package/framework/schemas/ai-pin.schema.json +97 -0
- package/framework/schemas/config.schema.json +107 -0
- package/framework/schemas/feature.schema.json +23 -4
- package/framework/skills/core/create-umbraco-lp/SKILL.md +1 -1
- package/framework/skills/core/migrate-lp-to-umbraco/SKILL.md +1 -1
- package/framework/skills/core/morph-apply/SKILL.md +26 -5
- package/framework/skills/core/morph-brainstorming/SKILL.md +1 -1
- package/framework/skills/core/morph-eval/SKILL.md +1 -1
- package/framework/skills/core/morph-hotfix/SKILL.md +2 -2
- package/framework/skills/core/morph-implement/SKILL.md +18 -2
- package/framework/skills/core/morph-media/SKILL.md +215 -0
- package/framework/skills/core/morph-plan/SKILL.md +33 -2
- package/framework/skills/core/morph-proposal/SKILL.md +1 -1
- package/framework/skills/core/morph-review/SKILL.md +166 -9
- package/framework/skills/core/morph-stack-scan/SKILL.md +1 -1
- package/framework/skills/core/morph-standards/SKILL.md +1 -1
- package/framework/skills/core/morph-uiux/SKILL.md +1 -1
- package/framework/standards/PROVENANCE.json +81 -3
- package/framework/standards/STANDARDS.json +1483 -839
- package/framework/standards/ai-agents/agent-archetypes.md +160 -0
- package/framework/standards/ai-agents/agent-session.md +164 -68
- package/framework/standards/ai-agents/agent-spec.md +196 -0
- package/framework/standards/ai-agents/batching.md +61 -37
- package/framework/standards/ai-agents/context-providers.md +97 -24
- package/framework/standards/ai-agents/conversational-agent-with-phases.md +239 -0
- package/framework/standards/ai-agents/cost-and-budget.md +256 -0
- package/framework/standards/ai-agents/durable-workflows-hitl.md +98 -5
- package/framework/standards/ai-agents/evals-with-cache.md +289 -0
- package/framework/standards/ai-agents/guardrails.md +222 -0
- package/framework/standards/ai-agents/llm-runtime-defaults.md +226 -0
- package/framework/standards/ai-agents/mcp-server.md +119 -18
- package/framework/standards/ai-agents/mcp-tools.md +85 -32
- package/framework/standards/ai-agents/media-pipeline.md +352 -0
- package/framework/standards/ai-agents/media-video.md +267 -0
- package/framework/standards/ai-agents/middleware-patterns.md +39 -5
- package/framework/standards/ai-agents/modalities-image-gen.md +354 -105
- package/framework/standards/ai-agents/modalities-speech-to-text.md +8 -2
- package/framework/standards/ai-agents/modalities-text-to-speech.md +10 -2
- package/framework/standards/ai-agents/modalities-vision.md +6 -2
- package/framework/standards/ai-agents/multi-agent-patterns.md +61 -5
- package/framework/standards/ai-agents/observability-patterns.md +227 -17
- package/framework/standards/ai-agents/production.md +38 -9
- package/framework/standards/ai-agents/prompt-sources.md +389 -0
- package/framework/standards/ai-agents/providers/model-registry.md +174 -39
- package/framework/standards/ai-agents/rag-custom-pgvector.md +69 -4
- package/framework/standards/ai-agents/service-tier-flex.md +27 -19
- package/framework/standards/ai-agents/setup.md +166 -50
- package/framework/standards/ai-agents/structured-output.md +34 -3
- package/framework/standards/ai-agents/sweet-spot.md +23 -3
- package/framework/standards/ai-agents/testing-ai.md +236 -0
- package/framework/standards/ai-agents/vector-data-extensions.md +50 -20
- package/framework/standards/ai-agents/workflows.md +27 -7
- package/framework/standards/frontend/design-system/ai-image-generation.md +12 -0
- package/framework/standards/frontend/design-system/ai-video-generation.md +13 -0
- package/framework/standards/infrastructure/docker/local-compose-isolation.md +3 -1
- package/framework/templates/.idea/morph-templates.xml +92 -0
- package/framework/templates/.vscode/morph-templates.code-snippets +186 -0
- package/framework/templates/IDE-SNIPPETS.md +262 -262
- package/framework/templates/REGISTRY.json +13 -0
- package/framework/templates/code/dotnet/abstractions/api-endpoint.cs +7 -7
- package/framework/templates/code/dotnet/ai-agents/AgentBootstrap.cs.template +39 -39
- package/framework/templates/code/dotnet/ai-agents/EmbeddingService.cs.template +68 -68
- package/framework/templates/code/dotnet/ai-agents/ModelRegistry.cs.template +117 -117
- package/framework/templates/code/dotnet/ai-agents/PromptRepository.cs.template +1 -1
- package/framework/templates/code/dotnet/ai-agents/ResilientToolMiddleware.cs.template +61 -61
- package/framework/templates/code/dotnet/ai-agents/VectorSearchTool.cs.template +107 -107
- package/framework/templates/code/dotnet/ai-agents/model-registry.json.template +58 -58
- package/framework/templates/code/dotnet/contracts/contracts-vsa.cs +281 -281
- package/framework/templates/code/dotnet/database/migration.cs +83 -83
- package/framework/templates/code/dotnet/feature/endpoint.cs.hbs +21 -21
- package/framework/templates/code/dotnet/feature/request.cs.hbs +4 -4
- package/framework/templates/code/dotnet/feature/response.cs.hbs +4 -4
- package/framework/templates/code/dotnet/feature/validator.cs.hbs +15 -15
- package/framework/templates/code/sql/ai-agents/agent-prompts-migration.sql.template +1 -1
- package/framework/templates/code/sql/ai-agents/pgvector-embeddings-migration.sql.template +91 -91
- package/framework/templates/code/typescript/contracts.ts +167 -167
- package/framework/templates/docs/clarifications.md +253 -253
- package/framework/templates/docs/schema-analysis.md +119 -119
- package/framework/templates/docs/ui-components.md +52 -52
- package/framework/templates/docs/ui-design-system.md +76 -76
- package/framework/templates/docs/ui-flows.md +167 -167
- package/framework/templates/docs/ui-mockups.md +98 -98
- package/framework/templates/dotnet/ai-kit/Directory.Build.props +25 -0
- package/framework/templates/dotnet/ai-kit/Directory.Packages.props +26 -0
- package/framework/templates/dotnet/ai-kit/Morph.AiKit.sln +56 -0
- package/framework/templates/dotnet/ai-kit/NuGet.config +12 -0
- package/framework/templates/dotnet/ai-kit/README.md +128 -0
- package/framework/templates/dotnet/ai-kit/global.json +9 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentApi.cs +65 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentFactory.cs +289 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentFactoryOptions.cs +91 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentSpec.cs +91 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentSpecOptions.cs +181 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/AiKitAssemblyMarker.cs +14 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/ConversationStore.cs +174 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/IConversationStore.cs +54 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/InMemoryConversationStore.cs +75 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/CountingChatClient.cs +71 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalAssertion.cs +246 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheMissException.cs +57 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheOptions.cs +78 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheStamp.cs +225 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalFixture.cs +463 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalMode.cs +32 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalQualityEvaluators.cs +49 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalScenario.cs +214 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalSummary.cs +168 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/OfflineChatClient.cs +142 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/ShortPathResponseCache.cs +349 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/IMediaGenerator.cs +42 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/IMediaProvider.cs +77 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaArtifact.cs +28 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaDisclosure.cs +107 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaGenerator.cs +253 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaIdempotencyKey.cs +103 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJob.cs +259 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJobId.cs +21 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJobState.cs +47 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaPricing.cs +102 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaProvenance.cs +44 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaRequest.cs +192 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaResult.cs +94 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaReview.cs +99 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaSeams.cs +97 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaStore.cs +133 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MorphMedia.cs +93 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/GoogleImageProvider.cs +112 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/OpenAIImageProvider.cs +288 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/VeoVideoProvider.cs +158 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/IUsageCostSink.cs +29 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/UsageCostMiddleware.cs +208 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/UsageCostRecord.cs +95 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Morph.AiKit.csproj +38 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Observability/MorphOpenTelemetryExtensions.cs +119 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Observability/MorphTelemetryOptions.cs +82 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/IProviderClientFactory.cs +33 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/ModelAlias.cs +267 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/ModelRegistry.cs +474 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/model-registry.v2.json +87 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Rag/DeltaIngest.cs +169 -0
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Rag/DeltaPlan.cs +49 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/AgentFactoryTests.cs +495 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/AgentSpecTests.cs +260 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/ConversationStoreTests.cs +405 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/DeltaIngestTests.cs +379 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalAssertionTests.cs +197 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalCacheMissTests.cs +139 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalCacheStampTests.cs +157 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalFailuresTests.cs +178 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalFixtureTests.cs +153 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalRefreshTests.cs +194 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalWorkspace.cs +86 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/KitEvals.cs +213 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/OfflineChatClientTests.cs +95 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeChatClient.cs +225 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeChatClientTests.cs +215 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeClock.cs +33 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeMediaProvider.cs +89 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeProviderClientFactory.cs +55 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeUsageCostSink.cs +60 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/InMemoryMediaStore.cs +131 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaAccessibilityTests.cs +133 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaAliasIsNotAnAgentTests.cs +127 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaDisclosureTests.cs +216 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaGeneratorTests.cs +264 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaIdempotencyKeyTests.cs +148 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaJobStateTests.cs +184 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaReviewTests.cs +89 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaStoreTests.cs +98 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/ModelRegistryTests.cs +451 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Morph.AiKit.Tests.csproj +76 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/SkeletonSmokeTests.cs +33 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/TelemetryTests.cs +261 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/UsageCostMiddlewareTests.cs +374 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/.morph-eval-cache.json +12 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/orcamento.acme.a-resposta-estruturada/1/CF70A7CE27BBD66A/contents.data +13 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/orcamento.acme.a-resposta-estruturada/1/CF70A7CE27BBD66A/entry.json +7 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/triagem.acme.a-saudacao/1/19F631228C6E00BA/contents.data +13 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/triagem.acme.a-saudacao/1/19F631228C6E00BA/entry.json +7 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/triagem.acme.b-escalonamento/1/A24A7C9BDD8239D8/contents.data +13 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/triagem.acme.b-escalonamento/1/A24A7C9BDD8239D8/entry.json +7 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/triagem.globex.a-saudacao/1/E38CD37DF003EDB7/contents.data +13 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-cache/cache/triagem.globex.a-saudacao/1/E38CD37DF003EDB7/entry.json +7 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-datasets/orcamento/acme/a-resposta-estruturada.json +24 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-datasets/triagem/acme/a-saudacao.json +23 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-datasets/triagem/acme/b-escalonamento.json +24 -0
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/eval-datasets/triagem/globex/a-saudacao.json +23 -0
- package/framework/templates/feature/decisions.md +122 -122
- package/framework/templates/frontend/nextjs/Dockerfile.nextjs.hbs +43 -43
- package/framework/templates/frontend/nextjs/client-component.tsx.hbs +26 -26
- package/framework/templates/frontend/nextjs/env.mjs.hbs +32 -32
- package/framework/templates/frontend/nextjs/feature-form.tsx.hbs +56 -56
- package/framework/templates/frontend/nextjs/page.tsx.hbs +22 -22
- package/framework/templates/frontend/nextjs/tsconfig.json.hbs +26 -26
- package/framework/templates/frontend/nextjs/use-feature.ts.hbs +54 -54
- package/framework/templates/infrastructure/docker/Dockerfile.template +58 -58
- package/framework/templates/infrastructure/docker/dockerfile-api.dockerfile +38 -38
- package/framework/templates/infrastructure/docker/dockerfile-web.dockerfile +48 -48
- package/framework/templates/infrastructure/github/README.md +56 -56
- package/framework/templates/infrastructure/github/actions/docker-build-push/action.yml.hbs +45 -45
- package/framework/templates/infrastructure/github/actions/health-check/action.yml.hbs +27 -27
- package/framework/templates/infrastructure/github/workflows/docker-build-push.yml.hbs +59 -59
- package/framework/templates/infrastructure/github/workflows/dotnet-build.yml.hbs +39 -39
- package/framework/templates/integrations/asaas-client.cs +387 -387
- package/framework/templates/integrations/asaas-webhook.cs +351 -351
- package/framework/templates/meta-prompts/fusion/fusion-agent.md +76 -76
- package/framework/templates/meta-prompts/fusion/fusion-aggregator.md +100 -100
- package/framework/templates/meta-prompts/validators/pre-commit-validator.md +95 -95
- package/framework/templates/project/validate.js +120 -120
- package/framework/templates/state.template.json +17 -17
- package/framework/templates/ui/design-system.css +226 -226
- package/package.json +3 -3
- package/src/commands/dashboard/dashboard.js +53 -53
- package/src/commands/e2e/e2e.js +15 -2
- package/src/commands/index.js +13 -13
- package/src/commands/project/checkpoint.js +209 -209
- package/src/commands/project/doctor.js +140 -5
- package/src/commands/project/update.js +469 -153
- package/src/commands/project/worktree.js +8 -1
- package/src/commands/state/advance.js +24 -2
- package/src/commands/state/approve.js +33 -1
- package/src/commands/state/create.js +72 -11
- package/src/commands/state/finish.js +333 -21
- package/src/commands/state/gate-check.js +190 -6
- package/src/commands/state/score.js +36 -1
- package/src/commands/validation/verify.js +134 -25
- package/src/core/state/phase-state-machine.js +197 -197
- package/src/core/state/state-manager.js +9 -1
- package/src/core/templates/template-renderer.js +477 -477
- package/src/dashboard/app/components/DetailPanel.jsx +11 -11
- package/src/dashboard/app/components/FlowGraph.jsx +17 -17
- package/src/dashboard/app/components/SearchFilter.jsx +22 -22
- package/src/dashboard/app/hooks/useDetailPanel.js +15 -15
- package/src/dashboard/app/main.jsx +13 -13
- package/src/dashboard/app/pages/AgentMap.jsx +370 -370
- package/src/dashboard/app/pages/PhasePipeline.jsx +408 -408
- package/src/dashboard/app/pages/RulesMap.jsx +273 -273
- package/src/dashboard/app/pages/SkillsLibrary.jsx +366 -366
- package/src/dashboard/app/pages/Standards.jsx +332 -332
- package/src/dashboard/app/styles/index.css +47 -47
- package/src/dashboard/index.html +12 -12
- package/src/dashboard/package.json +24 -24
- package/src/dashboard/scanner/phases-scanner.js +15 -15
- package/src/dashboard/scanner/rubrics-scanner.js +12 -12
- package/src/dashboard/scanner/rules-scanner.js +37 -37
- package/src/dashboard/scanner/skills-scanner.js +43 -43
- package/src/dashboard/scanner/squads-scanner.js +13 -13
- package/src/dashboard/vite-plugin-morph-data.js +17 -17
- package/src/dashboard/vite.config.js +24 -24
- package/src/lib/ai/ai-checks.js +182 -0
- package/src/lib/ai/ai-project-scan.js +347 -0
- package/src/lib/ai/code-audit.js +181 -0
- package/src/lib/ai/comment-stripper.js +184 -0
- package/src/lib/ai/pin-drift.js +138 -0
- package/src/lib/ai/registry-audit.js +285 -0
- package/src/lib/detectors/design-system-detector.js +188 -188
- package/src/lib/detectors/index.js +217 -217
- package/src/lib/e2e/compose-env.js +50 -4
- package/src/lib/evaluator-report.js +269 -15
- package/src/lib/generators/design-system-generator.js +508 -508
- package/src/lib/generators/settings-generator.js +209 -209
- package/src/lib/git/default-branch.js +92 -4
- package/src/lib/hooks/hook-executor.js +167 -167
- package/src/lib/installers/effects.js +193 -0
- package/src/lib/installers/mcp-installer.js +719 -719
- package/src/lib/installers/templates-reconcile.js +3 -3
- package/src/lib/risk-detector.js +284 -0
- package/src/lib/standards/verified-against.js +276 -0
- package/src/lib/tasks/eval-node.js +485 -0
- package/src/lib/tasks/linux-runner.js +297 -0
- package/src/lib/tasks/task-classifier.js +30 -30
- package/src/lib/tasks/task-parser.js +209 -209
- package/src/lib/tasks/test-nodes.js +486 -0
- package/src/lib/tasks/test-runner.js +410 -33
- package/src/lib/validators/checkpoint-persistence.js +39 -39
- package/src/lib/validators/css/css-validator.js +340 -340
- package/src/lib/validators/shared/emit-validator-dispatch.js +14 -14
- package/src/lib/validators/shared/issue-counter.js +14 -14
- package/src/lib/validators/verify-runner.js +583 -28
- package/src/lib/worktree/docker-audit.js +287 -0
- package/src/lib/worktree/env-allocator.js +24 -0
- package/src/lib/worktree/worktree-audit.js +30 -1
- package/src/scripts/setup-infra.js +24 -11
- package/src/utils/agent-id-mapper.js +11 -11
- package/src/utils/agents-installer.js +18 -8
- package/src/utils/claude-md-injector.js +10 -5
- package/src/utils/claude-settings-manager.js +300 -300
- package/src/utils/file-copier.js +11 -4
- package/src/utils/hooks-installer.js +29 -26
- package/src/utils/skills-installer.js +36 -29
- package/src/utils/version-checker.js +68 -3
- 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
|
+
}
|
package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaAliasIsNotAnAgentTests.cs
ADDED
|
@@ -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
|
+
}
|