Skip to main content

Diagnostics

A question set is code that Minos turns into a request, so mistakes in it are best found while you type, not when a call fails. Minos.NET ships a set of Roslyn analyzers for this. They live inside the package, next to the [Questions] generator, so there is nothing else to install. The editor shows their findings as you type, and the build reports them too.

Every rule has an id that starts with MIN, a severity and a short title. The severity says how much the finding matters:

  • Error stops the build, because the question set cannot work.
  • Warning is something that is probably a mistake, such as an empty description. The set still works.
  • Info is a suggestion. It does not fail the build, and the editor shows it as a hint.

Two groups of rules​

The rules come in two groups.

  • MIN001 to MIN006 check the set against the Jev API's own rules. MIN001 and MIN002 follow the schema of TypeSafe's official SDK, which needs at least one option or level. The others are advice and never block the generator. The Score level range behind MIN005 is guidance from the API sketch, not a limit of the schema. TypeSafe's API documents at most 255 options for a Choice question. MIN005 warns about a larger set at build time, and since 0.9 the TypeSafe and OpenRouter presets also refuse it before sending, with Unsupported and MIN203.
  • MIN101 to MIN108 are errors about declarations that cannot be turned into a request. The generator cannot read them, so it cannot write the call. The generator reports MIN101 to MIN107 at build time, for a [Questions] type. MIN108 has no analyzer: it is reported only by Build() of a set built at run time.

A set with any error is invalid. The generator writes no Definition, no Create and no IQuestionSet for it, so EvaluateAsync<T> does not compile for that type. The generator still gives each unimplemented question property a stub that throws. That way a command-line build reports the MIN error, and not CS9248, "partial property must have an implementation part", which would hide it.

One thing can still hide the analyzers. An error in your own declaration, such as CS0238 for sealed on a property that overrides nothing, stops a command-line build before any analyzer runs. The editor runs the analyzers live and still shows them.

The rules​

IdSeverityTitleIt fires when
MIN001ErrorChoice enum has no membersThe enum of a Choice<T> has no members. The API needs at least one option.
MIN002ErrorScore enum has no membersThe enum of a Score<T> has no members. The API needs at least one level.
MIN003WarningEmpty instructions or descriptionInstructions, a description, or an Examples or NotFor entry is empty or only white space. A null text is fine, because the API accepts none.
MIN004WarningInstructions refer to an unknown state memberA name in backticks in the instructions matches no public property or field of the State type, as typed evaluation describes. For an array state the element type is checked.
MIN005WarningOption or level count outside the API guidanceA Score enum has fewer than 2 or more than 10 levels, or a Choice enum has more than 255 options, the most TypeSafe's API documents.
MIN006InfoChoice option has no descriptionA member of a Choice enum has no [Criterion]. It is still an option, sent with no description, and a description usually helps.
MIN101ErrorUnsupported question set typeThe type with [Questions] is not a non-generic, non-abstract, non-static, top-level partial class or record that is not file-local.
MIN102ErrorUnsupported question propertyA question property is not a partial, get-only instance property, or it is virtual, new, sealed or override, or it is named Definition or Create, which the generator reserves.
MIN103ErrorQuestion attribute does not match the property typeA question property has more than one question attribute, or one that does not fit its type: [Noul] needs a Noul, [Choice] a Choice<T> and [Score] a Score<T>.
MIN104ErrorScore level has no descriptionA member of a Score enum has no [Level]. The API does not accept a level without a description.
MIN105ErrorQuestion set has no parameterless constructorThe set has no constructor that can be called without arguments, or it has required members and that constructor lacks [SetsRequiredMembers]. A constructor whose parameters all have defaults counts.
MIN106ErrorDuplicate wire keyTwo questions in the set use the same wire key, or two options of one Choice do, or a Key on a question attribute or a [Criterion] is empty.
MIN107ErrorInvalid state typeThe State type is not a class, struct, record or array type.

A set built at run time is checked against the same limits, with the same rule ids. That page says which rules apply there, and which of them fail Build().

Reported by the run-time builder​

One rule has no analyzer, because only a set built at run time can carry JSON. A declared set's instructions and descriptions are always text.

IdOutcomeIt fires when
MIN108Build() failsJSON instructions, a JSON description, or a JSON yes/no meaning nests more than 60 levels deep.

Reported by the client​

MIN201 to MIN206 are provider rules, checked at run time. Whether a request breaks one depends on the provider, its capabilities and the images of the call, and none of those is known when the code is compiled, so no analyzer reports them. The client checks them before it sends anything. A request that breaks any of them returns a DecisionError of kind Unsupported, with one QuestionFailure per problem in DecisionError.Failures. No request is sent, and it is not retried by default: DecisionRetryOptions.IsTransient excludes it, though a custom ShouldRetry can retry it.

IdIt fires when
MIN201A question's kind is not in the provider's QuestionKinds.
MIN202The set has more questions than the provider's MaxQuestions.
MIN203A Choice question has more options than the provider's MaxChoiceOptions, 255 on TypeSafe and OpenRouter.
MIN204A Noul has WhenTrue or WhenFalse, and the provider is OpenAI, whose predicate takes only instructions.
MIN205A criterion has Examples or NotFor, and the provider is OpenAI, whose descriptions are plain strings.
MIN206The request has images, and the provider's SupportsImages is off.

The Unsupported error shows the messages and the order of the failures, and the provider matrix which provider each rule applies to.

Where a rule is reported​

A rule about an enum, such as MIN001, MIN005, MIN006 and MIN104, is reported on the enum and its members, and not on each set that uses it. The other rules are reported on the set type or on the property they are about. An enum from another assembly has no declaration in your project, so its findings are reported on the question property that uses it.

Code fixes​

Two rules come with a code fix. Both write the missing description for you, from the member's own name.

RuleFix title, for a member named NeedsAttentionWhat it adds
MIN006Add [Criterion("Needs attention")]A [Criterion] on the Choice member.
MIN104Add [Level("Needs attention")]A [Level] on the Score member.

The name is split into words and written as a sentence, so NeedsAttention becomes "Needs attention", HTTPError becomes "HTTP error" and NEEDS_ATTENTION becomes "Needs attention". That is a starting point. Read it and write something that tells the model what the option means, because the model reads the description.

Both fixes can fix every occurrence in a document, a project or a solution at once. The fix adds a using for Minos.NET only when the attribute is not already in scope. There is no fix when the enum is declared in another assembly, because there is no member in your code to put the attribute on.

Suppressing a rule​

Suppress a warning or an Info rule that does not apply to your case. Do it narrowly, and say why. A #pragma around the one declaration is the narrowest. This enum has 11 levels on purpose, a 0 to 10 scale, so the MIN005 warning is turned off for that enum and nothing else. The example is compiled with warnings treated as errors, so it only builds because the suppression works.

using Minos;

// The Jev API's guidance is 2 to 10 levels for a Score, so MIN005 warns about this enum. A 0 to 10 scale has 11
// levels by definition, so the warning is suppressed here, for this enum only, and the reason is written next to it.
#pragma warning disable MIN005 // A 0 to 10 recommendation scale has 11 levels on purpose.
public enum Recommendation
{
[Level("0: Not at all likely")] Zero,
[Level("1")] One,
[Level("2")] Two,
[Level("3")] Three,
[Level("4")] Four,
[Level("5: Neutral")] Five,
[Level("6")] Six,
[Level("7")] Seven,
[Level("8")] Eight,
[Level("9")] Nine,
[Level("10: Extremely likely")] Ten,
}
#pragma warning restore MIN005

[Questions]
public partial record SurveyReply
{
[Score("How likely is this customer to recommend us to a friend?")]
public partial Score<Recommendation> Recommend { get; }
}

To change a rule for a whole project, or for the files an .editorconfig section covers, set its severity there:

[*.cs]
dotnet_diagnostic.MIN005.severity = none

The same setting can go the other way. If you want every Choice option to have a description, raise MIN006 from Info to a warning, and the build will then fail until each option has one:

[*.cs]
dotnet_diagnostic.MIN006.severity = warning

A project-wide <NoWarn>MIN005</NoWarn> in the project file works too.

An error does not go away that way. Suppressing MIN001, MIN002 or one of MIN101 to MIN107 hides the message, but the set stays invalid. The generator does not read the diagnostics. It applies the same rules itself, so a suppressed set still gets no Definition, no Create and no IQuestionSet. EvaluateAsync<T> does not compile for it, and the stub properties throw. Fix the declaration instead.

Next​