pub trait EnvironmentMutations: Send + Sync {
Show 32 methods
// Required methods
fn create_environment(
&self,
env_id: &EnvId,
name: String,
host_config: EnvironmentHostConfig,
) -> Result<Environment, StoreError>;
fn update_environment(
&self,
env_id: &EnvId,
patch: UpdateEnvironmentPayload,
) -> Result<Environment, StoreError>;
fn load_environment(
&self,
env_id: &EnvId,
) -> Result<Environment, StoreError>;
fn trust_root_is_seeded(&self, env_id: &EnvId) -> Result<bool, StoreError>;
fn migrate_merge_bindings(
&self,
target_env_id: &EnvId,
payload: MigrateMergePayload,
) -> Result<(Vec<String>, Vec<String>), StoreError>;
fn stage_revision(
&self,
env_id: &EnvId,
payload: StageRevisionPayload,
idempotency_key: IdempotencyKey,
) -> Result<Revision, StoreError>;
fn warm_revision(
&self,
env_id: &EnvId,
payload: WarmRevisionPayload,
idempotency_key: IdempotencyKey,
) -> Result<RevisionTransitionOutcome, StoreError>;
fn drain_revision(
&self,
env_id: &EnvId,
revision_id: RevisionId,
idempotency_key: IdempotencyKey,
) -> Result<RevisionTransitionOutcome, StoreError>;
fn archive_revision(
&self,
env_id: &EnvId,
revision_id: RevisionId,
idempotency_key: IdempotencyKey,
) -> Result<RevisionTransitionOutcome, StoreError>;
fn add_bundle(
&self,
env_id: &EnvId,
payload: AddBundlePayload,
idempotency_key: IdempotencyKey,
) -> Result<BundleDeployment, StoreError>;
fn update_bundle(
&self,
env_id: &EnvId,
payload: UpdateBundlePayload,
idempotency_key: IdempotencyKey,
) -> Result<BundleDeployment, StoreError>;
fn remove_bundle(
&self,
env_id: &EnvId,
deployment_id: DeploymentId,
idempotency_key: IdempotencyKey,
) -> Result<RemoveBundleOutcome, StoreError>;
fn add_pack_binding(
&self,
env_id: &EnvId,
binding: EnvPackBinding,
idempotency_key: IdempotencyKey,
) -> Result<EnvPackBinding, StoreError>;
fn update_pack_binding(
&self,
env_id: &EnvId,
slot: CapabilitySlot,
binding: EnvPackBinding,
idempotency_key: IdempotencyKey,
) -> Result<(EnvPackBinding, u64), StoreError>;
fn remove_pack_binding(
&self,
env_id: &EnvId,
slot: CapabilitySlot,
idempotency_key: IdempotencyKey,
) -> Result<(EnvPackBinding, u64), StoreError>;
fn rollback_pack_binding(
&self,
env_id: &EnvId,
slot: CapabilitySlot,
idempotency_key: IdempotencyKey,
) -> Result<(EnvPackBinding, u64), StoreError>;
fn add_extension_binding(
&self,
env_id: &EnvId,
binding: ExtensionBinding,
idempotency_key: IdempotencyKey,
) -> Result<ExtensionBinding, StoreError>;
fn update_extension_binding(
&self,
env_id: &EnvId,
key: ExtensionKey,
binding: ExtensionBinding,
idempotency_key: IdempotencyKey,
) -> Result<(ExtensionBinding, u64), StoreError>;
fn remove_extension_binding(
&self,
env_id: &EnvId,
key: ExtensionKey,
idempotency_key: IdempotencyKey,
) -> Result<(ExtensionBinding, u64), StoreError>;
fn rollback_extension_binding(
&self,
env_id: &EnvId,
key: ExtensionKey,
idempotency_key: IdempotencyKey,
) -> Result<(ExtensionBinding, u64), StoreError>;
fn set_traffic_split(
&self,
env_id: &EnvId,
payload: SetTrafficSplitPayload,
idempotency_key: IdempotencyKey,
) -> Result<ApplyTrafficSplitOutcome, StoreError>;
fn rollback_traffic_split(
&self,
env_id: &EnvId,
deployment_id: DeploymentId,
idempotency_key: IdempotencyKey,
) -> Result<RollbackTrafficSplitOutcome, StoreError>;
fn add_messaging_endpoint(
&self,
env_id: &EnvId,
payload: AddMessagingEndpointPayload,
idempotency_key: IdempotencyKey,
) -> Result<MessagingEndpoint, StoreError>;
fn link_messaging_bundle(
&self,
env_id: &EnvId,
endpoint_id: MessagingEndpointId,
bundle_id: BundleId,
updated_by: String,
idempotency_key: IdempotencyKey,
) -> Result<MessagingEndpoint, StoreError>;
fn unlink_messaging_bundle(
&self,
env_id: &EnvId,
endpoint_id: MessagingEndpointId,
bundle_id: BundleId,
updated_by: String,
idempotency_key: IdempotencyKey,
) -> Result<MessagingEndpoint, StoreError>;
fn set_messaging_welcome_flow(
&self,
env_id: &EnvId,
payload: SetMessagingWelcomeFlowPayload,
idempotency_key: IdempotencyKey,
) -> Result<MessagingEndpoint, StoreError>;
fn remove_messaging_endpoint(
&self,
env_id: &EnvId,
endpoint_id: MessagingEndpointId,
) -> Result<MessagingEndpointId, StoreError>;
fn rotate_messaging_webhook_secret(
&self,
env_id: &EnvId,
endpoint_id: MessagingEndpointId,
updated_by: String,
idempotency_key: IdempotencyKey,
) -> Result<MessagingEndpoint, StoreError>;
fn bootstrap_trust_root(
&self,
env_id: &EnvId,
) -> Result<TrustRootSeed, StoreError>;
fn seed_trust_root_if_absent(
&self,
env_id: &EnvId,
) -> Result<Option<TrustRootSeed>, StoreError>;
fn add_trusted_key(
&self,
env_id: &EnvId,
key_id: String,
public_key_pem: String,
idempotency_key: IdempotencyKey,
) -> Result<TrustRootAddOutcome, StoreError>;
fn remove_trusted_key(
&self,
env_id: &EnvId,
key_id: String,
idempotency_key: IdempotencyKey,
) -> Result<TrustRootRemoveOutcome, StoreError>;
}Expand description
The typed-verb persistence operations a Greentic environment store
performs in response to op … CLI verbs.
Replaces the closure-based LocalFsStore::transact pattern with one
method per logical verb. All methods take &self — concurrency is the
impl’s responsibility (flock for LocalFsStore, optimistic CAS via
If-Match for HttpEnvironmentStore against an A8-compliant server).
Methods that need idempotency replay (per A8 §2) take an
IdempotencyKey; methods that are intrinsically idempotent (e.g.
seed_trust_root_if_absent) do not.
Errors: all methods return StoreError. Impls may map their
transport-specific errors into the existing variants; new variants land
alongside the impl that needs them.
Required Methods§
Sourcefn create_environment(
&self,
env_id: &EnvId,
name: String,
host_config: EnvironmentHostConfig,
) -> Result<Environment, StoreError>
fn create_environment( &self, env_id: &EnvId, name: String, host_config: EnvironmentHostConfig, ) -> Result<Environment, StoreError>
Create a fresh environment with empty bundles/revisions/packs. Rejects if the env already exists.
Sourcefn update_environment(
&self,
env_id: &EnvId,
patch: UpdateEnvironmentPayload,
) -> Result<Environment, StoreError>
fn update_environment( &self, env_id: &EnvId, patch: UpdateEnvironmentPayload, ) -> Result<Environment, StoreError>
Patch the named scalar fields on an existing environment.
FieldUpdate::Keep fields are skipped, FieldUpdate::Set
writes the new value, FieldUpdate::Clear resets optional
fields to None. The full updated Environment is returned.
Covers what was previously split across update_environment (no
listen_addr), set_public_url (single field), and set_config
(host-level fields including listen_addr). One verb, one HTTP
endpoint, one impl body per backend.
Sourcefn load_environment(&self, env_id: &EnvId) -> Result<Environment, StoreError>
fn load_environment(&self, env_id: &EnvId) -> Result<Environment, StoreError>
Read the current environment state. This is the single READ verb on an
otherwise mutation-only trait: the remote dispatch needs it to evaluate
client-side preconditions — e.g. the warm health-gate’s
expected_lifecycle, which the local closure-based path reads inline
under its flock. Maps to GET /environments/{env_id} on the HTTP
backend and delegates to
EnvironmentStore::load on
LocalFsStore.
Sourcefn trust_root_is_seeded(&self, env_id: &EnvId) -> Result<bool, StoreError>
fn trust_root_is_seeded(&self, env_id: &EnvId) -> Result<bool, StoreError>
True when the env’s trust root holds at least one operator key. Maps to
GET /environments/{env_id}/trust-root on the HTTP backend and to a
trust-root file read on LocalFsStore. A read-only probe (like
Self::load_environment) — remote env apply --check uses it to diff
a declared trust_root instead of assuming it is seeded (the env
document does not carry trust-root state).
Sourcefn migrate_merge_bindings(
&self,
target_env_id: &EnvId,
payload: MigrateMergePayload,
) -> Result<(Vec<String>, Vec<String>), StoreError>
fn migrate_merge_bindings( &self, target_env_id: &EnvId, payload: MigrateMergePayload, ) -> Result<(Vec<String>, Vec<String>), StoreError>
Merge pack bindings and extension bindings into target_env_id.
Skips slots / extension keys already bound in the target. Returns
the list of newly-merged slots + extension key strings (in the
form "<kind_path>::<instance_id>").
payload.seed_if_missing is the optional seed-on-create-target
branch used by op env migrate-dev to migrate a legacy dev env
into a fresh local target atomically — load, fallback-to-seed,
merge, save all happen under one lock.
Sourcefn stage_revision(
&self,
env_id: &EnvId,
payload: StageRevisionPayload,
idempotency_key: IdempotencyKey,
) -> Result<Revision, StoreError>
fn stage_revision( &self, env_id: &EnvId, payload: StageRevisionPayload, idempotency_key: IdempotencyKey, ) -> Result<Revision, StoreError>
Stage a fresh revision under deployment_id. The caller supplies
the pinned artifact pointers; LocalFsStore’s CLI helper resolves
them from a local .gtbundle upstream of this call so the trait
stays storage-only.
idempotency_key: A8 §2 — same-key replay returns the originally
staged Revision without re-minting the ULID or advancing the
sequence. Local impl accepts and ignores; HTTP backends cache it.
Sourcefn warm_revision(
&self,
env_id: &EnvId,
payload: WarmRevisionPayload,
idempotency_key: IdempotencyKey,
) -> Result<RevisionTransitionOutcome, StoreError>
fn warm_revision( &self, env_id: &EnvId, payload: WarmRevisionPayload, idempotency_key: IdempotencyKey, ) -> Result<RevisionTransitionOutcome, StoreError>
Transition a revision through its warm lifecycle chain, applying the
client-evaluated health-gate outcome. The deployer CLI runs the
runner-side health checks locally and ships the result in
WarmRevisionPayload::health_gate: Ok(()) advances the revision
to Ready; Err(failure) flips it to Failed atomically. This
replaces the closure-based gate so the operation can cross the A8
HTTP wire contract.
Sourcefn drain_revision(
&self,
env_id: &EnvId,
revision_id: RevisionId,
idempotency_key: IdempotencyKey,
) -> Result<RevisionTransitionOutcome, StoreError>
fn drain_revision( &self, env_id: &EnvId, revision_id: RevisionId, idempotency_key: IdempotencyKey, ) -> Result<RevisionTransitionOutcome, StoreError>
Drain a Ready revision (graceful step-down → Drained).
idempotency_key is required for A8 mutation consistency even though
drain is logically idempotent — the key enables audit-event replay.
Sourcefn archive_revision(
&self,
env_id: &EnvId,
revision_id: RevisionId,
idempotency_key: IdempotencyKey,
) -> Result<RevisionTransitionOutcome, StoreError>
fn archive_revision( &self, env_id: &EnvId, revision_id: RevisionId, idempotency_key: IdempotencyKey, ) -> Result<RevisionTransitionOutcome, StoreError>
Archive a Drained / Failed revision (terminal).
idempotency_key is required for A8 mutation consistency even though
archive is logically idempotent — the key enables audit-event replay.
Sourcefn add_bundle(
&self,
env_id: &EnvId,
payload: AddBundlePayload,
idempotency_key: IdempotencyKey,
) -> Result<BundleDeployment, StoreError>
fn add_bundle( &self, env_id: &EnvId, payload: AddBundlePayload, idempotency_key: IdempotencyKey, ) -> Result<BundleDeployment, StoreError>
idempotency_key is required for A8 §2 mutation replay; the local
impl accepts and ignores, the HTTP backend caches the original
outcome.
Sourcefn update_bundle(
&self,
env_id: &EnvId,
payload: UpdateBundlePayload,
idempotency_key: IdempotencyKey,
) -> Result<BundleDeployment, StoreError>
fn update_bundle( &self, env_id: &EnvId, payload: UpdateBundlePayload, idempotency_key: IdempotencyKey, ) -> Result<BundleDeployment, StoreError>
idempotency_key is required for A8 §2 mutation replay; the local
impl accepts and ignores, the HTTP backend caches the original
outcome.
Sourcefn remove_bundle(
&self,
env_id: &EnvId,
deployment_id: DeploymentId,
idempotency_key: IdempotencyKey,
) -> Result<RemoveBundleOutcome, StoreError>
fn remove_bundle( &self, env_id: &EnvId, deployment_id: DeploymentId, idempotency_key: IdempotencyKey, ) -> Result<RemoveBundleOutcome, StoreError>
Remove a bundle deployment from the env. Refuses if the deployment
still has live traffic splits or non-archived revisions —
callers must op traffic clear and archive revisions first.
Also drops archived revisions for the same deployment_id so the
env stays compact. The pruned IDs are surfaced on the outcome so
the destructive side effect is explicit on the contract —
HTTP backends can apply a separate authorization check against
the prune set, the CLI logs the IDs in the audit target.
idempotency_key is required for A8 §2 mutation replay; the local
impl accepts and ignores, the HTTP backend caches the original
outcome.
fn add_pack_binding( &self, env_id: &EnvId, binding: EnvPackBinding, idempotency_key: IdempotencyKey, ) -> Result<EnvPackBinding, StoreError>
Sourcefn update_pack_binding(
&self,
env_id: &EnvId,
slot: CapabilitySlot,
binding: EnvPackBinding,
idempotency_key: IdempotencyKey,
) -> Result<(EnvPackBinding, u64), StoreError>
fn update_pack_binding( &self, env_id: &EnvId, slot: CapabilitySlot, binding: EnvPackBinding, idempotency_key: IdempotencyKey, ) -> Result<(EnvPackBinding, u64), StoreError>
Returns (new_binding, new_generation) — the bumped audit generation
is surfaced for downstream observability.
fn remove_pack_binding( &self, env_id: &EnvId, slot: CapabilitySlot, idempotency_key: IdempotencyKey, ) -> Result<(EnvPackBinding, u64), StoreError>
fn rollback_pack_binding( &self, env_id: &EnvId, slot: CapabilitySlot, idempotency_key: IdempotencyKey, ) -> Result<(EnvPackBinding, u64), StoreError>
fn add_extension_binding( &self, env_id: &EnvId, binding: ExtensionBinding, idempotency_key: IdempotencyKey, ) -> Result<ExtensionBinding, StoreError>
fn update_extension_binding( &self, env_id: &EnvId, key: ExtensionKey, binding: ExtensionBinding, idempotency_key: IdempotencyKey, ) -> Result<(ExtensionBinding, u64), StoreError>
fn remove_extension_binding( &self, env_id: &EnvId, key: ExtensionKey, idempotency_key: IdempotencyKey, ) -> Result<(ExtensionBinding, u64), StoreError>
fn rollback_extension_binding( &self, env_id: &EnvId, key: ExtensionKey, idempotency_key: IdempotencyKey, ) -> Result<(ExtensionBinding, u64), StoreError>
fn set_traffic_split( &self, env_id: &EnvId, payload: SetTrafficSplitPayload, idempotency_key: IdempotencyKey, ) -> Result<ApplyTrafficSplitOutcome, StoreError>
fn rollback_traffic_split( &self, env_id: &EnvId, deployment_id: DeploymentId, idempotency_key: IdempotencyKey, ) -> Result<RollbackTrafficSplitOutcome, StoreError>
Sourcefn add_messaging_endpoint(
&self,
env_id: &EnvId,
payload: AddMessagingEndpointPayload,
idempotency_key: IdempotencyKey,
) -> Result<MessagingEndpoint, StoreError>
fn add_messaging_endpoint( &self, env_id: &EnvId, payload: AddMessagingEndpointPayload, idempotency_key: IdempotencyKey, ) -> Result<MessagingEndpoint, StoreError>
idempotency_key is both A8 §2 transport metadata AND domain state
in this group: the key is embedded in the endpoint’s updated_by
stamp and drives same-key replay detection (see
greentic_deploy_spec::engine::messaging).
fn link_messaging_bundle( &self, env_id: &EnvId, endpoint_id: MessagingEndpointId, bundle_id: BundleId, updated_by: String, idempotency_key: IdempotencyKey, ) -> Result<MessagingEndpoint, StoreError>
fn unlink_messaging_bundle( &self, env_id: &EnvId, endpoint_id: MessagingEndpointId, bundle_id: BundleId, updated_by: String, idempotency_key: IdempotencyKey, ) -> Result<MessagingEndpoint, StoreError>
Sourcefn set_messaging_welcome_flow(
&self,
env_id: &EnvId,
payload: SetMessagingWelcomeFlowPayload,
idempotency_key: IdempotencyKey,
) -> Result<MessagingEndpoint, StoreError>
fn set_messaging_welcome_flow( &self, env_id: &EnvId, payload: SetMessagingWelcomeFlowPayload, idempotency_key: IdempotencyKey, ) -> Result<MessagingEndpoint, StoreError>
idempotency_key semantics as on
Self::add_messaging_endpoint — transport metadata that doubles
as the replay-detection stamp.
fn remove_messaging_endpoint( &self, env_id: &EnvId, endpoint_id: MessagingEndpointId, ) -> Result<MessagingEndpointId, StoreError>
fn rotate_messaging_webhook_secret( &self, env_id: &EnvId, endpoint_id: MessagingEndpointId, updated_by: String, idempotency_key: IdempotencyKey, ) -> Result<MessagingEndpoint, StoreError>
Sourcefn bootstrap_trust_root(
&self,
env_id: &EnvId,
) -> Result<TrustRootSeed, StoreError>
fn bootstrap_trust_root( &self, env_id: &EnvId, ) -> Result<TrustRootSeed, StoreError>
Load (or generate) the backend’s operator signing key and add it to
the env trust root. Idempotent re-grant: a second call with the same
operator key is a no-op on the key set (case-insensitive key_id
dedup), and it never rejects on an existing trust root.
Sourcefn seed_trust_root_if_absent(
&self,
env_id: &EnvId,
) -> Result<Option<TrustRootSeed>, StoreError>
fn seed_trust_root_if_absent( &self, env_id: &EnvId, ) -> Result<Option<TrustRootSeed>, StoreError>
Idempotent variant called from op env init: returns Some(seed)
when a key was minted, None when a trust root already existed.
Sourcefn add_trusted_key(
&self,
env_id: &EnvId,
key_id: String,
public_key_pem: String,
idempotency_key: IdempotencyKey,
) -> Result<TrustRootAddOutcome, StoreError>
fn add_trusted_key( &self, env_id: &EnvId, key_id: String, public_key_pem: String, idempotency_key: IdempotencyKey, ) -> Result<TrustRootAddOutcome, StoreError>
idempotency_key is required for A8 mutation consistency even though
add_trusted_key is intrinsically idempotent on key_id collision —
the key enables HTTP-backend audit-event replay. Local-FS impls accept
and ignore it; HTTP impls cache it for replay.
Sourcefn remove_trusted_key(
&self,
env_id: &EnvId,
key_id: String,
idempotency_key: IdempotencyKey,
) -> Result<TrustRootRemoveOutcome, StoreError>
fn remove_trusted_key( &self, env_id: &EnvId, key_id: String, idempotency_key: IdempotencyKey, ) -> Result<TrustRootRemoveOutcome, StoreError>
idempotency_key is required for A8 mutation consistency. remove
is logically idempotent on the trust-root state, but the wire-shape
removed_public_key_pem field would be null on retry (the original
PEM is gone) — so a retry without replay loses recovery material AND
audit fidelity. The key enables HTTP-backend replay of the original
response/audit; local-FS impls accept and ignore it.
Dyn Compatibility§
This trait is dyn compatible.
In older versions of Rust, dyn compatibility was called "object safety".