Make typeDiagram a complete single source of truth for API DTOs — full requirements
Companion to #38 (scalar types) and #29 (optional-vs-null). This is the complete list of what a contract-first DTO generator must express so consumers never hand-maintain a parallel copy. Every item below is something we currently CANNOT put in the .td and are therefore forced to hand-write in Python (Pydantic) — which then drifts from the contract. Each maps cleanly to Pydantic Field(...), C# DataAnnotations/JSON attributes, and OpenAPI.
1. Field-level constraints (validation metadata) — HIGHEST PRIORITY
Real fields we hand-maintain solely for these:
| our field |
needed metadata |
ChatRequest.message |
min_length=1, max_length=100_000 |
SshKeyRequest.public_key |
min_length=1, max_length=10_000 |
BundleFileWriteRequest.content |
max_length=2_000_000 |
ConversationSummary.message_count |
ge=0 |
ToolFileWriteRequest.mode |
ge=0, le=0o777 |
Manifest.build_command |
min_length=1, max_length=1000 |
Proposed syntax (attributes on the field):
type ChatRequest {
message: Option<String> @minLen(1) @maxLen(100000)
}
type ConversationSummary {
message_count: Int @min(0)
}
Constraint vocabulary: @minLen/@maxLen, @min/@max (inclusive), @exclusiveMin/@exclusiveMax, @multipleOf, @pattern("regex").
Lowering: Pydantic Field(min_length=, max_length=, ge=, le=, gt=, lt=, multiple_of=, pattern=); C# [MinLength]/[MaxLength]/[Range]/[RegularExpression]; OpenAPI minLength/maxLength/minimum/maximum/pattern.
2. Explicit default values (not just Option→null)
Fields whose default is a non-null literal — currently un-expressible:
ToolEntry.method = "POST", Manifest.template_dir = "template", ProvisionRequest.workspace_git_push_mode = "manual_only", ToolResultIn.ok = true.
type ToolEntry { method: String @default("POST") }
Lowering: Pydantic field default; C# property initializer; OpenAPI default.
3. String formats
@format(email|uri|uuid|date-time|hostname|ipv4|ipv6|byte) → Pydantic EmailStr/AnyUrl/Field(pattern=...), OpenAPI format.
4. Field descriptions / doc comments
Doc comments must survive into the output (Pydantic Field(description=...), C# XML ///, OpenAPI description). Example we hand-maintain: MessageResponse.tool_calls has a paragraph explaining the content/tool_calls split.
type MessageResponse {
/// Tool calls emitted this turn, separate from `content` so UIs can hide tool activity.
tool_calls: List<ToolCallOut>
}
5. Field aliases (wire name ≠ language name)
We have from_: str = Field(alias="from", serialization_alias="from") because from is a Python keyword. Need @alias("from") → Pydantic alias + populate_by_name, C# [JsonPropertyName].
6. Model-level config
@extra(forbid|ignore|allow), @frozen. We use extra="forbid" on request DTOs to reject typos (ConversationSummary, WorkspaceInstanceSummary) and extra="ignore" elsewhere. Lowering: Pydantic model_config = ConfigDict(extra=, frozen=); C# record immutability / JsonExtensionData.
7. @example(...) and @deprecated
For OpenAPI/docs quality: per-field examples and a deprecation marker.
8. (from #38) native DateTime/Uuid/Decimal scalars + fail-closed on unknown type identifiers.
Why this is critical infrastructure
With items 1–6 NAP can delete its entire hand-written DTO module and generate 100% from the .td. Without them, every constrained/aliased/defaulted/documented field forces a hand copy, the hand copy drifts, and we ship incidents (the conversation-history leak we just fixed was exactly this failure mode). We will adopt a patch build the moment 1, 2, and 4 land — those three remove ~90% of our hand-maintenance.
Make typeDiagram a complete single source of truth for API DTOs — full requirements
Companion to #38 (scalar types) and #29 (optional-vs-null). This is the complete list of what a contract-first DTO generator must express so consumers never hand-maintain a parallel copy. Every item below is something we currently CANNOT put in the
.tdand are therefore forced to hand-write in Python (Pydantic) — which then drifts from the contract. Each maps cleanly to PydanticField(...), C# DataAnnotations/JSON attributes, and OpenAPI.1. Field-level constraints (validation metadata) — HIGHEST PRIORITY
Real fields we hand-maintain solely for these:
ChatRequest.messagemin_length=1, max_length=100_000SshKeyRequest.public_keymin_length=1, max_length=10_000BundleFileWriteRequest.contentmax_length=2_000_000ConversationSummary.message_countge=0ToolFileWriteRequest.modege=0, le=0o777Manifest.build_commandmin_length=1, max_length=1000Proposed syntax (attributes on the field):
Constraint vocabulary:
@minLen/@maxLen,@min/@max(inclusive),@exclusiveMin/@exclusiveMax,@multipleOf,@pattern("regex").Lowering: Pydantic
Field(min_length=, max_length=, ge=, le=, gt=, lt=, multiple_of=, pattern=); C#[MinLength]/[MaxLength]/[Range]/[RegularExpression]; OpenAPIminLength/maxLength/minimum/maximum/pattern.2. Explicit default values (not just Option→null)
Fields whose default is a non-null literal — currently un-expressible:
ToolEntry.method = "POST",Manifest.template_dir = "template",ProvisionRequest.workspace_git_push_mode = "manual_only",ToolResultIn.ok = true.Lowering: Pydantic field default; C# property initializer; OpenAPI
default.3. String formats
@format(email|uri|uuid|date-time|hostname|ipv4|ipv6|byte)→ PydanticEmailStr/AnyUrl/Field(pattern=...), OpenAPIformat.4. Field descriptions / doc comments
Doc comments must survive into the output (Pydantic
Field(description=...), C# XML///, OpenAPIdescription). Example we hand-maintain:MessageResponse.tool_callshas a paragraph explaining the content/tool_calls split.5. Field aliases (wire name ≠ language name)
We have
from_: str = Field(alias="from", serialization_alias="from")becausefromis a Python keyword. Need@alias("from")→ Pydantic alias +populate_by_name, C#[JsonPropertyName].6. Model-level config
@extra(forbid|ignore|allow),@frozen. We useextra="forbid"on request DTOs to reject typos (ConversationSummary,WorkspaceInstanceSummary) andextra="ignore"elsewhere. Lowering: Pydanticmodel_config = ConfigDict(extra=, frozen=); C# record immutability /JsonExtensionData.7.
@example(...)and@deprecatedFor OpenAPI/docs quality: per-field examples and a deprecation marker.
8. (from #38) native
DateTime/Uuid/Decimalscalars + fail-closed on unknown type identifiers.Why this is critical infrastructure
With items 1–6 NAP can delete its entire hand-written DTO module and generate 100% from the
.td. Without them, every constrained/aliased/defaulted/documented field forces a hand copy, the hand copy drifts, and we ship incidents (the conversation-history leak we just fixed was exactly this failure mode). We will adopt a patch build the moment 1, 2, and 4 land — those three remove ~90% of our hand-maintenance.