Spectre 0.2.2 adds the publication boundary for canonical Definitions. A
Spectre.Definition.Manifest seals two separate contracts around one
Spectre.Definition.Ref:
Spectre.Authority.Enveloperecords only capabilities actually granted by a trusted host ceiling;Spectre.Execution.Closurerecords the data, contract, generator, state, and compiled-build dependencies used when the Definition was composed.
The digest still proves integrity rather than trust. Publisher approval, revocation, current-time policy, ownership fencing, and activation remain host decisions.
Compose a Manifest
Compiled Stack V1 declarations are requests, never grants. The read-only V1 adapter intersects those requests with an explicit ceiling and emits native Manifest Contract V2 data:
canonical = Spectre.Definition.canonical!(MyApp.SupportAgent)
manifest =
Spectre.Definition.manifest!(MyApp.SupportAgent,
authority_requests: %{
state_reads: [:profile],
prompt_budget_classes: [:standard]
},
authority_ceiling: %{
operations: [:knowledge_lookup],
actions: [:send_reply],
state_reads: [:profile],
prompt_budget_classes: [:standard]
},
publisher_ref: "publisher:my-app",
provenance_refs: ["git:0123456789abcdef"]
)V1 Stack operations, actions, and resources become requests automatically;
:authority_requests supplies request classes V1 cannot express. Omitting
:authority_ceiling still produces an empty effective envelope. Authority
lists are canonical sets, and limits are reduced to the stricter requested or
ceiling value. Spectre.Authority.Envelope.allows?/3 inspects the resulting
grant; it never consults the original request.
Manifest creation also snapshots the component contract registry. Unknown
:must_understand components fail closed. Unknown :advisory and
:descriptive components remain opaque and preserved, so older readers cannot
silently ignore new security semantics.
Publish before activation
Spectre.Definition.Store is an adapter behaviour for immutable opaque blobs.
Core, rather than the adapter, owns canonical encoding, parent checks,
publication receipts, and verification:
{:ok, store} =
Spectre.Definition.Store.Memory.start_link(id: :support_definitions)
config = {Spectre.Definition.Store.Memory, server: store}
{:ok, receipt} =
Spectre.Definition.Store.publish(config, canonical, manifest)
{:ok, resolution} =
Spectre.Definition.Resolver.resolve(config, receipt.definition_ref)The memory adapter is deliberately :volatile; it is suitable only for tests
and entirely ephemeral Instances. When an Instance has a configured
Spectre.Instance.CheckpointStore, publish/4 and
resolve_for_activation/3 reject a volatile Definition Store. A durable host
adapter must implement:
@behaviour Spectre.Definition.Store
@impl true
def identity(opts), do: Keyword.fetch!(opts, :store_id)
@impl true
def durability(_opts), do: :durable
@impl true
def get(definition_ref, opts), do: MyBackend.get(definition_ref, opts)
@impl true
def put(definition_ref, bytes, opts), do: MyBackend.put_if_absent(definition_ref, bytes, opts)put/3 must be idempotent for identical bytes and reject different bytes under
the same Ref. After every successful callback, core reads the artifact back and
compares it byte-for-byte before returning its receipt.
Parent Definitions must already resolve. Therefore a process crash after publish but before an activation CAS leaves only an immutable orphan; it cannot leave an activation pointing at an unpublished Definition.
Publish a bootstrap Candidate
Spectre 0.2.3 adds the minimal Candidate boundary used by activation. Trusted host code can publish a Candidate directly or derive one from a verified resolution:
{:ok, candidate_ref} =
Spectre.Definition.Resolver.bootstrap_candidate(config, definition_ref,
source: :compiled,
checkpoint_store: checkpoint_store,
provenance_refs: ["deployment:2026-08-10"]
)The Store verifies that the Candidate's Manifest digest and publication id
match its Definition artifact and that any parent Candidate resolves. The
Candidate Ref is content-addressed. Activation always re-fetches it by Ref;
receiving a %Spectre.Definition.Candidate{} value directly grants no trust.
This remains the direct bootstrap path. Spectre 0.2.9 additionally stores
governed Candidate state and immutable gate receipts produced through
Spectre.Governance.Composer, Review, and Approval. Both paths use the same
content-addressed Candidate artifact and activation re-read boundary; neither
authorizes its own activation. See Governed Definition Changes.
Resolve and detect drift
Resolution verifies the lookup Ref, Definition bytes, Manifest binding, component contract snapshot, Manifest digest, and publication receipt. It then reports compiled code observations explicitly:
{:ok, resolution} =
Spectre.Definition.Resolver.resolve(config, definition_ref,
observed_builds: %{
"beam:Elixir.MyApp.SupportAgent" => current_beam_digest
}
)
resolution.drift.status # :matchedWithout trusted observations the status is :unobserved. Changed or missing
fingerprints return an error by default. on_drift: :report preserves the
drift as evidence but does not claim reproducibility or grant permission to
execute it.
resolve_for_activation/3 validates Store durability and re-reads the complete
artifact. In 0.2.3, resolve_candidate_for_activation/3 also re-reads the
Candidate and is the only resolution accepted by the Instance activation
sequencer. See Stable Identity, Activation, and Definition-Pinned Runs.
Conformance and compatibility
Spectre.Definition.Store.Conformance can be called from adapter test suites.
It checks immutable duplicate publication and verified read-back; its
read_after_restart/4 check verifies that two configurations around an adapter
restart resolve identical data.
The Manifest V2 fixture under test/fixtures/compatibility/0.2.2 is decoded and
re-encoded by both supported CI combinations: OTP 28/Elixir 1.19 and OTP
29/Elixir 1.20. It binds the unchanged canonical Definition fixture introduced
in 0.2.1. The 0.2.3 fixture additionally restores an Activation and its pinned
Run from canonical checkpoint schema 2.