diff --git a/docs/consumers/pkg-promptkit.md b/docs/consumers/pkg-promptkit.md index d160420..2dac3ab 100644 --- a/docs/consumers/pkg-promptkit.md +++ b/docs/consumers/pkg-promptkit.md @@ -154,6 +154,50 @@ rendered messages, and use prepared execution when that work must remain tied to later execution. The method's [GoDoc](../../engine.go) owns exact fields, hash, ownership, and error semantics. +## Inspect Requirements Before Preparation + +Use [`Engine.InspectRequirements`](../../requirements.go) when the application +needs the prompt's recognizable input and caller-variable dependencies for a +particular context before it has input artifacts. Unlike `InspectPrompt`, it +also walks captured template syntax and selects a profile identity; unlike +`Prepare`, it does not load artifacts or render messages. + +```go +requirements, err := engine.InspectRequirements(ctx, promptkit.RequirementInspectionRequest{ + PromptID: "meeting.summary", + Vars: map[string]string{"audience": "project leads"}, +}) +if err != nil { + return err +} + +for _, input := range requirements.Inputs { + if input.Kind == promptkit.RequirementRequired { + // Plan to bind this input before preparation. + } + if input.Kind == promptkit.RequirementOptional && input.Explanation != "" { + // A recognizable reference may occur only on a known skipped path. + } +} +for _, variable := range requirements.Variables { + if variable.Kind == promptkit.RequirementRequired && !variable.Supplied { + // The key is required even though this inspection did not receive it. + } +} +if !requirements.Complete { + // Preserve the partial inventory and account for its discovery issues. +} +``` + +Optional entries can include recognizable inputs and variables found only in +inactive branches. A missing required variable is reported separately from +discovery completeness: missing a value used only for direct text substitution +does not by itself make the inventory incomplete. Conversely, an unresolved +dependency-bearing branch can make discovery incomplete while retaining known +conditional entries. Do not treat an empty partial inventory as proof that no +input bindings are needed. See the method's [GoDoc](../../requirements.go) for +the exact classifications, supported syntax, ownership, and error contract. + ## Prepare Without Model Execution [`Engine.Prepare`](../../engine.go) resolves the selected prompt and profile, diff --git a/docs/formats.md b/docs/formats.md index 6d8cf67..f0f9267 100644 --- a/docs/formats.md +++ b/docs/formats.md @@ -124,6 +124,12 @@ Optional `session_id` uses the same template variables and input helper. Its rendered value is trimmed, omitted if empty, and limited to 256 Unicode code points. A nonblank direct request session ID bypasses the template. +`InspectRequirements` can enumerate recognizable dependencies from captured +templates before artifact content is available. Its conservative syntax +coverage does not change template validity or rendering; see the [consumer +guide](consumers/pkg-promptkit.md#inspect-requirements-before-preparation) and +the method's [GoDoc](../requirements.go) for that inspection boundary. + ### Cache Control Optional `cache_control` has required `type: ephemeral` and optional `ttl`, diff --git a/docs/roadmap/implementation.md b/docs/roadmap/implementation.md index d20bb1c..c0e2779 100644 --- a/docs/roadmap/implementation.md +++ b/docs/roadmap/implementation.md @@ -416,6 +416,13 @@ suite solely for this unit; commit. ## Stage 7 — Document And Verify The Assembled Feature +**Status: Complete (2026-10-08).** GoDoc, consumer guidance, framework-format +cross-references, and the maintained preparation example now describe the +implemented discovery boundary. Final offline verification passed: +`go test -race -vet=off -mod=readonly -count=1 ./...`, +`go vet -mod=readonly ./...`, and `python3 scripts/check-examples.py`, with +`GOTOOLCHAIN=local GOWORK=off GOPROXY=off GONOPROXY=none GOSUMDB=off`. + **Outcome:** Implemented semantics and analysis limits are documented in their canonical owners, with final verification ready for user review. diff --git a/docs/roadmap/inspection.md b/docs/roadmap/inspection.md index 70ce6b0..e77cc7a 100644 --- a/docs/roadmap/inspection.md +++ b/docs/roadmap/inspection.md @@ -1,8 +1,10 @@ # Effective Prompt Requirement Inspection -Status: Implementation planned; the feature is not implemented. The -[implementation plan](implementation.md) owns the ordered implementation steps. -This document owns the purpose, scope, and target end state. +Status: Implemented (2026-10-08). The +[implementation plan](implementation.md) records the completed work units and +final verification. Current public API details live in GoDoc; the consumer +guide, format reference, and internal documents link to those canonical +contracts. ## Purpose @@ -16,7 +18,7 @@ The originating request is [Scriptorium's feature request](scriptorium-feature-r Promptkit supplies application-neutral inspection; consumers own presentation, binding checks, and their own workflows. -## Current Baseline +## Original Review Baseline At review baseline `b595dca`: @@ -183,28 +185,19 @@ metadata. A directly resolved input identifier may equal a supplied variable value; reporting that binding name is necessary, without echoing its originating expression or variable map. -## Work Needed From The Baseline +## Implemented Outcome -Add request/result values and the engine method through the root facade. -Keep selection coordination internal and place syntax discovery with the -existing prompt-template component. Reuse captured templates and selection -helpers rather than creating another preparation path. +The root API selects a captured prompt and profile identity, then returns +caller-owned input and variable inventories with conservative classifications +and structured discovery limitations. The internal prompt walker uses captured +template syntax and effective metadata; inspection does not enter artifact, +schema, credential, rendering, provider, or execution preparation. -Capture required metadata presence at the final ConfigKit handoff and retain it -alongside inspection snapshots. Apply [ADR 0001](../adr/0001-configuration-boundary.md): -interpret final effective definitions, not raw/intermediate configuration or -history. Operator provenance remains a separate explicit API. - -Implement a small syntax walker, simple context tests, classification -aggregation, and safe limitation reporting. Keep request-specific state separate -from captured definitions. Focus tests on representative enumeration, -classifications, scope mistakes, selection, capture/ownership, and absence of -execution effects, under the [testing policy](../policy/testing.md). Do not test -a general interpreter or repeat ConfigKit's source-loading matrix. - -When implementation lands, update GoDoc, the consumer guide, relevant format -cross-references, internal documents, and a maintained example. Canonical owners -document the implemented boundary; release notes summarize rather than own it. +The final verification and the commit sequence are recorded in the +[implementation plan](implementation.md). The exact public contract is owned by +[GoDoc](../../requirements.go); consumer usage, framework-format cross-reference, +and internal selection/source descriptions are maintained in their respective +documents. ## Acceptance Outcomes diff --git a/examples/go-library/prepare/main.go b/examples/go-library/prepare/main.go index b2d14aa..2129c70 100644 --- a/examples/go-library/prepare/main.go +++ b/examples/go-library/prepare/main.go @@ -36,6 +36,17 @@ func main() { if _, found := view.Explain(configkit.Keys("profiles", "offline-example", "model")); !found { exit(fmt.Errorf("configured profile model was not found")) } + requirements, err := engine.InspectRequirements(context.Background(), promptkit.RequirementInspectionRequest{ + PromptID: "example.prepare", + }) + if err != nil { + exit(err) + } + if !requirements.Complete || len(requirements.Inputs) != 1 || + requirements.Inputs[0].Name != "note" || + requirements.Inputs[0].Kind != promptkit.RequirementRequired { + exit(fmt.Errorf("required note input was not discovered before preparation")) + } prepared, err := engine.Prepare(context.Background(), promptkit.RunRequest{ PromptID: "example.prepare", diff --git a/requirements.go b/requirements.go index f78a3f3..3fadb32 100644 --- a/requirements.go +++ b/requirements.go @@ -10,43 +10,74 @@ import ( // RequirementInspectionRequest selects a prompt and supplies caller context // for requirement discovery. It requires no artifact bindings or credentials. type RequirementInspectionRequest struct { - PromptID string + // 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 string - Vars map[string]string - SessionID 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; values are not returned in the inspection. + 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. +// caller variable for the supplied context. It is independent of whether a +// caller-variable value is present. type RequirementKind string const ( - // RequirementRequired identifies a declaration or definite access. + // RequirementRequired identifies an input declared required or a dependency + // reached by a recognized definite access. RequirementRequired RequirementKind = "required" - // RequirementConditional identifies a dependency whose reachability is - // unresolved for the supplied context. + // 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 reference used only on a known skipped path. + // 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 string - Kind RequirementKind - Declared bool + // 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 string - Kind RequirementKind - Supplied bool + // 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 } @@ -54,31 +85,67 @@ type VariableRequirement struct { type RequirementIssueCode string const ( - RequirementIssueMissingVariable RequirementIssueCode = "missing_variable" - RequirementIssueInputContent RequirementIssueCode = "input_content" - RequirementIssueDynamicInputName RequirementIssueCode = "dynamic_input_name" + // 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 RequirementIssueCode - InputName string + // 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 string + // Description is a safe human-readable summary, not a string to parse. + Description string } // RequirementInspection contains discovered dependencies for one selected -// prompt context. Complete describes discovery, not renderability or readiness. +// 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 string + // PromptID is the selected exact prompt ID. + PromptID string + // PromptVersion is the selected exact prompt version. PromptVersion string - ProfileID string - Inputs []InputRequirement - Variables []VariableRequirement - Complete bool - Issues []RequirementIssue + // 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 @@ -89,6 +156,14 @@ type RequirementInspection struct { // 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. +// // 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 @@ -98,7 +173,9 @@ type RequirementInspection struct { // 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. A nil Engine matches ErrInvalidConfig. +// 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,