@evident-ai/runner-cdk 3.5.2-dev.e22029a → 3.5.2-dev.f258bd6

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 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. Installing this package is all you
13
- need: the image build context ships inside it (see [The MicroVM image](#the-microvm-image)
14
- below), so there is no checkout of the source repository involved. See
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,25 @@ 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
+
117
+ The `DoorbellSecret` CloudFormation logical ID remains stable for Evident's own
118
+ default stack. It identifies the deployed secret resource, not the customer-facing
119
+ name of the signing key.
120
+
102
121
  ### Two deliberate "no default" choices
103
122
 
104
123
  - **The endpoints are required.** Defaulting them would either bake *our* dev environment
@@ -127,58 +146,54 @@ MicroVM image versions are quota-bound. `@evident-ai/lambda-microvm-cdk` ships t
127
146
 
128
147
  See the `@evident-ai/lambda-microvm-cdk` package README for the CLI flags and safety semantics.
129
148
 
130
- `EvidentMicrovmConstruct`'s `imageSource` is a **local directory** holding the image build
131
- context, which `cdk deploy` zips and uploads. The Lambda MicroVM service then builds the
132
- image in *your* account, from a base image ARN you discover there — so, unlike the Fargate
133
- strategy, there is no registry image to pull.
149
+ `EvidentMicrovmConstruct`'s `buildContextPath` is a **local directory** holding the
150
+ Dockerfile you control and everything it copies. `cdk deploy` zips and uploads that
151
+ directory, and the Lambda MicroVM service builds the image in *your* account. Start the
152
+ Dockerfile from a pinned `ghcr.io/sroze/evident-microvm-base:X.Y.Z` tag; the base image
153
+ contains the hook server, lifecycle hooks and common agent tooling.
134
154
 
135
- The context has three parts: this package supplies the Dockerfile, the per-phase hook
136
- scripts and the bundled hook server, published inside the tarball at
137
- `dist/microvm-image-context/`; **your** repository is baked in as the agent's workspace;
138
- and an optional overlay supplies deployment-specific build steps. `stageMicrovmImageContext()`
139
- puts them together:
155
+ `stageMicrovmBuildContext()` combines your build-context directory with a full checkout
156
+ of the workspace in a fresh temporary directory. The workspace is written at
157
+ `workspace/`, and the returned path is the value for `buildContextPath`:
140
158
 
141
159
  ```ts
142
- import path from 'node:path';
143
160
  import {
144
161
  EvidentMicrovmConstruct,
145
162
  HOOKS_PORT,
146
163
  HOOK_TIMEOUT_SECONDS,
147
- stageMicrovmImageContext,
164
+ stageMicrovmBuildContext,
148
165
  } from '@evident-ai/runner-cdk';
149
166
 
167
+ const buildContextPath = stageMicrovmBuildContext({
168
+ // A full (non-shallow) clone: the agent branches, commits and opens PRs.
169
+ workspaceRepositoryPath: '/path/to/your/checkout',
170
+ // Credential-free: the token the agent pushes with arrives per-session.
171
+ workspaceOriginUrl: 'https://github.com/acme/widgets.git',
172
+ buildContextPath: '/path/to/your/microvm-build-context',
173
+ });
174
+
150
175
  new EvidentMicrovmConstruct(this, 'Runner', {
151
- imageSource: stageMicrovmImageContext({
152
- // A full (non-shallow) clone: the agent branches, commits and opens PRs.
153
- repositoryPath: '/path/to/your/checkout',
154
- // Credential-free — this string ships inside the shared snapshot. The
155
- // token the agent pushes with arrives per-session, never baked in.
156
- originUrl: 'https://github.com/acme/widgets.git',
157
- destination: path.join(__dirname, '..', 'build', 'image'),
158
- // Optional deployment-specific installs and build warm-up.
159
- overlayDir: '/path/to/microvm-overlay',
160
- }),
176
+ buildContextPath,
161
177
  // Must match the published image these were baked into, so export them
162
178
  // rather than restating the numbers.
163
179
  hooksPort: HOOKS_PORT,
164
180
  hookTimeoutSeconds: HOOK_TIMEOUT_SECONDS,
165
181
  baseImageArn,
166
182
  baseImageVersion,
167
- doorbellSecret,
183
+ controllerSigningSecret,
168
184
  runnerSecret,
169
185
  runnerOpencodeConfigPath: 'opencode.runner.jsonc',
170
186
  });
171
187
  ```
172
188
 
173
- Pass `overlayDir` to add deployment-specific build steps to the staged image. The
174
- directory provides `setup-root` for root-owned installs and `setup-workspace` for
175
- dependency installation and workspace build warm-up. See the [MicroVM image
176
- README](https://github.com/evident-run/evident-run/blob/main/runner/docker-images/microvm/README.md)
177
- for the full overlay contract.
189
+ Write project-specific installation and build steps in your Dockerfile. The [MicroVM base
190
+ image README](../../runner/docker-images/microvm/README.md) documents the consumer
191
+ contract, including the required `USER`, `WORKDIR`, ownership and `git reset --quiet`
192
+ steps.
178
193
 
179
- The image bakes your repository checked out but not installed and makes no assumptions about
180
- its package manager. To speed up first boot, install dependencies from your overlay's
181
- `setup-workspace`; otherwise, the agent installs them on first use.
194
+ The staged image includes your repository checkout, while your Dockerfile controls its
195
+ package manager and build warm-up. Do not copy credentials into the build context or bake
196
+ them into image environment variables.
182
197
 
183
198
  | MicroVM prop | Omitted behavior |
184
199
  | --- | --- |
@@ -186,12 +201,27 @@ its package manager. To speed up first boot, install dependencies from your over
186
201
  | `runnerOpencodeConfigPath` | OpenCode uses the baked project configuration. Relative paths resolve from the workspace; absolute paths resolve in the image. |
187
202
  | `extraImageEnvironment` | Non-secret values baked into every VM launched from this image version; every VM can read them, so never put secrets here. |
188
203
  | `gitUserName` / `gitUserEmail` | The hook uses the Evident bot defaults for git identity. |
189
- | `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 `doorbellSecret`). A failure to report never fails your deploy — it warns and Evident reads that provisioner as *unknown*, never as up to date or stale. |
190
-
191
- `extraImageEnvironment` is the runtime counterpart to an overlay. Use it when an
192
- overlay-installed component needs an environment variable after the VM boots. The
193
- values are baked into every VM launched from that image version and are readable by
194
- all of them, so never put a secret in this prop.
204
+ | `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. |
205
+
206
+ `extraImageEnvironment` is the runtime environment for the image. Values are baked into
207
+ every VM launched from that image version and are readable by all of them, so never put a
208
+ secret in this prop.
209
+
210
+ ## Breaking changes in this release
211
+
212
+ - `stageMicrovmImageContext` is removed. Use `stageMicrovmBuildContext()` with your
213
+ Dockerfile and build context.
214
+ - `imageSource` is renamed to `buildContextPath`.
215
+ - `MicrovmShape.constructId` is removed. CloudFormation child identity derives from
216
+ the shape `name`.
217
+ - `doorbellSecret` is renamed to `controllerSigningSecret`.
218
+ - The default shape's `imageName` changes to `evident-microvm-standard`. The
219
+ `AWS::Lambda::MicrovmImage` resource, its auto-created build role and execution
220
+ role, and both roles' default policies are replaced with the new shape identity.
221
+ - The JSON field in the caller-supplied Secrets Manager secret changes from
222
+ `DOORBELL_SECRET` to `CONTROLLER_SIGNING_SECRET`. Rename that field in your own
223
+ secret. If you do not, the controller reads no signing key and every doorbell
224
+ returns HTTP 401 at runtime; TypeScript cannot catch this field-key change.
195
225
 
196
226
  ## Status / limitations
197
227
 
@@ -50036,13 +50036,13 @@ async function shouldRecreateForNewerImage(doorbellRequest, shape, runningVersio
50036
50036
  async function handleDoorbell({
50037
50037
  rawBody,
50038
50038
  signatureHeader,
50039
- doorbellSecret,
50039
+ controllerSigningSecret,
50040
50040
  shapes,
50041
50041
  microvm,
50042
50042
  sleep: sleep4,
50043
50043
  random
50044
50044
  }) {
50045
- if (!verifyEvidentSignature(rawBody, signatureHeader, doorbellSecret)) {
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 cachedDoorbellSecret;
50417
- async function getDoorbellSecret(secretArn, key) {
50418
- if (cachedDoorbellSecret !== void 0) {
50419
- return cachedDoorbellSecret;
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(`doorbell secret ${secretArn} has no SecretString`);
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(`doorbell secret ${secretArn} is missing a non-empty '${key}' field`);
50427
+ throw new Error(`controller signing secret ${secretArn} is missing a non-empty '${key}' field`);
50428
50428
  }
50429
- cachedDoorbellSecret = value;
50429
+ cachedControllerSigningSecret = value;
50430
50430
  return value;
50431
50431
  }
50432
50432
  function createRuntimeMicrovm(executionRoleArn) {
@@ -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 doorbellSecret = await getDoorbellSecret(
50505
- requireEnv("DOORBELL_SECRET_ARN"),
50506
- requireEnv("DOORBELL_SECRET_KEY")
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
- doorbellSecret,
50514
+ controllerSigningSecret,
50515
50515
  shapes,
50516
50516
  microvm: createRuntimeMicrovm(executionRoleArn),
50517
50517
  sleep: sleep3,
@@ -29,15 +29,17 @@ var import_client_secrets_manager = require("@aws-sdk/client-secrets-manager");
29
29
  var PHYSICAL_RESOURCE_ID = "evident-image-version-report";
30
30
  var REPORT_TIMEOUT_MS = 5e3;
31
31
  var secrets = new import_client_secrets_manager.SecretsManagerClient({});
32
- async function fetchDoorbellSecret(secretArn, secretKey) {
32
+ async function fetchControllerSigningSecret(secretArn, secretKey) {
33
33
  const { SecretString } = await secrets.send(new import_client_secrets_manager.GetSecretValueCommand({ SecretId: secretArn }));
34
34
  if (!SecretString) {
35
- throw new Error(`doorbell secret ${secretArn} has no SecretString`);
35
+ throw new Error(`controller signing secret ${secretArn} has no SecretString`);
36
36
  }
37
37
  const parsed = JSON.parse(SecretString);
38
38
  const value = parsed !== null && typeof parsed === "object" ? parsed[secretKey] : void 0;
39
39
  if (typeof value !== "string" || value === "") {
40
- throw new Error(`doorbell secret ${secretArn} is missing a non-empty '${secretKey}' field`);
40
+ throw new Error(
41
+ `controller signing secret ${secretArn} is missing a non-empty '${secretKey}' field`
42
+ );
41
43
  }
42
44
  return value;
43
45
  }
@@ -63,7 +65,7 @@ function response() {
63
65
  return { PhysicalResourceId: PHYSICAL_RESOURCE_ID };
64
66
  }
65
67
  var runtimeDependencies = {
66
- getSecret: fetchDoorbellSecret,
68
+ getSecret: fetchControllerSigningSecret,
67
69
  fetch: globalThis.fetch
68
70
  };
69
71
  async function handleImageVersionReport(event, getConfig, dependencies = runtimeDependencies) {
@@ -77,10 +79,15 @@ async function handleImageVersionReport(event, getConfig, dependencies = runtime
77
79
  versions = readImageVersions(event.ResourceProperties?.imageVersions);
78
80
  const config = getConfig();
79
81
  runnerId = config.evidentRunnerId;
80
- const secret = await dependencies.getSecret(config.doorbellSecretArn, config.doorbellSecretKey);
82
+ const secret = await dependencies.getSecret(
83
+ config.controllerSigningSecretArn,
84
+ config.controllerSigningSecretKey
85
+ );
81
86
  const body = JSON.stringify({
82
87
  type: "runner.microvm_image_versions_reported",
83
- versions
88
+ versions,
89
+ // The API orders reports by this value because replacing an image restarts its version counter.
90
+ reporter_clock_ms: Date.now()
84
91
  });
85
92
  const signature = (0, import_node_crypto.createHmac)("sha256", secret).update(body).digest("hex");
86
93
  const url = `${config.evidentApiUrl.replace(/\/+$/, "")}/v1/runners/${encodeURIComponent(
@@ -117,8 +124,8 @@ async function handleImageVersionReport(event, getConfig, dependencies = runtime
117
124
  return response();
118
125
  }
119
126
  var handler = (event) => handleImageVersionReport(event, () => ({
120
- doorbellSecretArn: requireEnv("DOORBELL_SECRET_ARN"),
121
- doorbellSecretKey: requireEnv("DOORBELL_SECRET_KEY"),
127
+ controllerSigningSecretArn: requireEnv("CONTROLLER_SIGNING_SECRET_ARN"),
128
+ controllerSigningSecretKey: requireEnv("CONTROLLER_SIGNING_SECRET_KEY"),
122
129
  evidentApiUrl: requireEnv("EVIDENT_API_URL"),
123
130
  evidentRunnerId: requireEnv("EVIDENT_RUNNER_ID")
124
131
  }));
package/dist/index.d.ts CHANGED
@@ -3,4 +3,4 @@ export { EvidentWaker, type EvidentWakerProps } from './waker/construct';
3
3
  export { EvidentMicrovmConstruct, type EvidentMicrovmConstructProps } from './microvm/construct';
4
4
  export { MICROVM_SHAPES, validateShapes, type MicrovmShape } from './microvm/shapes';
5
5
  export { MICROVM_MAX_RUN_SECONDS, HOOKS_PORT, HOOK_TIMEOUT_SECONDS, } from './microvm/constants';
6
- export { stageMicrovmImageContext, type StageMicrovmImageContextOptions, } from './microvm/image/stage-context';
6
+ export { MICROVM_WORKSPACE_DIRECTORY, stageMicrovmBuildContext, type StageMicrovmBuildContextOptions, } from './microvm/image/stage-context';
package/dist/index.js CHANGED
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  Object.defineProperty(exports, "__esModule", { value: true });
3
- exports.stageMicrovmImageContext = exports.HOOK_TIMEOUT_SECONDS = exports.HOOKS_PORT = exports.MICROVM_MAX_RUN_SECONDS = exports.validateShapes = exports.MICROVM_SHAPES = exports.EvidentMicrovmConstruct = exports.EvidentWaker = exports.EvidentScaleToZeroConstruct = void 0;
3
+ exports.stageMicrovmBuildContext = exports.MICROVM_WORKSPACE_DIRECTORY = exports.HOOK_TIMEOUT_SECONDS = exports.HOOKS_PORT = exports.MICROVM_MAX_RUN_SECONDS = exports.validateShapes = exports.MICROVM_SHAPES = exports.EvidentMicrovmConstruct = exports.EvidentWaker = exports.EvidentScaleToZeroConstruct = void 0;
4
4
  var evident_scale_to_zero_construct_1 = require("./evident-scale-to-zero-construct");
5
5
  Object.defineProperty(exports, "EvidentScaleToZeroConstruct", { enumerable: true, get: function () { return evident_scale_to_zero_construct_1.EvidentScaleToZeroConstruct; } });
6
6
  var construct_1 = require("./waker/construct");
@@ -17,4 +17,5 @@ Object.defineProperty(exports, "MICROVM_MAX_RUN_SECONDS", { enumerable: true, ge
17
17
  Object.defineProperty(exports, "HOOKS_PORT", { enumerable: true, get: function () { return constants_1.HOOKS_PORT; } });
18
18
  Object.defineProperty(exports, "HOOK_TIMEOUT_SECONDS", { enumerable: true, get: function () { return constants_1.HOOK_TIMEOUT_SECONDS; } });
19
19
  var stage_context_1 = require("./microvm/image/stage-context");
20
- Object.defineProperty(exports, "stageMicrovmImageContext", { enumerable: true, get: function () { return stage_context_1.stageMicrovmImageContext; } });
20
+ Object.defineProperty(exports, "MICROVM_WORKSPACE_DIRECTORY", { enumerable: true, get: function () { return stage_context_1.MICROVM_WORKSPACE_DIRECTORY; } });
21
+ Object.defineProperty(exports, "stageMicrovmBuildContext", { enumerable: true, get: function () { return stage_context_1.stageMicrovmBuildContext; } });
@@ -6,4 +6,4 @@ export declare const SUSPENDING_POLL_ATTEMPTS = 4;
6
6
  export declare const SUSPENDING_POLL_INTERVAL_MS = 500;
7
7
  export declare const MICROVM_MAX_RUN_SECONDS = 28800;
8
8
  export declare const SESSION_CLEANUP_MAX_AGE = "24h";
9
- export declare const DOORBELL_SECRET_KEY = "DOORBELL_SECRET";
9
+ export declare const CONTROLLER_SIGNING_SECRET_KEY = "CONTROLLER_SIGNING_SECRET";
@@ -4,7 +4,7 @@
4
4
  // across them. The image template pins matching copies, guarded by
5
5
  // `microvm/image/dockerfile.test.ts`.
6
6
  Object.defineProperty(exports, "__esModule", { value: true });
7
- exports.DOORBELL_SECRET_KEY = exports.SESSION_CLEANUP_MAX_AGE = exports.MICROVM_MAX_RUN_SECONDS = exports.SUSPENDING_POLL_INTERVAL_MS = exports.SUSPENDING_POLL_ATTEMPTS = exports.RUN_HOOK_PAYLOAD_MAX_BYTES = exports.HOOK_TIMEOUT_SECONDS = exports.HOOKS_DIR = exports.HOOKS_PORT = void 0;
7
+ exports.CONTROLLER_SIGNING_SECRET_KEY = exports.SESSION_CLEANUP_MAX_AGE = exports.MICROVM_MAX_RUN_SECONDS = exports.SUSPENDING_POLL_INTERVAL_MS = exports.SUSPENDING_POLL_ATTEMPTS = exports.RUN_HOOK_PAYLOAD_MAX_BYTES = exports.HOOK_TIMEOUT_SECONDS = exports.HOOKS_DIR = exports.HOOKS_PORT = void 0;
8
8
  // The port the image's hook server binds, baked into the published Dockerfile
9
9
  // (`ENV HOOKS_PORT`) and passed to the construct as `hooksPort`. Exported from
10
10
  // the package index because a consumer building the published image context
@@ -38,6 +38,9 @@ exports.MICROVM_MAX_RUN_SECONDS = 28800;
38
38
  // The 24h window rather than 7d keeps the session database bounded at the growth
39
39
  // rate observed in #537. The sweep runs on the default 1h interval.
40
40
  exports.SESSION_CLEANUP_MAX_AGE = '24h';
41
- // The JSON field inside the doorbell secret is shared by the Lambdas that name
42
- // it and by whatever produces the secret; this is never the secret value.
43
- exports.DOORBELL_SECRET_KEY = 'DOORBELL_SECRET';
41
+ // The JSON field inside the controller signing secret is shared by the Lambdas
42
+ // that name it and by whatever produces the secret; this is never the secret
43
+ // value. It is customer-facing and separate from persisted doorbell messages
44
+ // and telemetry. Changing the value silently stops an existing adopter's
45
+ // secret from being read.
46
+ exports.CONTROLLER_SIGNING_SECRET_KEY = 'CONTROLLER_SIGNING_SECRET';
@@ -4,12 +4,10 @@ import * as secretsmanager from 'aws-cdk-lib/aws-secretsmanager';
4
4
  import { type MicrovmShape } from './shapes';
5
5
  export interface EvidentMicrovmConstructProps {
6
6
  /**
7
- * Build context for the image, already staged on disk. An argument rather
8
- * than something this construct stages itself, so synthesising the stack
9
- * stays a pure function of its inputs instead of depending on how the
10
- * ambient checkout was cloned.
7
+ * Directory containing the consumer's Dockerfile and every file it copies,
8
+ * typically the return value of `stageMicrovmBuildContext()`.
11
9
  */
12
- readonly imageSource: string;
10
+ readonly buildContextPath: string;
13
11
  /**
14
12
  * ARN of the Lambda-managed base MicroVM image, from
15
13
  * `aws lambda-microvms list-managed-microvm-images`. A string, not a `CfnParameter`
@@ -33,14 +31,14 @@ export interface EvidentMicrovmConstructProps {
33
31
  readonly shapes?: readonly MicrovmShape[];
34
32
  /**
35
33
  * The secret holding the shared HMAC key the controller verifies every
36
- * doorbell against, under the field `DOORBELL_SECRET`. Any Secrets Manager
34
+ * doorbell against, under the field `CONTROLLER_SIGNING_SECRET`. Any Secrets Manager
37
35
  * secret, however the caller manages it — this construct has no opinion on
38
36
  * SOPS, KMS, or any other production mechanism. Required (not defaulted
39
37
  * here): a default would need a scope of its own, and a caller-owned
40
38
  * default keeps its own logical id — the caller builds one exactly once,
41
39
  * in its own scope, and passes the result in either case.
42
40
  */
43
- readonly doorbellSecret: secretsmanager.ISecret;
41
+ readonly controllerSigningSecret: secretsmanager.ISecret;
44
42
  /**
45
43
  * Optional runner secret whose JSON values are exported by `/run`. Omitting it
46
44
  * leaves GitHub and MCP credentials unavailable to a generic consumer.
@@ -65,7 +63,7 @@ export interface EvidentMicrovmConstructProps {
65
63
  * TCP port the image's hook server listens on, baked into both the image
66
64
  * (`HOOKS_PORT` env var) and the `MicrovmImage`'s `hooks.port` — a mismatch
67
65
  * means AWS probes a port nothing binds. Caller-supplied because the hook
68
- * server is part of `imageSource`, which this construct does not build.
66
+ * server is part of the base image, which this construct does not build.
69
67
  */
70
68
  readonly hooksPort: number;
71
69
  /**
@@ -146,42 +146,31 @@ class EvidentMicrovmConstruct extends constructs_1.Construct {
146
146
  lifecycleTimeout: cdk.Duration.seconds(props.hookTimeoutSeconds),
147
147
  buildTimeout: cdk.Duration.seconds(IMAGE_HOOK_TIMEOUT_SECONDS),
148
148
  };
149
- // Built first and with no `executionRole` prop, exactly as before this
150
- // shape catalogue existed: same construct id (`Image`, frozen on the
151
- // shape record — see shapes.ts), same `imageName`, same description. The
152
- // construct therefore creates `Image/ExecutionRole` at its current path
153
- // and current logical id — zero CloudFormation churn on the live,
154
- // hand-deployed stack (D3/D9). Every additional shape below borrows this
155
- // role rather than getting one of its own, which is why `validateShapes`
156
- // requires the default shape to be `shapes[0]`: it must exist before any
157
- // other shape can reference its execution role.
158
- const defaultImage = new lambda_microvm_cdk_1.MicrovmImage(this, defaultShape.constructId, {
149
+ // Built first without an `executionRole` prop, so every additional shape
150
+ // can borrow this image's execution role. `validateShapes` requires the
151
+ // default shape to be `shapes[0]` because that role must exist first.
152
+ const defaultImage = new lambda_microvm_cdk_1.MicrovmImage(this, defaultShape.name, {
159
153
  imageName: defaultShape.imageName,
160
154
  description: defaultShape.description,
161
155
  // A local directory, so the zip is uploaded by `cdk deploy` itself and the
162
156
  // image ARN is a stack reference rather than a deploy-time input.
163
- source: props.imageSource,
157
+ source: props.buildContextPath,
164
158
  baseImageArn: props.baseImageArn,
165
159
  baseImageVersion: props.baseImageVersion,
166
160
  memoryMiB: defaultShape.memoryMiB,
167
161
  environment: imageEnvironment,
168
162
  hooks: imageHooks,
169
163
  });
170
- // WART, accepted deliberately: every additional shape's image is built
171
- // with `defaultImage.executionRole`, so this role's construct path
172
- // (`Image/ExecutionRole`) reads as if it belongs to just the default
173
- // shape's image, when in fact it is shared by every shape. That is
174
- // intentional — see D3 in the plan. Do NOT "fix" this with
175
- // `overrideLogicalId`: renaming the logical id IS the CloudFormation
176
- // churn this design exists to avoid (a delete-and-recreate of the role a
177
- // running MicroVM has live session credentials from). The wart is one
178
- // comment; the override would undo the point of D3.
164
+ // WART: every additional shape shares `defaultImage.executionRole`, so the
165
+ // role's path under the default image reads as if it belongs to one shape
166
+ // even though it serves all of them. Keeping one role avoids replacing a
167
+ // role that running MicroVMs may still have credentials from.
179
168
  const images = new Map([[defaultShape.name, defaultImage]]);
180
169
  for (const shape of additionalShapes) {
181
- images.set(shape.name, new lambda_microvm_cdk_1.MicrovmImage(this, shape.constructId, {
170
+ images.set(shape.name, new lambda_microvm_cdk_1.MicrovmImage(this, shape.name, {
182
171
  imageName: shape.imageName,
183
172
  description: shape.description,
184
- source: props.imageSource,
173
+ source: props.buildContextPath,
185
174
  baseImageArn: props.baseImageArn,
186
175
  baseImageVersion: props.baseImageVersion,
187
176
  memoryMiB: shape.memoryMiB,
@@ -237,8 +226,8 @@ class EvidentMicrovmConstruct extends constructs_1.Construct {
237
226
  // The ARN and field name, never the value: the controller reads it from
238
227
  // Secrets Manager at runtime, so the key is not exposed to anyone with
239
228
  // lambda:GetFunctionConfiguration.
240
- DOORBELL_SECRET_ARN: props.doorbellSecret.secretArn,
241
- DOORBELL_SECRET_KEY: constants_1.DOORBELL_SECRET_KEY,
229
+ CONTROLLER_SIGNING_SECRET_ARN: props.controllerSigningSecret.secretArn,
230
+ CONTROLLER_SIGNING_SECRET_KEY: constants_1.CONTROLLER_SIGNING_SECRET_KEY,
242
231
  MICROVM_SHAPES: shapeCatalogueJson,
243
232
  // The role every shape's MicroVM shares (D3) — still one role, still
244
233
  // this same expression, now documented as shared rather than singular.
@@ -253,7 +242,7 @@ class EvidentMicrovmConstruct extends constructs_1.Construct {
253
242
  // emit N near-identical policy statements and break the "exactly one
254
243
  // statement granting X" guarantees this construct's test encodes.
255
244
  defaultImage.grantRun(controller);
256
- props.doorbellSecret.grantRead(controller);
245
+ props.controllerSigningSecret.grantRead(controller);
257
246
  const functionUrl = controller.addFunctionUrl({
258
247
  // HMAC verification of the doorbell body IS the auth, as for the ECS waker.
259
248
  authType: lambda.FunctionUrlAuthType.NONE,
@@ -263,7 +252,7 @@ class EvidentMicrovmConstruct extends constructs_1.Construct {
263
252
  // no custom resource — this branch must not run for them.
264
253
  if (props.evidentApiUrl && props.evidentRunnerId) {
265
254
  new construct_1.ImageVersionReporter(this, 'ImageVersionReporter', {
266
- doorbellSecret: props.doorbellSecret,
255
+ controllerSigningSecret: props.controllerSigningSecret,
267
256
  evidentApiUrl: props.evidentApiUrl,
268
257
  evidentRunnerId: props.evidentRunnerId,
269
258
  imageVersions: Object.fromEntries(shapes.map((shape) => [shape.name, images.get(shape.name).latestActiveImageVersion])),
@@ -17,10 +17,10 @@ export type DoorbellResult = {
17
17
  kind: ActionKind;
18
18
  };
19
19
  };
20
- export declare function handleDoorbell({ rawBody, signatureHeader, doorbellSecret, shapes, microvm, sleep, random, }: {
20
+ export declare function handleDoorbell({ rawBody, signatureHeader, controllerSigningSecret, shapes, microvm, sleep, random, }: {
21
21
  rawBody: string;
22
22
  signatureHeader: string | undefined;
23
- doorbellSecret: string;
23
+ controllerSigningSecret: string;
24
24
  shapes: ShapeCatalogue;
25
25
  microvm: MicrovmClient;
26
26
  } & Timing): Promise<DoorbellResult>;
@@ -174,8 +174,8 @@ async function shouldRecreateForNewerImage(doorbellRequest, shape, runningVersio
174
174
  // been deactivated, and recreating would not roll it forward. Resume.
175
175
  return compareImageVersions(running, latest) < 0;
176
176
  }
177
- async function handleDoorbell({ rawBody, signatureHeader, doorbellSecret, shapes, microvm, sleep, random, }) {
178
- if (!(0, sdk_1.verifyEvidentSignature)(rawBody, signatureHeader, doorbellSecret)) {
177
+ async function handleDoorbell({ rawBody, signatureHeader, controllerSigningSecret, shapes, microvm, sleep, random, }) {
178
+ if (!(0, sdk_1.verifyEvidentSignature)(rawBody, signatureHeader, controllerSigningSecret)) {
179
179
  return decide({
180
180
  statusCode: 401,
181
181
  action: 'rejected',
@@ -1,42 +1,17 @@
1
- export interface StageMicrovmImageContextOptions {
2
- /**
3
- * The repository baked into the image as the agent's workspace. This is the
4
- * CONSUMER's repository, not Evident's — the image is a dev box for whatever
5
- * codebase the agent works on. Must be a full (non-shallow) clone.
6
- */
7
- readonly repositoryPath: string;
8
- /**
9
- * `origin` of the baked repository, as the VM will see it. Must be a
10
- * credential-free URL (typically `https://github.com/<owner>/<repo>.git`):
11
- * this string ships inside the shared snapshot, so a token in it would be
12
- * baked into every VM. The credential arrives per-VM instead.
13
- */
14
- readonly originUrl: string;
15
- /** Directory to write the build context to. Removed and recreated. */
16
- readonly destination: string;
17
- /**
18
- * Overlay scripts the image build runs as root and as `runner`. The overlay
19
- * ships inside the shared snapshot, so it may install software but must carry
20
- * no credential. When omitted, deterministic no-op scripts are written.
21
- */
22
- readonly overlayDir?: string;
23
- /**
24
- * The published template to copy from. Defaults to the one inside this
25
- * package; named directly by the tests, which assert against a template they
26
- * built themselves rather than whichever `dist/` happens to be present.
27
- */
28
- readonly templateDir?: string;
1
+ /** The fixed directory that contains the workspace in a staged build context. */
2
+ export declare const MICROVM_WORKSPACE_DIRECTORY = "workspace";
3
+ export interface StageMicrovmBuildContextOptions {
4
+ /** The customer's directory containing their Dockerfile and COPY sources. */
5
+ readonly buildContextPath: string;
6
+ /** A full local checkout to bake into the image's `workspace/` directory. */
7
+ readonly workspaceRepositoryPath: string;
8
+ /** Credential-free origin URL carried by the baked checkout. */
9
+ readonly workspaceOriginUrl: string;
29
10
  }
30
11
  /**
31
- * Writes the build context AWS unpacks — the Dockerfile, the hook server, the
32
- * per-phase hook scripts, the deployment overlay and the repository — and
33
- * returns its path. The result is what `EvidentMicrovmConstruct`'s `imageSource`
34
- * takes.
12
+ * Stages customer-owned image inputs in a fresh temporary directory.
35
13
  *
36
- * The Dockerfile, hook server and phase hooks come from this package's
37
- * published `dist/`, so a consumer needs no checkout of the source repository and no
38
- * copy of those files. A caller may supply an overlay; the default is
39
- * deterministic no-op scripts. `repositoryPath` is the only required caller
40
- * input.
14
+ * Staging keeps the image asset hash a pure function of the workspace commit while
15
+ * leaving the customer in control of the Dockerfile and its build context.
41
16
  */
42
- export declare function stageMicrovmImageContext(options: StageMicrovmImageContextOptions): string;
17
+ export declare function stageMicrovmBuildContext(options: StageMicrovmBuildContextOptions): string;