@polymorphism-tech/morph-spec 8.38.0 → 8.40.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +19 -8
- package/README.md +2 -2
- package/bin/morph-spec.js +60 -43
- package/claude-plugin.json +1 -1
- package/docs/CHEATSHEET.md +1 -1
- package/docs/QUICKSTART.md +1 -1
- package/framework/CLAUDE.md +3 -2
- package/framework/MORPH.md +1 -1
- package/framework/agents.json +36 -25
- package/framework/docs/CLI.md +2 -2
- package/framework/evals/rubrics/code-quality.md +4 -4
- package/framework/evals/rubrics/condensed.md +2 -2
- package/framework/mcp/registry.json +33 -2
- package/framework/rules/infrastructure-standards.md +0 -1
- package/framework/rules/morph-workflow.md +9 -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 +5 -1
- 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 +1 -1
- package/framework/skills/core/morph-implement/SKILL.md +6 -1
- package/framework/skills/core/morph-media/SKILL.md +1 -1
- package/framework/skills/core/morph-plan/SKILL.md +4 -3
- package/framework/skills/core/morph-proposal/SKILL.md +1 -1
- package/framework/skills/core/morph-review/SKILL.md +1 -1
- 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 +6 -5
- package/framework/standards/STANDARDS.json +3 -3
- package/framework/standards/ai-agents/context-providers.md +16 -26
- package/framework/standards/ai-agents/durable-workflows-hitl.md +15 -21
- package/framework/standards/ai-agents/modalities-image-gen.md +21 -25
- package/framework/standards/ai-agents/modalities-speech-to-text.md +4 -15
- package/framework/standards/ai-agents/modalities-text-to-speech.md +2 -11
- package/framework/standards/ai-agents/modalities-vision.md +1 -18
- package/framework/standards/ai-agents/prompt-sources.md +0 -4
- package/framework/standards/architecture/code-comments.md +52 -16
- package/framework/standards/backend/integrations/neon-auth/neon-auth.md +63 -50
- package/framework/standards/frontend/design-system/ai-image-generation.md +5 -5
- package/framework/standards/integration/mcp/mcp-tools.md +48 -117
- package/framework/templates/code/dotnet/abstractions/result.cs +0 -2
- package/framework/templates/code/dotnet/ai-agents/AgentBootstrap.cs.template +26 -39
- package/framework/templates/code/dotnet/ai-agents/EmbeddingService.cs.template +28 -68
- package/framework/templates/code/dotnet/ai-agents/ModelRegistry.cs.template +99 -117
- package/framework/templates/code/dotnet/ai-agents/PromptRepository.cs.template +2 -44
- package/framework/templates/code/dotnet/ai-agents/ResilientToolMiddleware.cs.template +40 -61
- package/framework/templates/code/dotnet/ai-agents/VectorSearchTool.cs.template +54 -107
- package/framework/templates/code/dotnet/ai-agents/model-registry.json.template +53 -58
- package/framework/templates/code/dotnet/contracts/contracts-vsa.cs +192 -281
- package/framework/templates/code/dotnet/database/migration.cs +49 -83
- package/framework/templates/code/dotnet/database/vector-entity.cs +2 -46
- package/framework/templates/code/dotnet/extensions/result-extensions.cs +0 -2
- package/framework/templates/code/dotnet/feature/validator.cs.hbs +11 -15
- package/framework/templates/code/dotnet/jobs/agent.cs +3 -51
- package/framework/templates/code/dotnet/jobs/job.cs +2 -97
- package/framework/templates/code/dotnet/test.cs +2 -87
- package/framework/templates/code/typescript/contracts.ts +140 -167
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentApi.cs +1 -48
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentFactory.cs +3 -79
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentFactoryOptions.cs +1 -70
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentSpec.cs +1 -47
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentSpecOptions.cs +2 -99
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/AiKitAssemblyMarker.cs +0 -6
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/ConversationStore.cs +3 -88
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/IConversationStore.cs +1 -41
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/InMemoryConversationStore.cs +0 -21
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/CountingChatClient.cs +1 -25
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalAssertion.cs +2 -63
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheMissException.cs +0 -30
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheOptions.cs +1 -52
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalCacheStamp.cs +3 -78
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalFixture.cs +4 -109
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalMode.cs +0 -22
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalQualityEvaluators.cs +1 -36
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalScenario.cs +2 -57
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalSummary.cs +0 -75
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/OfflineChatClient.cs +2 -59
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Evals/ShortPathResponseCache.cs +4 -111
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/IMediaGenerator.cs +0 -34
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/IMediaProvider.cs +1 -57
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaArtifact.cs +0 -19
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaDisclosure.cs +2 -74
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaGenerator.cs +2 -33
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaIdempotencyKey.cs +1 -43
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJob.cs +1 -96
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJobId.cs +0 -13
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaJobState.cs +0 -27
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaPricing.cs +0 -33
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaProvenance.cs +0 -31
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaRequest.cs +3 -75
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaResult.cs +0 -42
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaReview.cs +0 -39
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaSeams.cs +1 -55
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MediaStore.cs +3 -59
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/MorphMedia.cs +0 -46
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/GoogleImageProvider.cs +3 -28
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/OpenAIImageProvider.cs +7 -61
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/VeoVideoProvider.cs +2 -37
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/IUsageCostSink.cs +1 -23
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/UsageCostMiddleware.cs +2 -82
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/UsageCostRecord.cs +1 -68
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Observability/MorphOpenTelemetryExtensions.cs +1 -62
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Observability/MorphTelemetryOptions.cs +2 -62
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/IProviderClientFactory.cs +1 -25
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/ModelAlias.cs +0 -124
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Providers/ModelRegistry.cs +1 -73
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Rag/DeltaIngest.cs +1 -66
- package/framework/templates/dotnet/ai-kit/src/Morph.AiKit/Rag/DeltaPlan.cs +0 -41
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/AgentFactoryTests.cs +2 -112
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/AgentSpecTests.cs +2 -43
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/ConversationStoreTests.cs +5 -110
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/DeltaIngestTests.cs +7 -103
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalAssertionTests.cs +1 -42
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalCacheMissTests.cs +1 -30
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalCacheStampTests.cs +1 -19
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalFailuresTests.cs +0 -48
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalFixtureTests.cs +1 -29
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalRefreshTests.cs +2 -38
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/EvalWorkspace.cs +1 -14
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/KitEvals.cs +4 -69
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Evals/OfflineChatClientTests.cs +1 -10
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeChatClient.cs +1 -57
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeChatClientTests.cs +0 -26
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeClock.cs +1 -19
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeMediaProvider.cs +0 -32
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeProviderClientFactory.cs +0 -14
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeUsageCostSink.cs +0 -24
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/InMemoryMediaStore.cs +0 -32
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaAccessibilityTests.cs +0 -37
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaAliasIsNotAnAgentTests.cs +0 -24
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaDisclosureTests.cs +0 -78
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaGeneratorTests.cs +0 -53
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaIdempotencyKeyTests.cs +0 -44
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaJobStateTests.cs +0 -44
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaReviewTests.cs +0 -19
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaStoreTests.cs +0 -24
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/ModelRegistryTests.cs +6 -48
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/SkeletonSmokeTests.cs +1 -13
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/TelemetryTests.cs +2 -71
- package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/UsageCostMiddlewareTests.cs +5 -100
- package/framework/templates/frontend/nextjs/Dockerfile.nextjs.hbs +39 -43
- package/framework/templates/frontend/nextjs/env.mjs.hbs +29 -32
- package/framework/templates/frontend/nextjs/page.tsx.hbs +21 -22
- package/framework/templates/frontend/nextjs/use-feature.ts.hbs +42 -54
- package/framework/templates/integrations/asaas-client.cs +332 -387
- package/framework/templates/integrations/asaas-webhook.cs +290 -351
- package/framework/templates/integrations/neon-auth-config.cs +9 -105
- package/framework/templates/project/validate.js +97 -120
- package/package.json +2 -2
- package/src/commands/dashboard/dashboard.js +80 -53
- package/src/commands/mcp/mcp-setup.js +37 -9
- package/src/commands/project/doctor.js +3 -5
- package/src/commands/project/init.js +6 -3
- package/src/commands/project/install-plugin-cmd.js +1 -1
- package/src/commands/project/update.js +5 -0
- package/src/dashboard/app/App.jsx +80 -16
- package/src/dashboard/app/components/ActivityFeed.jsx +172 -0
- package/src/dashboard/app/components/CommandPalette.jsx +155 -0
- package/src/dashboard/app/components/DetailPanel.jsx +47 -11
- package/src/dashboard/app/components/EntityLink.jsx +55 -0
- package/src/dashboard/app/components/ErrorBoundary.jsx +54 -0
- package/src/dashboard/app/components/FlowGraph.jsx +19 -17
- package/src/dashboard/app/components/KnowledgeMarkdown.jsx +53 -0
- package/src/dashboard/app/components/Markdown.jsx +47 -0
- package/src/dashboard/app/components/PhaseRail.jsx +111 -0
- package/src/dashboard/app/components/SearchFilter.jsx +40 -22
- package/src/dashboard/app/components/Sidebar.jsx +146 -37
- package/src/dashboard/app/hooks/useMorphQuery.js +54 -0
- package/src/dashboard/app/hooks/usePref.js +38 -0
- package/src/dashboard/app/lib/format.js +69 -0
- package/src/dashboard/app/lib/phases.js +56 -0
- package/src/dashboard/app/lib/project.js +50 -0
- package/src/dashboard/app/pages/Activity.jsx +24 -0
- package/src/dashboard/app/pages/AgentMap.jsx +371 -370
- package/src/dashboard/app/pages/FeatureDetail.jsx +604 -0
- package/src/dashboard/app/pages/Features.jsx +400 -0
- package/src/dashboard/app/pages/Guide.jsx +146 -0
- package/src/dashboard/app/pages/HookInspector.jsx +357 -322
- package/src/dashboard/app/pages/PhasePipeline.jsx +277 -408
- package/src/dashboard/app/pages/Portfolio.jsx +156 -0
- package/src/dashboard/app/pages/Rubrics.jsx +101 -0
- package/src/dashboard/app/pages/RulesMap.jsx +300 -273
- package/src/dashboard/app/pages/SkillsLibrary.jsx +483 -366
- package/src/dashboard/app/pages/Standards.jsx +407 -332
- package/src/dashboard/app/styles/index.css +187 -47
- package/src/dashboard/index.html +14 -12
- package/src/dashboard/package.json +32 -24
- package/src/dashboard/scanner/activity-scanner.js +351 -0
- package/src/dashboard/scanner/agents-scanner.js +59 -1
- package/src/dashboard/scanner/docs-scanner.js +59 -0
- package/src/dashboard/scanner/feature-status.js +10 -0
- package/src/dashboard/scanner/features-scanner.js +360 -0
- package/src/dashboard/scanner/index.js +14 -7
- package/src/dashboard/scanner/phases-scanner.js +14 -15
- package/src/dashboard/scanner/portfolio.js +153 -0
- package/src/dashboard/scanner/project-api.js +195 -0
- package/src/dashboard/scanner/rules-scanner.js +42 -37
- package/src/dashboard/scanner/skills-scanner.js +105 -43
- package/src/dashboard/scanner/standards-scanner.js +2 -0
- package/src/dashboard/vite-plugin-morph-data.js +134 -17
- package/src/lib/detectors/claude-config-detector.js +2 -7
- package/src/lib/detectors/index.js +5 -1
- package/src/lib/evaluator-report.js +22 -0
- package/src/lib/generators/settings-generator.js +208 -209
- package/src/lib/installers/mcp-installer.js +724 -719
- package/src/lib/installers/memory-installer.js +1 -2
- package/src/lib/projects/registry.js +154 -0
- package/src/lib/validators/code-budget/code-budget-validator.js +32 -4
- package/src/scripts/install-plugin.js +1 -1
- package/src/scripts/setup-infra.js +2 -4
- package/src/utils/agents-installer.js +57 -12
- package/src/utils/banner.js +1 -1
- package/src/commands/index.js +0 -13
- package/src/commands/project/checkpoint.js +0 -209
- package/src/core/state/phase-state-machine.js +0 -197
- package/src/dashboard/app/hooks/useDetailPanel.js +0 -15
- package/src/dashboard/app/pages/LiveState.jsx +0 -206
- package/src/dashboard/scanner/squads-scanner.js +0 -13
- package/src/dashboard/scanner/state-scanner.js +0 -85
- package/src/lib/generators/index.js +0 -10
- package/src/lib/hooks/hook-executor.js +0 -167
- package/src/lib/validators/checkpoint-persistence.js +0 -39
- package/src/lib/validators/shared/emit-validator-dispatch.js +0 -14
- package/src/lib/validators/shared/index.js +0 -12
- package/src/lib/validators/shared/issue-counter.js +0 -14
- package/src/lib/validators/shared/result-formatter.js +0 -124
package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeMediaProvider.cs
CHANGED
|
@@ -2,30 +2,6 @@ using Morph.AiKit.Media;
|
|
|
2
2
|
|
|
3
3
|
namespace Morph.AiKit.Tests.Fakes;
|
|
4
4
|
|
|
5
|
-
/// <summary>
|
|
6
|
-
/// <see cref="IMediaProvider"/> roteirizado: a costura que dispensa rede, chave de API e
|
|
7
|
-
/// <b>fatura</b> na suite inteira de midia.
|
|
8
|
-
/// </summary>
|
|
9
|
-
/// <remarks>
|
|
10
|
-
/// <para>
|
|
11
|
-
/// <b>Por que ele existe:</b> o unico ponto do subsistema que gasta dinheiro e um
|
|
12
|
-
/// <c>IMediaProvider</c>. Trocando-o por uma fila de respostas escritas a mao, todo o resto
|
|
13
|
-
/// — <c>MediaGenerator</c>, <c>MediaWorker</c>, <c>MediaJob</c>, <c>MediaStore</c> — passa a
|
|
14
|
-
/// ser testavel sem socket, sem segredo e sem cobranca. Mesmo desenho do
|
|
15
|
-
/// <see cref="FakeChatClient"/>, pela mesma razao.
|
|
16
|
-
/// </para>
|
|
17
|
-
/// <para>
|
|
18
|
-
/// Ele so e construivel porque <c>InternalsVisibleTo("Morph.AiKit.Tests")</c> existe no
|
|
19
|
-
/// <c>.csproj</c> da biblioteca. Um projeto consumidor <b>nao</b> consegue escrever esta
|
|
20
|
-
/// classe — e essa impossibilidade e a regra dura de <c>ai-agents/media-pipeline.md</c>,
|
|
21
|
-
/// asserida em <c>MediaAccessibilityTests</c>.
|
|
22
|
-
/// </para>
|
|
23
|
-
/// <para>
|
|
24
|
-
/// <b>Chamada nao roteirizada e defeito de teste.</b> Esgotada a fila, o fake devolve uma
|
|
25
|
-
/// falha nomeada com a contagem de chamadas, em vez de inventar bytes — um artefato
|
|
26
|
-
/// fabricado pelo proprio fake faria um teste passar por engano.
|
|
27
|
-
/// </para>
|
|
28
|
-
/// </remarks>
|
|
29
5
|
internal sealed class FakeMediaProvider : IMediaProvider
|
|
30
6
|
{
|
|
31
7
|
private readonly Queue<MediaResult<MediaArtifact>> _scripted = new();
|
|
@@ -37,19 +13,14 @@ internal sealed class FakeMediaProvider : IMediaProvider
|
|
|
37
13
|
Supported = supports;
|
|
38
14
|
}
|
|
39
15
|
|
|
40
|
-
/// <inheritdoc />
|
|
41
16
|
public string Provider { get; }
|
|
42
17
|
|
|
43
|
-
/// <summary>Que tipo de midia este fake diz atender.</summary>
|
|
44
18
|
public MediaKind Supported { get; }
|
|
45
19
|
|
|
46
|
-
/// <summary>Quantas vezes foi chamado. E como um teste prova que NAO houve segunda chamada paga.</summary>
|
|
47
20
|
public int Calls { get; private set; }
|
|
48
21
|
|
|
49
|
-
/// <summary>Os pedidos vistos, na ordem.</summary>
|
|
50
22
|
public List<MediaRequest> Seen { get; } = [];
|
|
51
23
|
|
|
52
|
-
/// <summary>Enfileira bytes de sucesso.</summary>
|
|
53
24
|
public FakeMediaProvider Returns(byte[] bytes, string mediaType = "image/png", string? revisedPrompt = null)
|
|
54
25
|
{
|
|
55
26
|
_scripted.Enqueue(MediaResult<MediaArtifact>.Ok(new MediaArtifact(
|
|
@@ -57,17 +28,14 @@ internal sealed class FakeMediaProvider : IMediaProvider
|
|
|
57
28
|
return this;
|
|
58
29
|
}
|
|
59
30
|
|
|
60
|
-
/// <summary>Enfileira uma recusa com o codigo dado — inclusive <c>PolicyBlocked</c>.</summary>
|
|
61
31
|
public FakeMediaProvider Fails(MediaErrorCode code, string message = "roteirizado")
|
|
62
32
|
{
|
|
63
33
|
_scripted.Enqueue(MediaResult<MediaArtifact>.Fail(code, message));
|
|
64
34
|
return this;
|
|
65
35
|
}
|
|
66
36
|
|
|
67
|
-
/// <inheritdoc />
|
|
68
37
|
public bool Supports(MediaKind kind) => kind == Supported;
|
|
69
38
|
|
|
70
|
-
/// <inheritdoc />
|
|
71
39
|
public Task<MediaResult<MediaArtifact>> ProduceAsync(
|
|
72
40
|
MediaRequest request,
|
|
73
41
|
MediaAliasBinding binding,
|
package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeProviderClientFactory.cs
CHANGED
|
@@ -3,40 +3,26 @@ using Morph.AiKit.Providers;
|
|
|
3
3
|
|
|
4
4
|
namespace Morph.AiKit.Tests.Fakes;
|
|
5
5
|
|
|
6
|
-
/// <summary>
|
|
7
|
-
/// <see cref="IProviderClientFactory"/> de teste: devolve <see cref="FakeChatClient"/> e
|
|
8
|
-
/// <b>conta quantas vezes foi chamada</b>. E o contador que torna a memoizacao do
|
|
9
|
-
/// <c>ModelRegistry</c> uma afirmacao verificavel em vez de uma promessa.
|
|
10
|
-
/// </summary>
|
|
11
6
|
public sealed class FakeProviderClientFactory : IProviderClientFactory
|
|
12
7
|
{
|
|
13
8
|
private readonly Func<ModelAlias, IChatClient?> _create;
|
|
14
9
|
private readonly Dictionary<string, IChatClient> _created = new(StringComparer.Ordinal);
|
|
15
10
|
private readonly List<ModelAlias> _requested = [];
|
|
16
11
|
|
|
17
|
-
/// <summary>Fabrica que devolve um <see cref="FakeChatClient"/> novo por alias.</summary>
|
|
18
12
|
public FakeProviderClientFactory()
|
|
19
13
|
: this(_ => new FakeChatClient())
|
|
20
14
|
{
|
|
21
15
|
}
|
|
22
16
|
|
|
23
|
-
/// <summary>
|
|
24
|
-
/// Fabrica com construcao sob medida — inclusive devolvendo nulo, para exercitar a
|
|
25
|
-
/// recusa do registry a um cliente inexistente.
|
|
26
|
-
/// </summary>
|
|
27
17
|
public FakeProviderClientFactory(Func<ModelAlias, IChatClient?> create)
|
|
28
18
|
=> _create = create;
|
|
29
19
|
|
|
30
|
-
/// <summary>Quantas vezes <see cref="CreateChatClient"/> foi realmente executada.</summary>
|
|
31
20
|
public int CreateCount { get; private set; }
|
|
32
21
|
|
|
33
|
-
/// <summary>Os aliases para os quais um cliente foi criado, na ordem.</summary>
|
|
34
22
|
public IReadOnlyList<ModelAlias> Requested => _requested;
|
|
35
23
|
|
|
36
|
-
/// <summary>Os clientes criados, por nome de alias.</summary>
|
|
37
24
|
public IReadOnlyDictionary<string, IChatClient> Created => _created;
|
|
38
25
|
|
|
39
|
-
/// <inheritdoc />
|
|
40
26
|
public IChatClient CreateChatClient(ModelAlias alias)
|
|
41
27
|
{
|
|
42
28
|
ArgumentNullException.ThrowIfNull(alias);
|
package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeUsageCostSink.cs
CHANGED
|
@@ -2,55 +2,31 @@ using Morph.AiKit.Middleware;
|
|
|
2
2
|
|
|
3
3
|
namespace Morph.AiKit.Tests.Fakes;
|
|
4
4
|
|
|
5
|
-
/// <summary>
|
|
6
|
-
/// Sink de teste: acumula os registros de custo publicados pelo
|
|
7
|
-
/// <see cref="UsageCostMiddleware"/>.
|
|
8
|
-
/// </summary>
|
|
9
|
-
/// <remarks>
|
|
10
|
-
/// <para>
|
|
11
|
-
/// Estava nos <c>outputs</c> da T9, mas depende de <see cref="IUsageCostSink"/> e
|
|
12
|
-
/// <see cref="UsageCostRecord"/>, que sao <c>outputs</c> da T11. A T9 recusou-se a criar os
|
|
13
|
-
/// dois tipos por fora (seriam arquivos de outra task, e duplica-los criaria a segunda
|
|
14
|
-
/// verdade do registro de custo) e deixou este codigo pronto no seu relatorio; a errata E8
|
|
15
|
-
/// do <c>spec.md</c> moveu formalmente o arquivo para ca. Nasce junto dos tipos que
|
|
16
|
-
/// implementa.
|
|
17
|
-
/// </para>
|
|
18
|
-
/// <para>
|
|
19
|
-
/// Seguro sob concorrencia pelo mesmo motivo do <see cref="FakeChatClient"/>: um mesmo
|
|
20
|
-
/// pipeline atende varias conversas, e um sink que perdesse linha sob concorrencia faria o
|
|
21
|
-
/// teste medir o sink em vez do middleware.
|
|
22
|
-
/// </para>
|
|
23
|
-
/// </remarks>
|
|
24
5
|
public sealed class FakeUsageCostSink : IUsageCostSink
|
|
25
6
|
{
|
|
26
7
|
private readonly Lock _gate = new();
|
|
27
8
|
private readonly List<UsageCostRecord> _records = [];
|
|
28
9
|
|
|
29
|
-
/// <summary>Tudo o que foi publicado, na ordem. Snapshot — iterar e seguro.</summary>
|
|
30
10
|
public IReadOnlyList<UsageCostRecord> Records
|
|
31
11
|
{
|
|
32
12
|
get { lock (_gate) { return [.. _records]; } }
|
|
33
13
|
}
|
|
34
14
|
|
|
35
|
-
/// <summary>O ultimo registro publicado, ou nulo se nenhum foi.</summary>
|
|
36
15
|
public UsageCostRecord? Last
|
|
37
16
|
{
|
|
38
17
|
get { lock (_gate) { return _records.Count == 0 ? null : _records[^1]; } }
|
|
39
18
|
}
|
|
40
19
|
|
|
41
|
-
/// <summary>Quantas linhas de custo foram publicadas.</summary>
|
|
42
20
|
public int Count
|
|
43
21
|
{
|
|
44
22
|
get { lock (_gate) { return _records.Count; } }
|
|
45
23
|
}
|
|
46
24
|
|
|
47
|
-
/// <summary>A soma dos custos <b>medidos</b> — os demais status nao entram na conta.</summary>
|
|
48
25
|
public decimal MeasuredTotalUsd
|
|
49
26
|
{
|
|
50
27
|
get { lock (_gate) { return _records.Where(r => r.IsMeasuredCost).Sum(r => r.CostUsd); } }
|
|
51
28
|
}
|
|
52
29
|
|
|
53
|
-
/// <inheritdoc />
|
|
54
30
|
public void Record(UsageCostRecord record)
|
|
55
31
|
{
|
|
56
32
|
ArgumentNullException.ThrowIfNull(record);
|
package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/InMemoryMediaStore.cs
CHANGED
|
@@ -3,29 +3,17 @@ using Morph.AiKit.Media;
|
|
|
3
3
|
|
|
4
4
|
namespace Morph.AiKit.Tests.Fakes;
|
|
5
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
6
|
public sealed class InMemoryMediaBlobs : IMediaBlobs
|
|
15
7
|
{
|
|
16
8
|
private readonly ConcurrentDictionary<string, byte[]> _files = new(StringComparer.Ordinal);
|
|
17
9
|
|
|
18
|
-
/// <summary>Quantas gravacoes de verdade aconteceram.</summary>
|
|
19
10
|
public int Writes { get; private set; }
|
|
20
11
|
|
|
21
|
-
/// <summary>Os caminhos gravados.</summary>
|
|
22
12
|
public IReadOnlyCollection<string> Paths => _files.Keys.ToArray();
|
|
23
13
|
|
|
24
|
-
/// <inheritdoc />
|
|
25
14
|
public ValueTask<bool> ExistsAsync(string relativePath, CancellationToken cancellationToken)
|
|
26
15
|
=> ValueTask.FromResult(_files.ContainsKey(relativePath));
|
|
27
16
|
|
|
28
|
-
/// <inheritdoc />
|
|
29
17
|
public ValueTask WriteAsync(string relativePath, ReadOnlyMemory<byte> bytes, CancellationToken cancellationToken)
|
|
30
18
|
{
|
|
31
19
|
Writes++;
|
|
@@ -34,25 +22,18 @@ public sealed class InMemoryMediaBlobs : IMediaBlobs
|
|
|
34
22
|
}
|
|
35
23
|
}
|
|
36
24
|
|
|
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
25
|
public sealed class InMemoryMediaStore : IMediaStore
|
|
42
26
|
{
|
|
43
27
|
private readonly MediaStore _inner;
|
|
44
28
|
|
|
45
|
-
/// <summary>Cria o store sobre um blob em memoria proprio.</summary>
|
|
46
29
|
public InMemoryMediaStore()
|
|
47
30
|
{
|
|
48
31
|
Blobs = new InMemoryMediaBlobs();
|
|
49
32
|
_inner = new MediaStore(Blobs);
|
|
50
33
|
}
|
|
51
34
|
|
|
52
|
-
/// <summary>O blob por baixo, para conferir gravacoes.</summary>
|
|
53
35
|
public InMemoryMediaBlobs Blobs { get; }
|
|
54
36
|
|
|
55
|
-
/// <inheritdoc />
|
|
56
37
|
public ValueTask<MediaStoreEntry> PutAsync(
|
|
57
38
|
ReadOnlyMemory<byte> bytes,
|
|
58
39
|
string mediaType,
|
|
@@ -60,25 +41,20 @@ public sealed class InMemoryMediaStore : IMediaStore
|
|
|
60
41
|
=> _inner.PutAsync(bytes, mediaType, cancellationToken);
|
|
61
42
|
}
|
|
62
43
|
|
|
63
|
-
/// <summary>Acervo de jobs em memoria — a costura com o banco do projeto, em teste.</summary>
|
|
64
44
|
public sealed class InMemoryMediaJobs : IMediaJobRepository
|
|
65
45
|
{
|
|
66
46
|
private readonly ConcurrentDictionary<Guid, MediaJob> _byId = new();
|
|
67
47
|
|
|
68
|
-
/// <summary>Quantas gravacoes aconteceram. Prova que o worker persistiu cada passo.</summary>
|
|
69
48
|
public int Saves { get; private set; }
|
|
70
49
|
|
|
71
|
-
/// <inheritdoc />
|
|
72
50
|
public ValueTask<MediaJob?> FindByIdempotencyKeyAsync(
|
|
73
51
|
MediaIdempotencyKey key,
|
|
74
52
|
CancellationToken cancellationToken)
|
|
75
53
|
=> ValueTask.FromResult(_byId.Values.FirstOrDefault(j => j.IdempotencyKey == key));
|
|
76
54
|
|
|
77
|
-
/// <inheritdoc />
|
|
78
55
|
public ValueTask<MediaJob?> FindAsync(MediaJobId id, CancellationToken cancellationToken)
|
|
79
56
|
=> ValueTask.FromResult(_byId.TryGetValue(id.Value, out var job) ? job : null);
|
|
80
57
|
|
|
81
|
-
/// <inheritdoc />
|
|
82
58
|
public ValueTask SaveAsync(MediaJob job, CancellationToken cancellationToken)
|
|
83
59
|
{
|
|
84
60
|
Saves++;
|
|
@@ -87,32 +63,26 @@ public sealed class InMemoryMediaJobs : IMediaJobRepository
|
|
|
87
63
|
}
|
|
88
64
|
}
|
|
89
65
|
|
|
90
|
-
/// <summary>Resolvedor de alias de teste: um alias, um binding.</summary>
|
|
91
66
|
public sealed class StubAliasResolver : IMediaAliasResolver
|
|
92
67
|
{
|
|
93
68
|
private readonly Dictionary<string, MediaAliasBinding> _bindings = new(StringComparer.Ordinal);
|
|
94
69
|
|
|
95
|
-
/// <summary>Declara um alias.</summary>
|
|
96
70
|
public StubAliasResolver With(MediaAliasBinding binding)
|
|
97
71
|
{
|
|
98
72
|
_bindings[binding.Alias] = binding;
|
|
99
73
|
return this;
|
|
100
74
|
}
|
|
101
75
|
|
|
102
|
-
/// <inheritdoc />
|
|
103
76
|
public MediaResult<MediaAliasBinding> Resolve(string alias)
|
|
104
77
|
=> _bindings.TryGetValue(alias, out var binding)
|
|
105
78
|
? MediaResult<MediaAliasBinding>.Ok(binding)
|
|
106
79
|
: MediaResult<MediaAliasBinding>.Fail(MediaErrorCode.InvalidRequest, $"alias '{alias}' nao existe.");
|
|
107
80
|
}
|
|
108
81
|
|
|
109
|
-
/// <summary>Teto de custo de teste: aceita ate um limite, e registra o que foi pedido.</summary>
|
|
110
82
|
public sealed class CappedBudget(decimal cap) : IMediaBudget
|
|
111
83
|
{
|
|
112
|
-
/// <summary>Os custos estimados que chegaram, na ordem.</summary>
|
|
113
84
|
public List<decimal> Seen { get; } = [];
|
|
114
85
|
|
|
115
|
-
/// <inheritdoc />
|
|
116
86
|
public ValueTask<bool> TryReserveAsync(
|
|
117
87
|
MediaRequest request,
|
|
118
88
|
decimal estimatedCostUsd,
|
|
@@ -123,9 +93,7 @@ public sealed class CappedBudget(decimal cap) : IMediaBudget
|
|
|
123
93
|
}
|
|
124
94
|
}
|
|
125
95
|
|
|
126
|
-
/// <summary>Relogio parado, para que o carimbo de tempo seja um fato do teste.</summary>
|
|
127
96
|
public sealed class FixedMediaClock(DateTimeOffset now) : IMediaClock
|
|
128
97
|
{
|
|
129
|
-
/// <inheritdoc />
|
|
130
98
|
public DateTimeOffset UtcNow { get; } = now;
|
|
131
99
|
}
|
package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaAccessibilityTests.cs
CHANGED
|
@@ -4,33 +4,10 @@ using Xunit;
|
|
|
4
4
|
|
|
5
5
|
namespace Morph.AiKit.Tests;
|
|
6
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
7
|
public sealed class MediaAccessibilityTests
|
|
30
8
|
{
|
|
31
9
|
private static readonly Assembly Kit = typeof(MediaJob).Assembly;
|
|
32
10
|
|
|
33
|
-
/// <summary>Os nomes que produzem, ou orquestram a producao de, bytes pagos.</summary>
|
|
34
11
|
public static TheoryData<string> ByteProducingTypes() =>
|
|
35
12
|
[
|
|
36
13
|
"Morph.AiKit.Media.IMediaProvider",
|
|
@@ -44,10 +21,6 @@ public sealed class MediaAccessibilityTests
|
|
|
44
21
|
"Morph.AiKit.Media.MediaArtifact",
|
|
45
22
|
];
|
|
46
23
|
|
|
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
24
|
[Fact]
|
|
52
25
|
public void TheTypeThatProducesBytesIsNotPublic()
|
|
53
26
|
{
|
|
@@ -61,7 +34,6 @@ public sealed class MediaAccessibilityTests
|
|
|
61
34
|
"compilador. E prosa outra vez.");
|
|
62
35
|
}
|
|
63
36
|
|
|
64
|
-
/// <summary>Uma rodada por tipo: nenhum deles pode vazar para fora do assembly.</summary>
|
|
65
37
|
[Theory]
|
|
66
38
|
[MemberData(nameof(ByteProducingTypes))]
|
|
67
39
|
public void NoByteProducingTypeIsVisibleOutsideTheAssembly(string fullName)
|
|
@@ -73,11 +45,6 @@ public sealed class MediaAccessibilityTests
|
|
|
73
45
|
Assert.False(type.IsNestedPublic, $"{fullName} nao pode ser aninhado publico.");
|
|
74
46
|
}
|
|
75
47
|
|
|
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
48
|
[Fact]
|
|
82
49
|
public void ThePublicEntryPointExistsAndNeverReturnsBytes()
|
|
83
50
|
{
|
|
@@ -101,10 +68,6 @@ public sealed class MediaAccessibilityTests
|
|
|
101
68
|
}
|
|
102
69
|
}
|
|
103
70
|
|
|
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
71
|
[Fact]
|
|
109
72
|
public void TheArtifactNeverAppearsInAPublicSignature()
|
|
110
73
|
{
|
package/framework/templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/MediaAliasIsNotAnAgentTests.cs
CHANGED
|
@@ -5,21 +5,6 @@ using Xunit;
|
|
|
5
5
|
|
|
6
6
|
namespace Morph.AiKit.Tests;
|
|
7
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
8
|
public sealed class MediaAliasIsNotAnAgentTests
|
|
24
9
|
{
|
|
25
10
|
private const string MediaAliasDocument = """
|
|
@@ -52,7 +37,6 @@ public sealed class MediaAliasIsNotAnAgentTests
|
|
|
52
37
|
private static AgentSpec Spec(string alias, AgentApi? api = null)
|
|
53
38
|
=> new() { Alias = alias, Instructions = "voce e um agente de teste", Api = api };
|
|
54
39
|
|
|
55
|
-
/// <summary>O registry ACEITA <c>images</c> e <c>videos</c> — um vocabulario so de alias.</summary>
|
|
56
40
|
[Theory]
|
|
57
41
|
[InlineData("imagem", AgentApi.Images)]
|
|
58
42
|
[InlineData("video", AgentApi.Videos)]
|
|
@@ -63,7 +47,6 @@ public sealed class MediaAliasIsNotAnAgentTests
|
|
|
63
47
|
Assert.Equal(expected, registry.Resolve(alias).Api);
|
|
64
48
|
}
|
|
65
49
|
|
|
66
|
-
/// <summary>E a fabrica RECUSA, na construcao, com erro nomeado.</summary>
|
|
67
50
|
[Theory]
|
|
68
51
|
[InlineData("imagem")]
|
|
69
52
|
[InlineData("video")]
|
|
@@ -78,10 +61,6 @@ public sealed class MediaAliasIsNotAnAgentTests
|
|
|
78
61
|
Assert.Contains("EnqueueAsync", ex.Message, StringComparison.Ordinal);
|
|
79
62
|
}
|
|
80
63
|
|
|
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
64
|
[Fact]
|
|
86
65
|
public void TheFactoryRefusesAMediaProtocolDeclaredOnTheSpec()
|
|
87
66
|
{
|
|
@@ -93,7 +72,6 @@ public sealed class MediaAliasIsNotAnAgentTests
|
|
|
93
72
|
Assert.Equal(AgentFactoryError.MediaAliasIsNotAnAgent, ex.Reason);
|
|
94
73
|
}
|
|
95
74
|
|
|
96
|
-
/// <summary>Alias de conversa continua passando — a recusa nova nao pode ser um bloqueio geral.</summary>
|
|
97
75
|
[Fact]
|
|
98
76
|
public void AChatAliasStillBuildsAnAgent()
|
|
99
77
|
{
|
|
@@ -103,7 +81,6 @@ public sealed class MediaAliasIsNotAnAgentTests
|
|
|
103
81
|
Assert.NotNull(factory.Create(Spec("texto")));
|
|
104
82
|
}
|
|
105
83
|
|
|
106
|
-
/// <summary>O predicado mora num lugar so — duas copias e como as duas discordam.</summary>
|
|
107
84
|
[Theory]
|
|
108
85
|
[InlineData(AgentApi.Responses, false)]
|
|
109
86
|
[InlineData(AgentApi.Chat, false)]
|
|
@@ -112,7 +89,6 @@ public sealed class MediaAliasIsNotAnAgentTests
|
|
|
112
89
|
public void IsMediaAnswersForEveryProtocol(AgentApi api, bool expected)
|
|
113
90
|
=> Assert.Equal(expected, api.IsMedia());
|
|
114
91
|
|
|
115
|
-
/// <summary>Um <c>api</c> inventado continua sendo recusado na carga do documento.</summary>
|
|
116
92
|
[Fact]
|
|
117
93
|
public void AnUnknownApiIsStillRefusedWithTheFullListOfValidValues()
|
|
118
94
|
{
|
|
@@ -4,37 +4,12 @@ using Xunit;
|
|
|
4
4
|
|
|
5
5
|
namespace Morph.AiKit.Tests;
|
|
6
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
7
|
public sealed class MediaDisclosureTests
|
|
32
8
|
{
|
|
33
9
|
private static readonly DateTimeOffset T0 = new(2026, 9, 8, 12, 0, 0, TimeSpan.Zero);
|
|
34
10
|
|
|
35
11
|
private static MediaDisclosure Label => MediaDisclosure.Create("Imagem gerada por IA", "legenda", T0);
|
|
36
12
|
|
|
37
|
-
/// <summary>Um job aprovado, na finalidade pedida.</summary>
|
|
38
13
|
private static MediaJob Approved(MediaPurpose purpose)
|
|
39
14
|
{
|
|
40
15
|
var job = MediaJob.Create(
|
|
@@ -50,11 +25,6 @@ public sealed class MediaDisclosureTests
|
|
|
50
25
|
return job.Approve(MediaReview.ApprovedBy("lucas", T0)).Value;
|
|
51
26
|
}
|
|
52
27
|
|
|
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
28
|
[Theory]
|
|
59
29
|
[InlineData(MediaPurpose.CatalogPhoto, true)]
|
|
60
30
|
[InlineData(MediaPurpose.Marketing, true)]
|
|
@@ -62,7 +32,6 @@ public sealed class MediaDisclosureTests
|
|
|
62
32
|
public void RequiresDisclosureIsDeclaredPerPurpose(MediaPurpose purpose, bool expected)
|
|
63
33
|
=> Assert.Equal(expected, purpose.RequiresDisclosure());
|
|
64
34
|
|
|
65
|
-
/// <summary>Rascunho é a única finalidade não publicável.</summary>
|
|
66
35
|
[Theory]
|
|
67
36
|
[InlineData(MediaPurpose.CatalogPhoto, true)]
|
|
68
37
|
[InlineData(MediaPurpose.Marketing, true)]
|
|
@@ -70,14 +39,6 @@ public sealed class MediaDisclosureTests
|
|
|
70
39
|
public void OnlyDraftIsUnpublishable(MediaPurpose purpose, bool expected)
|
|
71
40
|
=> Assert.Equal(expected, purpose.IsPublishable());
|
|
72
41
|
|
|
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
42
|
[Theory]
|
|
82
43
|
[InlineData(MediaPurpose.CatalogPhoto)]
|
|
83
44
|
[InlineData(MediaPurpose.Marketing)]
|
|
@@ -93,7 +54,6 @@ public sealed class MediaDisclosureTests
|
|
|
93
54
|
Assert.Contains("marcacao", result.Error.Message, StringComparison.OrdinalIgnoreCase);
|
|
94
55
|
}
|
|
95
56
|
|
|
96
|
-
/// <summary>Com a marcação, publica — e ela fica GRAVADA no job, não descartada.</summary>
|
|
97
57
|
[Fact]
|
|
98
58
|
public void PublishSucceedsWithADisclosureAndRecordsIt()
|
|
99
59
|
{
|
|
@@ -106,10 +66,6 @@ public sealed class MediaDisclosureTests
|
|
|
106
66
|
Assert.Equal(T0, published.Value.Disclosure.AppliedAt);
|
|
107
67
|
}
|
|
108
68
|
|
|
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
69
|
[Fact]
|
|
114
70
|
public void ADraftNeverPublishesEvenApprovedAndLabelled()
|
|
115
71
|
{
|
|
@@ -119,11 +75,6 @@ public sealed class MediaDisclosureTests
|
|
|
119
75
|
Assert.Contains("nao publica", result.Error!.Message, StringComparison.OrdinalIgnoreCase);
|
|
120
76
|
}
|
|
121
77
|
|
|
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
78
|
[Fact]
|
|
128
79
|
public void TheStateGateIsReportedBeforeTheDisclosureGate()
|
|
129
80
|
{
|
|
@@ -142,29 +93,6 @@ public sealed class MediaDisclosureTests
|
|
|
142
93
|
Assert.DoesNotContain("marcacao", result.Error.Message, StringComparison.OrdinalIgnoreCase);
|
|
143
94
|
}
|
|
144
95
|
|
|
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
96
|
[Fact]
|
|
169
97
|
public void TheTypeExposesNoPublicConstructor()
|
|
170
98
|
{
|
|
@@ -179,7 +107,6 @@ public sealed class MediaDisclosureTests
|
|
|
179
107
|
"MediaReview. Encontrados: " + string.Join(" | ", publicCtors.Select(c => c.ToString())));
|
|
180
108
|
}
|
|
181
109
|
|
|
182
|
-
/// <summary>Texto ou superfície em branco é recusado pela única porta que existe.</summary>
|
|
183
110
|
[Theory]
|
|
184
111
|
[InlineData("", "legenda")]
|
|
185
112
|
[InlineData(" ", "legenda")]
|
|
@@ -188,10 +115,6 @@ public sealed class MediaDisclosureTests
|
|
|
188
115
|
public void ABlankDisclosureIsRefusedByTheOnlyFactory(string text, string surface)
|
|
189
116
|
=> Assert.Throws<ArgumentException>(() => MediaDisclosure.Create(text, surface, T0));
|
|
190
117
|
|
|
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
118
|
[Fact]
|
|
196
119
|
public void TheDisclosureCarriesBothTheTextAndWhereItAppears()
|
|
197
120
|
{
|
|
@@ -201,7 +124,6 @@ public sealed class MediaDisclosureTests
|
|
|
201
124
|
Assert.Equal("sobreposicao", disclosure.Surface);
|
|
202
125
|
}
|
|
203
126
|
|
|
204
|
-
/// <summary>Uma publicação recusada não muda o job — o registro continua imutável.</summary>
|
|
205
127
|
[Fact]
|
|
206
128
|
public void ARefusedPublishLeavesTheJobUntouched()
|
|
207
129
|
{
|