// Package promptexec defines Weatherreporter's provider-neutral prompt execution contract. package promptexec import ( "context" "errors" "fmt" "time" ) const ( maxValidationDiagnostics = 10 maxDiagnosticBytes = 1024 maxErrorMessageBytes = 2048 ) // Executor inspects and executes configured prompts without exposing provider types. // Inspection is side-effect-free. Execute invokes prepared exactly once after a // successful preparation and before provider execution. If prepared returns an // error, Execute must not call the provider. Completed validation rejection is // returned as an Execution with a failed Validation status; operational failures // return no Execution. Sensitive debug values are populated only when requested. type Executor interface { InspectPrompt(context.Context, string, string) (PromptInspection, error) InspectProfile(context.Context, string) (ProfileInspection, error) Execute(context.Context, ExecuteRequest, PreparationCallback) (*Execution, error) } // PromptInspection describes one exact prompt definition without selecting a profile. type PromptInspection struct { PromptID string PromptVersion string PromptHash string DefaultProfileID string Inputs []InputDefinition Output OutputContract } // InputDefinition describes one declared prompt input. type InputDefinition struct { Name string Required bool ContentType string Description string } // OutputContract summarizes the output requirements declared by a prompt. type OutputContract struct { Format string ValidationMode string SchemaPath string } // ProfileInspection describes the safe, selected execution identity for one profile. type ProfileInspection struct { ProfileID string BackendID string ModelName string CredentialRequired bool APIKeyEnv string } // ExecuteRequest selects one exact prompt execution. DataPackage is the exact // YAML input; implementations must copy it before retaining it. type ExecuteRequest struct { PromptID string PromptVersion string ProfileID string DataPackage []byte CaptureDebug bool } // PreparationCallback receives safe preparation provenance before provider work. // The callback receives independent copies which it may retain or mutate. type PreparationCallback func(Preparation, *PreparationDebug) error // Preparation contains non-sensitive provenance from a completed preparation. type Preparation struct { PromptID string PromptVersion string PromptHash string RenderedPromptHash string InputHashes map[string]string ProfileID string BackendID string ModelName string Output OutputContract StartedAt time.Time EndedAt time.Time Duration time.Duration } // PreparationDebug contains content-rich preparation details for an explicitly // enabled sensitive-debug destination. It must never be persisted routinely. type PreparationDebug struct { RenderedMessages []RenderedMessage StructuredSchema []byte Endpoint string ParametersJSON []byte } // RenderedMessage is one fully rendered model message for sensitive debugging. type RenderedMessage struct { Role string Content string } // Execution contains the completed result of one provider run. RawOutput is // the generated content, not a provider transport response body. It is copied // before return and must be persisted separately from routine metadata. type Execution struct { RunID string PromptID string PromptVersion string PromptHash string RenderedPromptHash string InputHashes map[string]string ProfileID string BackendID string ModelName string GeneratedHash string Usage TokenUsage StartedAt time.Time EndedAt time.Time Duration time.Duration Validation Validation RawOutput []byte Debug *ExecutionDebug } // TokenUsage is provider-reported token accounting. type TokenUsage struct { PromptTokens int CompletionTokens int TotalTokens int CachedTokens int CacheWriteTokens int } // Validation records a completed output validation check. type Validation struct { Status ValidationStatus Mode string SchemaPath string Diagnostics []string } // NewValidation returns a completed validation value with bounded diagnostics. func NewValidation(status ValidationStatus, mode string, schemaPath string, diagnostics []string) Validation { return Validation{ Status: status, Mode: mode, SchemaPath: schemaPath, Diagnostics: boundDiagnostics(diagnostics), } } // ValidationStatus identifies the completed validation state. type ValidationStatus string const ( ValidationPassed ValidationStatus = "passed" ValidationFailed ValidationStatus = "failed" ValidationSkipped ValidationStatus = "skipped" ) // ExecutionDebug contains content-rich execution details for explicitly enabled // sensitive debugging. It must never be persisted routinely. type ExecutionDebug struct { RawOutput []byte ValidationDiagnostics []string } // ErrorCategory classifies a project-owned operational failure. type ErrorCategory string const ( InvalidConfiguration ErrorCategory = "invalid_configuration" InvalidRequest ErrorCategory = "invalid_request" PromptNotFound ErrorCategory = "prompt_not_found" PromptLoad ErrorCategory = "prompt_load" ProfileNotFound ErrorCategory = "profile_not_found" ProfileLoad ErrorCategory = "profile_load" MissingCredential ErrorCategory = "missing_credential" ArtifactLoad ErrorCategory = "artifact_load" PromptRender ErrorCategory = "prompt_render" Capacity ErrorCategory = "capacity" Generation ErrorCategory = "generation" OperationalValidation ErrorCategory = "operational_validation" ValidationRejected ErrorCategory = "validation_rejected" Canceled ErrorCategory = "canceled" DeadlineExceeded ErrorCategory = "deadline_exceeded" ) // Error is a bounded safe error suitable for workflow and persistence records. // Its optional cause remains available to errors.Is and errors.As but is never // included in Error's text. type Error struct { category ErrorCategory message string cause error } // NewError returns a classified error with a bounded, Weatherreporter-owned message. func NewError(category ErrorCategory, message string, cause error) *Error { messageLimit := maxErrorMessageBytes - len(category) - len(": ") return &Error{category: category, message: boundText(message, messageLimit), cause: cause} } func (e *Error) Error() string { if e == nil { return "" } if e.message == "" { return string(e.category) } return fmt.Sprintf("%s: %s", e.category, e.message) } // Unwrap preserves an underlying error identity without exposing its text. func (e *Error) Unwrap() error { if e == nil { return nil } return e.cause } // Category returns the stable classification. func (e *Error) Category() ErrorCategory { if e == nil { return "" } return e.category } // CapacityError adds the safe backend identity to a capacity failure. type CapacityError struct { BackendID string Err *Error } // NewCapacityError returns a classified capacity failure for backendID. func NewCapacityError(backendID string, message string, cause error) *CapacityError { return &CapacityError{BackendID: boundText(backendID, 256), Err: NewError(Capacity, message, cause)} } func (e *CapacityError) Error() string { if e == nil { return "" } if e.BackendID == "" { return e.Err.Error() } return boundText(fmt.Sprintf("%s (backend %q)", e.Err.Error(), e.BackendID), maxErrorMessageBytes) } func (e *CapacityError) Unwrap() error { if e == nil { return nil } return e.Err } // Category returns Capacity for every capacity error. func (e *CapacityError) Category() ErrorCategory { return Capacity } // CategoryOf returns the classification carried by err, including wrapped errors. func CategoryOf(err error) ErrorCategory { var categorized interface{ Category() ErrorCategory } if errors.As(err, &categorized) { return categorized.Category() } return "" }