diff --git a/docs/internal/adapters.md b/docs/internal/adapters.md index 14e787d..4b63d40 100644 --- a/docs/internal/adapters.md +++ b/docs/internal/adapters.md @@ -53,6 +53,11 @@ preparation or generation. formats a safe application-owned effective profile view. It intentionally does not require prompt, schema, or artifact sources. +The `config` family loads the same application ConfigKit result once and uses +its validation, effective-output, source, and explanation views directly. It +does not construct a Promptkit engine. The CLI maps source records into a small +JSON DTO for `sources`; `show` and `explain` use ConfigKit's own writers. + `serve` constructs Scriptorium's restricted HTTP artifact reader, injects it with `promptkit.WithArtifactReader`, passes the engine through the HTTP adapter's consumer-owned `Runner` interface, and starts the server. diff --git a/docs/internal/sources.md b/docs/internal/sources.md index 00531f7..6a46462 100644 --- a/docs/internal/sources.md +++ b/docs/internal/sources.md @@ -49,6 +49,10 @@ Every configuration-loading command uses this boundary. The CLI maps only explicitly visited application flags to ConfigKit overrides and prints structured load diagnostics before engine construction. The old application YAML decoder and merge path have been removed. +Application `config` inspection reuses the result's frozen effective, +provenance, and history views without constructing an engine or reloading +sources. It does not resolve API-key environment values; ordinary backend +parameters remain visible by the selected inspection policy. ## Restricted HTTP Artifact Reader diff --git a/docs/policy/testing.md b/docs/policy/testing.md index b9c90d4..fe021a5 100644 --- a/docs/policy/testing.md +++ b/docs/policy/testing.md @@ -77,6 +77,13 @@ Do not add tests merely because a function, branch, or line exists. Do not add a Test through the narrowest stable boundary that expresses the behavior clearly. +For application configuration, ConfigKit owns exhaustive YAML grammar, +source traversal, import/overlay composition, provenance, and inspection +renderer tests. Scriptorium tests its selected definition, source discovery, +CLI override presence, command prerequisites, runtime conversion, credential +boundary, and representative execution/inspection workflows with the tagged +library. Do not copy the upstream parser test matrix into this repository. + This is often the package API, but it may instead be: - a smaller pure function when dense domain logic is most clearly isolated there; diff --git a/docs/roadmap/configkit.md b/docs/roadmap/configkit.md index ffc4686..91dfa7d 100644 --- a/docs/roadmap/configkit.md +++ b/docs/roadmap/configkit.md @@ -2,11 +2,10 @@ ## Status And Intent -Accepted feature direction, drafted on 2026-09-26; not implemented. This -roadmap defines the target behavior and changes from the reviewed baseline. -The [implementation plan](implementation.md) owns sequencing, concrete interfaces, -verification steps, and implementation status. This document owns the desired -end state and the policy choices behind it. +Implemented feature direction, drafted on 2026-09-26 and delivered through the +[implementation plan](implementation.md). This roadmap records the target +behavior and changes from the reviewed baseline; the implementation plan owns +sequencing, concrete interfaces, and verification evidence. The user is adopting ConfigKit across an application suite to standardize configuration semantics. Scriptorium will make a clean cutover to ConfigKit's @@ -20,7 +19,7 @@ application-specific reason or a genuine ownership boundary, not backward compatibility alone. The [architecture policy](../policy/architecture.md) and [ADR 0004](../adr/0004-definition-boundary.md) remain the scope boundaries. -## Reviewed Baseline +## Reviewed Baseline (Historical) Scriptorium consumes Promptkit v0.11.0. Its `internal/config` package uses yaml.v3 directly and manually applies defaults, file settings, and CLI overrides. @@ -225,3 +224,8 @@ the relevant secret to the API key. Inspection therefore preserves parameter values and environment-reference names, while actual API-key values remain outside the application configuration pipeline. Visibility is intentional; this is not a promise to detect secrets placed inside arbitrary parameter values. + +The delivered `config show` and `config explain` commands use ConfigKit's +inspection writers without secret tags or a second redaction path. Both +effective and historical parameter values remain visible; `api_key_env` names +remain visible while Promptkit alone resolves selected credential values. diff --git a/docs/roadmap/implementation.md b/docs/roadmap/implementation.md index 744bfea..7eea22a 100644 --- a/docs/roadmap/implementation.md +++ b/docs/roadmap/implementation.md @@ -3,10 +3,9 @@ ## Status, Authority, And Historical Plans Planned on 2026-09-26 against Scriptorium commit -`43eea5c7c41d2a8134b3dece60f3af5c98b8fd1c`. No stage is implemented by this -planning change. Execute Stage 1 through Stage 7 in order. Each stage is sized -as one focused gpt-5.6-terra implementation task, including tests and current -documentation. The maintainer has resolved the inspection-policy question: +`43eea5c7c41d2a8134b3dece60f3af5c98b8fd1c`. This document records the +ordered implementation and its completion evidence. The maintainer resolved +the inspection-policy question: `extra_params` remain visible; only API-key values require secret protection. The [feature roadmap](configkit.md) owns scope, target behavior, and user intent. @@ -429,7 +428,12 @@ visible, and explanation neither resolves nor discloses API-key values. ## Stage 7: Verify Integration, Finish Documentation, And Record Release Handoff -**Status: Not started.** +**Status: Complete.** The feature roadmap, command paths, dependency boundary, +documentation, and attribution were audited. Maintained single-file and +composed examples passed configuration inspection and no-provider rendering; +the optional-credential/integer-parameter backend path passed with a local +provider. The full test, race, vet, build, formatting, link, and diff checks +passed. Release preparation is recorded below; no tag or publication occurred. 1. Review the feature roadmap against actual code. Confirm all five original command modes and all four new configuration subcommands use one definition @@ -488,6 +492,37 @@ is not guaranteed reloadable. Include dependency/attribution changes and the existing LICENSE | ATTRIBUTION footer. Preserve the still-relevant Promptkit v0.11 release handoff identified above when preparing a combined next release. +### Implementation And Release Preparation Record + +The ConfigKit v0.4.0 adoption is implemented in committed source. The next +breaking release still needs version selection, version-matched release notes, +candidate validation, tagging, and publication under the release policy; none +has occurred as part of this work. Keep the earlier Promptkit v0.11 handoff +linked above when preparing a combined release. + +The release note should summarize the deliberate configuration migration: +empty files now fail and `{}` means an empty mapping; explicitly empty strings +and zero values override lower values; `null` follows declared nullable types; +strict YAML rejects additional documents, anchors/aliases, unsupported scalar +forms, and unknown fields; `configkit` is reserved and ConfigKit's source and +resource limits apply. Explicit `--config ''` is invalid. Imports are opt-in +per file directive, while `--config-overlay` selects one named overlay under +the selected file's canonical directory; application path *values* remain +process-working-directory-relative. Only explicitly supplied application CLI +flags override configuration. An explicit empty server address is passed to +the standard HTTP server and does not restore Scriptorium's default. + +Document the four application-inspection modes and the selected policy: +`extra_params`, including history, and `api_key_env` reference names remain +visible, but actual API-key bytes are never resolved by ConfigKit. Inspection +output can be sensitive and is not guaranteed reloadable. Application +validation neither probes a provider nor completely validates Promptkit +definitions. Keep request retry overrides and Promptkit's credential rules +separate from application settings. Include the new dependency and attribution +notices, and retain the existing LICENSE | ATTRIBUTION footer. Deferred +secrets bootstrap, schema versioning, reload, and remote acquisition remain +deferred; do not imply source or binary publication has happened. + ## Open Questions None remain for this scope. On 2026-09-26 the maintainer selected no redaction diff --git a/internal/adapter/cli/run_test.go b/internal/adapter/cli/run_test.go index a65d844..6063e6d 100644 --- a/internal/adapter/cli/run_test.go +++ b/internal/adapter/cli/run_test.go @@ -1148,6 +1148,64 @@ backends: } } +func TestConfiguredBackendPreservesIntegerParameterAndOptionalCredential(t *testing.T) { + lib := newCLITestLibrary(t) + writePromptDefinition(t, lib.promptDir, "custom.yaml", `id: custom +version: "1" +default_profile: local-gpu +messages: + - role: user + content: "hello" +output: + format: text + validation_mode: none +`) + if err := os.WriteFile(filepath.Join(lib.profileDir, "local-gpu.yaml"), []byte("id: local-gpu\nbackend: local-gpu\nmodel: local-model\n"), 0o644); err != nil { + t.Fatal(err) + } + const credentialName = "SCRIPTORIUM_OPTIONAL_BACKEND_KEY" + t.Setenv(credentialName, "") + type observedRequest struct { + number json.Number + authorization string + } + observed := make(chan observedRequest, 1) + provider := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + request := observedRequest{authorization: r.Header.Get("Authorization")} + var body map[string]any + decoder := json.NewDecoder(r.Body) + decoder.UseNumber() + if err := decoder.Decode(&body); err == nil { + request.number, _ = body["synthetic_count"].(json.Number) + } + observed <- request + w.Header().Set("Content-Type", "application/json") + _, _ = w.Write([]byte(`{"choices":[{"message":{"role":"assistant","content":"generated"}}]}`)) + })) + defer provider.Close() + configPath := writeAppConfigFile(t, fmt.Sprintf(`prompt_dir: %q +profile_dir: %q +backends: + local-gpu: + endpoint: %q + api_key_env: %s + extra_params: + synthetic_count: 7 +`, lib.promptDir, lib.profileDir, provider.URL+"/v1", credentialName)) + cfg, err := parseRunArgs([]string{"--config", configPath, "--prompt", "custom"}) + if err != nil || len(cfg.backends) != 1 || cfg.backends[0].ExtraParams["synthetic_count"] != int64(7) { + t.Fatalf("ConfigKit integer did not reach runtime settings: cfg=%+v err=%v", cfg, err) + } + code, stdout, stderr := runCLICommand(t, runCommand, []string{"--config", configPath, "--prompt", "custom"}) + if code != ExitOK { + t.Fatalf("backend run failed: code=%d out=%q err=%q", code, stdout, stderr) + } + request := <-observed + if !strings.Contains(stdout, "generated") || request.number != "7" || request.authorization != "" { + t.Fatalf("backend request changed: code=%d out=%q err=%q count=%q auth=%q", code, stdout, stderr, request.number, request.authorization) + } +} + func TestRenderCommandMapsReasoningEffortAndSessionID(t *testing.T) { lib := newCLITestLibrary(t) writePromptDefinition(t, lib.promptDir, "session.yaml", `id: session