Skip to content

feat: support .enabled(input) on resources#172

Open
ItamarZand88 wants to merge 4 commits into
mainfrom
feat/enabled-core
Open

feat: support .enabled(input) on resources#172
ItamarZand88 wants to merge 4 commits into
mainfrom
feat/enabled-core

Conversation

@ItamarZand88

@ItamarZand88 ItamarZand88 commented Jul 21, 2026

Copy link
Copy Markdown
Contributor

.enabled(input) on resources: a boolean deployer input decides whether a resource exists. new alien.Kv("analytics").enabled(io.analyticsEnabled) — setup-created resources bake the answer into the install template and it is fixed for the deployment's lifetime; runtime-managed resources follow later edits, so disabling one deletes it (data included, by design) and re-enabling recreates it. This PR is the mechanism; the store emitters convert in the follow-up PRs stacked on this one (kv, storage, queue, vault).

What happens when a deployer answers the install, or edits the answer later:

  1. The setup template renders the resource behind its input (count = var.x ? 1 : 0 in Terraform, a named Condition in CloudFormation) — a disabled resource is also absent from IAM policies and the registration payload.
  2. The runner strips disabled resources from the desired stack at every entry point. ← the heart: the import's answer is final for setup-created resources; runtime-managed ones re-resolve the stored value (or its declared default) on every reconcile, and an unresolvable input is a typed error, never a guess
  3. The executor's ordinary create/delete planning does the rest — a resource that left the desired stack is deprovisioned, one that entered it is created, and the state converges back to running.

This PR changes resource creation from unconditional to opt-in through .enabled().

What's in the layer

  • SDK surface (packages/core) — .enabled(input) on the data-store builders, an optional enabledWhen on stack entries; stacks that don't use it serialize byte-identically.
  • Setup generators (alien-terraform, alien-cloudformation) — conditional rendering, the splices that keep a disabled resource out of IAM and the registration, and render-time re-validation for callers that skip preflights. No emitter converts yet; .enabled() on an unconverted type refuses at render time.
  • The rulebook (alien-preflights) — refuses any .enabled() that could not actually keep the resource out: non-boolean or non-deployer inputs, missing defaults, compute and framework types, the deployment secrets vault, '*'-scoped grants for a controlled type, dependents that don't share their dependency's input.
  • The runner (alien-deployment, alien-manager) — the value-aware strip, pruning of deprovisioned entries so updates converge, an optional inputValues on the deployment config (additive, no protocol change), and the import-route threading.
  • Update drift check — an .enabled() resource's grants are exempt for presence only; contents are still compared, so a grant escalation cannot hide behind the toggle.
  • Public API spec (client-sdks/platform/*.json) — the two new optional fields.

How I tested

  • cargo test across alien-core, alien-preflights, alien-deployment, alien-manager, alien-terraform, alien-cloudformation — including full-state-machine tests that drive the toggle end to end (provision → deprovision → converge → reprovision) and the disabled-import path. Rendered templates are validated with cfn-lint, terraform validate, and a deploy-time condition-resolution model, not string matching.
  • End to end on a real AWS account (us-east-2): released a demo app with one fixed-at-install store and one runtime-managed store, installed through the generated CloudFormation template, then verified with the aws CLI — a disabled install creates neither table nor IAM grants nor registration entry; turning the runtime-managed store off through a real stack update deleted the DynamoDB table and the deployment converged; turning it on recreated it; editing the fixed-at-install answer was refused and the stack rolled back intact.
  • Re-ran the full live round after rebasing on latest main, where the removed-resource update fix (fix: complete updates after resource removal #173) landed in parallel: a fresh install with both stores enabled, live toggle off deleted the table within a reconcile, toggle on recreated it, fixed-answer edit refused with a clean rollback. The rebase also reconciles the two convergence fixes: a resource removed from the release keeps its deletion tombstone, while a disabled gated resource still clears its state entry so re-enabling starts from a clean slate.

Security walk (this PR touches permissions and IAM rendering):

  • A '*'-scoped grant outliving a disabled resource — refused at compile time, management profile included.
  • A grant escalation hiding behind the toggle in the update drift check — a controlled scope present on both sides still has its contents compared; the '*' exemption covers only the derived management suffixes, compared by full reference.
  • A disabled resource keeping reachable access — its IAM statements and registration entry are spliced out, asserted through deploy-time resolution.
  • Nothing turned up.

@greptile-apps

greptile-apps Bot commented Jul 21, 2026

Copy link
Copy Markdown

Greptile Summary

This PR adds input-controlled resource enablement across deployment setup and reconciliation. The main changes are:

  • Adds .enabled(input) to resource builders and stack entries.
  • Validates supported gates and dependent resource rules during preflight.
  • Renders conditional resources, permissions, and registrations in setup templates.
  • Filters disabled resources during import and reconciliation.
  • Persists deployer input values for later reconciliation.
  • Extends the public API schemas with optional gate fields.

Confidence Score: 4/5

This should be fixed before merging.

  • SQLite persistence now retains gate answers across create and import paths.
  • Reconciliation can still ignore those stored answers when an older control-plane config omits the optional field.
  • Falling back to defaults can delete or recreate gated resources.

crates/alien-manager/src/loops/deployment.rs

Important Files Changed

Filename Overview
crates/alien-manager/src/loops/deployment.rs Builds reconciliation configuration from control-plane or persisted deployment data, but the control-plane branch can omit stored gate answers.
crates/alien-manager/src/stores/sqlite/deployment.rs Persists and reloads input values during deployment creation, import, and re-import.
crates/alien-manager/src/routes/sync.rs Carries stored input values into remote-runner configuration while preserving supplied control-plane values.

Comments Outside Diff (1)

  1. crates/alien-manager/src/loops/deployment.rs, line 566-585 (link)

    P1 Fall Back To Stored Answers When deployment_config exists but comes from a client that omitted the new optional inputValues field, its map is empty and this branch does not fall back to deployment.input_values. The runner then resolves resource gates from their defaults during reconciliation. This can delete an explicitly enabled resource and its data, or recreate an explicitly disabled resource. Preserve explicit control-plane values, but use the persisted deployment values when the supplied config has no gate answers.

    Prompt To Fix With AI
    This is a comment left during a code review.
    Path: crates/alien-manager/src/loops/deployment.rs
    Line: 566-585
    
    Comment:
    **Fall Back To Stored Answers** When `deployment_config` exists but comes from a client that omitted the new optional `inputValues` field, its map is empty and this branch does not fall back to `deployment.input_values`. The runner then resolves resource gates from their defaults during reconciliation. This can delete an explicitly enabled resource and its data, or recreate an explicitly disabled resource. Preserve explicit control-plane values, but use the persisted deployment values when the supplied config has no gate answers.
    
    How can I resolve this? If you propose a fix, please make it concise.
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-manager/src/loops/deployment.rs:566-585
**Fall Back To Stored Answers** When `deployment_config` exists but comes from a client that omitted the new optional `inputValues` field, its map is empty and this branch does not fall back to `deployment.input_values`. The runner then resolves resource gates from their defaults during reconciliation. This can delete an explicitly enabled resource and its data, or recreate an explicitly disabled resource. Preserve explicit control-plane values, but use the persisted deployment values when the supplied config has no gate answers.

Reviews (2): Last reviewed commit: "fix: persist deployer input values in th..." | Re-trigger Greptile

Comment thread crates/alien-manager/src/stores/sqlite/deployment.rs Outdated
@ItamarZand88
ItamarZand88 force-pushed the feat/enabled-core branch 2 times, most recently from 2012583 to 0f63183 Compare July 21, 2026 12:51
@ItamarZand88

Copy link
Copy Markdown
Contributor Author

Fixed the follow-up finding in 3c1d46e: when a supplied config carries no gate answers (a control plane that predates the field), both the reconcile loop and the sync-acquire target config now fall back to the deployment's persisted values instead of resolving gates from their declared defaults. Covered by the extended target-config test.

The sqlite store dropped stack input values at every write path, so a
standalone manager resolved gated live resources from their declared
defaults instead of the deployer's stored answers. Store them in a new
input_values column, feed them into the reconcile config, and carry
them into the sync-acquire target config for remote runners.
A deployment config supplied by a control plane that predates gate
answers carries an empty input-values map. Resolving gates from their
declared defaults instead of the stored answers could deprovision an
enabled resource, so both the reconcile loop and the sync-acquire
target config now use the persisted values whenever the supplied
config has none.
The sibling secret-sync tests serialize on a shared vault-dir guard;
without it this test races them through the process-global env var and
either side can read a half-written secrets file. Only bites plain
cargo test runs — nextest isolates processes.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant