Files
audita/docs/roadmap/implementation.md

30 KiB
Raw Blame History

Implementation Plan: PromptKit v0.12.0 Adoption

Last updated: 2026-10-03 Status: In progress; Stages 1–2 completed. Audience: Luna coding agent.

Scope and Execution

Implement the accepted target in the PromptKit roadmap. That document owns feature intent and policy choices; this plan owns the ordered implementation work. The planning baseline is Audita commit b31955fc97adec707b4e5f9a1b0c43c4cbc66750: ConfigKit v0.5.0 is adopted, PromptKit is v0.11.2, and the finalized roadmap includes the selected-profile, operator-override, example-layout, and major-release clarifications.

This replaces the completed ConfigKit plan at this path. Its completed implementation record remains available for readers following the ConfigKit completion roadmap.

Execute Stage 1 through Stage 9 in order. Each is sized as one bounded Luna implementation prompt. Commit completed units with short, plain English messages under the agent policy; no approval round is needed between stages. Record actual checks and any remaining expected migration failures. Stages 1 and 2 change formats before Stage 3 changes their consumer. Intermediate commits need not be runnable or release-ready. Do not add temporary readers, compatibility shims, fallback engines for execution, or scaffolding to make them so. Later stages complete the same migration rather than separate product modes.

Start with development guidance and the applicable agent, architecture, testing, documentation, and OSS policies. Use graph-first discovery. Read the current configuration, LLM runtime, prompt registry, and LLM integration contracts for the established behavior this migration preserves.

Target the released PromptKit v0.12.0 at commit 4b8169da286917fde7875425bb32b799b12c6af3, ConfigKit v0.5.0, and both maintained /v3 catalogs at v3.20260930.0. Read the pinned format reference and migration guide. The sibling checkout is reference material, not a dependency replacement.

Selected Implementation Decisions

  • Keep Go 1.25.5, application version: 2, current flags, role selection, application overlays, and main-file-relative path rules. Retain the profiles_dir name and default; broaden its accepted documents as agreed.
  • Use PromptKit's catalog and owned defaults, then caller underlays for embedded Audita prompts and operator directory inputs, then Sources.Main for application backend settings. Directory inputs compose strictly within their group; discovery order supplies no field precedence. Keep imports disabled.
  • Use one ConfigKit mapping for the final application source. For each application backend, supply its endpoint, concurrency_limit, queue_capacity, and explicit api_key_env: null. The null suppresses lower-tier credential lookup. Leave other supported provider fields available to native layering. Do not put resolved credential values in this mapping.
  • Keep selected-role registration, endpoint-match, competing-credential, and required-key checks. Add explicit nonblank effective model and endpoint checks for both selected roles. Valid unselected partial profiles remain permitted.
  • Preserve the adapter's complete per-call schemas, configured repairs/retries, operation context, shared variables, observation, and prepare/check/run flow. Operator messages can omit shared-variable references; do not append the shared text unconditionally or require particular message counts or roles beyond PromptKit's own format rules.
  • Use captured source inspection for consumed host configuration and message paths. Keep source-relative resource resolution with PromptKit and ConfigKit; Audita projects paths for its existing output collision checks.
  • Replace the old profile-reload test expectation with snapshot behavior. Successful construction uses only final effective definitions. A failed construction may make an embedded-only diagnostic construction to distinguish asset defects from operator errors; it never executes or substitutes for the requested configuration.
  • Maintained example roots are examples/profiles/local/ and examples/profiles/service/. Service testing profiles stay in the service root. Keep the literal temperature: 0 in examples as an explicit outbound setting, and use native presence semantics without a compatibility translator.

This unit is an intentional major-release migration. Exact version selection, release notes, tag/push, and publication timing remain release preparation under the release policy. Guided recovery, new report formats, hot reload, extra configuration commands, and scheduling changes remain outside this plan. Tests should protect changed integration behavior and established contracts, not reproduce the libraries' entire validation matrices.

Stage 1 — Convert Embedded Prompt Definitions

Primary files: The seven YAML files under internal/prompts/assets/prompts/.

  1. Wrap each existing definition beneath prompts, its exact current ID, and the string version v1. Remove the old inner id and version fields. Keep filenames and Audita registry identifiers unchanged; filenames are asset locations rather than PromptKit identities.
  2. Preserve messages, block-scalar text, variable references, task examples, output settings, and schema paths. Preserve the applicable references to HardeningPolicy, ProposalContract, and ValidationContract. Do not edit shared fragments, response schemas, or domain policies to accommodate the new container format.
  3. Review each before/after definition structurally, including YAML indentation inside block scalars. Do not introduce default profile selections or new repair counts. The adapter continues to own the runtime output contract.

Verification and completion: Check all seven migrated identities and version keys against RegisteredMetadata, and run git diff --check. Registry preparation tests become runnable with the new constructor in Stage 3. Commit the asset conversion; do not introduce an old-format reader to test it here.

Completion marker: Stage 1 complete. All seven registered prompt assets use keyed prompt/version mappings; parsed definitions were structurally checked against their prior content, and git diff --check passed.

Stage 2 — Migrate Operator Profiles, Example Roots, and YAML Fixtures

Primary files: examples/profiles/, the three maintained configuration examples, and profile-producing test fixtures in internal/framework/llm/{runtime_inspection,adapter,adapter_capacity}_test.go, internal/framework/proposal_generation/adapter_integration_test.go, internal/framework/validators/llm_integration_test.go, internal/framework/runner/cancellation_test.go, and cmd/audita/main_integration_test.go.

  1. Move local proposal/validation examples beneath profiles/local/; move the four service proposal/validation and testing files beneath profiles/service/. Convert each to profiles: {<ID>: <fields>} in readable YAML. Keep IDs, models, positive timeouts, and controls, including explicit zero temperature. No example needs a real credential or live model.
  2. Set profiles_dir: profiles/local in examples/minimal-config.yml and examples/configkit-inheritance.yml; set profiles_dir: profiles/service in examples/production-config.yml. Preserve the testing overlay's role IDs and the existing ConfigKit inheritance example. Select the leaf root, since recursive discovery at their common parent would still load both groups.
  3. Convert profile YAML emitted by the listed fixtures to keyed definitions. Search for remaining old profile construction strings and removed base_profile syntax, and migrate any genuine profile fixture found. Do not rewrite transcript/proposal id fields or domain schema fixtures. Keep test source names independent of profile IDs where already exercised.
  4. Keep negative tests negative for their selected behavioral reason. Give cases clean temporary layouts or replace their contribution completely rather than leaving an unrelated invalid definition behind for later cases. Eager validation now reads unselected final definitions too. Do not remove such definitions merely to simulate the old lazy loader where the test is intended to exercise eager validation.

Verification and completion: Review the six profiles, explicit root selections, and changed fixture text; run git diff --check and format touched Go test files. These formats become consumable in Stage 3. Commit this unit. Current documentation links and recipes are updated together in Stage 8.

Completion marker: Stage 2 complete. Six maintained profile documents are keyed and split into separately selected local/service roots; the listed Go fixtures use keyed definitions. Touched Go files were formatted and git diff --check passed. Runtime tests are deferred until Stage 3 replaces the current consumer, as the plan allows intermediate commits not to run.

Stage 3 — Adopt the Released Source API and Selected-Role Checks

Primary files: go.mod, go.sum, internal/framework/llm/runtime.go, and internal/prompts/registry_test.go.

  1. Upgrade with go get gitea.maximumdirect.net/eric/promptkit@v0.12.0 and reconcile the module files. Verify ConfigKit stays v0.5.0, the catalog dependencies become /v3 at v3.20260930.0, and obsolete /v2 requirements are removed when unused. Retain the Go minimum and unrelated selections; introduce no local replacements.
  2. Resolve profiles_dir exactly as the current runtime does. Obtain embedded inputs with pk.FilesystemInputs and operator inputs with pk.DirectoryInputs. An unavailable selected root is an operator error; an empty directory can contribute no definitions. Do not require one profile per file, IDs matching filenames, or profile-only top-level content.
  3. Construct the agreed pk.ConfigurationSources: two ordered caller underlays for embedded and operator groups, and one highest-priority application ck.Input whose source is the mapping specified above. Retain pk.WithSchemaFS, the supplied HTTP client, and configured transient retries. Remove old prompt/profile/backend registration options.
  4. Inspect registered prompt ID/version selections, then both selected roles. Reject a selected role whose effective model or endpoint is empty or whitespace-only. Retain backend registration, normalized endpoint equality, nonblank effective APIKeyEnv rejection, and required-credential checks. Preserve literal IDs; do not trim/re-key identities or require all profiles to be complete. Populate the same role inspection map for both clients.
  5. Migrate the registry test engine to FilesystemInputs and a programmatic ProfileSource fixture in its own tier, retaining its schema filesystem. A complete endpoint/model fixture without an application backend is valid for this direct library test; Audita's selected-role guard belongs in its runtime tests. Keep the embedded prompt preparation assertions scoped to embedded prompts.
  6. Remove the old profileSourceFS wrapper once unused. Source accounting is completed in Stage 5. Preserve the adapter boundary; change other production callers only to resolve actual API incompatibilities. Search for remaining uses of removed source options and old pk.Profile/pk.Backend values. Run go mod tidy after migrating construction callers and review its diff.

Verification and completion: Run go test ./internal/prompts and the existing offline profile assembly, unsafe-selection, and absolute-directory tests in internal/framework/llm. Run git diff --check and review the module diff. The obsolete file-reload assertion is intentionally replaced in Stage 6; do not restore reload behavior to make that assertion pass. Commit the runtime API migration.

Stage 4 — Preserve Startup Failure Classification and Validate Assembly

Primary files: internal/framework/llm/runtime.go, internal/framework/llm/runtime_inspection_test.go, and configuration-command tests in internal/cli. Update internal/cli/run.go only where the changed startup path needs existing exit behavior preserved.

  1. Treat embedded discovery failures as AssetError; treat operator-directory discovery failures as configuration input errors. Preserve wrapped causes for errors.Is/errors.As and retain configured-secret redaction at CLI boundaries. Caller cancellation remains a runtime exit, not an operator mistake, and should not trigger further diagnostic loading.
  2. PromptKit wraps both definition-loading and template/content failures in ErrInvalidConfig; some materialization errors have no public source/path type. Do not classify by parsing error strings or by that sentinel alone. On final engine-construction failure, perform an embedded-only diagnostic construction with the same embedded prompt/schema sources and library defaults, excluding operator inputs and application backend mappings. If that also fails, surface an AssetError with the asset cause; otherwise return the original final-construction error as an operator/configuration error. The diagnostic engine does no provider work and is never retained for execution. Keep a small unexported assembly seam accepting an embedded filesystem so tests can exercise asset failures without modifying shipped files or creating a global test hook.
  3. Do not run this diagnostic construction as a successful-path preflight. A valid final snapshot remains accepted even if an operator contribution repairs an otherwise invalid lower-tier prompt. Verify that property with the same small assembly test seam. This avoids strengthening upstream final-value validation into mandatory validation of displaced definitions.
  4. Cover incomplete selected profiles using omitted, empty, and null model contributions and a cleared selected endpoint. Exercise proposal and validation selection, including omission-derived validation selection, with a rejecting HTTP transport. Confirm valid unselected partial profiles are accepted; malformed unselected definitions, undefined backend references, and conflicting directory contributions still fail under native rules.
  5. Add focused boundary coverage that invalid operator YAML/template/resource input produces configuration exit 2, and test the asset classification directly at the runtime seam. Reuse existing CLI handling of AssetError for exit 1; no damaged-binary subprocess fixture is needed. Cover the shared NewRuntime validation route and preserve cancellation exit 1 in configuration commands if the new error path would otherwise classify it as 2. Avoid exact complete diagnostic wording assertions.

Verification and completion: Run targeted startup/inspection tests and go test ./internal/cli; check whitespace and commit. Preserve the original failure cause where available rather than flattening it to a generic error.

Stage 5 — Protect Consumed Configuration and Message Files

Primary files: internal/framework/llm/runtime.go or a focused source-path helper in that package, internal/cli/process.go, runtime source-accounting tests, and cmd/audita/main_integration_test.go.

  1. Rename the internal Runtime.ProfilePaths field to SourcePaths and update its process caller. It now covers consumed host YAML and message resources, rather than profiles alone. Populate it from the successfully captured final engine; do not expose source metadata as a new public output.
  2. Call InspectConfiguration. Index Sources() by source ID, and collect paths with Kind == ck.SourceKindFile; embedded filesystem and synthetic sources are not host paths. All loaded host YAML counts, including unused definitions and files whose contributions were later overlaid. Deduplicate paths and sort for a stable internal projection.
  3. For message resources, enumerate Metadata().HistoricalPaths() and retain effective, nonnull paths shaped as /prompts/<id>/<version>/messages/<index>/content_file. Use Lookup and the effective field's origin, not an inherited destination's guessed source. PromptKit successfully captures these fields only with a unique usable source context. Obtain the effective scalar from Explain(path).Effective and decode that scalar YAML into a string with the existing YAML dependency. This decodes captured inspection data; it is not a second configuration loader. When the contributing source is a host file, join the untrimmed resource name against that source file's directory and add it to the ledger. Ignore displaced references and non-host resources. Do not reread operator YAML, infer IDs from filenames, or reimplement resource-root validation.
  4. Pass SourcePaths through existing diagnostics.PrepareOutput protection. Leave canonicalization, symlink/hardlink checks, permitted in-place transcript handling, and output initialization with their current owner. Do not protect every unrelated file in the selected directory or add filesystem mutation guarantees beyond the existing collision contract.
  5. Extend TestSubprocessProfileSources and a focused runtime case to cover an arbitrarily named YAML contribution, a consumed source-relative message file, and conflicting transcript/report destinations. Use a non-YAML message filename, such as instruction.md, so directory discovery does not mistake plain prompt text for a configuration document. Include an inherited message reference from a different directory and a prompt ID/version with a JSON Pointer escape character so source provenance and literal keys determine the path. Check rejection before provider dispatch and unchanged input bytes. Preserve acceptance of an unused alternate output filename; do not turn the whole directory into a forbidden output location.

Verification and completion: Run focused source-ledger tests and subprocess collision cases, plus git diff --check, then commit. Check that resource accounting uses the frozen snapshot and never changes the engine's source or resource-resolution behavior to facilitate tracking.

Stage 6 — Verify Operator Prompt Overrides and Captured Execution

Primary files: internal/framework/llm/adapter_test.go and focused runtime tests. Change adapter.go only for an actual integration defect.

  1. Add a native keyed operator contribution overriding messages for an active prompt/version. Use the real runtime and controlled HTTP responses to prove the operator text reaches both the prepared snapshot and dispatched request. One case deliberately omits shared-variable references and still succeeds; do not require Audita to inject their text. A reference-bearing case renders the adapter-supplied value and retains existing protection against caller variables replacing the reserved shared variables.
  2. Override the prompt's definition-level output contract with another valid upstream contract. Prove that Audita's per-call JSON schema and configured repair budget still govern execution: a structurally invalid response is repaired or exhausted as the controlled budget dictates. Domain validators remain downstream. Reuse existing validator/proposal integration coverage for their rules rather than weakening them to fit an operator prompt.
  3. Replace the reload portion of TestPreparedAdapterRejectsTargetDriftAndPreparationErrors. After successful construction, change a profile or message file and verify the existing runtime still uses its captured settings/text. Construct a new runtime to observe a valid edit or reject a newly mismatched endpoint. Keep the missing prompt preparation failure case.
  4. Retain a focused test of the prepared-target check independently of reload: deliberately change the test runtime's application backend endpoint after construction and verify the inconsistency is blocked before dispatch. This tests the existing guard, not a supported live mutation/reload feature. Continue executing the same prepared handle whose details were inspected; do not add another preparation or an application-side retry loop.

Verification and completion: Run the prompt override, snapshot, and prepared-adapter tests. Compare semantic messages and selected targets, not old hash values or exact YAML formatting. Check whitespace and commit.

Stage 7 — Verify Native Profiles, Backend Authority, and Resilience

Primary files: Runtime inspection and adapter tests, internal/framework/llm/adapter_capacity_test.go, and existing framework/CLI integration tests where their established behavior is affected.

  1. Add a compact native profile inheritance fixture with literal keyed IDs unrelated to filenames, multiple definitions in one file, a partial parent, and a complete selected child. Verify inherited controls survive a child model change and child overrides win. Reuse the metadata-facing literal-key coverage from Stage 5 rather than repeating it in profile tests.
  2. Use controlled HTTP request bodies to distinguish explicit zero temperature from omission and inherited-control suppression by null. Check a representative recursively overlaid provider parameter and null clearing; do not promise that all providers accept every valid configured zero. Reuse existing deadline/cancellation coverage for unchanged outer deadlines; explicit zero profile timeout must not disable the operation context.
  3. Verify an operator backend contribution can add supported provider fields, while a conflicting endpoint/concurrency/queue/environment contribution cannot displace the final application fields. Preserve an explicit profile environment lookup as a selected-role error rather than hiding it with an adapter override. Include an application backend using a catalog ID to show it is allowed and its catalog credential lookup is suppressed. Use synthetic environment values and local HTTP fixtures, not provider catalog endpoints.
  4. Reuse TestRolesShareBackendCapacityAndReleaseAfterCancellation and TestDistinctBackendsHaveIndependentCapacity; verify the final mapped limits, including zero queue capacity, still coordinate both roles and remain backend-local. Strengthen a fixture with conflicting lower-tier capacity only where needed to prove the new mapping wins. Keep existing synchronization; do not infer admission with sleeps or add another limiter.
  5. Preserve offline selected-profile inspection and value-free application explanations. Check configured secrets, endpoint strings, arbitrary provider fields, and prompt text remain outside the current safe profile summary. Do not replace it with a full upstream dump. Its numeric projection is not proof that an omitted provider setting is sent; wire presence belongs to the HTTP coverage above.
  6. Run the existing transient retry, structural repair, attempt-count, observation, partial-validation, and all-model-failure subprocess cases after fixture migration. Preserve their error/recovery/debug classifications, cancellation, and continued output. Fix proven integration regressions; do not rewrite domain outcomes or reduce budgets to obtain a passing suite.

Verification and completion: Run go test ./internal/framework/llm ./internal/framework/proposal_generation ./internal/framework/validators ./internal/framework/runner ./internal/cli ./cmd/audita and git diff --check. Review whether each new test addresses a distinct changed boundary. Commit the native-profile and runtime verification unit.

Stage 8 — Document the Migration and Update Attribution

Primary files: docs/config.md, docs/internal/llm-runtime.md, docs/internal/prompts.md, docs/integrations/openai-compatible-llm.md, docs/operations.md, docs/internal/cli-config.md, README.md, and docs/oss/attribution.md.

  1. Update the configuration contract for unified profiles_dir content, exact keyed identities, directory composition, agreed tier precedence, profile inheritance/presence, selected-role completeness, eager validation of all final definitions, selected-root failures, and construction-time capture. Replace the obsolete statement that PromptKit profiles do not gain ConfigKit inheritance. Retain application overlays, defaults, and credential ownership.
  2. Include concise migration instructions in the profile section: wrap old profile fields beneath profiles/<id>, remove inner id, change base_profile to extends, review explicit values/nulls, and select roots whose final definitions have valid backend references. Show a small keyed inheritance/override snippet and link complete maintained examples. Explain explicit zero temperature versus omitted or null controls and link upstream for the full field table. State that this ships as an Audita major migration; do not invent an exact release version or automatic file conversion.
  3. Document intentional prompt overrides and optional shared-variable references. Supplying shared variables does not inject omitted text, while per-call schemas and domain validators remain enforced. Internal prompt documentation distinguishes shipped asset instructions from operator-effective messages; no prompt-quality rewrite or new global prompt-policy requirement is added.
  4. Describe layered runtime assembly, source accounting, snapshot use, and startup classification in their internal owner. Update the pinned PromptKit consumer link in the LLM integration contract. Expand operations' loaded source description to include consumed operator message files; keep physical output/collision rules with operations and diagnostics.
  5. Repair example navigation for separate local/service roots, especially the README's local-profile link. Update validation recipes to use the maintained config files' explicit roots and keep the service testing overlay workflow. Review other affected links without rewriting historical release notes.
  6. Update the PromptKit and both catalog attribution entries to selected versions and exact commit-pinned source/notice URLs. Resolve catalog tag commits from the selected module origins or authoritative upstream refs, not annotated tag object IDs. Check the releases' COPYRIGHT/LICENSE files and retain the applicable unchanged local LGPL/GPL and MIT texts. Keep the existing House Format; no new license selection, policy rewrite, or attribution inventory is needed. Review go.mod/go.sum against those entries.

Verification and completion: Check affected local links/anchors, format examples against the implemented loader, verify source revisions/notices, and run git diff --check. Commit the documentation and attribution unit.

Stage 9 — Validate the Integrated End State and Close the Plan

Primary files: This plan and the feature roadmap; correct an implementation file only if the final checks reveal an actual gap.

  1. Verify the full roadmap acceptance criteria against the landed code and the distinct tests from these stages. Review for unintended requirements: profile-only filtering, mandatory shared text in operator messages, filename-derived IDs, complete unselected profiles, catalog ID bans, successful-path asset preflights, compatibility readers, and new output guarantees are not part of the accepted target.

  2. Run go build -o ./bin/audita ./cmd/audita and go test ./.... Keep the generated binary out of commits. Reuse successful checks on unchanged content; rerun checks affected by any corrective code changes.

  3. Validate the maintained examples offline using the built binary:

    ./bin/audita config validate --config examples/minimal-config.yml
    ./bin/audita config validate --config examples/configkit-inheritance.yml
    AUDITA_SERVICE_KEY=synthetic-example ./bin/audita config validate --config examples/production-config.yml
    AUDITA_SERVICE_KEY=synthetic-example ./bin/audita config validate --config examples/production-config.yml --overlay testing
    ./bin/audita config print-effective --config examples/minimal-config.yml --view profiles
    ./bin/audita config print-effective --config examples/configkit-inheritance.yml --view explain
    

    Confirm local/service roots and testing selections work without provider dispatch. Check safe views exclude resolved secrets and preserve the established explanation fields. Do not call a model to validate examples.

  4. Check the selected dependency graph, attribution, affected links, and final diff. Run git diff --check. Confirm only the released modules are used and no obsolete /v2 catalog or removed construction API remains in active code. Historical release documents and historical roadmap links may retain old versions as accurate records.

  5. Mark this plan and the feature roadmap complete only after the full target passes. Record the tested implementation revision and checks accurately; distinguish a subsequent documentation-only completion commit. Commit the completion documentation and report remaining work, if any. Do not select a release number, publish, tag, push, poll CI, or add permanent release gates.

End state: Native PromptKit configuration and catalogs are adopted, operator overrides and captured resources work through the real runtime, application backend/domain ownership and resilience remain effective, and maintained examples and migration documentation match the implementation. No implementation-blocking decisions remain; exact major-release numbering and publication timing remain outside this implementation unit.