11 KiB
Request Validation Implementation Plan
Implement the finalized feature roadmap in the numbered order below. Each stage is scoped for one Luna coding-agent prompt. The feature roadmap owns the target behavior and downstream intent; this document owns the implementation sequence.
Baseline And Implementation Decisions
The baseline is commit 0c3651f. Engine.Prepare in engine.go clears execution
observation state, checks the engine, converts RunRequest, calls
internal/engine.Prepare, maps preparation errors, and converts the returned
internal value into a caller-owned public snapshot. The internal engine already
delegates to internal/usecase.Runner.Prepare, which runs both preparation
phases without admission or generation.
Add exactly this public operation:
func (e *Engine) ValidateRequest(ctx context.Context, req RunRequest) error
Extract the existing facade preparation plumbing into an unexported
(*Engine).prepareRequest helper returning (*domain.PreparedRun, error).
Prepare will call that helper and retain its existing public result conversion;
ValidateRequest will call it and discard the internal result. This shares
request conversion, context handling, nil-engine checks, and error mapping while
avoiding public snapshot copies for validation. The private helper is an
implementation choice, not a consumer contract.
No new internal-engine or runner operation is needed. Internal prepared values, hashing, schema preparation, and rendering may still occur; eliminating them would change the selected preparation-parity behavior or require work outside this small addition.
Working Instructions
- Start with the development guide and read the applicable agent, architecture, testing, and documentation policies. Use the existing facade GoDoc and runner description as current-behavior references.
- Preserve
Prepare's behavior and error wrapping, including its existing context classification. Do not add a generalerrors.Is(err, ctx.Err())guarantee, new error sentinels, aggregated diagnostics, stricter binding rules, provider checks, or capacity checks. - Use the full
RunRequestconversion path, not a separate subset of its fields or a dependency inventory fromInspectRequirements. Leave credential, resource, template, and request-format rules unchanged. - Keep tests offline, deterministic, and external-package at the public boundary. Use real preparation collaborators and small injected doubles for external effects. Test representative forwarding risks rather than reproducing the complete preparation suite.
- Commit each completed stage after its checks and relevant diff review, with a plain-English message. Do not push, tag, or run a publication command as part of this plan.
Stage 1: Share Facade Preparation And Add ValidateRequest
Scope: the small facade change, GoDoc, and basic contract tests.
- Review
Engine.Prepare,toDomainRunRequest,mapPublicError, andfromDomainPreparedRun. Extract only the existing preparation plumbing into the private helper described above. Keep the existing operation order and wrapping at each error boundary; do not change error semantics as incidental cleanup. - Make
Preparecall the helper and perform the existingfromDomainPreparedRunconversion on success. Its signature, snapshot ownership, and failure behavior remain unchanged. - Add
ValidateRequestinengine.go. Return the helper's error, discarding its internal prepared value. Do not call the publicPreparemethod, construct a public snapshot, or introduce an internal validation pipeline. - Add GoDoc explaining that nil means the concrete request passed preparation
now, no LLM call occurs, resource I/O still occurs, and no executable snapshot
is retained. State parity with preparation's errors and context handling,
including the nil-engine
ErrInvalidConfigconvention. Link conceptually toPrepareandPrepareExecution; do not promise subsequent provider success. - Add
validate_request_contract_test.goin packagepromptkit_test. Reuse compatible helpers fromrequirements_inspection_contract_test.goandconsumer_test_helpers_test.gowhere helpful. Construct a synthetic prompt and explicit offline profile rather than depending on catalog model choices. Cover a successful request, a nil engine, a conversion-level invalid request such as an invalid appended-message role, and one missing required input. - In those tests, invoke both
PrepareandValidateRequestfor the same test-controlled request and compare success or the relevant public sentinel througherrors.Is. Use one intentional failure per case rather than expecting a particular error from several independently failing inputs. Inject a counting client and verify neither operation generates a model call. - Format changed Go files and run
go test . ./internal/usecase ./internal/prompt, followed bygit diff --check. ExistingPreparetests must still pass.
Completion: external consumers can call the new error-only method, all facade
preparation plumbing is shared, and the public snapshot conversion remains only
on Prepare's success path.
Completed: 2026-10-12.
Stage 2: Verify Concrete-Request Parity And Resource Boundaries
Scope: focused tests of the finalized roadmap's clarifications.
- Extend the root contract tests with a prompt using
{{if eq .mode "detailed"}}{{input "details"}}{{end}}. For an input not declared required, verifymode=briefsucceeds without it andmode=detailedfails without it but succeeds with an inline binding. Use a second definition with the input declared required to verify absence fails even on the brief branch. Compare each case withPrepare. - Cover an evaluated direct variable lookup with an absent key and with an
explicitly supplied empty string. Include a missing
indexlookup to show its existing optional semantics are retained. These assertions concern preparation only, not nonempty model-output rules. An extra unrelated caller variable must remain acceptable wherePrepareaccepts it. - Add a case with an unreadable supplied input that is not referenced on the
selected branch. A synthetic missing file under
t.TempDir()is sufficient. Both operations must report the applicable artifact-load error, proving validation uses actual preparation instead of a names-only or lazy subset. - Cover representative selected-profile and output-schema preparation failures
with existing valid fixtures: a missing profile should retain
ErrProfileNotFound, and an unavailable selected schema should retain the category already returned byPrepare. Avoid adding source-level or intermediate-configuration validation. - Add one successful request with a profile default and a runtime override that
completes a partial profile, plus a valid direct session ID and appended
message where useful. Inspect
Prepare's result only as needed to establish that the fixture exercised those settings;ValidateRequestmust accept the same request. Existing tests own the exhaustive override and message matrix. - Cover a controlled canceled-context or canceled-loader failure through the
public facade, asserting the existing preparation category for both calls.
Do not assert a stronger universal relationship to
context.Canceledor introduce timing-based tests. Keep the model-call count at zero across the validation cases. - Run
go test -race . ./internal/usecase ./internal/promptandgit diff --check, formatting changed Go files. Review tests for redundant preparation coverage and assertions on incidental error text or private call order.
Completion: success/failure parity includes concrete branch choices, declared required inputs, variable presence, all supplied artifact bindings, representative resource/selection errors, and ordinary request forwarding. No provider or capacity-readiness interpretation has been introduced.
Stage 3: Document Consumer Use And Verify The Integrated Feature
Scope: canonical documentation, the maintained preparation example, and final validation.
- Add a small
ValidateRequestexample todocs/consumers/pkg-promptkit.md, next to requirements inspection and offline preparation. Show a fullRunRequestand nil/error handling, and link to GoDoc for exact behavior. Distinguish concrete-request validation from dependency discovery and from obtaining a prepared execution handle. - Explain that Scriptorium can map the error to its own diagnostics and exit
status, and should build equivalent requests for its
validateandruncommands. Keep this as consumer guidance, not CLI code or a new PromptKit transport contract. Explicitly note that separate subprocess calls prepare independently and cannot transfer the opaque handle. - Update
examples/go-library/prepare/main.goto demonstrate the method using the same request value as its existingPreparedemonstration. Keep its semantic output unchanged. Explain in the consumer guide that showing both operations is illustrative, not a requirement to validate beforePrepareorRun; it performs preparation twice. - Update
docs/internal/runner.mdto describe the error-only facade's reuse of the existing preparation path, and updatedocs/internal/overview.mdto include the implemented public operation. Keep business logic under the existing internal ownership and avoid describing a new validator component. - Ensure GoDoc and guidance state the same boundaries: resource I/O and cost remain, all supplied inputs follow existing loading behavior, only the concrete invocation is checked, errors are not aggregated, and no future provider or execution success is certified. Do not change format references to create new requirements.
v0.14.0is already tagged at planning time; leave that historical note unchanged. Record this optional API addition in the next release notes when that release is prepared. Release publication and new version selection are separate from this implementation plan.- Run
go test -race ./...,go vet ./...,python3 scripts/check-examples.py, andgit diff --check. Check affected documentation links and fragments. Review exported declarations to confirm that onlyEngine.ValidateRequestwas added, and review the complete diff against the feature roadmap.
Completion: API, focused tests, canonical documentation, and the maintained example are coherent; existing preparation and execution workflows remain available; and the integrated checks pass. Report actual check results and stage commits. The completed roadmap can be retired during later release preparation.
Open Questions
None. The finalized roadmap resolves the public name, request shape, result, preparation scope, and downstream boundary required for these stages.