28 KiB
Prompt-Definition Inspection Implementation Plan
Status: Complete.
Purpose
This document is the decision-complete implementation plan for prompt-definition inspection. It is written for a coding agent that will implement each stage in order.
The feature roadmap owns the motivation, consumer workflow, policy choices, compatibility requirements, non-goals, and target end state. This document owns the concrete design, file-level changes, implementation sequence, test ownership, documentation work, validation commands, and completion gates.
Implementation Rules
- Complete the stages in order. Keep the repository compiling and the focused tests passing at every stage boundary.
- Preserve unrelated working-tree changes. In particular, retain the accepted feature roadmap and the corresponding future-catalog and Weatherreporter wishlist edits that may already be uncommitted.
- Follow every policy under
docs/policy/, the task-specific reading guide indocs/development.md, and the accepted behavior inprompt-inspection.md. - Keep the supported API in the root
promptkitpackage. Prompt repositories and internal prompt definitions remain below Go'sinternal/boundary. - Reuse the exact prompt source, ID/version selection, definition
normalization, referenced-content loading, and prompt hashing used by
Prepare,PrepareExecution, andRun. Do not create a second parser, source abstraction, or hash algorithm. - Preserve the observable validation order, error identities, and results of existing preparation and execution methods.
- The new inspection operation must not require a profile, backend, request override, credential, artifact, schema source, renderer, validator, capacity admission, output repairer, or model client.
- Treat structural validation as the existing prompt-repository contract. Do not add template parsing, input media-type enforcement, schema loading, schema compilation, profile validation, source enumeration, or provider connectivity checks.
- Return only the metadata accepted by the feature roadmap. Do not expose raw YAML, prompt descriptions, source paths, message or session templates, cache-control declarations, rendered content, or schema bodies.
- Return caller-owned public values. The input-definition slice must not alias repository, runner, engine, or another result's state.
- Keep tests lean and behavior-focused. Reuse existing prompt-repository and preparation coverage rather than duplicating their complete format and source matrices.
- Update exact contracts in GoDoc with the exported declarations. Update current-state consumer, format, and internal documentation only after the corresponding code exists.
- Do not add release notes, change a module version, create a release, commit, or tag as part of this work.
Fixed Design
Public API
Add these root-package values in types.go immediately after
ProfileInspection:
type PromptInputDefinition struct {
Name string
Required bool
ContentType string
Description string
}
type PromptInspection struct {
PromptID string
PromptVersion string
PromptHash string
DefaultProfileID string
Inputs []PromptInputDefinition
OutputContract OutputContract
}
Add this method to Engine in engine.go, immediately before
InspectProfile:
func (e *Engine) InspectPrompt(
ctx context.Context,
promptID string,
promptVersion string,
) (*PromptInspection, error)
Do not accept a RunRequest, inputs, variables, profile ID, output override,
or options on this method. Exact inspection of one prompt ID and optional
version is the complete operation.
PromptInputDefinition and PromptInspection have no stable JSON contract.
Do not add JSON tags, MarshalJSON, UnmarshalJSON, or custom string
representations. Their nested OutputContract continues to use its existing
stable JSON representation when encoded independently, but that does not make
the enclosing inspection value stable.
The exact type and method GoDoc must establish:
promptIDis required and whitespace-only is invalid;- nonblank
promptIDandpromptVersionvalues are passed to ordinary prompt selection unchanged rather than trimmed or otherwise canonicalized; - lookup is case-sensitive and exact;
- an empty version succeeds only when exactly one definition has the selected ID, while a nonempty version selects one exact ID/version pair;
- the engine's configured prompt source and existing source-option selection are used without merging, fallback, or enumeration;
- a successful result proves that the selected definition and referenced message content files were structurally loaded through the ordinary prompt repository;
- input metadata is returned in definition order and includes name, required status, content type, and description;
DefaultProfileIDis declared metadata only and is not resolved;OutputContractis the normalized declared contract, not a request-level effective override;- a JSON Schema path is returned when declared, but the schema and its references are not loaded or compiled;
PromptHashis the same opaque equality value used byPreparedRun.PromptHashfor the same selected definition and observed source state;- hash spelling, length, encoding, algorithm, and security properties are not public contracts;
- prompt bodies, templates, source paths, schemas, rendered messages, and execution settings are not returned;
- no profile, credential, artifact, rendering, schema, validation, capacity, provider, or model-generation work is performed;
- the returned result and input slice are caller-owned;
- filesystem-backed inspection is a point-in-time lookup and does not freeze a definition for later execution;
- a nil engine matches
ErrInvalidConfig; - a blank prompt ID matches
ErrInvalidRequest; - an absent exact ID or version matches
ErrPromptNotFoundand notErrPromptLoad; - malformed, unreadable, duplicate, ambiguous, referenced-content, or hashing
failures match
ErrPromptLoad; - cancellation observed during lookup matches
ErrPromptLoadwhile preserving the context error througherrors.Is; and - the method returns no partial result on error.
Update the Engine type GoDoc to include InspectPrompt among operations safe
for concurrent calls. Do not imply that mutable injected filesystems or other
collaborators become safe when their own contracts do not provide that
guarantee.
Update doc.go in the same stage:
- include
Engine.InspectPromptin the package operation list; - include prompt inspection in the concurrency and caller-ownership summary;
- list
PromptInputDefinitionandPromptInspectionamong construction and inspection values without stable JSON representations; and - retain the existing statement that exposed hashes are opaque.
Do not add either new value to the stable JSON list.
Internal Result
Add this internal value to internal/domain/domain.go near
PromptDefinition and PromptInput:
// PromptInspection is the resolved result of exact prompt inspection.
type PromptInspection struct {
PromptID string
PromptVersion string
PromptHash string
DefaultProfileID string
Inputs []PromptInput
OutputContract OutputContract
}
Do not add JSON or YAML tags. The internal value is a use-case result, not a source format or persistence contract.
The use-case operation must copy the definition's Inputs slice before
putting it on this result. PromptInput currently contains only scalar fields,
so a new slice with value copies is sufficient.
Shared Prompt Selection And Hashing
Add internal/usecase/prompt_inspection.go. Define one private selection
value:
type resolvedPromptDefinition struct {
definition *domain.PromptDefinition
hash string
}
Add a private runner helper:
func (r *Runner) resolvePromptDefinition(
ctx context.Context,
promptID string,
promptVersion string,
) (*resolvedPromptDefinition, error)
The helper must perform these operations in order:
- reject a blank or whitespace-only
promptIDwithErrInvalidRequest; - pass every nonblank ID and the version to the repository unchanged;
- reject a nil runner or nil prompt repository with
ErrPromptLoadrather than panicking; - call
promptdef.Repository.GetPromptDefinitionexactly once; - wrap every repository error as
ErrPromptLoadwith%wwhile preserving the repository error, includingpromptdef.ErrPromptDefinitionNotFound; - reject a nil definition returned without an error as
ErrPromptLoad; - calculate the equality value with the existing
hashPromptDefinitionfunction exactly once; - classify a hash failure as
ErrPromptLoadusing the existing preparation wording and behavior; and - return the repository-owned definition for internal per-call use and the hash.
Do not trim a nonblank ID or version, clone the complete prompt definition, parse its templates, inspect multiple definitions, load schemas, or resolve the default profile.
Refactor the prompt-loading and hashing block in
Runner.resolvePreparation to call resolvePromptDefinition. Retain the
existing early blank-ID check before direct session normalization so a request
with both a blank prompt ID and an invalid direct session preserves its
current error priority. The shared helper may defensively repeat the blank-ID
check.
After the helper returns, ordinary preparation uses
selection.definition and selection.hash exactly where it currently uses
the repository result and prompt hash. Leave direct-session normalization,
profile selection, target resolution, credentials, output-contract
resolution, artifacts, schemas, rendering, admission, generation, and all
later ordering unchanged.
This refactor must preserve:
- current prompt ID and version selection behavior;
- existing public not-found versus prompt-load classification;
- prompt default-profile selection;
- prompt hashing before any request-specific definition copy or session template clearing;
PreparedRun.PromptHash,RunResult.PromptHash, and prepared-execution behavior; and- all current
Prepare,PrepareExecution,Run, andRunPreparedresults and failure ordering.
Do not change hashPromptDefinition or introduce a second equality
calculation. Its current serialization and SHA-256 implementation remain an
internal mechanism behind the opaque public value.
Internal Inspection Operation
Add this method in internal/usecase/prompt_inspection.go:
func (r *Runner) InspectPrompt(
ctx context.Context,
promptID string,
promptVersion string,
) (*domain.PromptInspection, error)
It performs these operations:
- reject a blank or whitespace-only ID as
ErrInvalidRequest; - if the supplied context is already canceled, return an error wrapping both
ErrPromptLoadandctx.Err()without consulting the repository; - call
resolvePromptDefinitionwith the original ID and version; - allocate and copy the selected definition's input slice in its existing order;
- return the selected definition's normalized ID, version, default profile,
declared
Validationcontract, copied inputs, and shared prompt hash.
The returned OutputContract comes directly from the loaded definition's
normalized Validation value. Do not call resolveOutputContract, because no
request override participates in inspection.
Do not call profile or backend repositories, resolveProfileSelection,
resolveExecutionTarget, credential checks, schema loaders, artifact readers,
renderers, validators, capacity admission, output repair, or the model client.
Do not return the definition itself.
The operation returns no partial result. Repository cancellation that occurs
after lookup begins remains wrapped by ErrPromptLoad through the shared
helper while preserving the underlying context error where the repository
does so.
Root Conversion
Add this conversion in convert.go near
fromDomainProfileInspection:
func fromDomainPromptInspection(
inspection *domain.PromptInspection,
) *PromptInspection
The conversion must:
- return
nilfor a nil internal value; - copy every scalar identity, hash, and default-profile field;
- allocate a new
[]PromptInputDefinitionin the same order and copy every name, required flag, content type, and description; - convert the contract through the existing
fromDomainOutputContract; and - return a non-aliased root value.
Do not expose internal PromptInput or PromptDefinition types at the root
boundary. Do not use serialization as a copying mechanism.
Root Facade And Error Mapping
Engine.InspectPrompt follows the existing inspection facade pattern:
- reject a nil engine or nil runner with
ErrInvalidConfig; - pass the context, ID, and version directly to
Runner.InspectPrompt; - map internal failures through the existing
mapPublicError; and - convert a successful result with
fromDomainPromptInspection.
No request conversion is needed. Do not add a public sentinel or typed error.
The existing error mapping already has the required ordering:
- an underlying
promptdef.ErrPromptDefinitionNotFoundmaps toErrPromptNotFoundbefore the enclosing use-caseErrPromptLoadis considered; - other
usecase.ErrPromptLoadfailures map toErrPromptLoad; and usecase.ErrInvalidRequestmaps toErrInvalidRequest.
Do not reorder or otherwise change errors.go unless a focused public test
proves the current mapping fails a required identity. Preserve underlying
repository and context errors through errors.Is.
Ownership, Consistency, And Concurrency
The internal result has a copied input slice, and the root conversion creates another public slice. A consumer may mutate the result and its inputs without affecting:
- the prompt repository or selected definition;
- the engine;
- a later
InspectPromptcall; Prepare, prepared execution, orRun; or- another result already returned to a caller.
OutputContract and each input element contain scalar values, so no deeper
mutable tree exists in the accepted result shape.
No new mutable engine state, source cache, global registry, lock, or goroutine is needed. Concurrency safety follows from existing repository contracts and per-call result allocation. Inspection does not freeze mutable source state; prepared execution remains the exact snapshot-to-execution workflow.
Test Ownership
Add focused internal tests in
internal/usecase/prompt_inspection_test.go. Reuse package fakes where doing
so remains clearer than adding a new fixture, or define one small counting
prompt repository local to this test file.
The internal tests own:
- blank-ID rejection without repository access;
- a pre-canceled context matching both
ErrPromptLoadand the context error without repository access; - exactly one repository lookup with the original nonblank ID and version;
- a successful result containing normalized identity, hash, default profile, complete ordered input metadata, and the declared output contract;
- successful operation with every non-prompt runner collaborator nil;
- copied inputs across repeated inspections;
- missing-prompt wrapping that retains
promptdef.ErrPromptDefinitionNotFound; - nil repository and nil returned definition defenses; and
- representative preservation of ordinary preparation after it is switched to the shared selection-and-hash helper.
Combine these into a small number of readable behavioral tests. Prompt repository tests remain the owners of YAML discovery, strict decoding, normalization, content-file containment and reading, duplicate selection, and ID/version matrices. Existing preparation tests remain the owners of session, profile, artifact, schema, rendering, credential, and execution ordering.
Add external-package public contract tests in public_contract_test.go. They
own:
- the exported method and result shape through normal Go use;
- successful exact-version inspection from an
fs.FSprompt source with multiple versions; - normalized input and output metadata, including a declared JSON Schema path, without configuring or loading a schema source;
- referenced
content_fileloading without returning its body; - no default-profile resolution, demonstrated by a nonexistent declared profile;
- no model invocation, using an existing deterministic fake client where useful;
- nil-engine, blank-ID, missing exact version, ambiguous omitted version, malformed definition or referenced-content, and pre-canceled public error identities;
- that not-found does not also match
ErrPromptLoad; - caller ownership across repeated inspection and later preparation; and
- equality between
PromptInspection.PromptHashandPreparedRun.PromptHashfor the same prompt and observed source state.
Use a simple executable prompt with an in-memory profile for the hash
equivalence test. Use a separate JSON Schema declaration for the
schema-independent inspection test so Prepare is not needed there.
Do not add JSON golden or round-trip tests because the new inspection values deliberately have no stable JSON contract. Do not duplicate the full prompt-repository parser, source-option precedence, schema-validation, preparation, or public error suites at the root layer.
Existing tests that must continue passing without weakened assertions include:
- prompt-definition filesystem and
fs.FSrepository tests; - prompt-source option replacement tests;
PrepareandRunprompt selection and hashing tests;- direct-session prompt-hash invariance tests;
- prepared-execution frozen-source and details tests;
- public error mapping tests; and
- profile inspection tests.
Documentation Ownership
After the implementation and public tests pass, update current-state documentation:
docs/consumers/pkg-promptkit.md: add a concise task-oriented section after engine construction showingEngine.InspectPrompt, explaining how a consumer can validate declared inputs and output workflow before preparing, and distinguishing it fromInspectProfile,Prepare, and prepared execution. Link to GoDoc for exact fields and errors.docs/formats.md: note that exact prompt inspection uses the same prompt source, strict decoding, content-file resolution, and ID/version selection described by the format reference. Do not redefine the exported method.docs/internal/runner.md: add a shared prompt-selection boundary for inspection and preparation, including shared hashing, and explain that inspection stops before every execution-dependent collaborator.docs/internal/sources.md: record that exact prompt inspection performs one point-in-time prompt lookup, validates referenced message content through the repository, and does not parse templates or read profile, input, or schema sources.docs/internal/overview.md: add prompt inspection to the existing root facade andinternal/usecaseresponsibility descriptions. Do not add a new component or package row.docs/roadmap/future.md: remove the statement that prompt inspection is in active planning and leave structured capacity errors intact.docs/roadmap/weatherreporter-promptkit-wishlist.md: change the prompt inspection disposition from accepted planning to implemented behavior, link to durable consumer guidance or GoDoc, and remove or condense proposed API detail that would compete with the implemented declarations.docs/roadmap/prompt-inspection.md: change its status toCompleteonly after code, tests, current-state documentation, and full validation are complete.docs/roadmap/implementation.md: change its status toCompleteonly after every completion gate in this plan is satisfied.
The accepted feature roadmap already describes only purpose, scope, policy, and target end state; it contains no stage sequence to remove during this planning pass.
Do not update release guidance in this feature implementation. A later release pass decides whether the additive API warrants a supplemental release document.
Stage 1: Share Prompt Selection And Implement Internal Inspection
Objective
Add the internal inspection result and operation, share exact prompt loading and hashing with ordinary preparation, and prove the internal behavior without publishing the root API yet.
Implementation Prompt
Implement only Stage 1 of
docs/roadmap/implementation.md. Read the complete feature roadmap,
implementation rules, and fixed design above before editing.
- Add
domain.PromptInspectiontointernal/domain/domain.gowithout source or serialization tags. - Add
internal/usecase/prompt_inspection.gowithresolvedPromptDefinition,resolvePromptDefinition, andRunner.InspectPromptexactly as specified. - Refactor
Runner.resolvePreparationininternal/usecase/runner.goto use the shared prompt selection and hash while preserving existing validation and execution ordering. - Add lean behavioral tests in
internal/usecase/prompt_inspection_test.go. - Run the focused validation below and repair regressions before ending the stage.
Do not add the root public type or method, update current-state documentation, or alter prompt formats, template behavior, profile selection, schema handling, capacity, validation, model-client, or provider behavior in this stage.
Focused Validation
Run:
gofmt -w internal/domain/domain.go \
internal/usecase/prompt_inspection.go \
internal/usecase/prompt_inspection_test.go \
internal/usecase/runner.go
go test ./internal/usecase ./internal/promptdef
go test ./internal/usecase -run \
'TestRunner(InspectPrompt|Prepare|Run|PrepareExecution|RunPrepared)'
go vet ./internal/usecase ./internal/promptdef
If actual existing test names do not match the focused expression, run the smallest truthful package or expression that covers the listed behavior rather than weakening or skipping assertions.
Completion Gate
Stage 1 is complete only when:
- inspection loads and hashes one exact prompt through the configured repository;
- prompt selection and hashing are shared with ordinary preparation;
- nonblank ID and version values reach the repository unchanged;
- successful inspection needs no non-prompt runner collaborator;
- inputs are copied and no definition or content body escapes;
- cancellation and load failures preserve the required internal identities;
- ordinary preparation and execution behavior remains unchanged; and
- no root public API or current-state documentation claims the feature yet.
Stage 2: Publish The Root Facade And Public Contract
Objective
Expose the minimal caller-owned inspection API through Engine, preserve
public error and JSON compatibility, and protect the consumer-visible
contract.
Implementation Prompt
Implement only Stage 2 of
docs/roadmap/implementation.md after Stage 1 satisfies its completion gate.
Re-read the fixed public API, conversion, error, ownership, and test sections
before editing.
- Add
PromptInputDefinitionandPromptInspectionwith exact GoDoc totypes.goimmediately afterProfileInspection. Do not add JSON tags. - Add
fromDomainPromptInspectiontoconvert.gousing a newly allocated input slice and the existing output-contract converter. - Add
Engine.InspectPromptand exact GoDoc toengine.goimmediately beforeInspectProfile. - Update
EngineGoDoc to include concurrent prompt inspection. - Update package GoDoc in
doc.gofor operation discovery, concurrency, ownership, opaque hashes, and non-stable JSON classification. - Add compact external-package contract coverage in
public_contract_test.go, reusing existing fixtures and fakes where they remain clear. - Confirm that existing
errors.gomapping meets the plan; do not change it unless a required public identity test fails for a genuine mapping reason. - Run the focused validation below and repair regressions before ending the stage.
Do not add enumeration, source validation across a corpus, request overrides, template or schema output, profile resolution, JSON stability, caching, new errors, or current-state prose documentation in this stage.
Focused Validation
Run:
gofmt -w doc.go types.go convert.go engine.go public_contract_test.go
go test .
go test ./internal/usecase ./internal/promptdef
go test . -run \
'Test(InspectPrompt|.*Prompt.*PublicError|.*Prompt.*Contract)'
go vet .
go build .
If the repository's actual focused test names differ, use the implemented test names rather than weakening or skipping the intended assertions.
Completion Gate
Stage 2 is complete only when:
- a consumer can call
Engine.InspectPromptthrough the root package; - exact ID/version selection matches ordinary preparation;
- the result contains only accepted identity, input, default-profile, output-contract, and opaque equality metadata;
- no prompt body, schema body, profile, credential, or execution setting is exposed or resolved;
- nil, blank, missing, malformed, ambiguous, referenced-content, and cancellation errors have the required public identities;
- not-found remains distinct from prompt-load failure;
PromptHashmatches ordinary preparation for the same source state;- repeated calls and caller mutation cannot alter engine-owned state;
- no stable JSON or new error contract was introduced; and
- existing public preparation, execution, profile-inspection, and stable JSON tests remain unchanged and passing.
Stage 3: Update Documentation And Validate The Repository
Objective
Make implemented prompt inspection discoverable in its canonical documentation, reconcile temporary roadmap state, and complete full repository validation.
Implementation Prompt
Implement only Stage 3 of
docs/roadmap/implementation.md after Stages 1 and 2 satisfy their completion
gates.
- Update
docs/consumers/pkg-promptkit.md,docs/formats.md,docs/internal/runner.md,docs/internal/sources.md, anddocs/internal/overview.mdaccording to the documentation ownership section above. - Update the future catalog and Weatherreporter wishlist disposition so they no longer describe prompt inspection as merely accepted work.
- Check every changed Markdown link and confirm its file and heading target.
- Run the full validation sequence below.
- Only after every check passes, set the feature roadmap and this
implementation plan to
**Status:** Complete. - Re-run
git diff --checkafter the status edits.
Do not add release notes, a new example program, a new public package, or a duplicate API reference. Keep detailed contracts in GoDoc and task-oriented usage in the consumer guide.
Full Validation
Run from the repository root:
gofmt -w internal/domain/domain.go \
internal/usecase/prompt_inspection.go \
internal/usecase/prompt_inspection_test.go \
internal/usecase/runner.go \
doc.go types.go convert.go engine.go public_contract_test.go
gofmt -l $(git ls-files '*.go')
go test ./...
go test -race ./...
go vet ./...
go build ./...
go run ./examples/go-library/prepare
git diff --check
git status --short
The gofmt -l command must print no paths. The maintained example must remain
offline and must not require a real credential or provider.
Inspect the final diff and confirm:
- only files required by this feature and pre-existing user changes are present;
- no credentials, private source content, workspace files, local replacements, generated binaries, or unrelated formatting changes were added;
- the public declarations and GoDoc own exact API behavior;
- current-state documentation describes only implemented behavior and links to canonical owners;
- roadmap documents contain scope or completion status rather than a duplicate current API reference; and
- no release, commit, or tag was created.
Completion Gate
The implementation is complete only when:
- every Stage 1 and Stage 2 gate remains satisfied;
- the complete ordinary and race-enabled suites pass;
- vet, build, formatting, the maintained offline example, Markdown links, and whitespace checks pass;
- consumer, format, internal, future, and wishlist documentation are consistent with the implemented boundary;
- both roadmap statuses are
Complete; - the working tree contains no unintended files or changes; and
- the repository is ready for maintainer review without a commit or release having been created by this plan.
Open Questions
None. The accepted feature roadmap and the fixed decisions above fully specify the implementation boundary.