feat: gate every user resource — compute, email, and search - #200
feat: gate every user resource — compute, email, and search#200ItamarZand88 wants to merge 10 commits into
Conversation
The gateability policy drops its compute arm — declining a live workload rides the same removal path as deleting it from a release, and its provisioning baseline persists so acceptance can return — and empties the not-yet-generic list now that email and opensearch render gated through the post-pass (proven by their new matrix fixtures; the SES inbound grant demonstrably follows the Email gate, and a Live gate is proven invisible to setup while its input still reaches the deployer). Worker, Daemon, Container, Email, and the experimental AwsOpenSearch builders gain .enabled(); the regenerated manifest keeps the SDK surface test honest about which lifecycles each type gates in.
The initial path stripped declined resources after the mutations while the update path stripped before them, so declining a live resource on an update rederived service accounts, profiles, and capacity from a stack missing it — tripping the frozen-compatibility check that the initial path's ordering never trips. Both paths now agree: frozen declines apply before the mutations (a declined setup resource never existed, so nothing may be derived from it), and live declines apply after them, at the boundary where the executor's desired set is built, so a declined workload's provisioning baseline stays identical to the accepted render and acceptance can return. Proven by the worker decline/reaccept state-machine test. A frozen gate on a direct deployment now resolves the initial input values (provided value, else declared default, never a guess); the empty state previously carried no answer and the resource was created regardless of the deployer's choice. The manager's import route composes the two strips back to back — no mutations run between them there.
The canonical resolved answer for every input gating a Frozen resource is recorded on the runtime metadata when the deployment is created — the same record/state/sync surface that already carries the prepared stack, so no store or protocol shape changes. The update path refuses input values that conflict with a recorded answer; states from before this contract derive their answers from the settled stack state (presence IS the original answer once setup completed) and upgrade atomically before the check, refusing rather than guessing when resources sharing one gate disagree. Deployment protocol CURRENT moves to 2 (MIN stays 1): an actor unaware of the fixity contract would step a deployment against the deployer's recorded answer, so older actors refuse newer states instead. Live-gate transitions now emit a structured audit event when detected, with an operation id derived from resource, input, answer, and release — identical across retries of the same step by construction, fresh for a later flip — completed or failed by the executor's ordinary per-resource status transitions. The preflight gains the pause-consumer contract tests: a gated worker consuming an ungated queue passes (the queue's retention policy governs the backlog), while an ungated worker consuming a gated queue stays refused.
The import route now resolves every frozen-gating input's answer before the strip and records it on the created deployment, so imported deployments carry their answers from birth instead of deriving them on their first update. A re-registration whose derived answers conflict with the recorded ones is refused synchronously with FROZEN_GATE_ANSWER_CHANGED — and because the caller is the setup artifact's custom resource mid-stack-update, the failed response is what forces CloudFormation to roll the parameter edit back, restoring whatever its conditionals just created or deleted. The refusal runs before setup-update authorization is minted, so an answer flip cannot ride an otherwise-authorized setup update; answers for inputs a new release introduces are recorded, and recorded answers are never overwritten.
The internal protocol rules are explicit: an additive optional field does not bump the version, and the earlier justification stretched the actor-responsibility clause. Re-derived from first principles, the bump bought nothing — an older actor's strip already resolves frozen presence from state, so a flipped input is ignored rather than applied, and answers dropped by an old write-back are rebuilt faithfully by the derive-when-empty fallback — while costing a lot: pull agents upgrade on the customer's schedule, and a version-2 state would hard-refuse every older agent the moment a newer manager writes one. New actors refuse conflicting answers loudly; older actors keep the silent-ignore behavior they always had. Nothing can flip a frozen resource either way.
Adds a distribution test app with a worker plus a matched on/off pair of every gated resource type (KV, storage, queue, vault) behind .enabled(input). The test answers the four *-on inputs true and the four *-off inputs false, provisions to AWS via Terraform, then asserts each enabled resource and its grant reach the imported stack_state and the account while each declined resource is absent. Wires TestApp::EnabledDemo through the harness (input_values -> tfvars) and adds the terraform-aws matrix entry to e2e-cloud.yml behind an enabled-demo app filter.
The compute gate rides the live strip, so the real-cloud proof is the pair pattern the app already uses: the accepted worker's function must exist while the declined worker's function is never provisioned — and both dedicated profiles' service accounts must exist, the persisted baseline that lets a later acceptance recreate the function without a setup change. Neither gated worker links anything, so no grant depends on them.
Greptile SummaryThe PR expands
Confidence Score: 3/5The PR should not merge until updates correctly resolve and persist newly introduced frozen gates instead of silently omitting accepted resources. A deployment that already records one frozen-gate answer skips resolution for later gates, after which state-based stripping removes a newly accepted frozen resource before compatibility checks and leaves its answer unrecorded. Files Needing Attention: crates/alien-deployment/src/updating.rs and crates/alien-deployment/src/pending.rs
|
| Filename | Overview |
|---|---|
| crates/alien-deployment/src/pending.rs | Splits frozen/live stripping, resolves and enforces gate answers, and audits live transitions; state-presence inference is central to update behavior. |
| crates/alien-deployment/src/updating.rs | Reorders update preparation around the split strips but fails to resolve newly added frozen gates when another persisted answer already exists. |
| crates/alien-core/src/deployment/state.rs | Adds backward-compatible persisted frozen-gate answers to runtime metadata. |
| crates/alien-manager/src/routes/stack.rs | Records imported frozen answers and rejects conflicting setup re-registrations synchronously. |
| crates/alien-core/src/gateability.rs | Broadens gateability policy to live compute and frozen email/OpenSearch resources. |
| crates/alien-preflights/src/compile_time/resource_enabled_valid.rs | Allows gated compute while retaining rejection of ungated dependents on gated resources. |
| packages/core/src/worker.ts | Adds the TypeScript .enabled() builder surface for workers. |
| packages/core/src/container.ts | Adds the TypeScript .enabled() builder surface for containers. |
| packages/core/src/daemon.ts | Adds the TypeScript .enabled() builder surface for daemons. |
| packages/core/src/email.ts | Adds the TypeScript .enabled() builder surface for email resources. |
| packages/core/src/experimental/aws-opensearch.ts | Adds the TypeScript .enabled() builder surface for experimental OpenSearch collections. |
Flowchart
%%{init: {'theme': 'neutral'}}%%
flowchart TD
A[Deployment update] --> B[Load persisted frozen answers]
B --> C{Answer map empty?}
C -- Yes --> D[Resolve answers from current state]
C -- No --> E[Keep existing map unchanged]
D --> F[Enforce frozen-gate fixity]
E --> F
F --> G[Strip frozen resources absent from state]
G --> H[Run mutations and compatibility checks]
H --> I[Strip declined live resources]
I --> J[Persist pending prepared stack and answers]
Prompt To Fix All With AI
Fix the following 1 code review issue. Work through them one at a time, proposing concise fixes.
---
### Issue 1 of 1
crates/alien-deployment/src/updating.rs:81-87
**New frozen gates stay unresolved**
When an update adds a frozen gated resource to a deployment that already has another persisted gate answer, this nonempty-map guard skips resolution for the new gate. Fixity enforcement then has no entry to check, and the state-based frozen strip treats the not-yet-created resource as declined regardless of a supplied `true` value, causing the update to succeed without creating the resource or recording its answer.
Reviews (1): Last reviewed commit: "test(e2e): gate a worker pair in the ena..." | Re-trigger Greptile
| if persisted_gate_answers.is_empty() { | ||
| persisted_gate_answers = crate::pending::resolve_frozen_gate_answers( | ||
| &target_stack, | ||
| &stack_state, | ||
| &Default::default(), | ||
| )?; | ||
| } |
There was a problem hiding this comment.
New frozen gates stay unresolved
When an update adds a frozen gated resource to a deployment that already has another persisted gate answer, this nonempty-map guard skips resolution for the new gate. Fixity enforcement then has no entry to check, and the state-based frozen strip treats the not-yet-created resource as declined regardless of a supplied true value, causing the update to succeed without creating the resource or recording its answer.
Knowledge Base Used: Deployment Engine (alien-deployment)
Prompt To Fix With AI
This is a comment left during a code review.
Path: crates/alien-deployment/src/updating.rs
Line: 81-87
Comment:
**New frozen gates stay unresolved**
When an update adds a frozen gated resource to a deployment that already has another persisted gate answer, this nonempty-map guard skips resolution for the new gate. Fixity enforcement then has no entry to check, and the state-based frozen strip treats the not-yet-created resource as declined regardless of a supplied `true` value, causing the update to succeed without creating the resource or recording its answer.
**Knowledge Base Used:** [Deployment Engine (alien-deployment)](https://app.greptile.com/alien/-/custom-context/knowledge-base/alienplatform/alien/-/docs/deployment-engine.md)
How can I resolve this? If you propose a fix, please make it concise.|
Superseded by the same commits on a branch named per the PR validator; reopened as a new PR. |
Summary
.enabled()now covers every user resource: compute gates as a live existence choice, email and search gate at setup — each admitted with zero type-specific gating code, which is what the generator-owned mechanism exists for. This PR also pins the semantics: a frozen gate is answered once and enforced for the deployment's lifetime, and a declined live workload keeps its derived baseline so acceptance can return.What happens when a deployer changes a gate's value on an update:
FROZEN_GATE_ANSWER_CHANGED); a re-registration that flips one fails synchronously, so a CloudFormation parameter edit rolls the whole stack update back.This PR changes the gateable set from four store types to every user resource, with frozen answers fixed and live answers reversible.
What was broken
What I did
.enabled(), held to the policy by the generated manifest.Files touched
crates/alien-core/src/gateability.rs,deployment/state.rs— policy widening;GateAnswerson the runtime metadatacrates/alien-deployment/src/{pending,updating,lib,error}.rs— the split strip, answer resolution and enforcement, audit events, typed refusalscrates/alien-manager/src/routes/stack.rs,tests/stack_import.rs— import-time recording and the synchronous re-import refusalcrates/alien-preflights/src/compile_time/resource_enabled_valid.rs— compute ban removed; pause-consumer tests both directionspackages/core/src/{worker,daemon,container,email}.ts,experimental/aws-opensearch.ts, tests, manifest — the SDK surfacecrates/alien-deployment/tests/test_platform.rs— worker decline/re-accept baseline test; frozen-answer lifetime testtests/e2e/test-apps/enabled-demo/*,crates/alien-test/{src,tests}/distribution.rs— the gated worker pair and its cloud assertionsHow I tested
cargo test -p alien-deployment --test test_platformdrives the full cycles — a live worker declined and re-accepted across updates with its service account asserted present in the prepared stack throughout, and a frozen answer recorded, refused on conflict, accepted on match, and still refused after wiping the recorded answers to simulate an older deployment.cargo test -p alien-manager --test stack_import— a fresh import records the declined and accepted answers; a re-registration that flips one gets a synchronous 400 withFROZEN_GATE_ANSWER_CHANGED.tsc.terraform_aws_push_enabled_demoeach failed during image upload with an ngrok gateway error (Service Unavailable, before Terraform ran — no gating code in the path, no cloud resources created), so the run is dispatched on this branch in the cloud e2e workflow with theenabled-demoapp filter; the PR should merge on that run's green.I also ran a security review on the diff. What it checked:
Nothing turned up.
Out of scope