{
  "version": "2.0.0",
  "standards": [
    {
      "id": "ai-agents-agent-archetypes",
      "name": "agent archetypes",
      "path": "ai-agents/agent-archetypes.md",
      "category": "ai-agents",
      "subcategory": null,
      "tags": [
        "ai-agents",
        "maf",
        "architecture",
        "archetype"
      ],
      "stacks": [
        "dotnet"
      ],
      "aliases": [
        "agent archetype",
        "arquetipo de agente",
        "qual forma de agente",
        "conversational agent",
        "one-shot agent",
        "batch agent",
        "vision agent",
        "chat with history"
      ],
      "anchors": {
        "escolha": "#como-escolher",
        "conversacional": "#conversacional-com-fases",
        "batch": "#one-shot-em-batch",
        "visao": "#visão-com-structured-output",
        "historico": "#chat-com-histórico",
        "lastro": "#lastro-de-campo-por-arquétipo",
        "checklist": "#checklist-verifiable-by-morph-eval"
      },
      "digest": "The four shapes an agent can take, and the plan-phase question that picks one: conversational-with-phases, one-shot batch, vision + structured output, chat with history. Field ballast is UNEVEN and the standard says so: batch has 3 exemplars, conversational has 1, vision+structured has ZERO (it is a projected shape, not a distilled one), and conversational and chat-with-history are two AXES OF THE SAME AGENT (phases x history ownership), not alternatives. Pairs with ai-agents-sweet-spot, which decides agent-vs-workflow before this file decides which shape.",
      "verifiedAgainst": "Microsoft.Agents.AI 1.20.0 + Microsoft.Extensions.AI 10.9.0 (ai-pin 2026-09-08); provado por `templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentFactory.cs` e `.../Conversation/ConversationStore.cs` (arquétipos **one-shot em batch** e **chat com histórico** — o conversacional com fases NÃO tem lastro no kit, e `ai-agents-conversational-agent-with-phases` declara isso no próprio cabeçalho). O arquétipo visão + structured output é **verificação documental** — o ai-kit não exercita multimodalidade.",
      "provedBy": [
        "templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentFactory.cs",
        ".../Conversation/ConversationStore.cs"
      ]
    },
    {
      "id": "ai-agents-agent-session",
      "name": "agent session",
      "path": "ai-agents/agent-session.md",
      "category": "ai-agents",
      "subcategory": null,
      "tags": [
        "ai-agents",
        "maf",
        "conversation",
        "session"
      ],
      "stacks": [
        "dotnet"
      ],
      "aliases": [
        "agent session",
        "chat history",
        "conversation state",
        "chat reducer",
        "history truncation",
        "multi-turn",
        "persistent session",
        "ChatHistoryProvider",
        "own conversation table",
        "conversa propria",
        "ProviderSessionState"
      ],
      "anchors": {
        "when": "#o-critério--de-onde-o-histórico-deve-vir",
        "api": "#a-api",
        "example": "#exemplo-c--agente-conversacional-com-session-persistente-entre-turnos",
        "history": "#history-management"
      },
      "digest": "Where conversation history comes from — the CRITERION first, the verdict after. Own conversation storage (project table/jsonb behind a ChatHistoryProvider subclass) is the DEFAULT whenever the conversation carries business fields, must be readable and editable by the application (retry, audit, deletion), or is multi-tenant; AgentSession + a built-in provider only when history is just the message list; InMemoryChatHistoryProvider only for prototypes. Override ProvideChatHistoryAsync / StoreChatHistoryAsync; serialize with SerializeSessionAsync / DeserializeSessionAsync. Key invariant: ONE provider instance shared by every session — per-session state lives in ProviderSessionState<TState>, never in a provider field. ConversationId is scoped to the API key, never an authorization boundary. The pre-GA thread API was renamed and no longer exists in 1.20.0.",
      "verifiedAgainst": "Microsoft.Agents.AI 1.20.0 + Microsoft.Extensions.AI 10.9.0 (ai-pin 2026-09-08); provado por `templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/ConversationStore.cs` (a subclasse de `ChatHistoryProvider`, a invariante de instância única e o teste de duas sessões concorrentes). Nomes de tipo e membros medidos por reflexão sobre `Microsoft.Agents.AI.Abstractions` 1.20.0 em 2026-09-08.",
      "provedBy": [
        "templates/dotnet/ai-kit/src/Morph.AiKit/Conversation/ConversationStore.cs"
      ]
    },
    {
      "id": "ai-agents-agent-spec",
      "name": "agent spec",
      "path": "ai-agents/agent-spec.md",
      "category": "ai-agents",
      "subcategory": null,
      "tags": [
        "ai-agents",
        "maf",
        "agent-config",
        "catalog"
      ],
      "stacks": [
        "dotnet"
      ],
      "aliases": [
        "agent spec",
        "AgentSpec",
        "agent catalog",
        "AgentCatalog",
        "um agente por spec",
        "defaults do agente",
        "quantos agentes"
      ],
      "anchors": {
        "dado": "#o-agente-como-dado",
        "spec": "#agentspec",
        "catalogo": "#agentcatalog",
        "defaults": "#defaults-fora-do-código",
        "higiene": "#higiene-de-publicação",
        "checklist": "#checklist-verifiable-by-morph-eval"
      },
      "digest": "The agent as DATA: one immutable AgentSpec record per agent (Alias required, Instructions required, Api?, Tools, OutputSchema?, Options, Name?, Description?), a catalog that enumerates them, and defaults outside the code. The doctrine this standard freezes: a NULL property means \"do not send to the provider\", never \"send the framework default\" — and an empty list is NOT treated as null, it is sent as written. Precedence between the spec api and the alias api lives in ONE method on the record, never inside the factory. Blank Alias/Instructions are rejected at construction. A catalog entry with no runtime consumer is dead weight (measured: a seeded agent with a versioned prompt and zero C# callers).",
      "verifiedAgainst": "Microsoft.Agents.AI 1.20.0 + Microsoft.Extensions.AI 10.9.0 (ai-pin 2026-09-08); provado por `templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentSpec.cs`, `.../Agents/AgentSpecOptions.cs` e `.../Agents/AgentFactory.cs` — o record, o bag de opções e a materialização são exatamente o que o kit compila.",
      "provedBy": [
        "templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentSpec.cs",
        ".../Agents/AgentSpecOptions.cs",
        ".../Agents/AgentFactory.cs"
      ]
    },
    {
      "id": "ai-agents-batching",
      "name": "batching",
      "path": "ai-agents/batching.md",
      "category": "ai-agents",
      "subcategory": null,
      "tags": [
        "ai-agents",
        "maf",
        "batching",
        "cost-optimization"
      ],
      "stacks": [
        "dotnet"
      ],
      "aliases": [
        "batching",
        "batch api",
        "bulk prompts",
        "batch processing",
        "async batch",
        "bulk inference",
        "offline batch",
        "CreateBatchOperation",
        "BatchClient"
      ],
      "anchors": {
        "when": "#quando-usar-batch",
        "api": "#a-api",
        "example": "#exemplo-c-completo",
        "when-not": "#quando-não-usar"
      },
      "digest": "OpenAI Batch API for N independent prompts with no user waiting: 50% cheaper, up to 24h, separate rate limits. Flow is upload JSONL -> submit -> poll -> download, and the poll belongs in a background job. Measured against OpenAI 2.13.0: Batch is a PROTOCOL API, not a model one — CreateBatchAsync takes BinaryContent (there is no (fileId, endpoint, completionWindow) overload), GetBatchAsync returns a raw ClientResult, BatchJob.Status is a non-exported type, and OpenAIBatch does not exist; read the status from the JSON. GetBatchClient(), OpenAI.Batch, FileUploadPurpose.Batch, UploadFileAsync and DownloadFileAsync are confirmed. Persist CreateBatchOperation.RehydrationToken if the poller can restart. Always handle error_file_id.",
      "verifiedAgainst": "OpenAI 2.13.0 (ai-pin 2026-09-08) + Microsoft.Extensions.AI 10.9.0. **Verificação documental** do fluxo da Batch API (platform.openai.com/docs/guides/batch, lido 2026-09-08), sem cláusula `provado por`: o ai-kit **referencia** o pacote `OpenAI` no `.csproj` — contrato de versão, régua = restore (`NU1102`) — mas **não o exercita**; não existe um único `using OpenAI` em `src/` (medido 2026-09-08), logo um rename de API não reprova a PR. Ver a tabela \"Dois níveis de garantia\" no README do kit. **Todos os nomes de tipo e assinaturas .NET foram medidos por reflexão sobre a assembly `OpenAI` 2.13.0 em 2026-09-08**, e os 9 marcadores `// VERIFY` desta página foram RESOLVIDOS por essa medição.",
      "provedBy": []
    },
    {
      "id": "ai-agents-context-providers",
      "name": "context providers",
      "path": "ai-agents/context-providers.md",
      "category": "ai-agents",
      "subcategory": null,
      "tags": [
        "ai-agents",
        "maf",
        "context",
        "dependency-injection"
      ],
      "stacks": [
        "dotnet"
      ],
      "aliases": [
        "context provider",
        "dynamic context",
        "context injection",
        "AIContextProvider",
        "memory provider",
        "inject context",
        "system prompt injection",
        "compaction",
        "MAAI001",
        "tool before provider",
        "ordem de escolha"
      ],
      "anchors": {
        "when": "#quando-usar-context-provider",
        "api": "#a-api",
        "example": "#exemplo-c--context-provider-que-injeta-data-atual-e-perfil-do-tenant",
        "comparison": "#context-provider-vs-tools-vs-rag"
      },
      "digest": "Order of choice first: a TOOL comes before an AIContextProvider — the provider is for cross-cutting context (memory, compaction, tenant) the LLM must not be able to skip. Passive RAG as a provider is the concrete anti-pattern it prevents (a vector search paid on every \"hi\"). Phase plans (instructions + a tool allow-list per funnel stage) are a deterministic C# composer, not a provider — AIContext only ADDS tools, it cannot remove them. AIContext has exactly Instructions/Messages/Tools; AgentRequestMessageSourceType prevents re-injection loops. Same instance invariant as agent-session (ProviderSessionState<TState>). Compaction is [Experimental(\"MAAI001\")] and where you register it changes the result: on the ChatClientBuilder it compacts only the in-flight request; on ChatClientAgentOptions it skips the tool loop and leaks the summary into persisted history. UseAIContextProviders ships in Microsoft.Agents.AI, not .Hosting.",
      "verifiedAgainst": "Microsoft.Agents.AI 1.20.0 (ai-pin 2026-09-08) + doc oficial de context providers e de compaction (learn.microsoft.com, `ms.date` 2026-07-30). **Verificação documental** para o desenho — o ai-kit não registra `AIContextProvider`; os nomes de tipo, o pacote de cada extensão e o atributo `[Experimental]` da compaction foram **medidos por reflexão sobre `Microsoft.Agents.AI` 1.20.0** em 2026-09-08.",
      "provedBy": []
    },
    {
      "id": "ai-agents-conversational-agent-with-phases",
      "name": "conversational agent with phases",
      "path": "ai-agents/conversational-agent-with-phases.md",
      "category": "ai-agents",
      "subcategory": null,
      "tags": [
        "ai-agents",
        "maf",
        "conversation",
        "phases"
      ],
      "stacks": [
        "dotnet"
      ],
      "aliases": [
        "agente conversacional",
        "fases do agente",
        "allow-list de tools",
        "RequireAny",
        "ChatToolMode",
        "compositor de turno",
        "teto de tool calls",
        "turno de conversa"
      ],
      "anchors": {
        "quando": "#quando-este-arquétipo",
        "compositor": "#compositor-de-turno-determinístico",
        "allowlist": "#allow-list-por-fase",
        "requireany": "#exigir-tool-no-turno",
        "teto": "#teto-de-tool-calls-e-isenta",
        "auxiliar": "#agente-auxiliar-em-sequência",
        "checklist": "#checklist-verifiable-by-morph-eval"
      },
      "digest": "ONE agent, phase as turn data — the deep dive on the first archetype, and it DECLARES a single field source in its own body. A deterministic turn composer (pure static class, no I/O) yields the turn plan; the prompt is born decomposed into NAMED BLOCKS and the text sent IS the concatenation, derived without cache ON PURPOSE, \"so telemetry cannot lie\" — the invariant that makes a prompt hash worth anything. Allow-list prunes in two stages (phase, then tenant capability), a tool absent from the map is AVAILABLE, and pruning preserves order. Tool-call ceiling with an exempt tool, at agent level (not ChatOptions). ChatToolMode.RequireAny exists (measured by reflection) and is the default when every action is a tool — but it ships with an EXIT CRITERION, not a blind prescription: the field alternative (a second pass) has 7 tests and a \"recovered\" outcome, so migrate when the recovered rate stops paying for the second paid call. An auxiliary agent runs in sequence, never nested inside a tool call.",
      "verifiedAgainst": "Microsoft.Extensions.AI 10.9.0 + Microsoft.Agents.AI 1.20.0 (ai-pin 2026-09-08). `ChatToolMode.RequireAny` foi **medido por reflexão sobre `Microsoft.Extensions.AI.Abstractions` 10.9.0** em 2026-09-08. O restante é **verificação documental**, sem cláusula `provado por` — o ai-kit ainda não traz um compositor de turno, e o padrão é destilado de um único repositório de campo (ver a nota de fonte única abaixo).",
      "provedBy": []
    },
    {
      "id": "ai-agents-cost-and-budget",
      "name": "cost and budget",
      "path": "ai-agents/cost-and-budget.md",
      "category": "ai-agents",
      "subcategory": null,
      "tags": [
        "ai-agents",
        "maf",
        "cost",
        "budget"
      ],
      "stacks": [
        "dotnet"
      ],
      "aliases": [
        "custo de llm",
        "cost middleware",
        "UsageCostMiddleware",
        "teto de custo",
        "budget middleware",
        "tarifa por token",
        "cost_usd",
        "orçamento do agente"
      ],
      "anchors": {
        "tarifa": "#tarifa-no-registry",
        "middleware": "#o-middleware-de-custo",
        "armadilhas": "#armadilhas-de-fórmula",
        "custonulo": "#cost_usd-nunca-nulo",
        "teto": "#teto-de-orçamento",
        "total": "#medir-o-total-não-o-piso",
        "fronteira": "#fronteira-com-observability-patterns",
        "checklist": "#checklist-verifiable-by-morph-eval"
      },
      "digest": "The only owner of COST as a subject. The SDK gives tokens and does not give money: UsageDetails (measured by reflection on Microsoft.Extensions.AI.Abstractions 10.9.0) exposes Input/Output/Total/Cached/Reasoning counts, the text-audio pairs, AdditionalCounts and Add() — and NO price property. So the tariff lives per alias in the registry, WITH a verification date, and an alias without a tariff must carry a written waiver. One middleware, above function-invocation in the pipeline (measured: above it, Usage already totals every tool round and one line is published per call). Three formula traps: reasoning tokens billed as output, CACHED input is a SUBSET of input (adding them double-counts), and hosted-operation flat fees never appear in UsageDetails. cost_usd is NEVER null: zero with an explicit state — Computed / Waived / UsageMissing. Budget UNIT is a parameter (lead, tenant, operation, day); the measured pain is one of PLACE — a ceiling living inside a tool does not govern the pipeline. Warn before blocking, and the block returns an actionable sentence to the model, never an exception. Two-way delegation with ai-agents-observability-patterns: span attributes stay there, tariff and ceiling live here.",
      "verifiedAgainst": "Microsoft.Extensions.AI 10.9.0 + Microsoft.Agents.AI 1.20.0 (ai-pin 2026-09-08); provado por `templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/UsageCostMiddleware.cs`, `.../Middleware/UsageCostRecord.cs` e `.../Providers/ModelAlias.cs` — o middleware, o record de custo não-nulo e a conta de tarifa são o que o kit compila. A ausência de qualquer propriedade de preço em `UsageDetails` foi **medida por reflexão sobre `Microsoft.Extensions.AI.Abstractions` 10.9.0** em 2026-09-08. O teto de orçamento é **verificação documental** — o ai-kit não traz um middleware de orçamento.",
      "provedBy": [
        "templates/dotnet/ai-kit/src/Morph.AiKit/Middleware/UsageCostMiddleware.cs",
        ".../Middleware/UsageCostRecord.cs",
        ".../Providers/ModelAlias.cs"
      ]
    },
    {
      "id": "ai-agents-durable-workflows-hitl",
      "name": "durable workflows hitl",
      "path": "ai-agents/durable-workflows-hitl.md",
      "category": "ai-agents",
      "subcategory": null,
      "tags": [
        "ai-agents",
        "maf",
        "workflow",
        "durability"
      ],
      "stacks": [
        "dotnet"
      ],
      "aliases": [
        "durable workflow",
        "checkpoint",
        "checkpointing",
        "human in the loop",
        "hitl",
        "requestport",
        "requestinfoevent",
        "resume workflow",
        "long running workflow",
        "approval workflow",
        "icheckpointstore",
        "tool approval",
        "ApprovalRequiredAIFunction",
        "ToolApprovalAgent"
      ],
      "anchors": {
        "when": "#quando-usar-este-standard",
        "hitl": "#human-in-the-loop-via-requestport",
        "checkpointing": "#checkpointing--durável-sem-depender-de-azure",
        "durable-task": "#azure-durable-task-extension--escape-hatch-not-default"
      },
      "digest": "Two DIFFERENT human-in-the-loop mechanisms, with the boundary made explicit. Single agent with an irreversible tool: ApprovalRequiredAIFunction — no workflow at all. The run ENDS emitting ToolApprovalRequestContent (whose ToolCall is a ToolCallContent), you answer with CreateResponse(approved, reason) IN THE SAME SESSION, and you loop until no pending approval remains. ToolApprovalAgent (stable since 1.14) centralises AutoApprovalRules. Since 1.14 approval responses are BOUND to the request — code written against 1.0 must be reviewed, not just recompiled. Graph with a human step or a multi-day wait: RequestPort + CheckpointManager over a custom ICheckpointStore (no Azure). Durable Task was extracted to a separate repo in 1.17 and lags the framework. Approval is not the right guard when the person on the other end is the interested party — use a deterministic guard.",
      "verifiedAgainst": "Microsoft.Agents.AI 1.20.0 + Microsoft.Extensions.AI 10.9.0 (ai-pin 2026-09-08); `Microsoft.Agents.AI.Workflows` 1.20.0 e `Microsoft.Agents.AI.DurableTask` `1.16.0-preview.260730.1` **não estão no pin** — versões apuradas no nuget.org em 2026-09-08. **Verificação documental**, sem cláusula `provado por`: o ai-kit não exercita workflows nem aprovação de tool. Os nomes de tipo e membros de aprovação foram **medidos por reflexão sobre as assemblies do pin** em 2026-09-08.",
      "provedBy": []
    },
    {
      "id": "ai-agents-evals-with-cache",
      "name": "evals with cache",
      "path": "ai-agents/evals-with-cache.md",
      "category": "ai-agents",
      "subcategory": null,
      "tags": [
        "ai-agents"
      ],
      "stacks": [
        "dotnet"
      ],
      "aliases": [
        "eval",
        "evals",
        "eval de modelo",
        "regressao de prompt",
        "prompt regression",
        "model swap",
        "troca de modelo",
        "cache de respostas",
        "response cache",
        "dataset de eval",
        "EvalFixture",
        "OfflineChatClient",
        "DiskBasedResponseCacheProvider",
        "no evals",
        "runner evals",
        "--refresh-cache",
        "evals.watch"
      ],
      "anchors": {
        "porque": "#por-que-eval-não-é-teste-unitário",
        "dataset": "#dataset-por-agente-e-por-tenant",
        "chave": "#o-que-entra-na-chave-de-cache",
        "ttl": "#ttl-explícito-sempre--e-por-quê",
        "carimbo": "#o-carimbo-e-o-que-ele-responde-de-graça",
        "commit": "#o-que-se-commita-e-o-que-não",
        "gate": "#gate-duro-só-em-propriedade-determinística",
        "quando": "#quando-o-eval-roda",
        "watch": "#evalswatch-o-que-conta-como-mudei-o-prompt",
        "semeadura": "#semeando-a-cache-exemplar-sem-gastar-dinheiro",
        "fronteira": "#fronteira-com-testing-ai"
      },
      "digest": "Eval de modelo e de prompt lendo de uma cache de respostas COMMITADA: roda no CI sem credencial e sem rede, porque no hit da cache o cliente interno nunca e invocado e o cliente interno e um que RECUSA. Owns: dataset versionado por {agente}/{tenant}; o que entra na chave de cache (ProviderName + DefaultModelId; o endpoint NAO entra, apesar de a doc afirmar que entra - medido na 10.9.0); TTL explicito e longo (o default de 14 dias e DESTRUTIVO NA LEITURA e apaga a arvore commitada); o carimbo .morph-eval-cache.json que distingue cache sem carimbo de cache gravada sob outro pin; o que se commita (cache sim, resultados de execucao nao, por construcao); gate duro so em assercao deterministica, juizo de LLM como metrica reportada (2 dos 4 avaliadores de .Quality sao [Experimental] na 10.9.0); o no runner:evals no project.tests[] e o project.evals.watch[] que alimenta a regra de Gate 3; miss de cache reprova nomeado, JAMAIS skip. Fronteira com testing-ai: la o loop do agente contra fake, aqui o modelo real via cache.",
      "verifiedAgainst": "Microsoft.Extensions.AI.Evaluation 10.9.0 + `.Quality` + `.Reporting` (ai-pin 2026-09-08); provado por `templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalFixture.cs`, que compila e roda no CI contra exatamente essas versões. A superfície de `DiskBasedReportingConfiguration`, `DiskBasedResponseCacheProvider`, `ReportingConfiguration` e dos avaliadores de `.Quality` foi **medida por reflexão sobre as assemblies `Microsoft.Extensions.AI.Evaluation*` 10.9.0** em 2026-09-08.",
      "provedBy": [
        "templates/dotnet/ai-kit/src/Morph.AiKit/Evals/EvalFixture.cs"
      ]
    },
    {
      "id": "ai-agents-guardrails",
      "name": "guardrails",
      "path": "ai-agents/guardrails.md",
      "category": "ai-agents",
      "subcategory": null,
      "tags": [
        "ai-agents",
        "maf",
        "guardrails",
        "safety"
      ],
      "stacks": [
        "dotnet"
      ],
      "aliases": [
        "guardrail",
        "guard de tool",
        "regra proibida",
        "validacao de pertinencia",
        "fail-open guard",
        "argumento nao confiavel",
        "choke-point"
      ],
      "anchors": {
        "codigo": "#regra-proibida-vira-código",
        "chokepoint": "#onde-fica-o-choke-point",
        "argumento": "#argumento-de-tool-é-input-não-confiável",
        "pertinencia": "#pertinência-não-presença",
        "failopen": "#quando-o-guard-não-consegue-decidir",
        "checklist": "#checklist-verifiable-by-morph-eval"
      },
      "digest": "What the CODE guarantees once the prompt has already failed. A rule with real consequence leaves the prompt and becomes a C# condition — the measured reason, written in field code: the model obeys \"one entry per element of this list\" but errs badly when it must judge on its own which blocks have data (10 expected, 2 delivered). Three legitimate choke-points (before the turn in the composer; in the function-invocation middleware, with a CLOSED outcome vocabulary ok/error/invalid_args/limit/unavailable and required arguments read from the function JSON schema; after the run as backstop). Tool arguments are UNTRUSTED input: ids and money come from the turn closure, never from the model. Relevance, not presence: compare against something that did NOT come from the model — a deterministic rubric checked BY EQUALITY against the backend numbers, a provenance lock that demotes an unsupported \"found\" while PRESERVING the reasoning for audit, and a per-source confidence floor. When the guard cannot decide the answer is FAIL-OPEN, ratified with one hard condition: the escape is RECORDED (the anti-pattern is 81 of 83 catch blocks that never read the exception). Verdict is ternary — pass / reject / not_evaluated — because a boolean turns \"could not evaluate\" into \"approved\".",
      "verifiedAgainst": "Microsoft.Extensions.AI 10.9.0 + Microsoft.Agents.AI 1.20.0 (ai-pin 2026-09-08). **Verificação documental**, sem cláusula `provado por` — o ai-kit não traz um guard de exemplo; o padrão é destilado de dois repositórios em produção, com o local de cada evidência citado na seção correspondente.",
      "provedBy": []
    },
    {
      "id": "ai-agents-llm-runtime-defaults",
      "name": "llm runtime defaults",
      "path": "ai-agents/llm-runtime-defaults.md",
      "category": "ai-agents",
      "subcategory": null,
      "tags": [
        "ai-agents",
        "maf",
        "runtime",
        "defaults"
      ],
      "stacks": [
        "dotnet"
      ],
      "aliases": [
        "llm defaults",
        "runtime defaults",
        "timeout llm",
        "retry llm",
        "strict json schema",
        "temperature reasoning",
        "capability por alias",
        "network timeout"
      ],
      "anchors": {
        "fronteira": "#fronteira-com-o-model-registry",
        "retry": "#retry-herdado",
        "timeout": "#timeout-explícito",
        "strict": "#strict-json-schema-por-alias",
        "temperature": "#temperature-e-reasoning",
        "pin": "#pin-de-sdk-e-compatibilidade-binária",
        "checklist": "#checklist-verifiable-by-morph-eval"
      },
      "digest": "The runtime KNOBS of a model call, with the boundary against ai-agents-providers-model-registry (Layer 0, which owns the SCHEMA) written as the first section. Retry is INHERITED from the System.ClientModel pipeline — do not stack a loop on top (3 x 3 = 9 paid calls). Timeout is NOT inherited and must be created: ChatOptions has no timeout property (measured by reflection) and OpenAIClientOptions declares only Endpoint/OrganizationId/ProjectId/UserAgentApplicationId — the knob comes from the ClientPipelineOptions base. A CancellationToken is not a timeout. StrictJsonSchema DOES NOT EXIST: zero exported types containing \"Strict\" across the 14 pinned assemblies, and ChatResponseFormatJson has no strict field; the real mechanism is a key in ChatOptions.AdditionalProperties, so strict is per alias and NEVER a process-global flag. ReasoningEffort is a first-class enum (None/Low/Medium/High/ExtraHigh). Pin AI packages exactly — a floating 2.* range cost a MissingMethodException after the call was already paid for.",
      "verifiedAgainst": "Microsoft.Extensions.AI 10.9.0 + Microsoft.Agents.AI 1.20.0 + OpenAI 2.13.0 (ai-pin 2026-09-08); provado por `templates/dotnet/ai-kit/src/Morph.AiKit/Providers/ModelAlias.cs` e `.../Providers/ModelRegistry.cs` (alias, `api:`, e limite por alias com procedência obrigatória). A superfície do SDK citada aqui — `ChatOptions`, `ChatResponseFormatJson`, `ReasoningEffort`, `OpenAIClientOptions` — foi **medida por reflexão sobre as assemblies do pin** em 2026-09-08. O timeout é **provado por** `.../Agents/AgentSpecOptions.cs` e `.../Agents/AgentFactory.cs`, que o kit compila **por spec** (`TimeoutSeconds` vira um cliente de timeout no pipeline) — e **não** por alias: a allowlist de alias do registry não tem chave de timeout (medido). Os valores default do retry do `System.ClientModel` 1.15.0 são **verificação documental** — essa assembly está fora do conjunto que o probe carrega.",
      "provedBy": [
        "templates/dotnet/ai-kit/src/Morph.AiKit/Providers/ModelAlias.cs",
        ".../Providers/ModelRegistry.cs",
        ".../Agents/AgentSpecOptions.cs",
        ".../Agents/AgentFactory.cs"
      ]
    },
    {
      "id": "ai-agents-mcp-server",
      "name": "mcp server",
      "path": "ai-agents/mcp-server.md",
      "category": "ai-agents",
      "subcategory": null,
      "tags": [
        "ai-agents",
        "maf",
        "mcp",
        "server"
      ],
      "stacks": [
        "dotnet"
      ],
      "aliases": [
        "mcp server",
        "expose mcp",
        "build mcp server",
        "mcp host",
        "publish tools",
        "create mcp server",
        "ModelContextProtocol server",
        "request filters",
        "AddCallToolFilter",
        "stateless",
        "spec 2026-07-28"
      ],
      "anchors": {
        "when": "#quando-criar-um-mcp-server",
        "api": "#a-api",
        "example": "#exemplo-c--tools-por-atributo-stdio",
        "http": "#exemplo-c--transport-http",
        "security": "#segurança"
      },
      "digest": "Expose project capabilities as an MCP server. There is NO spec revision called \"MCP 2.0\": the spec is dated (current revision 2026-07-28) and 2.x is the C# SDK major (ModelContextProtocol 2.2.0). That revision removed sessions and Mcp-Session-Id (SEP-2567), removed initialize (version/capabilities travel in _meta per request, SEP-2575), deprecated Roots/Sampling/Logging (SEP-2577), moved Tasks to an extension, replaced elicitation/sampling with MRTR, and deprecated HTTP+SSE. The C# server API kept its names (AddMcpServer, WithStdioServerTransport, WithHttpTransport, [McpServerTool]) but Stateless is now the DEFAULT, not a choice. Multi-tenant needs TWO request filters: AddListToolsFilter hides a tool, only AddCallToolFilter stops the call — and a connected client never re-reads the list. Identity per request via context.User; tool arguments are untrusted input and tenant ids come from the credential, never from the model.",
      "verifiedAgainst": "ModelContextProtocol 2.2.0 + ModelContextProtocol.AspNetCore 2.2.0 (ai-pin 2026-09-08; o `.AspNetCore` não está no pin — versão apurada no nuget.org em 2026-09-08) + revisão de spec MCP **2026-07-28** (modelcontextprotocol.io/specification/versioning, lido 2026-09-08). **Verificação documental — o ai-kit não exercita MCP**, e por isso este standard NÃO carimba `provado por`. Os nomes de tipo, os defaults e os avisos de obsolescência abaixo foram **medidos por reflexão sobre as assemblies 2.2.0** em 2026-09-08.",
      "provedBy": []
    },
    {
      "id": "ai-agents-mcp-tools",
      "name": "mcp tools (agent consumer)",
      "path": "ai-agents/mcp-tools.md",
      "category": "ai-agents",
      "subcategory": null,
      "tags": [
        "ai-agents",
        "maf",
        "mcp",
        "tools"
      ],
      "stacks": [
        "dotnet"
      ],
      "aliases": [
        "mcp",
        "model context protocol",
        "mcp client",
        "consume mcp server",
        "mcp tools",
        "external tools",
        "agent mcp client",
        "McpClient.CreateAsync",
        "HttpClientTransport",
        "MRTR"
      ],
      "anchors": {
        "what": "#o-que-é-no-contexto-de-um-agente-maf",
        "api": "#a-api",
        "example": "#exemplo-c",
        "comparison": "#mcp-server-vs-tool-nativa"
      },
      "digest": "A MAF agent consuming external MCP servers, against ModelContextProtocol 2.2.0 (out of preview). Renames that break old code: McpClientFactory.CreateAsync became McpClient.CreateAsync, and SseClientTransport became HttpClientTransport with TransportMode = HttpTransportMode.Sse — neither old name exists in 2.2.0. McpClientTool derives from AIFunction, so .Cast<AITool>() is unnecessary. ElicitAsync/SampleAsync/RequestRootsAsync need a persistent session and fail against a stateless server; the cross-configuration path is MRTR (InputRequiredException + InputRequest.ForElicitation). Long-running Tasks moved to ModelContextProtocol.Extensions.Tasks with NO wire compatibility with the 1.3/1.4 implementation. Every discovered tool is re-sent in the definition on each turn — prune.",
      "verifiedAgainst": "ModelContextProtocol 2.2.0 + Microsoft.Agents.AI 1.20.0 + Microsoft.Extensions.AI 10.9.0 (ai-pin 2026-09-08) + revisão de spec MCP **2026-07-28**. **Verificação documental — o ai-kit não exercita MCP**, e por isso este standard NÃO carimba `provado por`. Todos os nomes de tipo abaixo foram **medidos por reflexão sobre `ModelContextProtocol.Core` 2.2.0** em 2026-09-08.",
      "provedBy": []
    },
    {
      "id": "ai-agents-media-pipeline",
      "name": "media pipeline",
      "path": "ai-agents/media-pipeline.md",
      "category": "ai-agents",
      "subcategory": null,
      "tags": [
        "ai-agents",
        "maf",
        "media",
        "pipeline",
        "compliance"
      ],
      "stacks": [
        "dotnet"
      ],
      "aliases": [
        "media pipeline",
        "media job",
        "media review",
        "C2PA",
        "SynthID",
        "AI Act",
        "content provenance",
        "marcação de IA",
        "idempotência de mídia"
      ],
      "anchors": {
        "hard-rule": "#a-regra-dura",
        "states": "#a-máquina-de-estados",
        "idempotency": "#as-duas-chaves-de-idempotência",
        "storage": "#storage",
        "provenance": "#proveniência-persistida",
        "labelling": "#marcação-c2pa-e-synthid",
        "review": "#revisão-humana",
        "budget": "#teto-de-custo",
        "telemetry": "#telemetria",
        "tests": "#testes",
        "reverify": "#como-re-verificar-este-standard"
      },
      "digest": "Onde mora a regra dura: NUNCA gerar midia dentro de um turno de conversa (WhatsApp, chat, webhook sincrono) — latencia documentada de ate 2 min em imagem e de 11 s a 6 min em video, custo por chamada, e zero revisao possivel dentro do turno. A regra e presa por TIPO e nao por prosa: o que produz bytes e internal ao assembly, a superficie publica devolve id de job. O limite do tipo esta declarado: ele fecha \"obter bytes\" e \"nomear o produtor\", NAO fecha \"aguardar inline\" — o gatilho do worker (IMediaJobRunner.RunAsync) e publico por necessidade, e um await dele dentro de um handler de turno compila e paga a latencia; quem fecha essa porta e a revisao de codigo. Maquina de estados pending/generating/generated/approved|rejected|rejected_by_policy/published + failed, onde bloqueio de moderacao e terminal e NAO retryable. DUAS chaves de idempotencia, distintas de proposito: a de PEDIDO (sha256 de provider+model+prompt+refs+opcoes) dedupe a chamada paga; a de CONTEUDO (sha256 dos bytes) dedupe o storage. Bytes no blob proprio porque URL de provedor expira; banco com caminho relativo + sha256. Proveniencia com nove campos e C2PA preservado sem re-encode. C2PA e SynthID vem embutidos e NAO bastam (caem com screenshot/re-encode; not_detected nao significa humano), entao a marcacao visivel e obrigacao separada no ponto de publicacao — EU AI Act art. 50, com a ressalva de fonte declarada. E ela e presa por TIPO, nao por prosa: Publish tem TRES portas (estado Approved; finalidade publicavel; marcacao presente quando a finalidade a exige) e recebe a marcacao como parametro, com texto E superficie obrigatorios na construcao. O limite e declarado: prova que a decisao foi gravada, nao que o rotulo chegou aos olhos de alguem — renderizar e do aplicativo consumidor. Approve exige humano com carimbo; juiz LLM e pre-filtro. Teto de custo por tenant/dia recusa no enqueue. O mecanismo de job NAO e reescrito: cita backend/integrations/hangfire/hangfire-jobs.md, e durable-workflows-hitl e a outra coisa (HITL dentro de um Workflow MAF).",
      "verifiedAgainst": "Microsoft.Extensions.AI 10.9.0 + OpenAI 2.13.0 + Google.GenAI 1.21.0 (ai-pin 2026-09-08); provado por `templates/dotnet/ai-kit/src/Morph.AiKit/Media/` — a máquina de estados (`MediaJob`), a revisão humana (`MediaReview`), a marcação exigida na publicação (`MediaDisclosure` + `MediaJob.Publish`), as duas chaves (`MediaIdempotencyKey` × `MediaHash`), o storage por conteúdo (`MediaStore`) e a acessibilidade `internal` de `IMediaProvider` compilam e são exercitados por teste. Preço, data de desligamento, C2PA/SynthID e o texto legal do AI Act são **verificação documental** contra as páginas de §Como re-verificar este standard.",
      "provedBy": [
        "templates/dotnet/ai-kit/src/Morph.AiKit/Media/"
      ]
    },
    {
      "id": "ai-agents-media-video",
      "name": "media video",
      "path": "ai-agents/media-video.md",
      "category": "ai-agents",
      "subcategory": null,
      "tags": [
        "ai-agents",
        "maf",
        "multimodal",
        "video-generation",
        "media"
      ],
      "stacks": [
        "dotnet"
      ],
      "aliases": [
        "media video",
        "video generation",
        "text to video",
        "image to video",
        "veo",
        "seedance",
        "kling",
        "runway",
        "GenerateVideosAsync"
      ],
      "anchors": {
        "when": "#quando-usar",
        "sdk": "#o-caminho-com-sdk",
        "no-sdk": "#o-caminho-sem-sdk",
        "sora": "#sora-não-planejar",
        "cost": "#custo-por-segundo",
        "limits": "#duração-resolução-e-referências",
        "expiry": "#o-arquivo-expira",
        "moderation": "#moderação-e-pessoas",
        "deprecations": "#deprecações",
        "reverify": "#como-re-verificar-este-standard"
      },
      "digest": "Geracao de VIDEO em runtime a partir de .NET. A fronteira esta na primeira linha: footage de LP scroll-driven por MCP em tempo de autoria e frontend/design-system/ai-video-generation.md; aqui e o worker do backend. Veo 3.1 via Google.GenAI 1.21.0 (GenerateVideosAsync + Operations.GetAsync em polling ~10 s) e o UNICO caminho com SDK .NET oficial depois do desligamento da Sora — sora-2, sora-2-pro e o VideoClient do openai-dotnet saem sem substituto, entao nao se escreve adaptador para eles. Seedance 2.0, Kling e Runway sao HttpClient tipado com DTOs proprios: criar tarefa, PERSISTIR o id antes de qualquer outra coisa, polling com back-off ou webhook, baixar, gravar. O arquivo do Veo expira em 2 dias, o que torna o download obrigatorio e nao otimizacao. Bloqueio de moderacao NAO cobra e e estado terminal (rejected_by_policy), nunca erro retryable. 8 s e obrigatorio no Veo para 1080p, 4K e imagens de referencia. Nenhum alias default e pinado para Seedance/Kling/Runway porque a faixa de preco medida varia 20x entre canais. Preco em #custo-por-segundo, datas em #deprecacoes.",
      "verifiedAgainst": "Google.GenAI 1.21.0 + OpenAI 2.13.0 (ai-pin 2026-09-08); provado por `templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/VeoVideoProvider.cs` — `Models.GenerateVideosAsync(model, GenerateVideosSource, GenerateVideosConfig, ct)`, o polling por `Operations.GetAsync` + `operation.Done` e o sinal de bloqueio `RaiMediaFilteredCount` COMPILAM ali. Preço, duração, retenção e data de desligamento são **verificação documental** contra as páginas listadas em §Como re-verificar este standard.",
      "provedBy": [
        "templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/VeoVideoProvider.cs"
      ]
    },
    {
      "id": "ai-agents-middleware-patterns",
      "name": "middleware patterns",
      "path": "ai-agents/middleware-patterns.md",
      "category": "ai-agents",
      "subcategory": null,
      "tags": [
        "ai-agents",
        "maf",
        "middleware"
      ],
      "stacks": [
        "dotnet"
      ],
      "aliases": [
        "middleware",
        "agent middleware",
        "function calling middleware",
        "retry",
        "anti-hallucination",
        "resilience",
        "telemetry middleware",
        "agent run middleware",
        "pipeline order",
        "streaming downgrade"
      ],
      "anchors": {
        "levels": "#os-3-níveis-de-middleware",
        "resilience": "#pattern-resilência-retry--rate-limit",
        "anti-hallucination": "#pattern-anti-hallucination"
      },
      "digest": "The three middleware levels (Agent Run, Function Calling, IChatClient) and where each one belongs. Registration is always .AsBuilder().Use(...).Build(); in a ChatClientBuilder the FIRST registered is the OUTERMOST. Pipeline ORDER is measured, not folklore: timeout -> usage-cost -> FunctionInvokingChatClient -> chat telemetry -> provider client. Usage-cost sits ABOVE the FICC because below it would read the same UsageDetails the FICC mutates to accumulate; chat telemetry sits BELOW it so the chat span closes before the tools. Supplying only the non-streaming delegate silently downgrades streaming calls — use Use(sharedFunc) when the middleware does not care. Patterns: telemetry, resilience (retry + rate limit), anti-hallucination.",
      "verifiedAgainst": "Microsoft.Agents.AI 1.20.0 + Microsoft.Extensions.AI 10.9.0 (ai-pin 2026-09-08); `provado por templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentFactory.cs` para a **ordem do pipeline** — a fábrica do kit é o único lugar que conhece essa ordem, e ela é medida por teste, não herdada de folclore. As assinaturas de `Use(...)` foram **medidas por reflexão sobre `AIAgentBuilder` e `ChatClientBuilder`** em 2026-09-08.",
      "provedBy": [
        "templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentFactory.cs"
      ]
    },
    {
      "id": "ai-agents-modalities-image-gen",
      "name": "modalities image gen",
      "path": "ai-agents/modalities-image-gen.md",
      "category": "ai-agents",
      "subcategory": null,
      "tags": [
        "ai-agents",
        "maf",
        "multimodal",
        "image-generation"
      ],
      "stacks": [
        "dotnet"
      ],
      "aliases": [
        "image generation",
        "text to image",
        "dall-e",
        "gpt-image",
        "mockup",
        "image synthesis",
        "generate image",
        "image from text",
        "MEAI001",
        "IImageGenerator"
      ],
      "anchors": {
        "when": "#quando-usar-geração-de-imagem",
        "api": "#a-api",
        "example": "#exemplo-c-completo",
        "params": "#parâmetros"
      },
      "digest": "Text-to-image with the OpenAI SDK: ImageClient.GenerateImageAsync(prompt, options, ct) — signature confirmed in OpenAI 2.13.0, alongside GenerateImagesAsync, GenerateImageEditAsync and GenerateImageVariationAsync. The abstraction path (IImageGenerator, AsIImageGenerator, ImageGeneratorBuilder) is [Experimental(\"MEAI001\")] and needs the diagnostic suppressed; the concrete ImageClient does not. HostedImageGenerationTool lets the MODEL decide to generate, and is documented as mapping to the Responses API — that mapping is internal and was not confirmable by reflection. There is no GetImageClient(alias) in the standard ModelRegistry; register the ImageClient yourself.",
      "verifiedAgainst": "OpenAI 2.13.0 + Microsoft.Extensions.AI 10.9.0 + Google.GenAI 1.21.0 (ai-pin 2026-09-08); provado por `templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/OpenAIImageProvider.cs` — `IImageGenerator`, `MEAI001`, `ImageGenerationRequest.OriginalImages` e a ausência de propriedade tipada para `quality` COMPILAM ali, e `OPENAI001` em `GeneratedImageQuality` foi medido pelo compilador. `HostedImageGenerationTool` foi medido por reflexão sobre a assembly do pin (existe, não é experimental); preço, data de desligamento e limites de referência são **verificação documental** contra as páginas de §Como re-verificar este standard.",
      "provedBy": [
        "templates/dotnet/ai-kit/src/Morph.AiKit/Media/Providers/OpenAIImageProvider.cs"
      ]
    },
    {
      "id": "ai-agents-modalities-speech-to-text",
      "name": "modalities speech to text",
      "path": "ai-agents/modalities-speech-to-text.md",
      "category": "ai-agents",
      "subcategory": null,
      "tags": [
        "ai-agents",
        "maf",
        "multimodal",
        "stt",
        "audio"
      ],
      "stacks": [
        "dotnet"
      ],
      "aliases": [
        "speech to text",
        "transcription",
        "whisper",
        "audio to text",
        "voice transcription",
        "STT",
        "audio transcription"
      ],
      "anchors": {
        "when": "#quando-usar-stt",
        "api": "#a-api",
        "example": "#exemplo-c-completo",
        "formats": "#formatos-e-limites"
      },
      "digest": "Use STT when the agent receives voice input or audio files that must be transcribed to text before processing. MAF wraps the Whisper/AudioClient API. Covers supported audio formats (mp3, wav, webm, mp4), file-size limits, language hints, and streaming vs batch transcription. Anti-patterns: piping raw audio into a chat message without transcribing first.",
      "verifiedAgainst": "OpenAI 2.13.0 + Microsoft.Extensions.AI 10.9.0 (ai-pin 2026-09-08). **Verificação documental**, sem cláusula `provado por` — o ai-kit não transcreve áudio. `AudioClient.TranscribeAudioAsync` e a ponte `AsISpeechToTextClient` foram **medidas por reflexão sobre as assemblies do pin** em 2026-09-08.",
      "provedBy": []
    },
    {
      "id": "ai-agents-modalities-text-to-speech",
      "name": "modalities text to speech",
      "path": "ai-agents/modalities-text-to-speech.md",
      "category": "ai-agents",
      "subcategory": null,
      "tags": [
        "ai-agents",
        "maf",
        "multimodal",
        "tts",
        "audio"
      ],
      "stacks": [
        "dotnet"
      ],
      "aliases": [
        "text to speech",
        "TTS",
        "voice synthesis",
        "audio generation",
        "speech synthesis",
        "voice output",
        "speak text"
      ],
      "anchors": {
        "when": "#quando-usar-tts",
        "api": "#a-api",
        "example": "#exemplo-c-completo",
        "params": "#parâmetros"
      },
      "digest": "Use TTS when the agent must return audio output — voice assistants, accessibility narration, podcast generation. MAF uses AudioClient for text-to-speech with configurable voice, speed, and output format (mp3, opus, flac). Covers streaming audio response, voice selection, and integration with blob storage for async delivery. Anti-patterns: synthesizing long texts synchronously in a request handler.",
      "verifiedAgainst": "OpenAI 2.13.0 + Microsoft.Extensions.AI 10.9.0 (ai-pin 2026-09-08). **Verificação documental**, sem cláusula `provado por` — o ai-kit não gera áudio. `AudioClient.GenerateSpeechAsync`, a sobrecarga de streaming e a ponte `AsITextToSpeechClient` foram **medidas por reflexão sobre as assemblies do pin** em 2026-09-08.",
      "provedBy": []
    },
    {
      "id": "ai-agents-modalities-vision",
      "name": "modalities vision",
      "path": "ai-agents/modalities-vision.md",
      "category": "ai-agents",
      "subcategory": null,
      "tags": [
        "ai-agents",
        "maf",
        "multimodal",
        "vision"
      ],
      "stacks": [
        "dotnet"
      ],
      "aliases": [
        "vision",
        "image to text",
        "OCR",
        "image analysis",
        "multimodal input",
        "image agent",
        "PDF analysis",
        "visual understanding"
      ],
      "anchors": {
        "when": "#quando-usar-vision",
        "api": "#a-api",
        "example": "#exemplo-c-completo",
        "content-types": "#datacontent-vs-uricontent"
      },
      "digest": "Use vision when the agent needs to interpret images, screenshots, scanned documents or PDFs. MAF exposes multimodal ChatMessage with DataContent (inline bytes) or UriContent (remote URL). Covers OCR, diagram understanding, UI screenshot analysis, and document extraction. Anti-patterns: sending raw Base64 blobs without MIME type, ignoring resolution limits.",
      "verifiedAgainst": "Microsoft.Extensions.AI 10.9.0 + Microsoft.Agents.AI 1.20.0 (ai-pin 2026-09-08). **Verificação documental**, sem cláusula `provado por` — o ai-kit não exercita conteúdo multimodal. Os três tipos de conteúdo citados (`TextContent`, `UriContent`, `DataContent`) foram **conferidos por reflexão sobre `Microsoft.Extensions.AI.Abstractions` 10.9.0** em 2026-09-08 e continuam existindo com esses nomes.",
      "provedBy": []
    },
    {
      "id": "ai-agents-multi-agent-patterns",
      "name": "multi-agent patterns",
      "path": "ai-agents/multi-agent-patterns.md",
      "category": "ai-agents",
      "subcategory": null,
      "tags": [
        "ai-agents",
        "maf"
      ],
      "stacks": [
        "dotnet"
      ],
      "aliases": [
        "multi-agent",
        "agent as tool",
        "agent composition",
        "sequential agents",
        "handoff",
        "sub-agent",
        "agent orchestration",
        "nested agent",
        "agente auxiliar",
        "AsAIFunction"
      ],
      "anchors": {
        "agent-as-tool": "#agent-as-tool-padrão-recomendado",
        "sequential": "#sequential-cadeia-fixa",
        "decision-tree": "#decision-tree"
      },
      "digest": "Composing agents, cheapest first: Agent-as-Tool (agent.AsAIFunction(), nesting <= 2) before Sequential before Workflow. Handoff has GRADUATED — HandoffWorkflowBuilder carries no [Experimental] in 1.20.0 — but still comes after agent-as-tool; it needs a stable Id per agent plus WithAutonomousMode(turnLimit:) or a termination condition, and group chat stays at <= 3 agents. Field-measured anti-pattern: a helper agent nested INSIDE a tool — two function-calling loops overlap in the same turn (CallId reconciliation) and the main turn stays open while the helper runs. The correct contract is two visible calls, helper in sequence, orchestrated by code. Structured output between agents; no deep cascades.",
      "verifiedAgainst": "Microsoft.Agents.AI 1.20.0 (ai-pin 2026-09-08) + Microsoft.Agents.AI.Workflows 1.20.0 (**fora do pin**; versão apurada no nuget.org em 2026-09-08). **Verificação documental**, sem cláusula `provado por` — o ai-kit não compõe agentes. As assinaturas de `AgentWorkflowBuilder`/`HandoffWorkflowBuilder` e a **ausência** do atributo `[Experimental]` no Handoff foram **medidas por reflexão sobre a assembly 1.20.0** em 2026-09-08.",
      "provedBy": []
    },
    {
      "id": "ai-agents-observability-patterns",
      "name": "observability patterns",
      "path": "ai-agents/observability-patterns.md",
      "category": "ai-agents",
      "subcategory": null,
      "tags": [
        "ai-agents",
        "maf",
        "observability"
      ],
      "stacks": [
        "dotnet"
      ],
      "aliases": [
        "observability",
        "opentelemetry",
        "telemetry",
        "logging",
        "tracing",
        "log investigation",
        "troubleshooting",
        "aspire dashboard",
        "correlation id",
        "agent observability",
        "agent telemetry",
        "EnableSensitiveData",
        "span duplication",
        "invoke_agent"
      ],
      "anchors": {
        "setup": "#setup--opentelemetry",
        "enrichment": "#enrichment--o-que-preencher-em-spans-e-logs",
        "investigation": "#investigação-e-análise--quando-algo-dá-errado",
        "rastreabilidade": "#rastreabilidade-do-que-foi-ao-provedor",
        "convencao": "#estabilidade-da-convenção-genai--a-ressalva-que-muda-como-ler-a-tabela-acima"
      },
      "digest": "Canonical treatment of OpenTelemetry for MAF agents: setup, enrichment, investigation. Two DIFFERENT ActivitySources must be registered — Experimental.Microsoft.Agents.AI (span invoke_agent) and Experimental.Microsoft.Extensions.AI (spans chat and execute_tool, metrics gen_ai.client.operation.duration and gen_ai.client.token.usage). AgentOpenTelemetryConsts and agent.WithOpenTelemetry() do NOT exist in 1.20.0; the API is builder.UseOpenTelemetry(sourceName, configure) and the sensitive-data switch is EnableSensitiveData (same name on OpenTelemetryAgent and OpenTelemetryChatClient). Duplication rule, measured: instrumenting the two LEVELS is normal and does not duplicate; instrumenting the same level twice does. With a custom pipeline (UseProvidedChatClientAsIs) the MAF guard does not reach it and chat instrumentation becomes mandatory. Issue 3637 (no GenAI ActivityEvents) is a known open limitation. TRACEABILITY (added 2026-09-08): the question the instrumentation exists to answer is whether the turn can be RECONSTRUCTED — raw prompt only in dev/harness with opt-in, HASH in production, and the invariant that makes the hash worth anything is that the text sent is derived from named blocks WITHOUT cache (a cache between assembly and send lets telemetry and request diverge in silence). Two closing assertions: the offered tool list came from the real turn list, and a CANCELLED turn writes nothing. CONVENTION STABILITY: the GenAI semantic conventions LEFT the main OpenTelemetry repo — semconv v1.42.0 (2026-06-12) deprecated and moved all of gen_ai.*, v1.43.0 carries none, the content lives in open-telemetry/semantic-conventions-genai where NOTHING GenAI is Stable (all Development; only error.type/server.address/server.port are stable, and those are common attributes), while OpenTelemetryChatClient declares v1.37 — behind the split. Instrument anyway, but do not build an alert whose only identifier is a gen_ai.* name without a rename plan. Cost delegation is two-way: tariff, cost formula and budget ceiling live in ai-agents-cost-and-budget; span attributes stay here. The Gate 3 ruler for this property is evals/proof-rubrics/observabilidade-genai.md.",
      "verifiedAgainst": "Microsoft.Agents.AI 1.20.0 + Microsoft.Extensions.AI 10.9.0 (ai-pin 2026-09-08); `provado por templates/dotnet/ai-kit/src/Morph.AiKit/Observability/MorphOpenTelemetryExtensions.cs` — o guard de span duplicado, a regra de \"um nível, uma instrumentação\" e o default `EnableSensitiveData = false` são exatamente o que o kit compila, com os spans contados em `tests/Morph.AiKit.Tests/TelemetryTests.cs`. Nomes de símbolo, de span, de métrica e das duas `ActivitySource` foram **medidos por reflexão e por leitura dos literais das assemblies do pin** em 2026-09-08, e em 2026-09-09 **todos os treze nomes `gen_ai.*` desta página** foram remedidos, um a um, por **teste de exclusividade**: os quatro `SetTag` da seção *Enrichment* e os nove das seções de span, métrica e atributo de agente. **Os treze existem** — depois de uma correção: `gen_ai.response.finish_reason`, no **singular**, não existia, só aparecia como cabeça de `finish_reason`**s**, e sobreviveu a duas varreduras porque as duas usaram `includes` — e **`includes` de um prefixo não prova o literal autônomo**. Os treze estão tabelados, em **duas** tabelas com propósitos diferentes: a de *Enrichment* registra os **4** `SetTag` daquele bloco (mais os **cinco** nomes rejeitados: o `finish_reason` singular, `PromptTokens`, `CompletionTokens`, `prompt_tokens` e `completion_tokens`), e a de distribuição, na seção *Nomes de span, métricas e atributos — medidos*, registra os **13** por assembly. A conferência é reproduzível por `node scripts/api-probe/probe.mjs tags` sobre este arquivo. A **instabilidade da convenção** — o split do semconv **v1.42.0 (2026-06-12)**, que depreciou e moveu todo `gen_ai.*` para `open-telemetry/semantic-conventions-genai`, onde nada GenAI está `Stable`, contra a v1.37 que o `OpenTelemetryChatClient` declara implementar — é **verificação documental**, apurada em 2026-09-08.",
      "provedBy": [
        "templates/dotnet/ai-kit/src/Morph.AiKit/Observability/MorphOpenTelemetryExtensions.cs"
      ]
    },
    {
      "id": "ai-agents-production",
      "name": "production",
      "path": "ai-agents/production.md",
      "category": "ai-agents",
      "subcategory": null,
      "tags": [
        "ai-agents"
      ],
      "stacks": [
        "dotnet"
      ],
      "aliases": [
        "agent middleware",
        "production agent",
        "a2a protocol",
        "mcp integration",
        "agent observability",
        "agent telemetry",
        "anti-hallucination",
        "semantic caching",
        "multi-tenant isolation",
        "AgentIsolationKeyProvider",
        "concurrent tool invocation"
      ],
      "anchors": {
        "middleware": "#middleware-pipeline",
        "a2a": "#a2a-protocol-agent-to-agent",
        "mcp": "#mcp-integration-model-context-protocol",
        "observability": "#observability--opentelemetry"
      },
      "digest": "Production panorama for MAF agents. Middleware, MCP integration and observability are summarized here and treated canonically in ai-agents-middleware-patterns / ai-agents-mcp-tools / ai-agents-mcp-server / ai-agents-observability-patterns. Owns: A2A protocol via Microsoft.Agents.AI.A2A (PRERELEASE, not GA — corrected 2026-09-08); Redis semantic + HybridCache caching; multi-tenant isolation renamed in 1.18 to AgentIsolationKeyProvider / IsolationKeyScopedAgentSessionStore / UseClaimsBasedAgentIsolation, all in the prerelease Microsoft.Agents.AI.Hosting, with the isolation key coming from the credential and never from the payload; concurrent tool invocation is opt-in (AllowConcurrentInvocation defaults to false) and is unsafe with a scoped DbContext. Security and production checklists.",
      "verifiedAgainst": "Microsoft.Agents.AI 1.20.0 (ai-pin 2026-09-08) + Microsoft.Agents.AI.Hosting `1.20.0-preview.260831.1` e Microsoft.Agents.AI.A2A `1.20.0-preview.260831.1` (**fora do pin**; versões apuradas no nuget.org em 2026-09-08). **Verificação documental**, sem cláusula `provado por` — este standard é panorama, e cada tema tem dono canônico noutro standard. Os nomes de isolamento multi-tenant foram **medidos por reflexão sobre `Microsoft.Agents.AI.Hosting`** em 2026-09-08.",
      "provedBy": []
    },
    {
      "id": "ai-agents-prompt-sources",
      "name": "prompt sources",
      "path": "ai-agents/prompt-sources.md",
      "category": "ai-agents",
      "subcategory": null,
      "tags": [
        "ai-agents",
        "maf",
        "prompts"
      ],
      "stacks": [
        "dotnet"
      ],
      "aliases": [
        "prompts in db",
        "agent prompts",
        "prompt versioning",
        "prompt hot-reload",
        "editable instructions",
        "prompt management",
        "prompt sources",
        "prompt por tenant",
        "spec publicado",
        "reconciliação de prompt"
      ],
      "anchors": {
        "pattern": "#o-pattern",
        "schema": "#schema-da-tabela",
        "versioning": "#versionamento",
        "fontes": "#as-duas-fontes-legítimas",
        "tenant": "#fonte-2-yaml-por-tenant",
        "reconciliacao": "#reconciliação"
      },
      "digest": "WHERE an agent prompt lives, and how to prove which version actually ran. Two legitimate sources, and the one-sentence rule that picks between them: if the text varies per customer, the prompt is not a record — it is the OUTPUT OF A COMPOSITION, and what you version is the source. (1) Versioned table agent_prompts (agent_key + version + is_active, INSERT + flip, never UPDATE, unique partial index, IMemoryCache with a short TTL) for single-tenant. (2) Repo YAML per tenant + a product frame with named holes -> a deterministic composer -> a rendered spec published to a jsonb column, for product x tenant; editing prompts then costs a repo cycle, which is a product trade-off, not a free upgrade. RECONCILIATION is the section that was missing: versioning and publishing do NOT prove the active version ran (measured: v7/v8 ignored in 6 of 9 executions). The execution record must carry the EFFECTIVE version, plus the hash of the rendered blob on the composed path; the two field-proven techniques are byte-identity equivalence between source and published blob, and a single structural prompt-as-text test asserting the ABSENCE of a revoked instruction.",
      "verifiedAgainst": "Microsoft.Agents.AI 1.20.0 + Microsoft.Extensions.AI 10.9.0 (ai-pin 2026-09-08). **Verificação documental**, sem cláusula `provado por` — o ai-kit não traz loader de prompt, e os dois mecanismos descritos aqui são destilados de três produtos em produção. As **quatro** superfícies de API que este standard cita foram medidas por reflexão, e vivem em **três assemblies diferentes** — o carimbo diz onde remedir, então o nome importa: `IChatClient.AsAIAgent(instructions:, name:)` é `Microsoft.Extensions.AI.ChatClientExtensions`, em **`Microsoft.Agents.AI` 1.20.0** (medida em 2026-09-08); `AIAgent.RunAsync` (as oito sobrecargas) e `AIAgent.CreateSessionAsync` são de `Microsoft.Agents.AI.AIAgent`, na assembly **`Microsoft.Agents.AI.Abstractions` 1.20.0** — **remedidas** em 2026-09-09, porque a leitura de 2026-09-08 estava errada no *quê*, e a de 2026-09-09 estava errada no *onde* (as duas retificações estão no callout abaixo); e `builder.AddAIAgent` (§Keyed singleton) é `Microsoft.Agents.AI.Hosting.HostApplicationBuilderAgentExtensions` — cinco sobrecargas, na assembly **`Microsoft.Agents.AI.Hosting` 1.20.0.0**, que vem do pacote **`Microsoft.Agents.AI.Hosting` `1.20.0-preview.260831.1`**: ele é **prerelease** e está **FORA do `ai-pin.json`** (apurado em `scripts/api-probe/companions.json`, 2026-09-08) — quem adotar esta seção registra a dependência prerelease em `decisions.md`, como manda `ai-agents-production`. **Duas ressalvas de escopo**, porque um carimbo que não as diz promete mais do que mediu: `Microsoft.Agents.AI.Abstractions` e `Microsoft.Extensions.AI.Abstractions` são nomes de **assembly** (elas viajam dentro dos pacotes `Microsoft.Agents.AI` e `Microsoft.Extensions.AI`), não ids de pacote; e `IMemoryCache`/`DbContext`, usados no padrão de cache e na §Keyed singleton, são superfícies de ASP.NET Core e EF Core — **fora do pin, não medidas aqui, verificação documental**.",
      "provedBy": []
    },
    {
      "id": "ai-agents-providers-model-registry",
      "name": "model registry",
      "path": "ai-agents/providers/model-registry.md",
      "category": "ai-agents",
      "subcategory": "providers",
      "tags": [
        "ai-agents",
        "maf",
        "providers",
        "model-config"
      ],
      "stacks": [
        "dotnet"
      ],
      "aliases": [
        "model registry",
        "provider agnostic",
        "model alias",
        "IChatClient factory",
        "multi-model",
        "model selection",
        "model config",
        "switch provider",
        "openai google anthropic ollama",
        "direct model",
        "api per alias",
        "responses vs chat",
        "reasoning effort",
        "temperature vs reasoning",
        "model pricing"
      ],
      "anchors": {
        "schema": "#schema-canonical",
        "providers": "#providers-first-class",
        "implementation": "#implementation-pattern",
        "checklist": "#checklist-verifiable-by-morph-eval"
      },
      "digest": "Single JSON file (model-registry.json v2) maps aliases to { provider, model, api, options, constraints }. Agents reference aliases, never model strings. EVERY alias declares `api`: `responses` (default when omitted) or `chat` (explicit opt-in); Ollama/OpenAI-compatible providers ignore the field but never travel the Responses path. `temperature` and `reasoningEffort` are mutually exclusive per alias, with the two origins labelled separately: the documented one (Chat Completions rejects tool calling with reasoning_effort != none from GPT-5.4) and the field-measured one (temperature on reasoning models — no primary source, so it is a per-alias constraint with mandatory provenance). .NET path: ChatOptions.Reasoning = new ReasoningOptions { Effort = ReasoningEffort.* } is CONFIRMED in M.E.AI 10.9.0; RawRepresentationFactory is the escape hatch for values the enum lacks. OPENAI001 still applies to the Responses types. No floating PackageReference ranges.",
      "verifiedAgainst": "Microsoft.Agents.AI 1.20.0 + Microsoft.Extensions.AI 10.9.0 + OpenAI 2.13.0 (ai-pin 2026-09-08); provado por `templates/dotnet/ai-kit/src/Morph.AiKit/Providers/ModelRegistry.cs` e `.../Providers/ModelAlias.cs` e `.../Agents/AgentApi.cs`. Nomes de tipo do `Microsoft.Extensions.AI` e do `OpenAI` medidos por reflexão sobre as assemblies do pin em 2026-09-08.",
      "provedBy": [
        "templates/dotnet/ai-kit/src/Morph.AiKit/Providers/ModelRegistry.cs",
        ".../Providers/ModelAlias.cs",
        ".../Agents/AgentApi.cs"
      ]
    },
    {
      "id": "ai-agents-rag-custom-pgvector",
      "name": "rag custom pgvector",
      "path": "ai-agents/rag-custom-pgvector.md",
      "category": "ai-agents",
      "subcategory": null,
      "tags": [
        "ai-agents",
        "maf",
        "rag",
        "pgvector"
      ],
      "stacks": [
        "dotnet"
      ],
      "aliases": [
        "rag",
        "vector search",
        "pgvector",
        "embeddings",
        "custom rag",
        "retrieval",
        "semantic search",
        "knowledge base",
        "RAG tool",
        "delta ingestion",
        "incremental ingestion",
        "lookup key",
        "content hash"
      ],
      "anchors": {
        "architecture": "#arquitetura",
        "tool": "#vectorsearchtool-tool-maf",
        "multi-tenant": "#multi-tenant"
      },
      "digest": "Custom RAG on pgvector/Neon you control: chunking, embedding model via the Model Registry `embeddings` alias, HNSW with vector_cosine_ops, tenant_id filtered in every query and never exposed to the LLM. Owns DELTA INGESTION by lookup-key: a pure Plan(incoming, lookupKey, contentHash, existingHashes) returning Added/Updated/Removed/Unchanged, where Unchanged is never re-embedded, Updated is an upsert on a deterministic id, Removed is applied AFTER the writes (no empty window), and a duplicate lookup-key fails the run naming both positions. Microsoft.Extensions.DataIngestion (still preview) solves hygiene, not embedding cost — its IncrementalIngestion deletes all pre-existing chunks of a document before rewriting. Open item: similarity threshold and k per scope.",
      "verifiedAgainst": "Microsoft.Agents.AI 1.20.0 + Microsoft.Extensions.AI 10.9.0 (ai-pin 2026-09-08); `provado por templates/dotnet/ai-kit/src/Morph.AiKit/Rag/DeltaIngest.cs` **restrito à seção \"Ingestão delta por lookup-key\"** — é a única parte deste standard que o ai-kit compila. Todo o resto (pgvector, SQL, HNSW, embeddings, multi-tenant) é **verificação documental**; a versão e o status de `Microsoft.Extensions.DataIngestion` foram apurados no nuget.org em 2026-09-08.",
      "provedBy": [
        "templates/dotnet/ai-kit/src/Morph.AiKit/Rag/DeltaIngest.cs"
      ]
    },
    {
      "id": "ai-agents-service-tier-flex",
      "name": "service tier flex",
      "path": "ai-agents/service-tier-flex.md",
      "category": "ai-agents",
      "subcategory": null,
      "tags": [
        "ai-agents",
        "maf",
        "cost-optimization",
        "service-tier"
      ],
      "stacks": [
        "dotnet"
      ],
      "aliases": [
        "service tier",
        "flex tier",
        "cost optimization",
        "async tier",
        "priority tier",
        "tier flex",
        "background inference",
        "ChatServiceTier"
      ],
      "anchors": {
        "when": "#quando-usar-tier-flex",
        "api": "#a-api",
        "example": "#exemplo-c",
        "decision": "#decision"
      },
      "digest": "Service tier trades latency for cost on async work (workers, Hangfire, nightly reports) — if the result goes to a database or a queue, use flex. OpenAI-specific: Microsoft.Extensions.AI.ChatOptions has no service-tier property (measured), so it goes through the concrete client or AdditionalProperties + RawRepresentationFactory. Measured in OpenAI 2.13.0: the property is ChatCompletionOptions.ServiceTier of type ChatServiceTier? (and CreateResponseOptions.ServiceTier of ResponseServiceTier?), with values Auto/Default/Flex/Scale — it is NOT a string, and there is no `priority` value. Never use flex on an interactive completion.",
      "verifiedAgainst": "OpenAI 2.13.0 + Microsoft.Extensions.AI 10.9.0 (ai-pin 2026-09-08). **Verificação documental** da política de tiers (platform.openai.com/docs/guides/service-tiers, lido 2026-09-08), sem cláusula `provado por`: o ai-kit **referencia** o pacote `OpenAI` no `.csproj` — contrato de versão, régua = restore (`NU1102`) — mas **não o exercita**; não existe um único `using OpenAI` em `src/` (medido 2026-09-08), logo um rename de API não reprova a PR. Ver a tabela \"Dois níveis de garantia\" no README do kit. **O nome e o tipo da propriedade C# foram medidos por reflexão sobre a assembly `OpenAI` 2.13.0 em 2026-09-08**, e o marcador `// VERIFY` desta página foi RESOLVIDO por essa medição.",
      "provedBy": []
    },
    {
      "id": "ai-agents-setup",
      "name": "setup",
      "path": "ai-agents/setup.md",
      "category": "ai-agents",
      "subcategory": null,
      "tags": [
        "ai-agents"
      ],
      "stacks": [
        "dotnet"
      ],
      "aliases": [
        "MAF setup",
        "Microsoft Agents AI",
        "ChatClient",
        "AsAIAgent",
        "AIAgent dotnet",
        "agent framework setup",
        "MAF packages",
        "MAF nuget",
        "responses api",
        "GetResponsesClient",
        "store false",
        "chat completions"
      ],
      "anchors": {
        "packages": "#1-packages-nuget",
        "quickstart": "#2-quick-start-canonical-minimal-agent",
        "providers": "#3-setup-by-provider",
        "telemetry": "#5-opentelemetry"
      },
      "digest": "Microsoft.Agents.AI 1.20.0 + Microsoft.Extensions.AI 10.9.0 + OpenAI 2.13.0, exact versions, no floating ranges. For OpenAI the DEFAULT path is the Responses API: new OpenAIClient(key).GetResponsesClient().AsAIAgent(model:, instructions:) — Chat Completions stays legitimate but is an explicit opt-in (`\"api\": \"chat\"` on the alias). `store: false` is the house rule (AsIChatClientWithStoredOutputDisabled) because the project owns its own conversation history. AsIChatClient comes from Microsoft.Extensions.AI.OpenAI; AsAIAgent from Microsoft.Agents.AI.OpenAI. Telemetry needs BOTH ActivitySources (Experimental.Microsoft.Agents.AI and Experimental.Microsoft.Extensions.AI) — AgentOpenTelemetryConsts does not exist. Do NOT use Semantic Kernel orchestration.",
      "verifiedAgainst": "Microsoft.Agents.AI 1.20.0 + Microsoft.Extensions.AI 10.9.0 + OpenAI 2.13.0 (ai-pin 2026-09-08); provado por `templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentFactory.cs` (pipeline e escolha de protocolo). A superfície do SDK `OpenAI` e das pontes `Microsoft.Agents.AI.OpenAI` / `Microsoft.Extensions.AI.OpenAI` foi **medida por reflexão sobre as assemblies do pin** em 2026-09-08 — o ai-kit **não exercita** o cliente concreto da OpenAI — referencia o pacote no `.csproj`, mas não há `using OpenAI` em `src/` (medido 2026-09-08); ver a tabela \"Dois níveis de garantia\" no README do kit.",
      "provedBy": [
        "templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentFactory.cs"
      ]
    },
    {
      "id": "ai-agents-structured-output",
      "name": "structured output",
      "path": "ai-agents/structured-output.md",
      "category": "ai-agents",
      "subcategory": null,
      "tags": [
        "ai-agents",
        "maf",
        "s-tier"
      ],
      "stacks": [
        "dotnet"
      ],
      "aliases": [
        "structured output",
        "typed response",
        "response format",
        "schema validated output",
        "record output",
        "JSON schema agent",
        "RunAsync generic",
        "typed agent output",
        "ToAgentResponseAsync",
        "response format limits"
      ],
      "anchors": {
        "api": "#a-api-maf-10-ga",
        "type": "#definindo-o-tipo-de-saída",
        "validation": "#validação-pós-call"
      },
      "digest": "Every agent whose output feeds another system returns a typed record, never free text. Form 1 is RunAsync<T>; Form 2 is ResponseFormat = ChatResponseFormat.ForJsonSchema<T>() in ChatOptions. Type is a sealed record with [Description] on the type and [JsonPropertyName] on every property. Documented limits: ResponseFormat does not accept a primitive or an array at the root (use a wrapper), and in streaming you must build the whole response with ToAgentResponseAsync() before deserialising. Schema validates shape, not semantics — always validate business rules after the call. Do not use temperature as the example of a normal ChatOption.",
      "verifiedAgainst": "Microsoft.Agents.AI 1.20.0 + Microsoft.Extensions.AI 10.9.0 (ai-pin 2026-09-08); `provado por templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentFactory.cs` **apenas** para o mapeamento de `AgentSpec.OutputSchema` → `ChatOptions.ResponseFormat = ChatResponseFormat.ForJsonSchema(...)`, que é o que o kit compila. `RunAsync<T>`, `Deserialize<T>` e os limites do `ResponseFormat` são **verificação documental**, com os nomes de tipo medidos por reflexão sobre `Microsoft.Extensions.AI.Abstractions` 10.9.0 em 2026-09-08.",
      "provedBy": [
        "templates/dotnet/ai-kit/src/Morph.AiKit/Agents/AgentFactory.cs"
      ]
    },
    {
      "id": "ai-agents-sweet-spot",
      "name": "sweet spot",
      "path": "ai-agents/sweet-spot.md",
      "category": "ai-agents",
      "subcategory": null,
      "tags": [
        "ai-agents",
        "maf",
        "decision-tree"
      ],
      "stacks": [
        "dotnet"
      ],
      "aliases": [
        "sweet spot",
        "when to use agent",
        "agent vs workflow",
        "when not to use workflow",
        "MAF decision",
        "simple agent default",
        "4 feature rule",
        "single agent default",
        "tool budget",
        "how many tools"
      ],
      "anchors": {
        "rule": "#the-4-feature-rule",
        "decision-tree": "#decision-tree-claude-reads-this-in-plan-phase",
        "anti-patterns": "#anti-patterns-what-claude-should-not-do"
      },
      "digest": "The 4-feature rule (Chat + Structured Output + Tools + RAG Custom) covers 95% of agents. Single-agent is the default; reach for multi-agent or workflow only when fan-in/out or genuine handoff is needed. Fifth signal: HOW MANY tools the agent carries — every registered tool re-sends its name, description and JSON Schema on every turn, so the practical ceiling is ~12 tools; above it prune, then use a per-phase allow-list, then agent-as-tool. Handoff has graduated out of experimental in 1.20.0, which changes the reason but not the order of preference. Read in plan phase before deciding architecture.",
      "verifiedAgainst": "Microsoft.Agents.AI 1.20.0 + Microsoft.Agents.AI.Workflows 1.20.0 (ai-pin 2026-09-08 para o primeiro; o segundo está **fora do pin**, versão apurada no nuget.org em 2026-09-08). **Verificação documental**, sem cláusula `provado por` — este standard é decisão de arquitetura, não API. A escada de complexidade foi reconferida e **continua correta**; o que entrou de novo é o custo de definição de tool por turno e o status do Handoff.",
      "provedBy": []
    },
    {
      "id": "ai-agents-testing-ai",
      "name": "testing ai",
      "path": "ai-agents/testing-ai.md",
      "category": "ai-agents",
      "subcategory": null,
      "tags": [
        "ai-agents",
        "maf",
        "testing",
        "evals"
      ],
      "stacks": [
        "dotnet"
      ],
      "aliases": [
        "testar agente",
        "fake IChatClient",
        "ScriptedChatClient",
        "harness de turno",
        "eval de agente",
        "teste sem rede",
        "golden de prompt"
      ],
      "anchors": {
        "fronteira": "#fronteira-com-o-testing-genérico",
        "fake": "#fake-de-ichatclient",
        "harness": "#harness-de-turno",
        "eval": "#eval-e-unit",
        "captura": "#captura-do-que-foi-ao-provedor",
        "golden": "#golden-com-parcimônia",
        "checklist": "#checklist-verifiable-by-morph-eval"
      },
      "digest": "Only what is agent-specific — backend-dotnet-testing keeps xUnit, FakeTimeProvider and isolation. The single point where code touches a provider is an IChatClient, so everything follows from replacing it: a SCRIPTED and DUMB fake (a queue of hand-written responses, capture of messages/options/call count) that THROWS when the queue runs out — a silent empty response makes a test pass by accident. Two independent products converged on this shape; the kit compiles the third. Run the REAL function-invocation loop on top of the fake: tool choice, arguments, guard and ceiling are only observable through the loop. The seam keeps classes sealed (a one-operation interface, or an internal constructor taking alias -> IChatClient). ZERO real provider calls inside dotnet test, verified by a sweep of tests/ for API keys, environment reads and HTTP clients — and the standard records the price of leaving the real harness outside the suite: 11 scenarios that were never run end to end. Eval means DETERMINISTIC eval over the full real path with the model scripted; a hard gate only over deterministic properties. Raw prompt only in dev/harness with opt-in, hash in production. Golden proves FORM, not content (counter-example: 13.6 KB frozen to prove two lines of concatenation, deliberately deleted). A versioned prompt gets AT MOST ONE structural test (keys the consumer reads, absence of a revoked instruction); what the model answers is proven at runtime by evals-with-cache, never by Contains over the prompt source.",
      "verifiedAgainst": "Microsoft.Extensions.AI 10.9.0 + Microsoft.Agents.AI 1.20.0 (ai-pin 2026-09-08); provado por `templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeChatClient.cs` e `.../src/Morph.AiKit/Providers/IProviderClientFactory.cs` — o fake roteirizado e a costura que dispensa rede são o que o kit compila e exercita. Harness de turno completo e evals com cache de resposta são **verificação documental** — o ai-kit não os traz.",
      "provedBy": [
        "templates/dotnet/ai-kit/tests/Morph.AiKit.Tests/Fakes/FakeChatClient.cs",
        ".../src/Morph.AiKit/Providers/IProviderClientFactory.cs"
      ]
    },
    {
      "id": "ai-agents-vector-data-extensions",
      "name": "vector data extensions",
      "path": "ai-agents/vector-data-extensions.md",
      "category": "ai-agents",
      "subcategory": null,
      "tags": [
        "ai-agents",
        "maf",
        "rag",
        "vector-data"
      ],
      "stacks": [
        "dotnet"
      ],
      "aliases": [
        "microsoft extensions vectordata",
        "vectorstorecollection",
        "vector store abstraction",
        "data ingestion",
        "ingestion pipeline",
        "semantic kernel connectors exception",
        "pgvector connector",
        "vectorstorewriter",
        "CommunityToolkit VectorData",
        "connector rename"
      ],
      "anchors": {
        "naming-trap": "#a-pegadinha-de-nomenclatura-crítico--leia-antes-de-tudo",
        "maturity": "#maturidade-atual-verificado-2026-07-06",
        "decision-matrix": "#decision-matrix--custom-pgvector-vs-vectordata",
        "data-ingestion": "#microsoftextensionsdataingestion-preview--pipeline-pattern-para-adoção-futura"
      },
      "digest": "Microsoft.Extensions.VectorData.Abstractions (10.9.0, GA) is the provider-agnostic vector-store abstraction. The concrete connectors MOVED: the Microsoft.SemanticKernel.Connectors.* names are a dead end (PgVector stopped at 1.74.0-preview, 2026-03-20, never stable) and the successor is CommunityToolkit.VectorData.* (PgVector 1.0.1, stable, 2026-08-31). The old naming trap — SK-named packages carrying zero SK orchestration — still matters for legacy projects. Microsoft.Extensions.DataIngestion remains preview. Decision matrix: keep ai-agents-rag-custom-pgvector where it already works (the \"it is preview\" argument has expired; \"do not rewrite what works\" has not); reach for VectorData on new projects or genuine multi-backend portability.",
      "verifiedAgainst": "Microsoft.Extensions.VectorData.Abstractions **10.9.0** + CommunityToolkit.VectorData.PgVector **1.0.1** + Microsoft.Extensions.DataIngestion **10.9.0-preview.1.26411.16** — nenhum deles está no `framework/ai-pin.json` (que cobre os 5 pacotes de agente), portanto **todas as versões acima foram apuradas na API do nuget.org em 2026-09-08** e são citadas com essa data, não como pin. **Verificação documental**, sem cláusula `provado por`: o ai-kit **não referencia nem exercita** VectorData — o pacote não está no `Morph.AiKit.csproj`, cujos cinco `PackageReference` são exatamente os do pin (medido 2026-09-08). Aqui nem a régua fraca do restore existe.",
      "provedBy": []
    },
    {
      "id": "ai-agents-workflows",
      "name": "workflows",
      "path": "ai-agents/workflows.md",
      "category": "ai-agents",
      "subcategory": null,
      "tags": [
        "ai-agents"
      ],
      "stacks": [
        "dotnet"
      ],
      "aliases": [
        "workflow",
        "agent workflow",
        "fan-in",
        "fan-out",
        "when not to use workflow",
        "orchestration",
        "AgentWorkflowBuilder",
        "concurrent workflow",
        "Microsoft.Agents.AI.Workflows",
        "declarative workflow"
      ],
      "anchors": {
        "when-not": "#quando-não-usar-um-workflow",
        "when-justified": "#quando-um-workflow-é-justificado",
        "canonical-example": "#exemplo-canônico--fan-in-concurrent"
      },
      "digest": "Workflows are advanced and rare — 95% of agents need only a single agent (see ai-agents-sweet-spot). Reach for a Workflow ONLY for genuine fan-in/out over many inputs or long-running human-in-the-loop; a foreach + LINQ aggregate usually beats one. PACKAGE CORRECTION: AgentWorkflowBuilder, WorkflowBuilder, InProcessExecution and CheckpointManager are NOT in Microsoft.Agents.AI — they live in the SEPARATE package Microsoft.Agents.AI.Workflows (1.20.0, stable). Declarative workflows (Microsoft.Agents.AI.Workflows.Declarative 1.20.0) are stable; declarative AGENTS (Microsoft.Agents.AI.Declarative 1.20.0-rc1) still need --prerelease. Since 1.17 a workflow fails when a participating agent returns an error.",
      "verifiedAgainst": "Microsoft.Agents.AI 1.20.0 (ai-pin 2026-09-08) + Microsoft.Agents.AI.Workflows 1.20.0, Microsoft.Agents.AI.Workflows.Declarative 1.20.0 e Microsoft.Agents.AI.Declarative `1.20.0-rc1` (**os três fora do pin**; versões apuradas no nuget.org em 2026-09-08). **Verificação documental**, sem cláusula `provado por` — o ai-kit não monta workflows. **A localização dos tipos de workflow foi medida por reflexão em 2026-09-08 e CORRIGE este standard**: eles não estão no pacote base.",
      "provedBy": []
    },
    {
      "id": "architecture-vertical-slice-vertical-slice",
      "name": "vertical slice",
      "path": "architecture/vertical-slice/vertical-slice.md",
      "category": "architecture",
      "subcategory": "vertical-slice",
      "tags": [
        "architecture",
        "vertical-slice"
      ],
      "stacks": [
        "dotnet"
      ],
      "aliases": [
        "vertical slice",
        "VSA",
        "slice architecture",
        "feature slice",
        "CQRS slice"
      ],
      "anchors": {
        "structure": "#estrutura-de-pastas",
        "rules": "#padrões-obrigatórios",
        "cqrs": "#anti-patterns-nunca-fazer-em-vsa"
      },
      "digest": "Organize by feature, not layer. Each slice contains Command/Query, Handler, Validator, Response. No cross-slice dependencies — shared code goes to a dedicated Shared project. Use MediatR for in-process CQRS dispatch.",
      "verifiedAgainst": ".NET 10 + EF Core 10 + FluentValidation 12 + Scrutor 7.",
      "provedBy": []
    },
    {
      "id": "backend-api-validation",
      "name": "validation",
      "path": "backend/api/validation.md",
      "category": "backend",
      "subcategory": "api",
      "tags": [
        "backend",
        "api"
      ],
      "stacks": [
        "dotnet"
      ],
      "aliases": [
        "FluentValidation",
        "input validation",
        "request validation",
        "API validation",
        "model validation"
      ],
      "anchors": {
        "fluent": "#validation-patterns",
        "rules": "#validation-patterns"
      },
      "digest": "Use FluentValidation for all request validation. Register validators as scoped services. Return 422 UnprocessableEntity with field-level error messages. Validate at API boundary — not inside domain logic. Never trust client-side validation alone.",
      "verifiedAgainst": ".NET 10 (pure state-validation pattern — no external package).",
      "provedBy": []
    },
    {
      "id": "backend-database-postgresql-database",
      "name": "database",
      "path": "backend/database/postgresql/database.md",
      "category": "backend",
      "subcategory": "database",
      "tags": [
        "backend",
        "database",
        "postgresql"
      ],
      "stacks": [
        "dotnet"
      ],
      "aliases": [
        "PostgreSQL",
        "postgres",
        "pg",
        "database design",
        "SQL schema"
      ],
      "anchors": {
        "schema": "#schema-design",
        "indexes": "#indexes"
      },
      "digest": "Use timestamptz for all timestamps (not timestamp). UUID primary keys for public-facing IDs. Add indexes on FK columns and frequently filtered columns. Use GIN indexes for JSON/array columns. JSONB over JSON. Row-level security for multi-tenant. Never store binary in columns — use storage URLs.",
      "verifiedAgainst": "PostgreSQL 16/17 + EF Core 10 + @neondatabase/serverless.",
      "provedBy": []
    },
    {
      "id": "backend-database-vector-search-rag",
      "name": "vector search rag",
      "path": "backend/database/vector-search-rag.md",
      "category": "backend",
      "subcategory": "database",
      "tags": [
        "backend",
        "database"
      ],
      "stacks": [
        "dotnet",
        "nextjs",
        "neon"
      ],
      "aliases": [
        "vector search",
        "RAG",
        "embeddings",
        "pgvector",
        "semantic search"
      ],
      "anchors": {
        "chunking": "#📥-indexação-de-documentos",
        "retrieval": "#🔍-vector-search-com-ef-core-10"
      },
      "digest": "Chunk documents with 512 token size and 50 token overlap for RAG. Embed with text-embedding-3-small. Store embedding + metadata (source, chunk index). Retrieve top-k=5 by cosine similarity. Re-rank before injecting into LLM context. Use pgvector HNSW index for performance at scale."
    },
    {
      "id": "backend-dotnet-fluent-validation-vsa",
      "name": "fluent validation vsa",
      "path": "backend/dotnet/fluent-validation-vsa.md",
      "category": "backend",
      "subcategory": "dotnet",
      "tags": [
        "backend",
        "dotnet",
        "vsa"
      ],
      "stacks": [
        "dotnet"
      ],
      "aliases": [
        "FluentValidation VSA",
        "validation decorator",
        "validation pipeline",
        "AbstractValidator",
        "ValidationDecorator"
      ],
      "anchors": {
        "validator": "#validator-structure",
        "decorator": "#validationdecorator-pattern",
        "conversion": "#fluentvalidation-to-validationerror-conversion"
      },
      "digest": "Use AbstractValidator<T> per request DTO in feature folders. ValidationDecorator intercepts all handlers via Scrutor, runs validators in parallel, and short-circuits to Result.Failure(ValidationError) on failure. Register AddValidatorsFromAssembly() before AddHandlersFromAssembly(). Never validate inside handlers.",
      "verifiedAgainst": ".NET 10 + FluentValidation 11 (`AbstractValidator`/`RuleFor` API).",
      "provedBy": []
    },
    {
      "id": "backend-dotnet-result-pattern",
      "name": "result pattern",
      "path": "backend/dotnet/result-pattern.md",
      "category": "backend",
      "subcategory": "dotnet",
      "tags": [
        "backend",
        "dotnet",
        "vsa"
      ],
      "stacks": [
        "dotnet"
      ],
      "aliases": [
        "Result<T>",
        "railway oriented",
        "error handling",
        "Match pattern",
        "Result monad"
      ],
      "anchors": {
        "anatomy": "#result-anatomy",
        "errors": "#error-types-7--custom",
        "match": "#match-pattern"
      },
      "digest": "Use Result<T> for all handler returns — never throw for expected failures. 7 error types (Failure, Unexpected, Validation, Conflict, NotFound, Unauthorized, Forbidden) map to HTTP status codes. Use Match() in endpoints to convert Result to IResult. Implicit conversions allow returning values or errors directly from handlers.",
      "verifiedAgainst": ".NET 10 (pure C# pattern — no external package).",
      "provedBy": []
    },
    {
      "id": "backend-dotnet-scalar-ui",
      "name": "scalar ui",
      "path": "backend/dotnet/scalar-ui.md",
      "category": "backend",
      "subcategory": "dotnet",
      "tags": [
        "backend",
        "dotnet",
        "vsa"
      ],
      "stacks": [
        "dotnet"
      ],
      "aliases": [
        "Scalar",
        "API documentation",
        "OpenAPI UI",
        "Swagger replacement",
        "Scalar.AspNetCore"
      ],
      "anchors": {
        "setup": "#programcs-configuration",
        "tags": "#apitags-organization",
        "metadata": "#endpoint-metadata"
      },
      "digest": "Use Scalar.AspNetCore 2.12.40 as API documentation UI — replaces Swagger. MapScalarApiReference() with DeepSpace theme. Group endpoints with .WithTags() and ApiTags constants. Document responses with .Produces<T>(). Guard MapOpenApi() with IsDevelopment(). No Swashbuckle needed.",
      "verifiedAgainst": ".NET 10 + Scalar.AspNetCore 2.x + Microsoft.AspNetCore.OpenApi 10.",
      "provedBy": []
    },
    {
      "id": "backend-dotnet-scrutor",
      "name": "scrutor",
      "path": "backend/dotnet/scrutor.md",
      "category": "backend",
      "subcategory": "dotnet",
      "tags": [
        "backend",
        "dotnet",
        "vsa"
      ],
      "stacks": [
        "dotnet"
      ],
      "aliases": [
        "Scrutor",
        "auto-discovery",
        "assembly scanning",
        "decorator registration",
        "handler registration"
      ],
      "anchors": {
        "handlers": "#handler-auto-discovery",
        "decorators": "#decorator-order",
        "endpoints": "#endpoint-auto-discovery"
      },
      "digest": "Use Scrutor for convention-based DI registration. AddHandlersFromAssembly() scans for IHandler<,> implementations. Decorate<> applies pipeline decorators (validation, logging) — last registered wraps outermost. Register validators before handlers. RegisterApiEndpointsFromAssembly() auto-discovers IApiEndpoint implementations.",
      "verifiedAgainst": ".NET 10 + Scrutor 7 (`Scan`/`Decorate` API).",
      "provedBy": []
    },
    {
      "id": "backend-dotnet-testing",
      "name": "testing",
      "path": "backend/dotnet/testing.md",
      "category": "backend",
      "subcategory": "dotnet",
      "tags": [
        "backend",
        "dotnet"
      ],
      "stacks": [
        "dotnet"
      ],
      "aliases": [
        "xunit",
        "TimeProvider",
        "FakeTimeProvider",
        "fake clock",
        "integration test",
        "ProblemDetails",
        "error body assertion"
      ],
      "anchors": {
        "clock": "#rule-1--inject-timeprovider-fake-it-with-faketimeprovider-in-tests",
        "errorbody": "#rule-2--error-path-tests-assert-the-response-body-not-just-the-status-code"
      },
      "digest": "Inject TimeProvider (never DateTime.UtcNow / DateOnly.FromDateTime(DateTime.UtcNow) directly in a handler or validator) and fake it with FakeTimeProvider (Microsoft.Extensions.TimeProvider.Testing) in integration tests — deterministic, no midnight-UTC flake, boundary dates (today vs. yesterday) become facts about the fake clock instead of when the suite happens to run. xUnit + WebApplicationFactory error-path tests (400/404/409) must assert the ProblemDetails response BODY (title/detail/violated field or error code), never just the HTTP status code — a status-only assertion lets the wrong validation rule or the wrong error branch pass silently as long as the status matches.",
      "verifiedAgainst": ".NET 10 + xUnit 2 + `Microsoft.Extensions.TimeProvider.Testing`.",
      "provedBy": []
    },
    {
      "id": "backend-dotnet-vsa-handler-patterns",
      "name": "vsa handler patterns",
      "path": "backend/dotnet/vsa-handler-patterns.md",
      "category": "backend",
      "subcategory": "dotnet",
      "tags": [
        "backend",
        "dotnet",
        "vsa"
      ],
      "stacks": [
        "dotnet"
      ],
      "aliases": [
        "VSA handler",
        "vertical slice handler",
        "feature slice",
        "IHandler pattern",
        "handler endpoint"
      ],
      "anchors": {
        "handler": "#ihandlertrequest-tresponse",
        "folder": "#feature-folder-structure-5-files-per-slice",
        "endpoint": "#complete-endpoint-example",
        "registration": "#programcs-di-registration-order"
      },
      "digest": "Each VSA feature is 5 files: Request, Response, Handler, Validator, Endpoint. Use IHandler<TReq,TRes> (not MediatR). Sealed records for DTOs, primary constructors for DI, Guid.CreateVersion7() for IDs. Handler injects ApplicationDbContext directly — no service layer, no generic repository (DbSet<T> is the repository, DbContext is the unit of work). CancellationToken in every async call.",
      "verifiedAgainst": ".NET 10 (minimal APIs, primary constructors, `Guid.CreateVersion7()`).",
      "provedBy": []
    },
    {
      "id": "backend-integrations-asaas-asaas-api",
      "name": "asaas api",
      "path": "backend/integrations/asaas/asaas-api.md",
      "category": "backend",
      "subcategory": "integrations",
      "tags": [
        "backend",
        "integrations",
        "asaas"
      ],
      "stacks": [
        "dotnet"
      ],
      "aliases": [
        "asaas",
        "payment",
        "PIX",
        "boleto",
        "Brazilian payment"
      ],
      "anchors": {
        "payments": "#create-pix-payment",
        "webhooks": "#webhook-handler"
      },
      "digest": "Use AsaasClient SDK — never call REST API directly. Register as scoped service. Implement webhook signature verification. Map SDK enums to domain enums at integration boundary. Use idempotency keys for retry safety. Test with Asaas sandbox environment.",
      "verifiedAgainst": "Asaas REST API v3 (no official Node SDK — direct REST).",
      "provedBy": []
    },
    {
      "id": "backend-integrations-hangfire-hangfire-jobs",
      "name": "hangfire jobs",
      "path": "backend/integrations/hangfire/hangfire-jobs.md",
      "category": "backend",
      "subcategory": "integrations",
      "tags": [
        "backend",
        "integrations",
        "hangfire"
      ],
      "stacks": [
        "dotnet"
      ],
      "aliases": [
        "hangfire",
        "background jobs",
        "scheduled jobs",
        "recurring",
        "cron",
        "fire and forget"
      ],
      "anchors": {
        "jobs": "#job-types",
        "retry": "#retry--error-handling"
      },
      "digest": "Use IDbContextFactory for scoped DB operations in jobs — never inject DbContext directly into job classes. BackgroundJob.Enqueue for fire-and-forget, RecurringJob.AddOrUpdate for cron. Add RetryCountAttribute for retries. Secure Hangfire Dashboard with auth in production. Never put long-running work in a single job — decompose.",
      "verifiedAgainst": ".NET 10 + Hangfire 1.8 (Core/SqlServer/AspNetCore).",
      "provedBy": []
    },
    {
      "id": "backend-integrations-neon-auth-neon-auth",
      "name": "neon auth",
      "path": "backend/integrations/neon-auth/neon-auth.md",
      "category": "backend",
      "subcategory": "integrations",
      "tags": [
        "backend",
        "integrations",
        "neon-auth",
        "better-auth"
      ],
      "stacks": [
        "neon"
      ],
      "aliases": [
        "neon auth",
        "better auth",
        "authentication",
        "OAuth",
        "JWT",
        "session management",
        "trusted origins",
        "EdDSA",
        "Ed25519",
        "BouncyCastle",
        "signature validator",
        "jwks",
        "middleware runtime",
        "api prefix",
        "data api",
        "postgrest",
        "bypass backend",
        "direct database access"
      ],
      "anchors": {
        "setup": "#installation--setup",
        "data-access-boundary": "#data-access-boundary--backend-first-by-default",
        "onboarding": "#onboarding-checklist--do-this-before-testing-login-mandatory",
        "middleware": "#route-protection-middleware",
        "api-prefix": "#api-prefix-convention--server-direct-vs-client-proxy",
        "dotnet": "#net-backend-integration",
        "rls": "#row-level-security-integration",
        "postmortem": "#known-failure-modes-postmortem"
      },
      "digest": "Use @neondatabase/auth SDK for Next.js. Auth data lives in neon_auth schema — no external provider, no webhooks. Server: createNeonAuth() with NEON_AUTH_BASE_URL + NEON_AUTH_COOKIE_SECRET. Client: createAuthClient(). Pages: AuthView component. Protection: auth.middleware() with runtime='nodejs' (Edge times out minting the session cookie). Register every dev/preview origin in trusted_origins before testing login. Two named prefix constants (BACKEND_API_PREFIX / PROXY_API_PREFIX) — never share one between server-direct and client-proxy calls. Frontend never queries Neon directly for business data by default — only its own auth/session SDK; a Data API bypass requires a decisions.md ADR. .NET: tokens are EdDSA/Ed25519 (OKP) — Microsoft.IdentityModel.Tokens can't verify OKP keys and there's no OIDC discovery; fetch+cache the JWKS yourself and verify signatures manually via BouncyCastle.Cryptography + TokenValidationParameters.SignatureValidator (must return Microsoft.IdentityModel.JsonWebTokens.JsonWebToken, not JwtSecurityToken — else IDX10506). NeonAuth:BaseUrl is mandatory in appsettings.Development.json (public value). RLS: auth.user_id() via pg_session_jwt.",
      "verifiedAgainst": "Neon Auth — @neondatabase/auth + @neondatabase/auth-ui (Better Auth).",
      "provedBy": []
    },
    {
      "id": "backend-integrations-resend-resend-email",
      "name": "resend email",
      "path": "backend/integrations/resend/resend-email.md",
      "category": "backend",
      "subcategory": "integrations",
      "tags": [
        "backend",
        "integrations",
        "resend"
      ],
      "stacks": [
        "dotnet"
      ],
      "aliases": [
        "resend",
        "email",
        "transactional email",
        "SMTP",
        "email notification"
      ],
      "anchors": {
        "sending": "#send-email-api-route",
        "templates": "#common-email-templates"
      },
      "digest": "Register ResendClient as singleton. Provide both HTML and plain-text alternatives. Handle bounce/complaint webhooks to maintain sender reputation. Never send from test domains in production. Queue emails via Hangfire for reliability — don't send inline in request handlers.",
      "verifiedAgainst": "Resend Node SDK 4 + Resend .NET SDK + React Email.",
      "provedBy": []
    },
    {
      "id": "data-nosql-cache-redis",
      "name": "redis",
      "path": "data/nosql/cache/redis.md",
      "category": "data",
      "subcategory": "nosql",
      "tags": [
        "data",
        "nosql",
        "cache"
      ],
      "stacks": [
        "dotnet",
        "azure"
      ],
      "aliases": [
        "redis",
        "cache",
        "distributed cache",
        "IDistributedCache",
        "cache-aside",
        "StackExchange.Redis"
      ],
      "anchors": {
        "patterns": "#cache-aside-pattern",
        "invalidation": "#ttl-strategy"
      },
      "digest": "IDistributedCache for cache-aside pattern. Set TTL on every entry — never cache without expiry. Serialize with System.Text.Json. Sliding expiration for sessions, absolute for computed results. Invalidate by key prefix. Redis eviction policy must be set (allkeys-lru). Never cache PII without encryption.",
      "verifiedAgainst": ".NET 10 + StackExchange.Redis 2.x + Microsoft.Extensions.Caching.StackExchangeRedis.",
      "provedBy": []
    },
    {
      "id": "data-nosql-cosmos-db",
      "name": "cosmos db",
      "path": "data/nosql/cosmos-db.md",
      "category": "data",
      "subcategory": "nosql",
      "tags": [
        "data",
        "nosql"
      ],
      "stacks": [
        "dotnet",
        "azure"
      ],
      "aliases": [
        "cosmos",
        "CosmosDB",
        "document database",
        "partition key",
        "RUs"
      ],
      "anchors": {
        "modeling": "#container-design",
        "queries": "#repository-pattern"
      },
      "digest": "Choose partition key with high cardinality for even distribution. Design documents for single-partition reads. Enable TTL for ephemeral data. Prefer point reads (O(1)) over cross-partition queries. Set provisioned throughput based on measured RUs. Use change feed for event-driven processing.",
      "verifiedAgainst": ".NET 10 + Microsoft.Azure.Cosmos SDK v3.",
      "provedBy": []
    },
    {
      "id": "data-vector-search-rag-chunking",
      "name": "rag chunking",
      "path": "data/vector-search/rag-chunking.md",
      "category": "data",
      "subcategory": "vector-search",
      "tags": [
        "data",
        "vector-search"
      ],
      "stacks": [
        "dotnet",
        "azure"
      ],
      "aliases": [
        "RAG chunking",
        "document chunking",
        "text splitting",
        "chunk overlap",
        "embedding chunks"
      ],
      "anchors": {
        "strategies": "#chunking-strategies",
        "retrieval": "#chunking-strategies"
      },
      "digest": "Recursive character splitting: 512 token chunks, 50 token overlap. Embed with text-embedding-3-small (1536 dims). Store chunk + metadata (source, page, section). Retrieve top-k=5 by cosine similarity. Re-rank with cross-encoder before LLM injection. Evaluate retrieval quality with RAGAS.",
      "verifiedAgainst": ".NET 10 (chunking algorithms — language-level, no external package).",
      "provedBy": []
    },
    {
      "id": "frontend-design-system-ai-image-generation",
      "name": "AI Image Generation",
      "path": "frontend/design-system/ai-image-generation.md",
      "category": "frontend",
      "subcategory": "design-system",
      "tags": [
        "image",
        "gemini",
        "asset",
        "ai-generation",
        "placeholder",
        "mcp"
      ],
      "stacks": [
        "*"
      ],
      "aliases": [
        "Image Generation",
        "AI Assets",
        "Gemini Images",
        "brand fidelity",
        "reference images",
        "favicon",
        "og image",
        "asset generation"
      ],
      "anchors": {
        "when-to-use": "#when-to-use-ai-generated-images",
        "prompt-patterns": "#prompt-engineering-patterns",
        "aspect-ratios": "#aspect-ratio-guide",
        "assets-md": "#assetsmd-format",
        "edit-workflows": "#iterative-editing-workflows",
        "brand-fidelity": "#brand-fidelity-reference-dont-invent",
        "to-video": "#downstream-images-that-become-video",
        "favicon-og": "#the-two-assets-everyone-forgets",
        "variations": "#variations-and-cost"
      },
      "digest": "Guidelines for generating project images with the Gemini Image MCP: when to use AI versus stock versus SVG, prompt patterns, aspect ratios, iterative editing and assets.md format. Core principle is brand fidelity over invention: always pass the client's real assets as referenceImages via edit_image rather than generating in a vacuum, never AI-recreate a property developer's architectural render or a physical product (animate or reference the real one), and chain derivatives from an approved hero so the whole set shares light, palette and identity. Frame anything destined to become video WITHOUT a face, because video moderation is stricter (E005), and generate scrub frames at the video's exact ratio with the subject opposite the copy. Always produce the two forgotten assets: a favicon derived from the real logo (never a new one) and a 1200x630 OG share image. Offer two to three genuinely different variations per image, varying angle, light or composition rather than the seed, and never advance with a weak image because every derivative inherits it.",
      "verifiedAgainst": "Gemini Image MCP + Next.js 15 `next/image`.",
      "provedBy": []
    },
    {
      "id": "frontend-design-system-ai-video-generation",
      "name": "AI Video Generation",
      "path": "frontend/design-system/ai-video-generation.md",
      "category": "frontend",
      "subcategory": "design-system",
      "tags": [
        "video",
        "seedance",
        "asset",
        "ai-generation",
        "motion",
        "mcp"
      ],
      "stacks": [
        "*"
      ],
      "aliases": [
        "video generation",
        "seedance",
        "img2vid",
        "endImage",
        "directional footage",
        "loop video",
        "E005",
        "animate render",
        "scroll scrub footage"
      ],
      "anchors": {
        "loop-vs-directional": "#section-1-the-distinction-that-decides-everything",
        "directional": "#section-2-building-directional-footage",
        "moderation": "#section-3-faces-trip-moderation",
        "cost-ladder": "#section-4-the-cost-ladder",
        "prompt-skeleton": "#section-5-prompt-skeleton"
      },
      "digest": "The decisive rule: a perfect loop has an end frame identical to its start, which is right for a self-playing ambient background and WRONG for scroll scrubbing, because start and end look the same and the page reads as frozen while the user scrolls. Scroll-driven footage needs directional movement via generate_video with an endImage (push-in/dolly is the most reliable, then reveal, pull-back, slow tilt); avoid orbits and any movement that returns to its origin. Client architectural renders are animated with img2vid, never AI-recreated. Human faces repeatedly trip moderation (E005), so crop the face out of frame (neck down, hands, product) and reserve faces for static image generation. Always climb the cost ladder: fast/480p/4s draft to judge framing and movement, 1080p only after approval, then re-extract frames with a bumped cache-bust. Prompts read like director notes with no hype words, and must reserve a calm dark region for the page copy with the subject on the opposite side. Generation is async: generate_video returns a request_id polled via get_video; list_videos reads local history and spends no credits.",
      "verifiedAgainst": "seedance MCP (generate_video, generate_loop_video, get_video, list_videos) over Replicate.",
      "provedBy": []
    },
    {
      "id": "frontend-design-system-animations",
      "name": "animations",
      "path": "frontend/design-system/animations.md",
      "category": "frontend",
      "subcategory": "design-system",
      "tags": [
        "frontend",
        "design-system"
      ],
      "stacks": [
        "*"
      ],
      "aliases": [
        "animations",
        "transitions",
        "motion",
        "CSS animations",
        "keyframes",
        "prefers-reduced-motion"
      ],
      "anchors": {
        "tokens": "#stagger--timing",
        "principles": "#css-animation-patterns"
      },
      "digest": "Use CSS custom properties for durations: --duration-fast (100ms), --duration-normal (200ms), --duration-slow (300ms). Prefer transform/opacity for GPU-accelerated animations. Always respect prefers-reduced-motion. No decorative animations over 300ms. Ease-out for entrances, ease-in for exits.",
      "verifiedAgainst": "CSS animations/transitions (language-level, no framework).",
      "provedBy": []
    },
    {
      "id": "frontend-design-system-naming",
      "name": "naming",
      "path": "frontend/design-system/naming.md",
      "category": "frontend",
      "subcategory": "design-system",
      "tags": [
        "frontend",
        "design-system"
      ],
      "stacks": [
        "*"
      ],
      "aliases": [
        "BEM",
        "CSS naming",
        "class naming",
        "design token naming",
        "component naming"
      ],
      "anchors": {
        "blocks": "#bem-for-reusable-components",
        "tokens": "#css-variables-with-namespace"
      },
      "digest": "BEM for CSS: .block__element--modifier. Design tokens: --color-primary-500, --spacing-4, --font-size-base. Component names PascalCase (React/Next.js) / kebab-case (CSS). No generic names (blue, big). Semantic names only: --color-action, --spacing-page-gutter. Tokens are the single source of truth.",
      "verifiedAgainst": "CSS (BEM + custom properties — language-level, no framework).",
      "provedBy": []
    },
    {
      "id": "frontend-design-system-premium-finish",
      "name": "premium finish",
      "path": "frontend/design-system/premium-finish.md",
      "category": "frontend",
      "subcategory": "design-system",
      "tags": [
        "frontend",
        "design-system"
      ],
      "stacks": [
        "*"
      ],
      "aliases": [
        "premium finish",
        "polish layer",
        "film grain",
        "atmosphere",
        "load-in stagger",
        "scrim",
        "text over video",
        "metallic accent",
        "looks like a template"
      ],
      "anchors": {
        "typography": "#section-1-typography-with-character",
        "atmosphere": "#section-2-atmosphere-never-flat-color",
        "loadin": "#section-3-one-orchestrated-load-in-beats-ten-micro-interactions",
        "legibility": "#section-4-legibility-over-video-close-to-a-hard-rule",
        "order": "#section-7-order-of-application"
      },
      "digest": "The finish layer that separates a working build from one a client reads as expensive, applied from the start rather than as a final polish pass. Principles, never a fixed recipe, and never converge on the same choices across projects: commit to a display font chosen from the brand (stack defaults and the AI-tell faces like Space Grotesk/Playfair announce machine-generated); use atmosphere instead of flat color (feTurbulence film grain at low opacity, radial brand glow, vignette); concentrate effort on ONE orchestrated hero load-in with staggered animation-delay rather than scattered micro-interactions; text over media always gets a DIRECTIONAL scrim (dark where the copy lives, clear over the subject) plus text-shadow as insurance against bright frames; one precious accent used in three places or fewer. Define brand tokens as CSS variables first, then atmosphere, then hero, then accent, then details.",
      "verifiedAgainst": "Production landing pages, CSS-only techniques, no runtime dependency.",
      "provedBy": []
    },
    {
      "id": "frontend-gsap-core",
      "name": "gsap core",
      "path": "frontend/gsap/gsap-core.md",
      "category": "frontend",
      "subcategory": "gsap",
      "tags": [
        "frontend",
        "gsap",
        "animation",
        "react"
      ],
      "stacks": [
        "*"
      ],
      "aliases": [
        "GSAP",
        "GreenSock",
        "gsap.to",
        "gsap.from",
        "useGSAP",
        "gsap timeline",
        "gsap utils",
        "gsap performance"
      ],
      "anchors": {
        "apis": "#section-3-core-animation-apis",
        "react": "#section-4-reactnextjs-integration",
        "timeline": "#section-5-timeline-sequencing",
        "performance": "#section-7-performance",
        "accessibility": "#section-8-accessibility"
      },
      "digest": "Use gsap.to/from/fromTo for imperative animations. In React, always use useGSAP() hook from @gsap/react with scope ref for automatic cleanup. Timelines sequence with position parameter. Animate only transforms + opacity for 60fps. Check prefers-reduced-motion. Import durations/easings from design tokens.",
      "verifiedAgainst": "GSAP 3.13 + @gsap/react.",
      "provedBy": []
    },
    {
      "id": "frontend-gsap-plugins",
      "name": "gsap plugins",
      "path": "frontend/gsap/gsap-plugins.md",
      "category": "frontend",
      "subcategory": "gsap",
      "tags": [
        "frontend",
        "gsap",
        "plugins"
      ],
      "stacks": [
        "*"
      ],
      "aliases": [
        "Flip",
        "SplitText",
        "MorphSVG",
        "DrawSVG",
        "MotionPath",
        "Draggable",
        "Observer",
        "ScrollToPlugin"
      ],
      "anchors": {
        "flip": "#flip-plugin-free",
        "splittext": "#splittext",
        "morphsvg": "#morphsvg",
        "drawsvg": "#drawsvg"
      },
      "digest": "Flip for layout animations (getState then from). SplitText for char/word/line reveals (always revert on cleanup). MorphSVG for path morphing. DrawSVG for stroke animation. MotionPath for path-following. Draggable for drag with bounds/snap. Observer for gesture detection. Register all plugins before use.",
      "verifiedAgainst": "GSAP 3.13 + @gsap/react.",
      "provedBy": []
    },
    {
      "id": "frontend-gsap-scrolltrigger",
      "name": "gsap scrolltrigger",
      "path": "frontend/gsap/gsap-scrolltrigger.md",
      "category": "frontend",
      "subcategory": "gsap",
      "tags": [
        "frontend",
        "gsap",
        "scrolltrigger",
        "scroll-animation"
      ],
      "stacks": [
        "*"
      ],
      "aliases": [
        "ScrollTrigger",
        "scroll animation",
        "pinning",
        "scrubbing",
        "parallax",
        "scroll-driven"
      ],
      "anchors": {
        "basic": "#section-2-basic-scrolltrigger",
        "scrub": "#section-3-scrubbing",
        "pin": "#section-4-pinning",
        "batch": "#section-5-batch-processing"
      },
      "digest": "Register ScrollTrigger plugin client-side only. Use scrub for scroll-linked progress. Pin sections with pin:true. Batch-process list items with ScrollTrigger.batch(). Call ScrollTrigger.refresh() after dynamic content. Kill all triggers in useGSAP cleanup.",
      "verifiedAgainst": "GSAP 3.13 ScrollTrigger + @gsap/react.",
      "provedBy": []
    },
    {
      "id": "frontend-nextjs-app-router",
      "name": "app router",
      "path": "frontend/nextjs/app-router.md",
      "category": "frontend",
      "subcategory": "nextjs",
      "tags": [
        "frontend",
        "nextjs"
      ],
      "stacks": [
        "nextjs"
      ],
      "aliases": [
        "app router",
        "Next.js 14",
        "server components",
        "app directory",
        "RSC"
      ],
      "anchors": {
        "layout": "#root-layout--required-providers",
        "routing": "#app-directory-structure"
      },
      "digest": "Server Components by default — only add 'use client' when needed (interactivity, browser APIs, hooks). Layouts persist across navigations. Parallel Routes with @folder for simultaneous views. Route Groups with (folder) to organize without affecting URL. Use generateMetadata() for dynamic SEO metadata.",
      "verifiedAgainst": "Next.js 15.",
      "provedBy": []
    },
    {
      "id": "frontend-nextjs-components",
      "name": "components",
      "path": "frontend/nextjs/components.md",
      "category": "frontend",
      "subcategory": "nextjs",
      "tags": [
        "frontend",
        "nextjs"
      ],
      "stacks": [
        "nextjs"
      ],
      "aliases": [
        "react components",
        "server component",
        "client component",
        "shadcn/ui",
        "UI components"
      ],
      "anchors": {
        "patterns": "#three-tier-hierarchy",
        "shadcn": "#tier-1--shadcnui-primitives"
      },
      "digest": "Server Components for data fetching and static content. Client Components for interactivity. shadcn/ui components are copy-paste — customize in src/components/ui/. Never use index.tsx for named exports. One component per file. Avoid prop drilling — use composition or Context API.",
      "verifiedAgainst": "Next.js 15 + shadcn/ui CLI + TanStack Table 8.",
      "provedBy": []
    },
    {
      "id": "frontend-nextjs-data-fetching",
      "name": "data fetching",
      "path": "frontend/nextjs/data-fetching.md",
      "category": "frontend",
      "subcategory": "nextjs",
      "tags": [
        "frontend",
        "nextjs"
      ],
      "stacks": [
        "nextjs"
      ],
      "aliases": [
        "TanStack Query",
        "react-query",
        "server actions",
        "data fetching",
        "useQuery"
      ],
      "anchors": {
        "server": "#server-component--tanstack-query-handoff",
        "client": "#usequery-hook-pattern"
      },
      "digest": "Server Components: fetch() with caching tags for static/ISR. Client Components: TanStack Query useQuery/useMutation for client-side state. Server Actions for mutations from Client Components. Never fetch in useEffect — use TanStack Query. Set revalidate intervals for ISR. Use React Suspense for loading states.",
      "verifiedAgainst": "Next.js 15 + TanStack Query 5 + Zod 4.",
      "provedBy": []
    },
    {
      "id": "frontend-nextjs-forms",
      "name": "forms",
      "path": "frontend/nextjs/forms.md",
      "category": "frontend",
      "subcategory": "nextjs",
      "tags": [
        "frontend",
        "nextjs"
      ],
      "stacks": [
        "nextjs"
      ],
      "aliases": [
        "react-hook-form",
        "Zod",
        "form validation",
        "Server Actions forms",
        "useFormState"
      ],
      "anchors": {
        "validation": "#schema-composition",
        "submission": "#complete-form-pattern"
      },
      "digest": "react-hook-form with Zod resolver for client forms. Define Zod schema before component. Server Actions for mutations: 'use server' directive. Use useFormState/useActionState for Server Action feedback. Never submit to API routes — use Server Actions or React Query mutations. Display field-level errors from Zod.",
      "verifiedAgainst": "Next.js 15 + react-hook-form 7 + Zod 4 + @hookform/resolvers 3.",
      "provedBy": []
    },
    {
      "id": "frontend-nextjs-motion-patterns",
      "name": "motion patterns",
      "path": "frontend/nextjs/motion-patterns.md",
      "category": "frontend",
      "subcategory": "nextjs",
      "tags": [
        "frontend",
        "nextjs",
        "framer-motion"
      ],
      "stacks": [
        "nextjs"
      ],
      "aliases": [
        "framer-motion",
        "animation variants",
        "motion design",
        "page transition",
        "AnimatePresence"
      ],
      "anchors": {
        "variants": "#section-1-core-variants-library",
        "tokens": "#section-2-design-token-bridge",
        "scroll": "#section-3-scroll-animations",
        "accessibility": "#section-7-wcag-accessibility-mandatory"
      },
      "digest": "Canonical Framer Motion variants (fadeIn, slideIn, scaleIn, stagger, pageTransition) with design token bridge. Use whileInView for scroll reveals. Respect prefers-reduced-motion via useReducedMotion() — strip transforms, keep opacity.",
      "verifiedAgainst": "Motion for React 12 (`motion`, imports from `motion/react`).",
      "provedBy": []
    },
    {
      "id": "frontend-nextjs-naming-conventions",
      "name": "naming conventions",
      "path": "frontend/nextjs/naming-conventions.md",
      "category": "frontend",
      "subcategory": "nextjs",
      "tags": [
        "frontend",
        "nextjs"
      ],
      "stacks": [
        "nextjs"
      ],
      "aliases": [
        "naming conventions",
        "file naming",
        "component naming",
        "Next.js naming",
        "TypeScript naming"
      ],
      "anchors": {
        "files": "#complete-reference-table",
        "components": "#complete-reference-table"
      },
      "digest": "Pages: lowercase kebab-case (user-profile/page.tsx). Components: PascalCase (UserProfile.tsx). Hooks: camelCase with 'use' prefix (useUserProfile.ts). API routes: kebab-case. Types: PascalCase. No default exports except page.tsx and layout.tsx. Feature-based co-location preferred.",
      "verifiedAgainst": "Next.js 15 + Zod 4.",
      "provedBy": []
    },
    {
      "id": "frontend-nextjs-nextjs-patterns",
      "name": "nextjs patterns",
      "path": "frontend/nextjs/nextjs-patterns.md",
      "category": "frontend",
      "subcategory": "nextjs",
      "tags": [
        "frontend",
        "nextjs"
      ],
      "stacks": [
        "nextjs"
      ],
      "aliases": [
        "Next.js patterns",
        "middleware",
        "auth patterns",
        "caching patterns",
        "image optimization"
      ],
      "anchors": {
        "auth": "#route-handlers-bff-pattern",
        "caching": "#react-query--neon"
      },
      "digest": "Middleware for auth redirects (use matcher config). next/image for all images (auto-optimization). next/link for navigation (prefetching). loading.tsx for Suspense boundaries per route. error.tsx for error boundaries. Use unstable_cache for expensive server computations with tag-based revalidation.",
      "verifiedAgainst": "Next.js 15 + TanStack Query 5 + Zod 4 + react-hook-form 7.",
      "provedBy": []
    },
    {
      "id": "frontend-nextjs-project-structure",
      "name": "project structure",
      "path": "frontend/nextjs/project-structure.md",
      "category": "frontend",
      "subcategory": "nextjs",
      "tags": [
        "frontend",
        "nextjs"
      ],
      "stacks": [
        "nextjs"
      ],
      "aliases": [
        "project structure",
        "folder structure",
        "src directory",
        "app directory organization"
      ],
      "anchors": {
        "structure": "#canonical-folder-tree",
        "organization": "#feature-index-pattern"
      },
      "digest": "src/app/ for routes, src/components/ for shared UI, src/lib/ for utilities, src/hooks/ for custom hooks, src/types/ for TypeScript types. Co-locate feature components near their routes. Public API via components/index.ts. Avoid barrel files in app/ directory. Keep pages thin — logic in hooks and utilities.",
      "verifiedAgainst": "Next.js 15 (App Router, `src/` layout).",
      "provedBy": []
    },
    {
      "id": "frontend-nextjs-state-management",
      "name": "state management",
      "path": "frontend/nextjs/state-management.md",
      "category": "frontend",
      "subcategory": "nextjs",
      "tags": [
        "frontend",
        "nextjs"
      ],
      "stacks": [
        "nextjs"
      ],
      "aliases": [
        "zustand",
        "React context",
        "global state",
        "state management",
        "client state"
      ],
      "anchors": {
        "patterns": "#state-decision-tree",
        "server": "#server-state-tanstack-query"
      },
      "digest": "TanStack Query for server state (caching, background sync, optimistic updates). Zustand for global client state (auth, theme, user preferences). URL params for shareable UI state (filters, pagination). useState for local component state. Avoid Redux — Zustand is sufficient for most Next.js use cases.",
      "verifiedAgainst": "Next.js 15 + TanStack Query 5 + Zustand 5.",
      "provedBy": []
    },
    {
      "id": "frontend-nextjs-testing",
      "name": "testing",
      "path": "frontend/nextjs/testing.md",
      "category": "frontend",
      "subcategory": "nextjs",
      "tags": [
        "frontend",
        "nextjs"
      ],
      "stacks": [
        "nextjs"
      ],
      "aliases": [
        "vitest",
        "jest",
        "testing-library",
        "Playwright",
        "Next.js testing",
        "component tests"
      ],
      "anchors": {
        "unit": "#component-test-pattern",
        "e2e": "#test-file-co-location"
      },
      "digest": "Vitest for unit tests, React Testing Library for component tests. Mock next/navigation with vi.mock. Test Server Actions with direct function calls. Use MSW for API mocking in component tests. Playwright for E2E tests against running app. Never test implementation details — test user behavior and output.",
      "verifiedAgainst": "Vitest 3 + React Testing Library 16 + MSW 2.",
      "provedBy": []
    },
    {
      "id": "frontend-scroll-driven-frame-scrub",
      "name": "frame scrub",
      "path": "frontend/scroll-driven/frame-scrub.md",
      "category": "frontend",
      "subcategory": "scroll-driven",
      "tags": [
        "frontend",
        "scroll-driven",
        "motion"
      ],
      "stacks": [
        "*"
      ],
      "aliases": [
        "frame scrub",
        "canvas scrub",
        "scroll-driven video",
        "scrollytelling video",
        "apple scroll video",
        "ffmpeg frames",
        "poster fallback"
      ],
      "anchors": {
        "why": "#section-1-canvas-not-video-currenttime",
        "extraction": "#section-2-frame-extraction",
        "cachebust": "#section-3-cache-busting-is-mandatory",
        "bug": "#section-5-the-1x1-canvas-bug",
        "responsive": "#section-6-responsive-and-reduced-motion",
        "verify": "#section-8-verify-with-motion-never-with-a-still"
      },
      "digest": "Scroll-driven video is a preloaded JPG sequence drawn into a <canvas>, never a <video currentTime> driven by scroll (unreliable seek on iOS Safari, black-frame flashes) and never an autoplay loop (reads as a demo, not as user-driven). Extract with ffmpeg (fps=30, ~120-300 frames, -q:v 5), always extract a poster, hflip when the subject collides with the copy, and treat total sequence weight as a performance budget. Always cache-bust the frame URL (?v=N) when re-extracting under the same filenames. Known bug: a section starting hidden measures 0 and the canvas becomes 1x1 drawing one stretched pixel, so keep the canvas display:none and resize on the first loaded frame. Mobile and prefers-reduced-motion collapse to the static poster in both the JS guard and the CSS. Verify by driving the scroll through positions and comparing captured frames (allow ~350ms for scrub:0.4 to settle), never with a single screenshot.",
      "verifiedAgainst": "GSAP 3.13 ScrollTrigger + Lenis 1.x + ffmpeg 6.x, shipped to production.",
      "provedBy": []
    },
    {
      "id": "frontend-scroll-driven-scroll-components",
      "name": "scroll components",
      "path": "frontend/scroll-driven/scroll-components.md",
      "category": "frontend",
      "subcategory": "scroll-driven",
      "tags": [
        "frontend",
        "scroll-driven",
        "motion"
      ],
      "stacks": [
        "*"
      ],
      "aliases": [
        "pinned gallery",
        "horizontal scroll",
        "scroll reveal",
        "parallax drift",
        "count-up",
        "lightbox",
        "scroll progress",
        "expanding modal"
      ],
      "anchors": {
        "reveal": "#section-1-usereveal-the-base-brick",
        "pinned": "#section-2-pinned-horizontal-gallery",
        "overlays": "#section-3-expanding-modal",
        "parallax": "#section-5-parallax-drift",
        "rules": "#section-9-cross-cutting-rules"
      },
      "digest": "Toolbox of scroll-driven interaction patterns, not a mandatory list: useReveal (fade/rise with once:true) covers most content; pinned horizontal gallery needs invalidateOnRefresh:true or the travel distance stays frozen after a resize; expanding modal and lightbox mount then add an .in class on the next frame, pause the scroller via window.__lenis?.stop(), and must render OUTSIDE any pinned section because the pin transform breaks position:fixed inside it; parallax drift stays subtle at 5-8% with overflow:hidden on the figure; count-up animates a plain object and formats per locale; scroll progress uses scaleX with transform-origin 0 50% to stay on the compositor; accordions use native <details>. Cross-cutting: anything clickable that is not a button or anchor gets role=button, tabIndex 0 and Enter/Space, Escape closes overlays, every effect has a static reduced-motion fallback and reverts on cleanup.",
      "verifiedAgainst": "GSAP 3.13 (ScrollTrigger, matchMedia, context) + React 18 + Lenis 1.x, shipped to production.",
      "provedBy": []
    },
    {
      "id": "frontend-scroll-driven-smooth-scroll",
      "name": "smooth scroll",
      "path": "frontend/scroll-driven/smooth-scroll.md",
      "category": "frontend",
      "subcategory": "scroll-driven",
      "tags": [
        "frontend",
        "scroll-driven",
        "motion"
      ],
      "stacks": [
        "*"
      ],
      "aliases": [
        "lenis",
        "smooth scroll",
        "scroll wiring",
        "gsap ticker",
        "window.__lenis",
        "scrolltrigger refresh",
        "HMR stale triggers"
      ],
      "anchors": {
        "registry": "#section-1-central-motion-registry",
        "raf": "#section-2-the-raf-handoff",
        "expose": "#section-3-expose-the-instance-on-window",
        "hmr": "#section-4-the-hmr-staleness-trap"
      },
      "digest": "Register GSAP plugins once in a shared lib/motion module that also exports prefersReducedMotion. Drive Lenis from gsap.ticker (never its own RAF loop), bind lenis.on(scroll, ScrollTrigger.update), set gsap.ticker.lagSmoothing(0), and call ScrollTrigger.refresh() on window load so triggers re-measure after fonts and images settle. Expose window.__lenis deliberately: overlays need stop()/start() to freeze the background, and automated verification needs scrollTo(y, {immediate:true}) to step through positions; consumers must use optional chaining because the instance is never created under reduced motion. After many hot reloads ScrollTrigger accumulates stale instances and pins/scrubs silently die, so full-reload before debugging, wrap every effect in gsap.context()/matchMedia() with revert on cleanup, and set invalidateOnRefresh on viewport-dependent triggers.",
      "verifiedAgainst": "Lenis 1.x + GSAP 3.13 ScrollTrigger + Vite 5, shipped to production.",
      "provedBy": []
    },
    {
      "id": "frontend-seo-meta-schema-audit",
      "name": "meta schema audit",
      "path": "frontend/seo/meta-schema-audit.md",
      "category": "frontend",
      "subcategory": "seo",
      "tags": [
        "frontend",
        "seo",
        "accessibility"
      ],
      "stacks": [
        "*"
      ],
      "aliases": [
        "seo",
        "meta tags",
        "open graph",
        "og:image",
        "schema.org",
        "json-ld",
        "rich results",
        "lighthouse",
        "go-live audit",
        "canonical"
      ],
      "anchors": {
        "meta": "#section-1-essential-meta",
        "opengraph": "#section-2-open-graph-and-twitter-cards",
        "schema": "#section-3-schemaorg-json-ld-by-business-type",
        "a11y": "#section-4-accessibility-also-an-seo-signal",
        "audit": "#section-5-pre-launch-audit"
      },
      "digest": "Head-level SEO is cheap and pays for itself: title/description/canonical/theme-color/favicon, correct lang, font preconnects, and a full Open Graph set with an absolute 1200x630 og:image (without it a pasted link renders as a bare card). Pick the JSON-LD @type from the business (ClothingStore/Store for retail, LocalBusiness for services, RealEstateAgent or Residence for property, SoftwareApplication for SaaS) and include ONLY true fields: aggregateRating goes in only with a real rating and review count from a real source, otherwise omit the block, because fabricated structured data is a trust and manual-action risk. Accessibility doubles as an SEO signal (alt text, contrast checked against the brightest frames, focus order, one h1, reduced-motion honored). Before go-live run Lighthouse, a clean-console check, a share-card check and cross-device; if a heavy frame sequence costs Performance, confirm the cost sits in the scrub and state the trade rather than letting a low score pass unexamined.",
      "verifiedAgainst": "Schema.org vocabulary + Lighthouse via Chrome DevTools MCP, shipped to production.",
      "provedBy": []
    },
    {
      "id": "infrastructure-docker-coolify-deploy",
      "name": "coolify deploy",
      "path": "infrastructure/docker/coolify-deploy.md",
      "category": "infrastructure",
      "subcategory": "docker",
      "tags": [
        "infrastructure",
        "docker"
      ],
      "stacks": [
        "docker"
      ],
      "aliases": [
        "coolify",
        "docker deploy",
        "self-hosted",
        "VPS deployment",
        "docker compose",
        "coolify api",
        "ssh deploy"
      ],
      "anchors": {
        "setup": "#coolify-service-config",
        "deployment": "#zero-downtime-deploys",
        "ssh": "#triggering-a-deploy-via-ssh--api"
      },
      "digest": "Coolify is the self-hosted Docker PaaS. No MCP/CLI exists — all access is via SSH into the VPS, hitting the Coolify API at localhost:8000/api/v1 with a server-side token (never printed, never leaves the SSH session). Configure environment variables in Coolify UI/API — not in Dockerfile. Configure /health endpoint for health checks. HTTPS via Let's Encrypt automatic (Traefik). Redeploys trigger via GET /deploy?uuid={uuid} over SSH.",
      "verifiedAgainst": ".NET 10 + Next.js 15 + Node 22 (Docker base images).",
      "provedBy": []
    },
    {
      "id": "infrastructure-docker-local-compose-isolation",
      "name": "local compose isolation",
      "path": "infrastructure/docker/local-compose-isolation.md",
      "category": "infrastructure",
      "subcategory": "docker",
      "tags": [
        "infrastructure",
        "docker"
      ],
      "stacks": [
        "docker"
      ],
      "aliases": [
        "compose isolation",
        "worktree ports",
        "container_name",
        "POSTGRES_PORT",
        "COMPOSE_PROJECT_NAME",
        "port block",
        "local docker compose"
      ],
      "anchors": {
        "layout": "#port-layout-one-contract-everywhere",
        "rules": "#core-rules",
        "enforcement": "#what-the-harness-enforces",
        "skeleton": "#minimal-compose-skeleton"
      },
      "digest": "Every worktree runs its OWN compose stack (project morph-{feature}, own postgres, own volumes). Ports come from the worktree block: PORT +0, API_PORT +1 (ASPNETCORE_URLS on .NET), POSTGRES_PORT +2, REDIS_PORT +3 — publish via ${VAR:-default}:container, never a fixed host port, never container_name (not prefixed by the project → collides). Stable POSTGRES_DB, healthcheck on every service, DATABASE_URL with an in-network default, playwright.config reads PLAYWRIGHT_BASE_URL. A non-isolable compose fails the verify e2e node inside a worktree. finish tears down with volumes (--keep-volumes opts out). Cloud DB credentials go in e2e.auth.envFile, never in .morph/worktree.env.",
      "verifiedAgainst": "Docker Compose v2 + morph-spec 8.34 (dedicated stack per worktree).",
      "provedBy": []
    },
    {
      "id": "infrastructure-neon-pgrag",
      "name": "neon pgrag",
      "path": "infrastructure/neon/neon-pgrag.md",
      "category": "infrastructure",
      "subcategory": "neon",
      "tags": [
        "infrastructure",
        "neon",
        "pgrag"
      ],
      "stacks": [
        "neon"
      ],
      "aliases": [
        "pgrag",
        "RAG in SQL",
        "embedding pipeline",
        "Neon RAG",
        "in-database RAG"
      ],
      "anchors": {
        "setup": "#install-pgrag",
        "pipeline": "#full-rag-pipeline-example"
      },
      "digest": "Use pgrag extensions (rag, rag_bge_small_en_v15, rag_jina_reranker_v1_tiny_en) for chunking, embedding, search, and reranking entirely in SQL. No external embedding service needed for basic RAG. Combine with pgvector HNSW indexes for production performance.",
      "verifiedAgainst": "Neon pgrag (`rag` extension family — experimental, see Core Rules).",
      "provedBy": []
    },
    {
      "id": "infrastructure-neon-pgvector",
      "name": "neon pgvector",
      "path": "infrastructure/neon/neon-pgvector.md",
      "category": "infrastructure",
      "subcategory": "neon",
      "tags": [
        "infrastructure",
        "neon",
        "pgvector"
      ],
      "stacks": [
        "neon"
      ],
      "aliases": [
        "pgvector",
        "vector embeddings",
        "similarity search",
        "Neon vectors",
        "HNSW index"
      ],
      "anchors": {
        "setup": "#enable-pgvector",
        "queries": "#similarity-search"
      },
      "digest": "Enable: CREATE EXTENSION vector. Define column as vector(1536) for OpenAI embeddings. Create HNSW index with tuned maintenance_work_mem. Use direct SQL for similarity search. Batch insert embeddings (100 at a time). RLS policies apply to vector queries.",
      "verifiedAgainst": ".NET 10 + Pgvector.EntityFrameworkCore + pgvector (HNSW).",
      "provedBy": []
    },
    {
      "id": "infrastructure-neon-setup",
      "name": "neon setup",
      "path": "infrastructure/neon/neon-setup.md",
      "category": "infrastructure",
      "subcategory": "neon",
      "tags": [
        "infrastructure",
        "neon",
        "postgresql"
      ],
      "stacks": [
        "neon"
      ],
      "aliases": [
        "Neon",
        "neonctl",
        "serverless postgres",
        "Neon CLI",
        "Neon setup"
      ],
      "anchors": {
        "setup": "#neon-cli-setup",
        "connection": "#connection-strings"
      },
      "digest": "Use neonctl CLI for project management and connection strings. Two connection strings: pooled (-pooler) for application code, direct for EF Core migrations. Configure connection retry for scale-to-zero cold starts. Use database branches for PR previews.",
      "verifiedAgainst": ".NET 10 + Npgsql/EF Core 10 + neonctl.",
      "provedBy": []
    },
    {
      "id": "integration-mcp-mcp-tools",
      "name": "mcp tools",
      "path": "integration/mcp/mcp-tools.md",
      "category": "integration",
      "subcategory": "mcp",
      "tags": [
        "integration",
        "mcp"
      ],
      "stacks": [
        "*"
      ],
      "aliases": [
        "MCP",
        "Model Context Protocol",
        "Claude tools",
        "MCP server",
        "MCP client"
      ],
      "anchors": {
        "tools": "#mcp-providers-by-phase",
        "setup": "#mcp-availability-detection"
      },
      "digest": "MCP tools extend Claude with external data and actions. Tools are synchronous request-response with typed inputSchema. Resources provide read-only data access. Prompts are reusable templates. Register MCP servers in .claude/settings.json. Prefer @context7 for live library docs. Test tools with MCP Inspector before integrating.",
      "verifiedAgainst": "Claude Code MCP tooling — context7, Playwright; GitHub via `gh` CLI; Neon via `neonctl`.",
      "provedBy": []
    }
  ],
  "sharedAliases": {
    "agent middleware": [
      "ai-agents-production",
      "ai-agents-middleware-patterns"
    ],
    "anti-hallucination": [
      "ai-agents-production",
      "ai-agents-middleware-patterns"
    ],
    "agent observability": [
      "ai-agents-production",
      "ai-agents-observability-patterns"
    ],
    "agent telemetry": [
      "ai-agents-production",
      "ai-agents-observability-patterns"
    ],
    "when not to use workflow": [
      "ai-agents-workflows",
      "ai-agents-sweet-spot"
    ],
    "rag": [
      "ai-agents-rag-custom-pgvector",
      "backend-database-vector-search-rag"
    ],
    "vector search": [
      "ai-agents-rag-custom-pgvector",
      "backend-database-vector-search-rag"
    ],
    "pgvector": [
      "ai-agents-rag-custom-pgvector",
      "backend-database-vector-search-rag",
      "infrastructure-neon-pgvector"
    ],
    "embeddings": [
      "ai-agents-rag-custom-pgvector",
      "backend-database-vector-search-rag"
    ],
    "semantic search": [
      "ai-agents-rag-custom-pgvector",
      "backend-database-vector-search-rag"
    ],
    "middleware": [
      "ai-agents-middleware-patterns",
      "frontend-nextjs-nextjs-patterns"
    ],
    "image generation": [
      "ai-agents-modalities-image-gen",
      "frontend-design-system-ai-image-generation"
    ],
    "mcp": [
      "ai-agents-mcp-tools",
      "integration-mcp-mcp-tools"
    ],
    "model context protocol": [
      "ai-agents-mcp-tools",
      "integration-mcp-mcp-tools"
    ],
    "mcp client": [
      "ai-agents-mcp-tools",
      "integration-mcp-mcp-tools"
    ],
    "mcp server": [
      "ai-agents-mcp-server",
      "integration-mcp-mcp-tools"
    ],
    "feature slice": [
      "architecture-vertical-slice-vertical-slice",
      "backend-dotnet-vsa-handler-patterns"
    ],
    "component naming": [
      "frontend-design-system-naming",
      "frontend-nextjs-naming-conventions"
    ],
    "video generation": [
      "ai-agents-media-video",
      "frontend-design-system-ai-video-generation"
    ],
    "seedance": [
      "ai-agents-media-video",
      "frontend-design-system-ai-video-generation"
    ]
  }
}
