package promptkit import ( "context" "gitea.maximumdirect.net/eric/promptkit/internal/domain" "gitea.maximumdirect.net/eric/promptkit/internal/usecase" ) // RequirementInspectionRequest selects a prompt and supplies caller context // for requirement discovery. It requires no artifact bindings or credentials. type RequirementInspectionRequest struct { // PromptID is the exact, case-sensitive configured prompt ID. PromptID string // PromptVersion selects an exact version. An empty value uses the prompt // selection rule that requires exactly one configured version. PromptVersion string // ProfileID selects the exact effective profile identity. If empty, the // prompt's default profile is used; the profile need not be ready for // execution. ProfileID string // Vars supplies string values for caller-variable lookups and simple // conditions. Presence is determined by exact map key, including an empty // value. Inspection reports caller keys and presence, not their values. // A supplied value may nevertheless appear as a resolved input identifier. Vars map[string]string // SessionID, when nonblank after the normal session-ID normalization, // bypasses the prompt's session template during discovery. SessionID string } // RequirementKind describes how a selected prompt may depend on an input or // caller variable for the supplied context. It is independent of whether a // caller-variable value is present. type RequirementKind string const ( // RequirementRequired identifies an input declared required or a dependency // reached by a recognized definite access. RequirementRequired RequirementKind = "required" // RequirementConditional identifies a dependency whose reachability or // identity remains unresolved for the supplied context. RequirementConditional RequirementKind = "conditional" // RequirementOptional identifies a dependency known not to be required, // including a nonrequired declaration, optional lookup, or reference used // only on a known skipped path. RequirementOptional RequirementKind = "optional" ) // InputRequirement describes a declared or template-referenced prompt input. type InputRequirement struct { // Name is the exact input identifier. Name string // Kind is the strongest classification established for this name. Kind RequirementKind // Declared reports whether the name appears in the effective input // declarations; helper-only references have Declared false. Declared bool // ContentType preserves effective metadata presence: nil means absent or // null, while a non-nil pointer may refer to an empty string. ContentType *string // Description preserves effective metadata presence independently of // Explanation, including an explicitly empty string. Description *string // Explanation is safe human-readable context, such as why a reference is // optional because it occurs only on a known skipped path. It is not a // machine-readable contract. Explanation string } // VariableRequirement describes a caller-map key referenced by a prompt. type VariableRequirement struct { // Name is the exact caller-map key, not a template-local name. Name string // Kind is the strongest classification established for this key. Kind RequirementKind // Supplied reports exact-key presence in the request Vars map. An empty // string is supplied; the value itself is not returned. Supplied bool // Explanation is safe human-readable context and is not a machine-readable // contract. Explanation string } // RequirementIssueCode identifies a limitation that can affect discovery. type RequirementIssueCode string const ( // RequirementIssueMissingVariable means an absent caller value prevents // resolving a dependency decision or input name. RequirementIssueMissingVariable RequirementIssueCode = "missing_variable" // RequirementIssueInputContent means unavailable input content affects // dependency discovery. RequirementIssueInputContent RequirementIssueCode = "input_content" // RequirementIssueDynamicInputName means a referenced input name cannot be // determined for the supplied context. RequirementIssueDynamicInputName RequirementIssueCode = "dynamic_input_name" // RequirementIssueUnsupportedConstruct means a dependency-relevant // expression or scope is outside the supported discovery analysis. RequirementIssueUnsupportedConstruct RequirementIssueCode = "unsupported_construct" ) // RequirementIssue describes one discovery limitation using safe summary // text. Descriptions are not intended as machine-readable details. type RequirementIssue struct { // Code is the machine-readable limitation category. Code RequirementIssueCode // InputName identifies an affected input when one is known; empty means no // particular input was identified. InputName string // VariableName identifies an affected caller key when one is known; empty // means no particular key was identified. VariableName string // Description is a safe human-readable summary, not a string to parse. Description string } // RequirementInspection contains discovered dependencies for one selected // prompt context. Input and variable entries are sorted by exact name. Issues // are deduplicated by code and associated names, then sorted by that tuple. // Their descriptions and entry explanations are safe summaries, not // machine-readable details. // // Complete describes the supported analysis's discovery coverage, not whether // requirements are supplied, templates will render, artifacts or schemas are // valid, or execution is ready. A complete result can still contain required // inputs or missing required variables. Valid templates using dependency- // relevant constructs outside the deliberately small recognition scope may // produce useful partial inventories with Complete false; this is not a // template-definition error or an authoring prohibition. type RequirementInspection struct { // PromptID is the selected exact prompt ID. PromptID string // PromptVersion is the selected exact prompt version. PromptVersion string // ProfileID is the selected profile identity; it does not indicate // execution readiness. ProfileID string // Inputs contains declared inputs and recognizable template references, // including entries known to be optional. Inputs []InputRequirement // Variables contains recognizable caller-map keys, including keys used // only on known skipped paths. Variables []VariableRequirement // Complete reports discovery coverage, not requirement satisfaction or // execution readiness. Complete bool // Issues contains deduplicated, structured discovery limitations. Issues []RequirementIssue } // InspectRequirements discovers inputs and caller variables for one selected // prompt before its input artifacts exist. It uses exact, case-sensitive // prompt selection and requires a profile ID either in the request or the // selected prompt's default. The selected profile establishes identity only; // it need not be execution-ready. A normalized nonblank SessionID bypasses the // captured session template. Vars are copied before analysis, and returned // slices and metadata pointers belong to the caller. // // Discovery recognizes literal input names, direct caller-map keys, literal- // key root-map lookups, simple caller-derived input names, and if/with // conditions based on direct values, literals, negation, or two-string // equality/inequality. It classifies recognizable references on known skipped // paths as optional. Other dependency-relevant expressions or scopes may leave // a useful partial result with Complete false. The returned inventories do // not reject otherwise valid templates or prohibit their use. // // Results exclude artifact payloads, prompt contents, and unrelated supplied // values. Resolved input identifiers may equal the caller values that supply // them; those identifiers are intentionally included in the inventory. // // This method does not perform artifact, schema, credential, rendering, // validation, capacity, observation, provider, or execution work. Complete // describes dependency discovery, not whether every dependency is supplied or // the prompt is ready to execute. Discovery limitations return a partial // inspection and no error; selection and operational failures return no // inspection. Blank prompt IDs, invalid session IDs, and absent profile // selection match ErrInvalidRequest; an absent profile selection also matches // ErrProfileRequired. Missing prompt/profile IDs match ErrPromptNotFound or // ErrProfileNotFound. Other selection/snapshot failures match ErrPromptLoad or // ErrProfileLoad. When cancellation prevents lookup or analysis, no inspection // is returned and the context error remains discoverable with errors.Is. A nil // Engine matches ErrInvalidConfig. func (e *Engine) InspectRequirements( ctx context.Context, req RequirementInspectionRequest, ) (*RequirementInspection, error) { if e == nil || e.internal == nil { return nil, ErrInvalidConfig } inspection, err := e.internal.InspectRequirements(ctx, usecase.RequirementInspectionRequest{ PromptID: req.PromptID, PromptVersion: req.PromptVersion, ProfileID: req.ProfileID, Vars: copyStringMap(req.Vars), SessionID: req.SessionID, }) if err != nil { return nil, mapPublicError(err) } return fromDomainRequirementInspection(inspection), nil } func fromDomainRequirementInspection(inspection *domain.RequirementInspection) *RequirementInspection { if inspection == nil { return nil } result := &RequirementInspection{ PromptID: inspection.PromptID, PromptVersion: inspection.PromptVersion, ProfileID: inspection.ProfileID, Complete: inspection.Complete, Inputs: make([]InputRequirement, len(inspection.Inputs)), Variables: make([]VariableRequirement, len(inspection.Variables)), Issues: make([]RequirementIssue, len(inspection.Issues)), } for index, input := range inspection.Inputs { result.Inputs[index] = InputRequirement{ Name: input.Name, Kind: RequirementKind(input.Kind), Declared: input.Declared, ContentType: copyRequirementString(input.ContentType), Description: copyRequirementString(input.Description), Explanation: input.Explanation, } } for index, variable := range inspection.Variables { result.Variables[index] = VariableRequirement{ Name: variable.Name, Kind: RequirementKind(variable.Kind), Supplied: variable.Supplied, Explanation: variable.Explanation, } } for index, issue := range inspection.Issues { result.Issues[index] = RequirementIssue{ Code: RequirementIssueCode(issue.Code), InputName: issue.InputName, VariableName: issue.VariableName, Description: issue.Description, } } return result } func copyRequirementString(value *string) *string { if value == nil { return nil } copy := *value return © }