# `Lotus.AI.Prompts.AdapterNotes`
[🔗](https://github.com/elixir-lotus/lotus/blob/v1.0.0/lib/lotus/ai/prompts/adapter_notes.ex#L1)

Resolves the prompt text an adapter contributes about its own query
language, falling back to core's language-agnostic defaults.

Core owns prompt *structure* — the workflow, the tool list, the
`UNABLE_TO_GENERATE` protocol, the fence. The adapter owns prompt
*content* about its own language. The split follows the enforcement:
`sanitize_query/3` is already an adapter callback, so the adapter, not
core, decides what counts as a write. Prompt text belongs where the
authority sits.

Adapter notes render **in place of** core's defaults, never appended
after them. Appending would make an adapter argue against core instead
of speaking for itself — the Elasticsearch adapter's `syntax_notes`
followed core's "never generate INSERT, UPDATE, DELETE", which names
operations Elasticsearch does not have.

## Falling back, never blanking

`Lotus.Source.Adapter.ai_context/1` drops both fields for adapters that
are not in `:trusted_source_adapters`. This module then supplies core's
default. It must never resolve to an empty string: an empty
`read_only_notes/1` would leave the prompt with no read-only
instruction at all, which is a way for an untrusted adapter to weaken
the guard by supplying a blank.

# `fence_label`

```elixir
@spec fence_label(map()) :: String.t()
```

The markdown fence label for statements in this language.

This is the language *family* — the part before the colon — because
editors and markdown renderers know `sql` and `json`, and do not know
`sql:clickhouse`.

Reads only the sanitized `:language` from `ai_context`, which
`Lotus.Source.Adapter.ai_context/1` has already constrained to
`~r/\A[a-z0-9]+:[a-z0-9_-]+\z/` or replaced with `"unknown"`. Never
interpolate a raw `query_language/1` here: that value has no
validation, so it could carry a newline or backticks and break out of
the fence. As a second line of defence, a family that is not plain
`[a-z0-9]+` is replaced with `"sql"` rather than emitted.

# `generation_notes`

```elixir
@spec generation_notes(map()) :: String.t()
```

Guidance on how to shape a good query for this source.

Returns the adapter's `:generation_notes` when it supplied one, else
core's language-agnostic default.

# `read_only_notes`

```elixir
@spec read_only_notes(map(), boolean()) :: String.t()
```

Guidance on which operations count as writes for this source, and so
must never be generated.

Returns the adapter's `:read_only_notes` when it supplied one, else
core's language-agnostic default. Passing `false` for `read_only?`
returns core's write-permitted text and ignores the adapter's notes,
which describe a restriction that is not in force.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
