Files
promptkit/docs/roadmap/audit/09-interfaces.md

11 KiB

API And Interface Contract Audit

Reviewed 2026-11-04 against commit 31f1e6a and the current worktree. Existing audit-document moves were preserved. No production code or repository tests were changed.

Scope And Assessment

The public root package remains a facade over configuration, execution, generation, capacity, rendering, validation, and observation components. ConfigKit owns composition before the final configuration handoff; PromptKit validates that handoff and captures its runtime indexes and templates. Public adapters translate owned request/result values and errors. Namespace views share execution ownership and capacity, while separately constructed engines remain independent.

The review compared exported GoDoc, configuration and integration contracts, internal collaborator interfaces, call paths, and existing behavioral tests. It covered preparation/preflight/execution parity, prepared-handle lifecycle, optional values and numeric presence, configuration inspection and listing, injected collaborator ownership, output repair, diagnostics, observations, serialization, cancellation, and public error categories. It did not treat different intentionally documented workflows as defects or infer additional admission, filesystem, provider, or semantic-validation guarantees.

Four findings warrant remediation. The configuration behaviors in findings 2 and 3 are consistent with the current format reference and implementation; their older contradictory descriptions should be corrected rather than used to change those behaviors.

1. ArtifactReader Permits Buffer Reuse Before PromptKit Can Copy It

Priority: P2. A documented ownership permission permits data races and inconsistent input snapshots.

Relevant interfaces/code: ArtifactReader GoDoc, public reader adapter, and copyBytes.

Contract defect: The interface says that Read may be called concurrently and that the reader may reuse or mutate the returned artifact and body after Read returns. The adapter can only copy those values after that return. There is no synchronization or acknowledgement between return and completion of the copy. “Immediately copies” does not make concurrent reuse safe.

Circumstances and consequence: A reader serializes its own Read calls with a mutex and returns a reusable buffer. Another invocation can overwrite that buffer as soon as the first call returns, while the first adapter is still copying it. Both calls follow the published reuse permission, yet preparation can race, mix contents, or associate metadata with the wrong bytes. This is not a demonstrated defect in the default file reader or in readers that return independent snapshots.

Evidence: A temporary, offline public-API diagnostic used two concurrent Prepare calls and a mutex-protected reader returning a shared 1 MiB buffer. go run -race reported a write in the next Read racing with copyBytes through publicArtifactReaderAdapter.Read. The adapter's existing copy test establishes detachment after copying, not safety during that copy.

Potential remediation: Resolve the return-value ownership contract before v1.0. The straightforward approach is for a successful Read to return a per-invocation snapshot that remains stable while PromptKit consumes it, with reuse permitted after the enclosing preparation/execution method returns. Returning fresh owned data is the simplest implementation for concurrent readers. If reuse immediately after Read itself returns must remain supported, it requires an explicit borrowing/release arrangement; another unsynchronized copy cannot provide that permission. Add focused coverage for the selected lifetime, without globally serializing unrelated engine work.

2. Profile Inspection Misstates Required-Credential Semantics

Priority: P2. The public inspection contract describes valid results as impossible and incorrectly limits the sources that can satisfy a requirement.

Relevant interfaces/code: Engine.InspectProfile, ProfileInspection.APIKeyRequired, profile-inspection consumer example, effective-target construction, and credential availability check.

Contract defect: Both method and field GoDoc claim that APIKeyRequired is mutually exclusive with a nonblank EffectiveModelParams.APIKeyEnv. They also describe the flag as requiring a direct credential or a request-level environment override. In fact, api_key_required: true suppresses a backend-derived environment source only when no explicit profile source was provided. An explicit profile environment source remains usable, as the credential format reference already states. Inspection can therefore legitimately return both the flag and a nonblank environment name. The consumer example also calls every reported environment source optional, regardless of the flag.

Circumstances and consequence: A profile sets api_key_required: true and api_key_env: PROMPTKIT_INTERFACE_AUDIT_KEY. Inspection returns true and that name; preparation succeeds using its populated environment variable without a direct key or request override. Consumers following the GoDoc may reject this valid combination or report credential readiness incorrectly.

Evidence: A temporary public-API diagnostic demonstrated that combination and successful preparation. The existing effective-target test also includes a required profile with an explicit environment source.

Potential remediation: Describe the flag as a local credential-availability requirement, distinguish source selection from required availability, and remove the mutual-exclusion/direct-only claims. Align the consumer explanation with the format reference and retain inspection's intentional lack of an environment-value lookup. Do not reject the valid configuration or change the public target's stable JSON shape to make the old wording true.

3. The Wire Contract Incorrectly Says Configured Provider Maps Replace

Priority: P2. Conflicting published contracts predict different outbound provider parameters.

Relevant interfaces/code: integration request-body contract, profile defaulting reference, overlayProviderMap, and runtime override merge.

Contract defect: The integration document says backend, profile, and request extra-parameter maps all use whole-map replacement. Actual configured profile maps recursively overlay backend defaults, including nested maps. A nonempty runtime ExecutionTargetOverride.ExtraParams instead replaces the entire configured map. The format reference and runtime-override GoDoc describe these distinct behaviors correctly.

Circumstances and consequence: A backend supplies backend_only: true and nested: {backend: true}; its profile supplies profile_only: true and nested: {profile: true}. All entries survive into the effective target and built-in HTTP body. A reader of the integration contract would expect the backend entries to disappear, potentially leading to surprising provider options or ineffective attempts to clear defaults. A runtime map containing only request_only: true does remove the configured entries.

Evidence: A temporary public-API diagnostic observed both forms through an injected client and confirmed the configured merged fields through a controlled built-in HTTP transport. No external request was made. The existing effective-target test also exercises recursive configured overlay.

Potential remediation: Replace the stale configuration-precedence claim with a short link to the format reference. Keep the integration contract focused on flattening the resulting effective map into the HTTP body and on reserved request fields. Preserve the distinct runtime replacement behavior; do not add another merge policy or duplicate its full configuration reference.

4. Identity GoDoc Still Describes Removed Normalization And Version Fields

Priority: P3. Small but concrete public-contract inconsistencies.

Relevant interfaces/code: ProfileInspection.ProfileID, ListPrompts GoDoc, and literal profile identity test.

Contract defect: ProfileInspection.ProfileID is described as trimmed, but configured IDs and lookups preserve meaningful whitespace exactly. ListPrompts promises ownership of each Version pointer even though PromptIdentity contains only ID; collection-level ReleaseVersion belongs to the separate collection inventory.

Circumstances and consequence: Inspecting the valid profile " padded " returns that exact ID, while looking up "padded" fails. Callers expecting the documented trimmed identity can fail later lookups. The nonexistent version field creates an erroneous expectation about available prompt metadata.

Evidence: The existing literal-identity test explicitly protects preserved whitespace and failure of the trimmed lookup; a temporary public-API diagnostic confirmed it. The current PromptIdentity declaration and facade conversion have no version field.

Potential remediation: State that the profile ID is the exact selected identity after any namespace qualification, and remove the nonexistent version pointer language. Keep literal identity behavior and collection metadata unchanged; no new normalization, version field, or compatibility guarantee is needed.

Existing Findings And Validation

The earlier error-handling audit already records loss of owning public error categories for nested injected causes, loss of successful-body I/O causes, and pre-dispatch smoke failures incorrectly becoming concluded reports. Those are also interface-contract concerns, not new duplicate findings here. The hidden ValidationPreparer requirement behind the runner's Validator parameter is already recorded in the complexity audit. No additional independent defect was established in the reviewed prepared-handle lifecycle, optional-value handling, inspection ownership, or observation/result mapping.

Focused existing tests passed with the local Go 1.27.0 toolchain:

go test -race -count=1 -run 'Test(InspectProfile|ListIdentities|ScopedIdentityListings|PublicArtifactReaderAdapter|PublicLLMClientAdapter|ConfigurationInspection|LoadBuildsEffectiveTargetsAfterInheritance)' . ./internal/configuration

The separate temporary reader-reuse diagnostic intentionally produced a race report and exited unsuccessfully, substantiating finding 1 despite the green existing tests. Temporary programs and outputs remain outside the repository. All diagnostics were offline with synthetic configuration and credentials; no paid provider calls, new repository tests, or production changes were used. These checks are review evidence, not additional release gates.