@evident-ai/runner-cdk 3.5.2-dev.1c27282 → 3.5.2-dev.3f77e80
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +89 -39
- package/dist/controller-lambda/handler.js +16 -16
- package/dist/evident-scale-to-zero-construct.js +7 -0
- package/dist/image-version-pruner-lambda/handler.js +28250 -0
- package/dist/image-version-reporter-lambda/handler.js +15 -8
- package/dist/index.d.ts +1 -1
- package/dist/index.js +3 -2
- package/dist/microvm/constants.d.ts +1 -1
- package/dist/microvm/constants.js +7 -4
- package/dist/microvm/construct.d.ts +12 -8
- package/dist/microvm/construct.js +42 -33
- package/dist/microvm/controller/handle-doorbell.d.ts +2 -2
- package/dist/microvm/controller/handle-doorbell.js +2 -2
- package/dist/microvm/image/stage-context.d.ts +13 -38
- package/dist/microvm/image/stage-context.js +31 -118
- package/dist/microvm/image-version-pruner/construct.d.ts +13 -0
- package/dist/microvm/image-version-pruner/construct.js +79 -0
- package/dist/microvm/image-version-pruner/handler.d.ts +20 -0
- package/dist/microvm/image-version-pruner/handler.js +116 -0
- package/dist/microvm/image-version-reporter/construct.d.ts +1 -1
- package/dist/microvm/image-version-reporter/construct.js +3 -3
- package/dist/microvm/image-version-reporter/handler.d.ts +2 -2
- package/dist/microvm/image-version-reporter/handler.js +9 -7
- package/dist/microvm/shapes.d.ts +10 -21
- package/dist/microvm/shapes.js +10 -17
- package/dist/waker-lambda/handler.js +3 -3
- package/package.json +3 -3
- package/dist/microvm-image-context/Dockerfile +0 -190
- package/dist/microvm-image-context/hook-server.js +0 -286
- package/dist/microvm-image-context/hooks/common.sh +0 -905
- package/dist/microvm-image-context/hooks/resume +0 -81
- package/dist/microvm-image-context/hooks/run +0 -95
- package/dist/microvm-image-context/hooks/suspend +0 -21
- package/dist/microvm-image-context/hooks/terminate +0 -36
package/README.md
CHANGED
|
@@ -9,9 +9,9 @@ Reusable AWS CDK constructs for running an [Evident](https://evident.run) agent
|
|
|
9
9
|
`desiredCount 0`, and an HMAC-authenticated waker Lambda scales it back up on the next
|
|
10
10
|
message. You pay for the time the agent is actually working.
|
|
11
11
|
- **`EvidentMicrovmConstruct`** — a per-session AWS Lambda MicroVM that boots on demand
|
|
12
|
-
and suspends between messages.
|
|
13
|
-
|
|
14
|
-
|
|
12
|
+
and suspends between messages. You provide a build-context directory and a full source
|
|
13
|
+
checkout to `stageMicrovmBuildContext`, then pass its returned directory as
|
|
14
|
+
`buildContextPath` (see [The MicroVM image](#the-microvm-image) below). See
|
|
15
15
|
[the AWS runner doc](https://evident.run/docs/aws-runner) for the full picture of both
|
|
16
16
|
strategies.
|
|
17
17
|
|
|
@@ -99,6 +99,26 @@ For the full end-to-end walkthrough for this construct, see
|
|
|
99
99
|
| `agentSecret`, `wakerSecret` | `secretsmanager.ISecret` | Any Secrets Manager secret. |
|
|
100
100
|
| `availableSecretKeys` | `Set<string>` | Keys present in the agent secret. Every listed key is injected; `EVIDENT_RUNNER_KEY` and `EVIDENT_AGENT_KEY` are injected from the `EVIDENT_AGENT_KEY` field, and `GH_TOKEN` is also injected for adopters that do not enumerate their secret. |
|
|
101
101
|
|
|
102
|
+
### MicroVM props
|
|
103
|
+
|
|
104
|
+
| Prop | Type | Notes |
|
|
105
|
+
| --- | --- | --- |
|
|
106
|
+
| `buildContextPath` | `string` | Directory containing your Dockerfile and every file it copies, normally the result of `stageMicrovmBuildContext()`. |
|
|
107
|
+
| `baseImageArn` / `baseImageVersion` | `string` | The AWS-managed base MicroVM image values from `aws lambda-microvms list-managed-microvm-images`. |
|
|
108
|
+
| `controllerSigningSecret` | `secretsmanager.ISecret` | Secret containing the shared HMAC key under the `CONTROLLER_SIGNING_SECRET` JSON field. |
|
|
109
|
+
| `hooksPort` / `hookTimeoutSeconds` | `number` | Values exported by the base image contract. |
|
|
110
|
+
| `shapes` | `readonly MicrovmShape[]?` | Shape catalogue to build. Defaults to the committed catalogue. |
|
|
111
|
+
| `runnerSecret` | `secretsmanager.ISecret?` | Optional JSON secret whose values are exported by `/run`. |
|
|
112
|
+
| `runnerOpencodeConfigPath` | `string?` | Optional baked OpenCode configuration path. |
|
|
113
|
+
| `gitUserName` / `gitUserEmail` | `string?` | Optional git identity for agent commits. |
|
|
114
|
+
| `extraImageEnvironment` | `Record<string,string>?` | Non-secret values baked into every VM launched from the image version. |
|
|
115
|
+
| `evidentApiUrl` / `evidentRunnerId` | `string?` | Supply both to report published image versions to Evident, or omit both. |
|
|
116
|
+
| `imageVersionPruning` | `boolean?` | Opts into construct-owned pruning for this deployment's images. Uses the engine defaults; omitting it creates no pruning resources. |
|
|
117
|
+
|
|
118
|
+
The `DoorbellSecret` CloudFormation logical ID remains stable for Evident's own
|
|
119
|
+
default stack. It identifies the deployed secret resource, not the customer-facing
|
|
120
|
+
name of the signing key.
|
|
121
|
+
|
|
102
122
|
### Two deliberate "no default" choices
|
|
103
123
|
|
|
104
124
|
- **The endpoints are required.** Defaulting them would either bake *our* dev environment
|
|
@@ -116,58 +136,73 @@ built from a `Dockerfile` you control. The generic runner image lives separately
|
|
|
116
136
|
|
|
117
137
|
## The MicroVM image
|
|
118
138
|
|
|
119
|
-
|
|
120
|
-
context, which `cdk deploy` zips and uploads. The Lambda MicroVM service then builds the
|
|
121
|
-
image in *your* account, from a base image ARN you discover there — so, unlike the Fargate
|
|
122
|
-
strategy, there is no registry image to pull.
|
|
139
|
+
### Prune image versions
|
|
123
140
|
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
141
|
+
MicroVM image versions are quota-bound. Opt into construct-owned pruning with one
|
|
142
|
+
property:
|
|
143
|
+
|
|
144
|
+
```ts
|
|
145
|
+
new EvidentMicrovmConstruct(this, 'Runner', {
|
|
146
|
+
// ...other required MicroVM props
|
|
147
|
+
imageVersionPruning: true,
|
|
148
|
+
});
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
This bounds the per-image version quota (`L-F8BECE9C`) for this deployment's shapes only.
|
|
152
|
+
Retention follows the engine defaults, and pruning runs before the image publish for the
|
|
153
|
+
deploy that invokes it. The custom-resource Lambda writes per-image verdicts and a summary
|
|
154
|
+
to CloudWatch Logs. Use the `prune-microvm-image-versions` CLI for deployments that do not
|
|
155
|
+
use this construct or that need non-default retention; see the
|
|
156
|
+
`@evident-ai/lambda-microvm-cdk` package README for its flags and safety semantics.
|
|
157
|
+
|
|
158
|
+
`EvidentMicrovmConstruct`'s `buildContextPath` is a **local directory** holding the
|
|
159
|
+
Dockerfile you control and everything it copies. `cdk deploy` zips and uploads that
|
|
160
|
+
directory, and the Lambda MicroVM service builds the image in *your* account. Start the
|
|
161
|
+
Dockerfile from a pinned `ghcr.io/sroze/evident-microvm-base:sha-3f3949d` tag; the base image
|
|
162
|
+
contains the hook server, lifecycle hooks and common agent tooling.
|
|
163
|
+
|
|
164
|
+
`stageMicrovmBuildContext()` combines your build-context directory with a full checkout
|
|
165
|
+
of the workspace in a fresh temporary directory. The workspace is written at
|
|
166
|
+
`workspace/`, and the returned path is the value for `buildContextPath`:
|
|
129
167
|
|
|
130
168
|
```ts
|
|
131
|
-
import path from 'node:path';
|
|
132
169
|
import {
|
|
133
170
|
EvidentMicrovmConstruct,
|
|
134
171
|
HOOKS_PORT,
|
|
135
172
|
HOOK_TIMEOUT_SECONDS,
|
|
136
|
-
|
|
173
|
+
stageMicrovmBuildContext,
|
|
137
174
|
} from '@evident-ai/runner-cdk';
|
|
138
175
|
|
|
176
|
+
const buildContextPath = stageMicrovmBuildContext({
|
|
177
|
+
// A full (non-shallow) clone: the agent branches, commits and opens PRs.
|
|
178
|
+
workspaceRepositoryPath: '/path/to/your/checkout',
|
|
179
|
+
// Credential-free: the token the agent pushes with arrives per-session.
|
|
180
|
+
workspaceOriginUrl: 'https://github.com/acme/widgets.git',
|
|
181
|
+
buildContextPath: '/path/to/your/microvm-build-context',
|
|
182
|
+
});
|
|
183
|
+
|
|
139
184
|
new EvidentMicrovmConstruct(this, 'Runner', {
|
|
140
|
-
|
|
141
|
-
// A full (non-shallow) clone: the agent branches, commits and opens PRs.
|
|
142
|
-
repositoryPath: '/path/to/your/checkout',
|
|
143
|
-
// Credential-free — this string ships inside the shared snapshot. The
|
|
144
|
-
// token the agent pushes with arrives per-session, never baked in.
|
|
145
|
-
originUrl: 'https://github.com/acme/widgets.git',
|
|
146
|
-
destination: path.join(__dirname, '..', 'build', 'image'),
|
|
147
|
-
// Optional deployment-specific installs and build warm-up.
|
|
148
|
-
overlayDir: '/path/to/microvm-overlay',
|
|
149
|
-
}),
|
|
185
|
+
buildContextPath,
|
|
150
186
|
// Must match the published image these were baked into, so export them
|
|
151
187
|
// rather than restating the numbers.
|
|
152
188
|
hooksPort: HOOKS_PORT,
|
|
153
189
|
hookTimeoutSeconds: HOOK_TIMEOUT_SECONDS,
|
|
154
190
|
baseImageArn,
|
|
155
191
|
baseImageVersion,
|
|
156
|
-
|
|
192
|
+
controllerSigningSecret,
|
|
157
193
|
runnerSecret,
|
|
158
194
|
runnerOpencodeConfigPath: 'opencode.runner.jsonc',
|
|
159
195
|
});
|
|
160
196
|
```
|
|
161
197
|
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
for the full overlay contract.
|
|
198
|
+
Write project-specific installation and build steps in your Dockerfile. The [MicroVM base
|
|
199
|
+
image README](../../runner/docker-images/microvm/README.md) documents the consumer
|
|
200
|
+
contract, including the required `USER`, `WORKDIR`, ownership and `git reset --quiet`
|
|
201
|
+
steps.
|
|
167
202
|
|
|
168
|
-
The image
|
|
169
|
-
|
|
170
|
-
|
|
203
|
+
The staged image includes your repository checkout, while your Dockerfile controls its
|
|
204
|
+
package manager and build warm-up. Do not copy credentials into the build context or bake
|
|
205
|
+
them into image environment variables.
|
|
171
206
|
|
|
172
207
|
| MicroVM prop | Omitted behavior |
|
|
173
208
|
| --- | --- |
|
|
@@ -175,12 +210,27 @@ its package manager. To speed up first boot, install dependencies from your over
|
|
|
175
210
|
| `runnerOpencodeConfigPath` | OpenCode uses the baked project configuration. Relative paths resolve from the workspace; absolute paths resolve in the image. |
|
|
176
211
|
| `extraImageEnvironment` | Non-secret values baked into every VM launched from this image version; every VM can read them, so never put secrets here. |
|
|
177
212
|
| `gitUserName` / `gitUserEmail` | The hook uses the Evident bot defaults for git identity. |
|
|
178
|
-
| `evidentApiUrl` / `evidentRunnerId` | No deploy-time report of the published image version — a byte-identical template to not having these props at all. Both non-secret (a URL, a public pool UUID) and both required together: set them if you have an Evident runner id configured for this provisioner, so each deploy that publishes a new image version tells Evident about it (a CloudFormation custom resource, HMAC-signed with `
|
|
179
|
-
|
|
180
|
-
`extraImageEnvironment` is the runtime
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
213
|
+
| `evidentApiUrl` / `evidentRunnerId` | No deploy-time report of the published image version — a byte-identical template to not having these props at all. Both non-secret (a URL, a public pool UUID) and both required together: set them if you have an Evident runner id configured for this provisioner, so each deploy that publishes a new image version tells Evident about it (a CloudFormation custom resource, HMAC-signed with `controllerSigningSecret`). A failure to report never fails your deploy — it warns and Evident reads that provisioner as *unknown*, never as up to date or stale. |
|
|
214
|
+
|
|
215
|
+
`extraImageEnvironment` is the runtime environment for the image. Values are baked into
|
|
216
|
+
every VM launched from that image version and are readable by all of them, so never put a
|
|
217
|
+
secret in this prop.
|
|
218
|
+
|
|
219
|
+
## Breaking changes in this release
|
|
220
|
+
|
|
221
|
+
- `stageMicrovmImageContext` is removed. Use `stageMicrovmBuildContext()` with your
|
|
222
|
+
Dockerfile and build context.
|
|
223
|
+
- `imageSource` is renamed to `buildContextPath`.
|
|
224
|
+
- `MicrovmShape.constructId` is removed. CloudFormation child identity derives from
|
|
225
|
+
the shape `name`.
|
|
226
|
+
- `doorbellSecret` is renamed to `controllerSigningSecret`.
|
|
227
|
+
- The default shape's `imageName` changes to `evident-microvm-standard`. The
|
|
228
|
+
`AWS::Lambda::MicrovmImage` resource, its auto-created build role and execution
|
|
229
|
+
role, and both roles' default policies are replaced with the new shape identity.
|
|
230
|
+
- The JSON field in the caller-supplied Secrets Manager secret changes from
|
|
231
|
+
`DOORBELL_SECRET` to `CONTROLLER_SIGNING_SECRET`. Rename that field in your own
|
|
232
|
+
secret. If you do not, the controller reads no signing key and every doorbell
|
|
233
|
+
returns HTTP 401 at runtime; TypeScript cannot catch this field-key change.
|
|
184
234
|
|
|
185
235
|
## Status / limitations
|
|
186
236
|
|
|
@@ -49799,9 +49799,9 @@ var MICROVM_MAX_RUN_SECONDS = 28800;
|
|
|
49799
49799
|
// src/microvm/controller/handle-doorbell.ts
|
|
49800
49800
|
var import_node_crypto12 = require("node:crypto");
|
|
49801
49801
|
|
|
49802
|
-
//
|
|
49802
|
+
// ../../sdk/src/signature.ts
|
|
49803
49803
|
var import_node_crypto11 = require("node:crypto");
|
|
49804
|
-
function
|
|
49804
|
+
function verifyEvidentSignature(rawBody, signatureHeader, secret) {
|
|
49805
49805
|
if (!signatureHeader || !secret) {
|
|
49806
49806
|
return false;
|
|
49807
49807
|
}
|
|
@@ -50036,13 +50036,13 @@ async function shouldRecreateForNewerImage(doorbellRequest, shape, runningVersio
|
|
|
50036
50036
|
async function handleDoorbell({
|
|
50037
50037
|
rawBody,
|
|
50038
50038
|
signatureHeader,
|
|
50039
|
-
|
|
50039
|
+
controllerSigningSecret,
|
|
50040
50040
|
shapes,
|
|
50041
50041
|
microvm,
|
|
50042
50042
|
sleep: sleep4,
|
|
50043
50043
|
random
|
|
50044
50044
|
}) {
|
|
50045
|
-
if (!
|
|
50045
|
+
if (!verifyEvidentSignature(rawBody, signatureHeader, controllerSigningSecret)) {
|
|
50046
50046
|
return decide({
|
|
50047
50047
|
statusCode: 401,
|
|
50048
50048
|
action: "rejected",
|
|
@@ -50413,20 +50413,20 @@ function parseShapeCatalogue(raw) {
|
|
|
50413
50413
|
// src/microvm/controller/handler.ts
|
|
50414
50414
|
var microvms = new import_client_lambda_microvms.LambdaMicrovmsClient({});
|
|
50415
50415
|
var secrets = new import_client_secrets_manager.SecretsManagerClient({});
|
|
50416
|
-
var
|
|
50417
|
-
async function
|
|
50418
|
-
if (
|
|
50419
|
-
return
|
|
50416
|
+
var cachedControllerSigningSecret;
|
|
50417
|
+
async function getControllerSigningSecret(secretArn, key) {
|
|
50418
|
+
if (cachedControllerSigningSecret !== void 0) {
|
|
50419
|
+
return cachedControllerSigningSecret;
|
|
50420
50420
|
}
|
|
50421
50421
|
const { SecretString } = await secrets.send(new import_client_secrets_manager.GetSecretValueCommand({ SecretId: secretArn }));
|
|
50422
50422
|
if (!SecretString) {
|
|
50423
|
-
throw new Error(`
|
|
50423
|
+
throw new Error(`controller signing secret ${secretArn} has no SecretString`);
|
|
50424
50424
|
}
|
|
50425
50425
|
const value = JSON.parse(SecretString)[key];
|
|
50426
50426
|
if (typeof value !== "string" || value === "") {
|
|
50427
|
-
throw new Error(`
|
|
50427
|
+
throw new Error(`controller signing secret ${secretArn} is missing a non-empty '${key}' field`);
|
|
50428
50428
|
}
|
|
50429
|
-
|
|
50429
|
+
cachedControllerSigningSecret = value;
|
|
50430
50430
|
return value;
|
|
50431
50431
|
}
|
|
50432
50432
|
function createRuntimeMicrovm(executionRoleArn) {
|
|
@@ -50470,7 +50470,7 @@ function createRuntimeMicrovm(executionRoleArn) {
|
|
|
50470
50470
|
// runner is outbound-only (it dials the Cloudflare relay), so AWS sees
|
|
50471
50471
|
// an idle endpoint no matter how busy the agent is. Idle is instead
|
|
50472
50472
|
// decided IN the VM by `evident run --idle-timeout`
|
|
50473
|
-
// (
|
|
50473
|
+
// (runner/docker-images/microvm/hooks/common.sh's `IDLE_TIMEOUT_SECONDS`), whose
|
|
50474
50474
|
// clean exit is what drives the controller's own `suspend()` below via
|
|
50475
50475
|
// the `runner.suspend_requested` doorbell. See `idle-policy.test.ts`
|
|
50476
50476
|
// in this directory, which scans this file's source for the key and
|
|
@@ -50501,9 +50501,9 @@ function requireEnv(name) {
|
|
|
50501
50501
|
}
|
|
50502
50502
|
var sleep3 = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
|
|
50503
50503
|
var handler = async (event) => {
|
|
50504
|
-
const
|
|
50505
|
-
requireEnv("
|
|
50506
|
-
requireEnv("
|
|
50504
|
+
const controllerSigningSecret = await getControllerSigningSecret(
|
|
50505
|
+
requireEnv("CONTROLLER_SIGNING_SECRET_ARN"),
|
|
50506
|
+
requireEnv("CONTROLLER_SIGNING_SECRET_KEY")
|
|
50507
50507
|
);
|
|
50508
50508
|
const shapes = parseShapeCatalogue(requireEnv("MICROVM_SHAPES"));
|
|
50509
50509
|
const executionRoleArn = requireEnv("MICROVM_EXECUTION_ROLE_ARN");
|
|
@@ -50511,7 +50511,7 @@ var handler = async (event) => {
|
|
|
50511
50511
|
const { statusCode, body } = await handleDoorbell({
|
|
50512
50512
|
rawBody,
|
|
50513
50513
|
signatureHeader: event.headers?.["x-evident-signature"],
|
|
50514
|
-
|
|
50514
|
+
controllerSigningSecret,
|
|
50515
50515
|
shapes,
|
|
50516
50516
|
microvm: createRuntimeMicrovm(executionRoleArn),
|
|
50517
50517
|
sleep: sleep3,
|
|
@@ -157,6 +157,13 @@ class EvidentScaleToZeroConstruct extends constructs_1.Construct {
|
|
|
157
157
|
// Object Get/Put/Delete scoped to this agent's prefix. DeleteObject is
|
|
158
158
|
// required: Litestream's retention/compaction prunes old LTX files.
|
|
159
159
|
replicaBucket.grantReadWrite(taskRole, `${this.replicaPrefix}/*`);
|
|
160
|
+
// Vault credentials are read and conditionally written, so this shared prefix
|
|
161
|
+
// needs no delete or list permission; conditional writes use request headers.
|
|
162
|
+
taskRole.addToPrincipalPolicy(new iam.PolicyStatement({
|
|
163
|
+
sid: 'ReadWriteCredentialVaultObjects',
|
|
164
|
+
actions: ['s3:GetObject', 's3:PutObject'],
|
|
165
|
+
resources: [replicaBucket.arnForObjects('credential-vaults/*')],
|
|
166
|
+
}));
|
|
160
167
|
this.service = new ecs.FargateService(this, 'Service', {
|
|
161
168
|
cluster,
|
|
162
169
|
taskDefinition,
|