Files
promptkit/docs/policy/architecture.md

203 lines
9.3 KiB
Markdown

# Architecture Policy
**Base Policy Version:** 1.3
**Last Updated**: 2026-09-27
**Upstream Source:** [architecture.md](https://gitea.maximumdirect.net/eric/policy/src/commit/d84a38ebab26f048cafbe59a2246700c35d29843/docs/policy/architecture.md)
## Purpose And Ownership
The adopted policy defines Promptkit's purpose and the architectural
constraints contributors must follow. It owns system boundaries, responsibility
and dependency rules, established invariants, and explicit non-goals.
Describe the concrete package inventory and implementation mechanics in internal
documentation. Link to canonical public contracts for their exact behavior and
to ADRs for decision rationale.
## Architectural Approach
Hexagonal architecture is strongly recommended, including encapsulating external
integrations and APIs within internal adapters. Keep domain and application
logic independent of external integration details, and make dependency direction
clear in the adopted policy.
Implement that separation with proportionate, idiomatic code. Choose interfaces
and package layout to serve the application's actual boundaries. Use adapters
for collaborators with external integration responsibilities.
## Adopted Constraints And Implemented Structure
Architectural constraints govern current and future development. Record
implemented structure accurately, proposals and implementation status in the
roadmap, and significant decisions and rationale in ADRs. Describe a target
structure as implemented when its changes land.
Constraints must reflect deliberately selected requirements and decisions.
Do not promote incidental implementation choices into architectural invariants.
## System Shape
Promptkit is an importable Go library. It does not ship a command, an HTTP
service, or another application process. Repository examples demonstrate
library use but are not Promptkit applications or release artifacts.
The module root contains package `promptkit`, which is the public facade. It
provides the supported engine, configuration and source options, requests,
results, public values, extension interfaces, profiles, and error sentinels.
The [internal component overview](../internal/overview.md) owns the inventory
of implemented packages and their concrete responsibilities. This policy owns
the boundaries and dependency rules those components must preserve.
The internal engine composes selected catalog and caller ConfigKit inputs once,
then assembles the captured definitions, capacity manager, renderer, validator,
outbound client, and use-case runner. The configuration adapter interprets and
validates only the final effective values, including presence, null, and
resource context. It retains source history for explicit operator inspection,
not for validation or a second resolution pass. The root facade translates
public values and errors at the library boundary.
The engine constructs capacity policy from final backend definitions, wraps
the selected built-in or injected model client, and supplies bounded admission
to the runner. Runtime/domain components consume execution-owned values, not
ConfigKit types. Artifact reading and validation use the domain model and
application-neutral defaults; validation also uses file-catalog helpers and
JSON Schema. The model client uses the domain model, defaults, and an injected
or standard-library HTTP client. The runner coordinates narrow lookup and
collaborator interfaces without rereading configuration.
The current implementation follows this dependency direction:
```text
downstream consumers, including Scriptorium
|
v
root promptkit public facade
|
v
internal framework components
|
v
narrow injected abstractions
```
The backend index depends on the domain model and shared JSON-value ownership
and has no mutation API after construction. The configuration adapter applies
the OpenAI-compatible reserved request-field rule owned by the model client
when it validates final provider maps. The
capacity component depends on the domain model and the narrow internal
model-client boundary, not on provider transport implementation. The model
client does not depend on registry or capacity configuration. The facade
coordinates internal components and adapts the supported public extension
interfaces to narrow internal abstractions. Internal components must not depend
on consumers or on Scriptorium.
`internal/domain` owns source-neutral invariants for values shared across
multiple input and execution boundaries, including execution-setting bounds,
OpenAI-compatible base endpoints, session identifiers, and output-contract
legality, plus shared resource-identity syntax and qualification. Callers retain
source parsing, required-field rules, other
source-specific normalization, defaulting, error classification, and policy
specific to their own boundary.
## Repository And Consumer Boundary
Scriptorium is a downstream application that consumes Promptkit through
the supported public facade. It is not a Promptkit package and must not become
an internal dependency.
Promptkit owns reusable, application-neutral library behavior. It does not own:
- binaries or executable packaging;
- CLI commands, parsing, streams, or exit codes;
- HTTP routes, servers, request DTOs, status mapping, or deployment policy;
- application configuration discovery or precedence;
- process lifecycle, operational state, or application logging; or
- consumer-specific filesystem or security policy.
Those concerns remain with Scriptorium or another consuming application.
## Package Ownership
The module root is the supported public facade. Framework implementation
packages belong under Go's `internal/` boundary unless a demonstrated, stable
consumer contract requires a public package.
Each package must have one cohesive responsibility and a clear dependency
direction. Internal packages must not expose their types merely to simplify
wiring, and the public facade must not leak internal representations through
exported signatures. Production packages under `internal/` must not import the
module root; the root facade may depend on internal implementation packages.
New public packages require a durable consumer need that cannot be served
cleanly by the root facade.
The [internal component overview](../internal/overview.md) must be updated as
packages are implemented or their responsibilities change.
## Exported API Discipline
Export the smallest contract required by real consumers. Exported declarations
must have accurate GoDoc, stable semantics, and tests proportionate to their
compatibility risk. Avoid speculative extension points, aliases for internal
types, and public constructors that expose assembly details.
Once an exported API exists, its Go declaration and GoDoc own its exact public
contract. Architecture documentation owns boundary rules, not a duplicate API
reference.
## Error Boundaries
Internal failures must cross the public facade as errors meaningful to a Go
consumer without exposing private package types or transport-specific policy.
Wrapping should add useful context while preserving any public error identity
needed with `errors.Is` or `errors.As`.
Promptkit must not assign CLI exit codes or HTTP status codes. Consumers map
public library outcomes into their own transport behavior.
## Dependency Injection
External effects and consumer-selected policy must enter through narrow
interfaces or functions at the boundary that uses them. Dependencies should be
explicitly supplied during construction or invocation rather than read from
consumer configuration or hidden process-global state.
Interfaces should be owned by the code that consumes the behavior and should
contain only the operations that code requires. Provide defaults only for
application-neutral behavior; consumer-specific restrictions and adapters
remain injected from the consuming project.
## Repository Independence
Promptkit must build, test, and validate independently of Scriptorium. Do not
commit `go.work`, `go.work.sum`, or a local filesystem `replace` directive.
Temporary workspace or replacement configuration may support coordinated local
development, but it is not part of either repository's architecture or release
state.
## Current-State Maintenance
Do not list planned packages as implemented components. When implementation
introduces a package, update the internal inventory and the owning contract or
subsystem document in the same change.
## Trust Boundaries And Threat Scope
Where useful, state the trust boundaries and threat scenarios relevant to the
application's adopted requirements, along with scenarios it explicitly excludes.
For example, distinguish handling untrusted application input from defending
against a privileged actor concurrently modifying the host filesystem.
Threat exclusions define the scope of the adopted requirements. Base defenses
and guarantees on those requirements and raise unsettled scope for a deliberate
decision under the [agent policy](agents.md).
Keep this discussion proportionate to the application. A separate threat
assessment or security document is optional when adopting this template.
For this library, the framework format and integration documents own credential
supply and external contracts; internal documents own implementation mechanisms.
Consumer-specific filesystem and security policy remains outside Promptkit's
boundary as described above.