Skip to content
Spice Framework on GitHub

ADR 0006: Pure Deterministic Generation Plans

frameworkMaturity: previewSource: spice@c0641b3Exact reviewed source

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.go
internal/spicegen/<target>/spice_configuration_gen.go
internal/spicegen/<target>/spice_providers_gen.go
internal/spicegen/<target>/spice_assembly_gen.go
internal/spicegen/<target>/spice_features_gen.go
internal/spicegen/<target>/spice_http_gen.go
internal/spicegen/<target>/spice_http_route_<symbol>_<id>_gen.go
internal/spicegen/<target>/spice_lifecycle_gen.go
internal/spicegen/<target>/spice_command_gen.go
internal/spicegen/<target>/sources/<source-directory>/<source>_spice_gen.go
.spice/<target>.manifest.json

There 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 @Bean functions 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.Coordinator on failure;
  • stores explicit exported start/stop method values in construction order;
  • exposes NewApplication, State, Start, Stop, and caller-policy Run;
  • 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.