ADR 0006: Pure Deterministic Generation Plans
Ownership update: generation implementation lives in
github.com/spice-framework/toolchain; generated output remains owned by each consuming application.
Status: Accepted (amended for manifest schema 5)
Decision
compiler/generate converts one valid immutable application model into a pure
in-memory Plan. The compiler phase never writes files, reloads packages,
rebuilds dependency resolution, executes source declarations, or accesses the
network.
The preferred package-main target layout is:
internal/spicegen/<target>/spice_contracts_gen.gointernal/spicegen/<target>/spice_configuration_gen.gointernal/spicegen/<target>/spice_providers_gen.gointernal/spicegen/<target>/spice_assembly_gen.gointernal/spicegen/<target>/spice_features_gen.gointernal/spicegen/<target>/spice_http_gen.gointernal/spicegen/<target>/spice_http_route_<symbol>_<id>_gen.gointernal/spicegen/<target>/spice_lifecycle_gen.gointernal/spicegen/<target>/spice_command_gen.gointernal/spicegen/<target>/sources/<source-directory>/<source>_spice_gen.go.spice/<target>.manifest.jsonThere is no catch-all target file. Concern-named units make execution boundaries
obvious, the assembly unit only sequences bounded phases, and every HTTP route
uses a readable stable symbol-and-hash-derived filename. Every contributing handwritten file
owns one mirrored source unit. The command package import-path base supplies
the default target name and portable ID. Target IDs use the portable
[a-z][a-z0-9_]* form. A target records one module root as an execution-only
field; the root is never serialized. Manifest schema 5 records the application
package layout, concern roles, exact source ownership, and generated ranges.
Schema-4 monoliths remain accepted only for guarded migration.
Generated source
Generated source:
- begins with
// Code generated by Spice. DO NOT EDIT.; - assigns explicit import aliases from sorted import paths;
- invokes exported
@Beanfunctions directly in the application IR’s dependency-first order; - passes dependency variables using the already validated graph edges;
- checks and wraps provider errors with stable provider and output identities;
- registers cleanup immediately after each successful provider call;
- aborts construction through
lifecycle.Coordinatoron failure; - stores explicit exported start/stop method values in construction order;
- exposes
NewApplication,State,Start,Stop, and caller-policyRun; - uses no reflection, service locator, runtime scanning, or global registry.
Providers remain in importable module packages and generated calls require exported provider/hook declarations. Same-package targets also reject collisions with fixed generated API names. Legacy dedicated packages retain their import-cycle and package-main restrictions.
Ownership manifest
The canonical JSON manifest records:
- schema, target, module, package, output, and manifest identities;
- Spice generator and Go formatter compatibility versions;
- a SHA-256 hash of canonical application IR and target inputs;
- sorted generated paths, concern roles, source mappings, and exact SHA-256 content hashes.
It contains no timestamps, absolute paths, raw environment values, random data, or host information. Manifest bytes and generated Go are byte-identical for the same model, target, schema, and supported formatter line.
Filesystem validation, locking, guarded replacement, stale removal, manual-edit detection, check mode, and bounded diffs are a separate layer that consumes the plan.