Skip to content

feat: support operations on push deployments without an Operator - #821

Merged
ab-alien-dev merged 28 commits into
mainfrom
alan/alien-1065-support-push-mode-model-for-operations
Oct 8, 2026
Merged

ab-alien-dev merged 28 commits into
mainfrom
alan/alien-1065-support-push-mode-model-for-operations

Conversation

@ab-alien-dev

@ab-alien-dev ab-alien-dev commented Oct 1, 2026 •

Copy link
Copy Markdown
Contributor

Summary

Operations are declared in alien.ts, and push deployments can run them without an Operator.

Declaring operations

  • Stack API: Stack.operations (OperationsConfig) and the TypeScript .operations({...}) builder. Built-in plugins are keyed by name with their settings and approval rules (an operation name or *; no rule means approval is needed). Published custom plugins go under plugins as name@version or an object. Changing operations takes a new release.
  • Plugin manifest settings: a plugin declares the settings it accepts (string, secret, or resources, with the env variable it reads). approval is reserved for approval rules and cannot be a setting key.
  • Sync contract: each OperationsBundleDownload carries the plugin's settings as env (redacted in Debug). SyncRequest.operationsConfig reports an Operator's own declaration without setting values.
  • Operators installed without a release: read the same JSON from OPERATOR_OPERATIONS (values are literals or { "env": "NAME" }). The Helm chart sets it from remoteOperator.operations, and remoteOperator.operationsEnv adds env entries (for example secretKeyRef) for those values.
  • CLI: alien operations list --deployment <id> lists a deployment's operations.
  • Examples: byoc-database declares cloudwatch and aws-vpc; custom-operations declares its settings.

Push deployments without an Operator

  • Reserved command target: operations-worker is reserved next to operator. Only an operations capability may address it, single-target shorthand never selects it, and its results follow the same rules as operator.
  • Supplied stacks: DeploymentRecord.supplied_stacks lets a deployment store that receives stacks from its control plane deploy those instead of re-reading the release by id. A same-release change must arrive as UpdatePending. Records without supplied stacks behave as before.
  • GCP: the Cloud Run worker's push subscription acknowledgement deadline is capped at Pub/Sub's 10–600s range, matching the Terraform emitter.
  • CLI access requests: the CLI reads approvalChannels from the coordinates and only waits for kubectl approval when that channel applies.

Removing permissions no longer requires setup

  • Update preflights accept permission profiles, management permissions, and frozen service accounts that only lost grants. The update runs with the installed identities, so it can still delete what the removed grants covered; the next setup trims them. Adding a grant, removing a role guard, or changing a permission set's contents still requires setup. Setup re-import keeps the exact comparison.
  • A frozen service account that only lost grants keeps its installed definition during the update.
  • Secret sync leaves a removed secret in place, with a warning, when the cloud denies the delete (403 / access denied), instead of failing the update.

Platform client SDK

The spec copies are regenerated: stack operations, operationsConfig on sync, approvalChannels on access requests, and removal of the per-project operations enablement and policy endpoints.

Release condition

approvalChannels is required on the coordinates response, and operations list --deployment uses a new API route. Do not cut a CLI release from this until the API that serves both is deployed.

Follow-up

operations-worker is reserved as a command target but not rejected as a stack resource id here; the control plane rejects the name at release creation.

Test plan

  • cargo test -p alien-preflights --lib compatibility (narrowing, guards, legacy capture, re-import strictness)
  • cargo test -p alien-manager --test stack_import
  • cargo test -p alien-deployment --lib (secret sync tolerance)
  • cargo test -p alien-operations-sdk --lib (settings, reserved approval)
  • cargo test -p alien-manager --lib loops::deployment (supplied-stack selection)
  • alien-commands and manager capability tests for the reserved target; CLI access-request tests
  • Helm generator test renders OPERATOR_OPERATIONS with real helm
  • End to end on an AWS push deployment: declaring operations installed the worker Lambda and ran an operation
  • End to end: removing operations deletes the worker without a setup rerun
  • CI green

- Reserve the `operations-worker` command target alongside `operator`:
  only an operations capability may address it, and single-target
  shorthand never selects it.
- Let a deployment store supply the stacks to deploy (`supplied_stacks`).
  The deployment loop uses them instead of re-reading the release, so a
  control plane can add deployment-specific resources.
- CLI: read `approvalChannels` from access-request coordinates and only
  wait for kubectl approval when that channel applies.
- Platform client SDK: approval channels and the access-request queue
  authorization.
@greptile-apps

greptile-apps Bot commented Oct 1, 2026 •

Copy link
Copy Markdown

RetriggerConfidence Score: 5/5

[High risk] Adds operations support without requiring an Operator component.

The PR appears safe to merge based on this review; no outstanding previous finding or actionable new issue was identified.

Summary

The PR adds stack-declared operations and a reserved operations worker for push deployments, carries control-plane-supplied stacks through deployment updates, and allows grant removals to wait for the next setup. Since the previous review, it also revises acquisition diagnostics, setup checkpoint finalization, and AWS service-account trust and policy reconciliation.

Diagram

%%{init: {'theme': 'neutral'}}%%
flowchart LR
  A[Stack operations declaration] --> B[Release and supplied deployment stack]
  B --> C[Push deployment operations worker]
  C --> D[Reserved operations command target]
  E[CLI setup] --> F[Checkpoint setup revision]
  F --> G[Release manager lease]
Loading

Reviews (10) · Last reviewed commit: "Merge remote-tracking branch 'origin/mai..." · Reviewed by Greptile

Comment thread crates/alien-manager/src/loops/deployment.rs
@greptile-apps

This comment has been minimized.

…rt-push-mode-model-for-operations

# Conflicts:
#	client-sdks/platform/openapi.json
#	client-sdks/platform/rust/openapi-3.0.json
#	client-sdks/platform/rust/openapi.json
#	crates/alien-cli/src/commands/access_requests.rs
#	crates/alien-manager/src/loops/deployment.rs
- Cap the Cloud Run worker's push subscription acknowledgement deadline at
  Pub/Sub's 10 to 600 second range, as the Terraform emitter already does.
  A worker timeout above 600s made the subscription fail to create.
- Enable the Pub/Sub API for stacks with a commands-enabled worker, not
  only for stacks with a queue.
@ab-alien-dev

Copy link
Copy Markdown
Contributor Author

@greptile-apps review

Comment thread crates/alien-preflights/src/mutations/gcp_service_activation.rs Outdated
- Revert enabling the Pub/Sub API for commands-enabled GCP workers. It
  adds a setup-owned resource, so every installed GCP deployment with
  such a worker would need a setup rerun on its next update. Commands-
  enabled workers already relied on the API being on, as it is by default.
- Document that a same-release supplied-stack change must arrive as
  UpdatePending.
- Move the misplaced coordinates doc comment to its function and rename
  it `fetch_approval_instructions`.
@ab-alien-dev

Copy link
Copy Markdown
Contributor Author

@greptile-apps review

ab-alien-dev and others added 16 commits October 3, 2026 19:32
…rt-push-mode-model-for-operations

# Conflicts:
#	client-sdks/platform/openapi.json
#	client-sdks/platform/rust/openapi-3.0.json
#	client-sdks/platform/rust/openapi.json
#	crates/alien-manager/src/traits/mod.rs
Adds Stack.operations_enabled (operationsEnabled in JSON, omitted when
false) and the enableOperations() builder method in Rust and TypeScript.
Deployments without an Operator get an operations worker and its
permission profile only when the stack sets it.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Replaces enableOperations() with operations({...}): built-in plugins by
name with settings and approval rules, and published custom plugins
pinned by name@version. Plugin manifests declare the settings they
accept. An Operator installed without a release reads the same JSON
from OPERATOR_OPERATIONS (Helm value remoteOperator.operations), resolves
{ env: NAME } settings locally, and reports the declaration without
values in sync. Bundle targets carry each plugin's settings env, kept in
memory only. The Manager forwards the reported declaration.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…rt-push-mode-model-for-operations

# Conflicts:
#	client-sdks/platform/openapi.json
#	client-sdks/platform/rust/openapi-3.0.json
#	client-sdks/platform/rust/openapi.json
#	crates/alien-core/src/lib.rs
#	packages/core/src/generated/schemas/stack.json
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Removing the last vault-backed worker drops the secrets vault, and setup
revokes the management role's write access with it. The secret sync then
tried to delete the values it wrote earlier and was denied. Forget them
instead of deleting them.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The update keeps setup-owned frozen resources in its target, so the
secrets vault stays in the stack after its last worker is removed. Check
the management role's vault grant instead of the vault's presence.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
An update that only removes grants runs with the installed identities and
leaves trimming them to the next setup. The removed grants are what the
update needs to delete the resources and secrets they covered; requiring
setup first revoked them, so those deletes were denied.

- compatibility checks accept profiles, management permissions and frozen
  service accounts that only lost grants (dropping a role guard still
  widens access and still needs setup)
- the update keeps a narrowed frozen service account's installed definition
- a denied secret delete leaves the value in place instead of failing
- a delete denied during an update fails; only teardown counts an
  unreachable resource as deleted

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…rt-push-mode-model-for-operations

# Conflicts:
#	crates/alien-preflights/src/compatibility/frozen_resources_unchanged.rs
The setup re-import check refuses settings that change the management
permissions setup installed. It now compares exactly; only update
preflights allow narrowing.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
…churn

- the settings env on a bundle is stored in the Operator's encrypted
  state; say so instead of claiming it is never persisted
- approval rules match an operation name or *, as implemented
- the stack field describes the declaration, not a consumer of it
- test that only teardown counts an unreachable resource as deleted
- revert formatting-only hunks in unrelated preflight files

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Takes main's executor delete rule (access denied ends a delete except
during a replace) and drops this branch's teardown-only variant: with
permission narrowing deferred to the next setup, an update still holds
the grants it needs to delete what a release removed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@ab-alien-dev

Copy link
Copy Markdown
Contributor Author

@greptile-apps review

2 similar comments
@ab-alien-dev

Copy link
Copy Markdown
Contributor Author

@greptile-apps review

@ab-alien-dev

Copy link
Copy Markdown
Contributor Author

@greptile-apps review

Comment thread packages/core/src/operations.ts
- a service account losing its last resource grant narrows when its
  profile holds no resource grants either; the uncaptured legacy format
  still needs setup
- plugin manifests may not declare a setting named approval, which
  operations() uses for approval rules
- only 403 or access denied lets a removed secret go; a 401 can be a
  transient credential failure and still fails the sync
- CLI approval text no longer says approval always happens on the
  customer's side

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@ab-alien-dev

Copy link
Copy Markdown
Contributor Author

@greptile-apps review

ab-alien-dev and others added 3 commits October 7, 2026 19:20
…rt-push-mode-model-for-operations

# Conflicts:
#	client-sdks/platform/openapi.json
#	client-sdks/platform/rust/openapi-3.0.json
#	client-sdks/platform/rust/openapi.json
#	crates/alien-manager/src/registry_access.rs
#	crates/alien-manager/src/traits/deployment_store.rs
Stack operations and the operations sync types appear in many schemas, so
the filtered client grew to about 240,000 lines, just past the cap.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>

@lilienblum lilienblum left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

One blocker remains: removing an Operator's final locally declared plugin does not report an empty declaration, so the control plane cannot clear its previously reported plugins and approval rules. See the inline finding.

Verdict: request changes.

Coverage: reviewed head a31dd7b against main at 78fa58c, merge base 093d0b5. Inspected the full 105-file change, operations types/builders, sync reporting, reserved command targets and capabilities, supplied-stack selection, permission narrowing, setup re-import strictness, secret cleanup and Helm generation. Ran cargo test -p alien-operations-sdk --lib (75 passed) and cargo test -p alien-preflights --lib compatibility (42 passed). CI still has a worker integration failure and a cancelled Rust job; the failed worker test reports a port-bind error, and I have not attributed it to this change. No live cloud deployment or full manager/deployment test suite was run. The documented API-before-CLI release order still applies.

Comment thread crates/alien-operator/src/loops/sync.rs Outdated
ab-alien-dev and others added 2 commits October 8, 2026 14:20
Removing the last plugin from OPERATOR_OPERATIONS left operationsConfig
out of sync, so the platform kept the previous declaration. A current
Operator now always reports one, empty when it declares nothing; only an
older Operator omits the field.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@ab-alien-dev

Copy link
Copy Markdown
Contributor Author

@greptile-apps review

Note for this review: the remaining blocker (a denied AWS read during deletion marking a still-existing resource gone) is in the delete-probe flow added on main by #935 (6f44ca0), which reached this branch through a merge. This PR does not change that flow; its only alien-infra changes are the GCP Pub/Sub ack-deadline clamp and two test-support struct fields. Please review it against main rather than attributing it to this change.

@ab-alien-dev

Copy link
Copy Markdown
Contributor Author

@greptile-apps review

The remaining blocker (a denied AWS read during deletion clearing a resource that may still exist) is in the delete-probe flow added on main by #935 and reached this branch through a merge; this PR does not change that code. It is tracked separately in #949 to be fixed against main.

…rt-push-mode-model-for-operations

# Conflicts:
#	client-sdks/platform/openapi.json
#	client-sdks/platform/rust/openapi-3.0.json
#	client-sdks/platform/rust/openapi.json
@ab-alien-dev

Copy link
Copy Markdown
Contributor Author

@greptile-apps review

@lilienblum lilienblum left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

…rt-push-mode-model-for-operations

# Conflicts:
#	crates/alien-manager/src/traits/mod.rs
@ab-alien-dev

Copy link
Copy Markdown
Contributor Author

@greptile-apps review

@ab-alien-dev
ab-alien-dev merged commit d860dd1 into main Oct 8, 2026
24 checks passed
@ab-alien-dev
ab-alien-dev deleted the alan/alien-1065-support-push-mode-model-for-operations branch October 8, 2026 15:32
lilienblum added a commit that referenced this pull request Oct 9, 2026
…ut the plugins key (ALIEN-1358) (#968)

## What changes

Since #821, `OPERATOR_OPERATIONS` (the `remoteOperator.operations` Helm
value) uses the `OperationsConfig` shape:

```json
{"plugins": {"kubernetes": {"approval": {"get-pods": "auto"}}}}
```

Before #821, the value was the plugin map itself:

```json
{"kubernetes": {"approval": {"get-pods": "auto"}}}
```

Values written in the earlier shape still deserialize, but serde ignores
the unknown top-level key. The Operator then starts with an empty
declaration and runs no operations. Nothing reports the problem: the
Operator logs no error, and it never requests an operations bundle. An
installation upgraded to an Operator built after #821 silently loses
every operation.

`parse_operator_operations` now reads a non-empty JSON object that has
neither a `plugins` nor a `custom` key as the plugin map, and logs a
warning naming the shape. Objects in the current shape, and `{}`, parse
exactly as before. Setting validation is unchanged. The format probe
only chooses whether to add the `plugins` wrapper. Typed deserialization
uses the original JSON text, so duplicate known fields remain errors in
both formats.

## What the next maintainer inherits

`with_plugins_key` in `alien-operator/src/cli.rs` is the only place the
earlier shape is accepted. It can go once no installation sets
`remoteOperator.operations` without `plugins`.

## Verification

- New test
`operator_operations_read_a_plugin_map_without_the_plugins_key`. It
fails on main, where the earlier shape yields no config, and passes with
this change. It also checks that both shapes parse to the same config.
- Original-head validation: `cargo test -p alien-operator --lib
operator_operations`: 2 passed.
- `operator_operations_reject_duplicate_fields` rejects duplicate
`plugins`, `custom`, `approval`, `settings`, custom plugin name, and
custom plugin version fields. It fails on the original PR head and
passes with the fix.
- An isolated harness compiled the exact production parser,
configuration types, error types, and parser tests with lockfile
dependency versions: 4 tests passed, including all eight duplicate-field
cases. Formatting and diff checks passed.
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.

2 participants