@learncard/lca-api-service 1.5.3 → 1.6.0
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/.env.example +10 -0
- package/CHANGELOG.md +26 -0
- package/bundle.mjs +1 -1
- package/dist/app.d.ts +4 -1
- package/dist/config/cacheEnvironment.d.ts +4 -4
- package/dist/config/environment.d.ts +145 -114
- package/dist/config/oidcEnvironment.d.ts +22 -22
- package/dist/didkit_wasm_bg.wasm +0 -0
- package/dist/helpers/bedrockGeneration.d.ts +17 -0
- package/dist/helpers/bedrockGeneration.test.d.ts +1 -0
- package/dist/helpers/bedrockSchema.d.ts +10 -0
- package/dist/helpers/imageGeneration.d.ts +42 -0
- package/dist/helpers/imageGeneration.test.d.ts +1 -0
- package/dist/index.js +2 -2
- package/dist/models/Notification.d.ts +3 -1
- package/dist/publish-metafile.json +1 -1
- package/dist/routes/ai.skill-icons.test.d.ts +1 -0
- package/dist/routes/keys.d.ts +1 -0
- package/dist/routes/notifications.d.ts +3 -1
- package/dist/types/notifications.d.ts +3 -1
- package/docs/bedrock-generation.md +119 -0
- package/docs/image-generation.md +120 -0
- package/package.json +20 -15
- package/scripts/node24-runtime.cjs +9 -0
- package/scripts/verify-node24-layer.cjs +49 -0
- package/serverless-local.yml +3 -1
- package/serverless.function-env.cjs +2 -4
- package/serverless.yml +76 -3
- package/src/config/environment.ts +14 -0
- package/src/helpers/bedrockGeneration.test.ts +222 -0
- package/src/helpers/bedrockGeneration.ts +110 -0
- package/src/helpers/bedrockSchema.ts +352 -0
- package/src/helpers/imageGeneration.test.ts +456 -0
- package/src/helpers/imageGeneration.ts +432 -0
- package/src/routes/ai.skill-icons.test.ts +92 -0
- package/src/routes/ai.test.ts +68 -91
- package/src/routes/ai.ts +31 -143
- package/src/routes/keys.ts +18 -0
- package/src/services/escrow-enclave/enclaveTrustAnchors.ts +19 -0
- package/src/services/escrow-enclave/logFailure.test.ts +8 -0
- package/src/services/escrow-enclave/logFailure.ts +17 -0
- package/src/services/escrow-enclave/remoteEnclave.tls.test.ts +10 -0
- package/test/keys-auth-share-escrow.spec.ts +103 -0
- package/test/oidc.spec.ts +41 -2
- package/tsconfig.bedrock.json +10 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/routes/keys.d.ts
CHANGED
|
@@ -153,6 +153,7 @@ export declare const keysRouter: import("@trpc/server").TRPCBuiltRouter<{
|
|
|
153
153
|
attemptsRemaining: number;
|
|
154
154
|
salt?: string | undefined;
|
|
155
155
|
};
|
|
156
|
+
priorEscrowEnrollment: boolean;
|
|
156
157
|
sssActivationState: "active" | "provisional";
|
|
157
158
|
escrowStale?: "mode-mismatch" | "key-rotated" | undefined;
|
|
158
159
|
} | null;
|
|
@@ -99,6 +99,7 @@ export declare const notificationsRouter: import("@trpc/server").TRPCBuiltRouter
|
|
|
99
99
|
email?: string | undefined;
|
|
100
100
|
image?: string | undefined;
|
|
101
101
|
heroImage?: string | undefined;
|
|
102
|
+
linkedInOrganizationId?: string | undefined;
|
|
102
103
|
websiteLink?: string | undefined;
|
|
103
104
|
isServiceProfile?: boolean | undefined;
|
|
104
105
|
type?: string | undefined;
|
|
@@ -138,6 +139,7 @@ export declare const notificationsRouter: import("@trpc/server").TRPCBuiltRouter
|
|
|
138
139
|
email?: string | undefined;
|
|
139
140
|
image?: string | undefined;
|
|
140
141
|
heroImage?: string | undefined;
|
|
142
|
+
linkedInOrganizationId?: string | undefined;
|
|
141
143
|
websiteLink?: string | undefined;
|
|
142
144
|
isServiceProfile?: boolean | undefined;
|
|
143
145
|
type?: string | undefined;
|
|
@@ -174,7 +176,7 @@ export declare const notificationsRouter: import("@trpc/server").TRPCBuiltRouter
|
|
|
174
176
|
vpUris?: string[] | undefined;
|
|
175
177
|
transaction?: {
|
|
176
178
|
id: string;
|
|
177
|
-
action: "write" | "
|
|
179
|
+
action: "write" | "update" | "consent" | "sync" | "withdraw";
|
|
178
180
|
date: string;
|
|
179
181
|
expiresAt?: string | undefined;
|
|
180
182
|
oneTime?: boolean | undefined;
|
|
@@ -126,6 +126,7 @@ export declare const PaginatedNotificationsValidator: z.ZodObject<{
|
|
|
126
126
|
email: z.ZodOptional<z.ZodOptional<z.ZodString>>;
|
|
127
127
|
image: z.ZodOptional<z.ZodOptional<z.ZodString>>;
|
|
128
128
|
heroImage: z.ZodOptional<z.ZodOptional<z.ZodString>>;
|
|
129
|
+
linkedInOrganizationId: z.ZodOptional<z.ZodOptional<z.ZodString>>;
|
|
129
130
|
websiteLink: z.ZodOptional<z.ZodOptional<z.ZodString>>;
|
|
130
131
|
isServiceProfile: z.ZodOptional<z.ZodOptional<z.ZodDefault<z.ZodBoolean>>>;
|
|
131
132
|
type: z.ZodOptional<z.ZodOptional<z.ZodString>>;
|
|
@@ -172,6 +173,7 @@ export declare const PaginatedNotificationsValidator: z.ZodObject<{
|
|
|
172
173
|
email: z.ZodOptional<z.ZodOptional<z.ZodString>>;
|
|
173
174
|
image: z.ZodOptional<z.ZodOptional<z.ZodString>>;
|
|
174
175
|
heroImage: z.ZodOptional<z.ZodOptional<z.ZodString>>;
|
|
176
|
+
linkedInOrganizationId: z.ZodOptional<z.ZodOptional<z.ZodString>>;
|
|
175
177
|
websiteLink: z.ZodOptional<z.ZodOptional<z.ZodString>>;
|
|
176
178
|
isServiceProfile: z.ZodOptional<z.ZodOptional<z.ZodDefault<z.ZodBoolean>>>;
|
|
177
179
|
type: z.ZodOptional<z.ZodOptional<z.ZodString>>;
|
|
@@ -244,9 +246,9 @@ export declare const PaginatedNotificationsValidator: z.ZodObject<{
|
|
|
244
246
|
}, z.core.$strip>>;
|
|
245
247
|
id: z.ZodString;
|
|
246
248
|
action: z.ZodEnum<{
|
|
249
|
+
update: "update";
|
|
247
250
|
write: "write";
|
|
248
251
|
consent: "consent";
|
|
249
|
-
update: "update";
|
|
250
252
|
sync: "sync";
|
|
251
253
|
withdraw: "withdraw";
|
|
252
254
|
}>;
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
# Bedrock text generation (LC-2271)
|
|
2
|
+
|
|
3
|
+
The three LCA text generation routes use the OpenAI SDK Responses API at
|
|
4
|
+
`https://bedrock-runtime.us-east-1.amazonaws.com/openai/v1`. This follows the merged
|
|
5
|
+
[AI Passport implementation](https://github.com/WeLibraryOS/AI-Passport/pull/92),
|
|
6
|
+
inspected at main `300c0af3125a4f1b1785820a2d33866723a88f30`.
|
|
7
|
+
|
|
8
|
+
| Route | Exact Runtime model profile | Effort | Reason |
|
|
9
|
+
| --------------------- | --------------------------- | -------- | -------------------------------------------------------------------------------------- |
|
|
10
|
+
| `generateBoostInfo` | `us.openai.gpt-6.1-sol` | `medium` | Interpret arbitrary descriptions and generate coherent credential copy across locales. |
|
|
11
|
+
| `generateBoostSkills` | `us.openai.gpt-6.1-sol` | `medium` | Infer appropriate skills and subskills within the existing hierarchy. |
|
|
12
|
+
| `generateSkillIcons` | `us.openai.gpt-6-luna` | `none` | Straightforward semantic mapping of supplied names to emoji. |
|
|
13
|
+
|
|
14
|
+
AWS's current [Sol model card](https://docs.aws.amazon.com/bedrock/latest/userguide/model-card-openai-gpt-6-1-sol.html),
|
|
15
|
+
[Luna model card](https://docs.aws.amazon.com/bedrock/latest/userguide/model-card-openai-gpt-6-luna.html)
|
|
16
|
+
and [Responses compatibility guide](https://docs.aws.amazon.com/bedrock/latest/userguide/inference-responses-api.html)
|
|
17
|
+
confirm these Runtime profiles and API support. The 8,192-token request ceiling is below
|
|
18
|
+
both documented model output limits. Although the current cards advertise native JSON
|
|
19
|
+
Schema output, this migration retains AI Passport's required-function pattern and
|
|
20
|
+
local validation to preserve existing optional-category semantics.
|
|
21
|
+
|
|
22
|
+
`generateImage` defaults to Bedrock Stable Image Core (`stability.stable-image-core-v1:1`)
|
|
23
|
+
in us-west-2, signed with SigV4 from the execution role, and still stores the result
|
|
24
|
+
through Filestack. DALL-E 3 (`OPENAI_API_KEY`) is only an explicit `IMAGE_PROVIDER=openai`
|
|
25
|
+
fallback; see `image-generation.md`. Embeddings and Nova are outside this migration.
|
|
26
|
+
|
|
27
|
+
## Runtime contract
|
|
28
|
+
|
|
29
|
+
- The official `@aws/bedrock-token-generator` resolves short-term bearer tokens from
|
|
30
|
+
the normal AWS SDK credential chain in `us-east-1`. On Lambda this means the execution
|
|
31
|
+
role and its rotating credentials. The SDK requests a token for every operation;
|
|
32
|
+
no process-lifetime static bearer is stored by LCA. AI Passport's Fly web-identity
|
|
33
|
+
override is specific to Fly and is not needed for this Lambda service.
|
|
34
|
+
- `BEDROCK_BASE_URL` is optional; only the exact default URL above, with an optional
|
|
35
|
+
trailing slash, is accepted. No arbitrary endpoint, model alias or family override.
|
|
36
|
+
The option is scoped to API functions and can also come from `RUNTIME_SECRETS_ID`.
|
|
37
|
+
- OpenAI SDK version is pinned to `6.47.0`, matching the inspected implementation.
|
|
38
|
+
No ambient OpenAI organization/project headers; SDK logging is off; redirects fail.
|
|
39
|
+
- These routes remain non-streaming. Requests use `store:false`, required function
|
|
40
|
+
selection, `parallel_tool_calls:false`, and a non-strict projected JSON schema.
|
|
41
|
+
The original Zod validators enforce enums, optional categories, unions and string
|
|
42
|
+
bounds after JSON parsing. Native strict structured-output support is not assumed.
|
|
43
|
+
- Requests have an 8,192 output-token ceiling (including reasoning) and a 25-second
|
|
44
|
+
SDK timeout (below the existing 29-second Lambda timeout). Failed/incomplete lifecycle, refusal, missing/wrong/duplicate tool,
|
|
45
|
+
malformed JSON and invalid output fail closed. Token-limit exhaustion is an error,
|
|
46
|
+
never a partial success. Input and output sizing still require staging acceptance.
|
|
47
|
+
- No automatic retry, provider fallback, or model downgrade. HTTP 429 maps to
|
|
48
|
+
`TOO_MANY_REQUESTS`, 503/504 to `SERVICE_UNAVAILABLE`, and other failures to a
|
|
49
|
+
sanitized `INTERNAL_SERVER_ERROR`. Provider bodies, validation values and credential
|
|
50
|
+
causes are not retained in those errors. Quota failures are not automatically retried.
|
|
51
|
+
- Icons still fill omitted requested names with `⭐` after a valid response and discard
|
|
52
|
+
unsolicited names. Provider failures now propagate instead of being logged raw and
|
|
53
|
+
subsequently crashing on an undefined completion. Learner DIDs are no longer sent
|
|
54
|
+
in text-generation provider metadata. Existing authenticated route access remains.
|
|
55
|
+
|
|
56
|
+
## Deployment and coordination gates
|
|
57
|
+
|
|
58
|
+
Before rollout, obtain approved staging tests for all three real schemas, locales,
|
|
59
|
+
typical and maximum input sizes, token ceilings, timeout behavior and error handling.
|
|
60
|
+
Offline tests do not establish provider acceptance, entitlement, latency or generation
|
|
61
|
+
quality. Live model calls require separate approval.
|
|
62
|
+
|
|
63
|
+
Responses does not support attaching native Bedrock Guardrails; if the approved data-handling
|
|
64
|
+
policy requires filtering, a separately approved pre-invocation path is a rollout gate.
|
|
65
|
+
|
|
66
|
+
The deployed Lambda role must have approved Bedrock/default-project access for both
|
|
67
|
+
exact cross-region US profiles and access to the Runtime endpoint. Confirm rotating
|
|
68
|
+
credential/token operation and the applicable retention, DPA, subprocessor, guardrail
|
|
69
|
+
and invocation-logging posture before learner traffic. The reviewed dev policy is managed by
|
|
70
|
+
Serverless/CloudFormation and is created with the stack on a `dev`/`us-east-1` deploy, so no
|
|
71
|
+
manual IAM edits are needed. This PR does not port AI Passport's
|
|
72
|
+
application-specific privacy gateway into LCA. US profiles can route across US regions;
|
|
73
|
+
the endpoint is not a single-region processing guarantee.
|
|
74
|
+
|
|
75
|
+
[LearnCard PR1670](https://github.com/learningeconomy/LearnCard/pull/1670) merged on
|
|
76
|
+
2026-10-09 at `7656a57d30c6d80b8a25a8bb14bcd3b08b3648c2`. This branch is synced with
|
|
77
|
+
main and preserves its `@learncard/service-config` bootstrap, `CONFIG_TENANT`,
|
|
78
|
+
tenant/stage selection and runtime-bundle precedence. The optional endpoint is
|
|
79
|
+
forwarded by the current API function-environment helper in both bundle and fallback
|
|
80
|
+
modes; OIDC and provider environments do not receive it. There is no new bearer-token
|
|
81
|
+
secret to provision. Configuration tests use the current deployment contract rather
|
|
82
|
+
than restoring the superseded function-environment test.
|
|
83
|
+
|
|
84
|
+
Rollback uses the prior application revision with its prior OpenAI credential, after
|
|
85
|
+
explicit deployment approval. There is no runtime silent fallback to the old models.
|
|
86
|
+
|
|
87
|
+
## Node 24 and owner-triggered staging deployment
|
|
88
|
+
|
|
89
|
+
All LCA Lambda functions and the native DIDKit layer declare `nodejs24.x`; esbuild
|
|
90
|
+
targets Node 24. The native addon uses stable N-API v4, supported by Node 24.
|
|
91
|
+
Both Node 20 and Node 24 Lambda use AL2023. Before deployment, the LCA-only
|
|
92
|
+
`verify-node24-layer.cjs` preflight requires the Linux x64 GNU artifact, both
|
|
93
|
+
packaged runtime dependencies, libc symbols no newer than glibc 2.34, and an actual
|
|
94
|
+
Node 24 native key-to-DID smoke check. Missing/incompatible artifacts stop the action
|
|
95
|
+
before Serverless deployment; WASM fallback is not evidence of native compatibility.
|
|
96
|
+
|
|
97
|
+
LCA pins serverless-esbuild 1.57.2 for Node 24 target support. The service-local
|
|
98
|
+
`node24-runtime.cjs` plugin adds Node 24 to Serverless 3.40's older runtime schema
|
|
99
|
+
without disabling validation or changing other services. Deployment tools run on
|
|
100
|
+
Node 24.12.0 in the LCA action. Local offline configuration uses Node 24 too.
|
|
101
|
+
|
|
102
|
+
The `LcaDevBedrockGenerationPolicy` CloudFormation resource attaches the scoped
|
|
103
|
+
policy to `SigningAuthorityExecutionRole` only for stage `dev` in `us-east-1`.
|
|
104
|
+
It preserves existing grants and also covers seedMigration, which shares that role.
|
|
105
|
+
Production receives no new Bedrock grant from this resource. ARNs use the deployment
|
|
106
|
+
account and partition; source profiles/default project stay in us-east-1, and the
|
|
107
|
+
verified backing models are scoped to us-east-1/us-east-2/us-west-2. Short-term bearer
|
|
108
|
+
use, matching model/profile conditions and default-project authorization are explicit.
|
|
109
|
+
The same policy also carries `StableImageCoreUsWest2` (InvokeModel on the us-west-2
|
|
110
|
+
Stable Image Core model only) for image generation; see `image-generation.md`.
|
|
111
|
+
|
|
112
|
+
Owner workflow: select this branch in Deploy, target environment `staging`, and
|
|
113
|
+
enable only `deploy-lca-api`. The LearnCard staging leg selects GitHub environment
|
|
114
|
+
`learn-card-app-api-staging`; SERVERLESS_STAGE and SERVERLESS_REGION come from its
|
|
115
|
+
variables and must be `dev`/`us-east-1` for this policy. The action downloads/builds
|
|
116
|
+
the native artifact, runs the preflight, and Serverless owns role-policy and runtime
|
|
117
|
+
updates. Do not trigger it until the owner approves deployment. After the stack
|
|
118
|
+
finishes, repeat role simulations and runtime checks before separately approved
|
|
119
|
+
synthetic model calls. CI and native preflight do not prove live model acceptance.
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
# Configurable image generation
|
|
2
|
+
|
|
3
|
+
`ai.generateImage({ prompt })` still requires a nonempty prompt and returns `{ url }`.
|
|
4
|
+
The LCA SDK still returns the URL string. Text generation is unchanged.
|
|
5
|
+
Amazon Bedrock Stable Image Core (`stability.stable-image-core-v1:1`, us-west-2) is
|
|
6
|
+
the default provider, so with LC-2271 every LCA model call goes through Bedrock.
|
|
7
|
+
Output intentionally differs from DALL-E (different model and style); the contract
|
|
8
|
+
is unchanged: one square PNG, uploaded through Filestack, returned as `{ url }`.
|
|
9
|
+
DALL-E 3 (one 1024 × 1024 image, URL stored through Filestack) remains available
|
|
10
|
+
only as an explicit fallback with `IMAGE_PROVIDER=openai`.
|
|
11
|
+
|
|
12
|
+
## Configuration
|
|
13
|
+
|
|
14
|
+
Use the existing service-config layering: schema defaults → base/stage JSON →
|
|
15
|
+
runtime secrets bundle → explicit nonempty environment. No new secrets loader is added.
|
|
16
|
+
|
|
17
|
+
| Setting | Default | Allowed values |
|
|
18
|
+
| ---------------------- | -------------------------------- | -------------------------------- |
|
|
19
|
+
| IMAGE_PROVIDER | bedrock | bedrock, openai |
|
|
20
|
+
| BEDROCK_IMAGE_REGION | us-west-2 | us-west-2 |
|
|
21
|
+
| BEDROCK_IMAGE_MODEL_ID | stability.stable-image-core-v1:1 | stability.stable-image-core-v1:1 |
|
|
22
|
+
| IMAGE_TIMEOUT_MS | 25000 | integer 1000–25000 |
|
|
23
|
+
|
|
24
|
+
Only the verified candidate and region are accepted for now. Additional models
|
|
25
|
+
require their own request/response validation. Do not choose Nova Canvas (retired
|
|
26
|
+
September 30, 2026). Lambda deployers can set these nonsecret values in existing stage JSON (rebuild
|
|
27
|
+
and deploy) or explicit Lambda environment; Docker/local may use environment.
|
|
28
|
+
The deadline is capped at 25 seconds, matching text generation, so the request
|
|
29
|
+
finishes inside the 29-second Lambda timeout (and the API Gateway HTTP API's 30-second
|
|
30
|
+
integration limit) rather than being killed mid-upload. Recycle functions after
|
|
31
|
+
configuration changes.
|
|
32
|
+
|
|
33
|
+
`OPENAI_API_KEY` is now only needed for the DALL-E fallback (`IMAGE_PROVIDER=openai`).
|
|
34
|
+
Text routes use Bedrock since LC-2271 (#1684) and the default image provider uses
|
|
35
|
+
Bedrock, so with the defaults no LCA model call needs an OpenAI key. To fall back
|
|
36
|
+
(e.g. rollback), set `IMAGE_PROVIDER=openai` in the stage JSON or runtime-secrets
|
|
37
|
+
bundle and make sure `OPENAI_API_KEY` is available, then recycle the functions.
|
|
38
|
+
There is no automatic fallback between providers.
|
|
39
|
+
|
|
40
|
+
## Access and approval prerequisites
|
|
41
|
+
|
|
42
|
+
Because Bedrock is the default, every stage that deploys this code needs the
|
|
43
|
+
following before `generateImage` will work (otherwise it returns the sanitized
|
|
44
|
+
INTERNAL_SERVER_ERROR). No live calls, Marketplace subscriptions/terms acceptance,
|
|
45
|
+
deployment, or merge were performed for this PR. An authorized operator must
|
|
46
|
+
verify model availability and entitlement in **us-west-2** and approve any required
|
|
47
|
+
Marketplace subscription/terms. Runtime access is model-scoped:
|
|
48
|
+
|
|
49
|
+
- Action: `bedrock:InvokeModel` (no streaming action, no wildcard)
|
|
50
|
+
- Resource: `arn:aws:bedrock:us-west-2::foundation-model/stability.stable-image-core-v1:1`
|
|
51
|
+
|
|
52
|
+
Use the workload's normal AWS credential chain and existing execution role. The
|
|
53
|
+
client pins SigV4 (`authSchemePreference: ['sigv4']`), so the rotating execution-role
|
|
54
|
+
credentials are used and a long-term `AWS_BEARER_TOKEN_BEDROCK` API key is never
|
|
55
|
+
picked up, consistent with the text adapter's short-term-bearer-only policy.
|
|
56
|
+
`generateImage` runs in the `trpc`/`api` functions, which use
|
|
57
|
+
`SigningAuthorityExecutionRole`.
|
|
58
|
+
|
|
59
|
+
**Dev/staging (stage `dev`, us-east-1): managed in `serverless.yml`.** The grant is
|
|
60
|
+
the `StableImageCoreUsWest2` statement in #1684's `LcaDevBedrockGenerationPolicy`,
|
|
61
|
+
under the same `LcaDevBedrockGeneration` condition (stage `dev` in us-east-1), so
|
|
62
|
+
Serverless/CloudFormation applies it with the stack and removes it on rollback. Do
|
|
63
|
+
not add it by hand in the console. `.github/tests/test-lca-node24.test.cjs` pins the
|
|
64
|
+
exact statement and rejects streaming actions and wildcard resources.
|
|
65
|
+
|
|
66
|
+
**Production still needs a grant.** The condition excludes every other stage,
|
|
67
|
+
so production (and ScoutPass stages) get no Bedrock access from this policy. Before
|
|
68
|
+
deploying this default to production, add a reviewed production grant for the same
|
|
69
|
+
single action/resource (alongside the production text-generation grant), or set
|
|
70
|
+
`IMAGE_PROVIDER=openai` for that stage as a stopgap.
|
|
71
|
+
|
|
72
|
+
Regional network connectivity (NAT egress from the VPC Lambdas to
|
|
73
|
+
`bedrock-runtime.us-west-2.amazonaws.com`) and existing Filestack storage access
|
|
74
|
+
must also be verified by the operator.
|
|
75
|
+
|
|
76
|
+
## Behavior and evaluation
|
|
77
|
+
|
|
78
|
+
The Bedrock adapter sends one InvokeModel request with `aspect_ratio: "1:1"`
|
|
79
|
+
and `output_format: "png"`. It accepts exactly one successful image, validates
|
|
80
|
+
base64, PNG header and square dimensions (640–1536), bounds decoded bytes to
|
|
81
|
+
10 MiB, response bytes to 14 MiB and PNG decompression to 20 MiB, then uploads bytes through existing Filestack.
|
|
82
|
+
Filtering (a `Filter reason:` finish reason, or a content-filter ValidationException) returns a
|
|
83
|
+
sanitized BAD_REQUEST. Provider failures are classified by AWS error name as well as HTTP status:
|
|
84
|
+
throttling and quota rejections (`ThrottlingException`, `ModelNotReadyException`, HTTP 429, and
|
|
85
|
+
`ServiceQuotaExceededException`, which Bedrock returns as HTTP **400**) map to TOO_MANY_REQUESTS;
|
|
86
|
+
`ServiceUnavailableException`/`InternalServerException`, HTTP 500/503, model timeouts (408/504)
|
|
87
|
+
and our own deadline map to SERVICE_UNAVAILABLE. Access, validation, payload and storage failures
|
|
88
|
+
return a generic INTERNAL_SERVER_ERROR. Client messages never include provider text.
|
|
89
|
+
|
|
90
|
+
Throttling/quota and unavailable rejections (not billed) get at most two extra attempts with full
|
|
91
|
+
jitter (≤ 500 ms, then ≤ 1 s), and only while at least 12 s of the deadline remains. Timeouts,
|
|
92
|
+
access, validation and filter errors are never retried, and DALL-E is never retried.
|
|
93
|
+
|
|
94
|
+
Every failure logs one `image_generation_failure` record (`console.error`, so CloudWatch) with
|
|
95
|
+
allowlisted fields only: provider, modelId, region, stage (invoke/decode/storage/timeout/config),
|
|
96
|
+
attempts, category, errorName, errorCode, httpStatus, awsRequestId, sdkRetryable, retryable,
|
|
97
|
+
durationMs, promptLength, promptHash (first 12 hex of SHA-256), finishReason, decodeReason and
|
|
98
|
+
the clientCode returned. Retries log `image_generation_retry`; success logs
|
|
99
|
+
`image_generation_success` with attempts and duration. Prompts, provider messages, headers,
|
|
100
|
+
credentials, URLs and image bytes are never logged or returned. A deadline bounds the request and
|
|
101
|
+
aborts model work; storage already in flight may finish after the deadline. No automatic provider
|
|
102
|
+
fallback is present. A manual retry after timeout may still incur another charge.
|
|
103
|
+
|
|
104
|
+
Output differing from DALL-E is accepted. Before learner traffic, still smoke-test
|
|
105
|
+
a small fixed prompt set on staging for badge/icon readability, filter behavior,
|
|
106
|
+
latency, failure rate and actual storage/display behavior. Verify current
|
|
107
|
+
[Bedrock pricing](https://aws.amazon.com/bedrock/pricing/) in the target region
|
|
108
|
+
and model before any calls; record invocation count and total cost, including
|
|
109
|
+
failed or timed-out requests.
|
|
110
|
+
|
|
111
|
+
Request/response fields were checked against the
|
|
112
|
+
[AWS Stable Image Core documentation](https://docs.aws.amazon.com/bedrock/latest/userguide/model-parameters-diffusion-stable-image-core-text-image-request-response.html).
|
|
113
|
+
|
|
114
|
+
## Integration with text LLM PR #1684
|
|
115
|
+
|
|
116
|
+
This branch now contains #1684 (merged in, base `feat/lca-bedrock-lc2205`): Node 24
|
|
117
|
+
runtime, OpenAI SDK 6.47.0 and the Bedrock text adapter. Text routes use
|
|
118
|
+
`generateStructuredOutput`; the image route uses `generateStoredImage`. Neither
|
|
119
|
+
provider selection controls the other: `BEDROCK_BASE_URL` configures text only and
|
|
120
|
+
`IMAGE_PROVIDER`/`BEDROCK_IMAGE_*` configure images only.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@learncard/lca-api-service",
|
|
3
|
-
"version": "1.
|
|
3
|
+
"version": "1.6.0",
|
|
4
4
|
"description": "",
|
|
5
5
|
"engines": {
|
|
6
6
|
"node": ">=24.12 <25"
|
|
@@ -28,10 +28,11 @@
|
|
|
28
28
|
"start-p-5000": "PORT=5000 serverless offline --httpPort 5000 --websocketPort 5001 --lambdaPort 5002 --albPort 5003 --config serverless-local.yml",
|
|
29
29
|
"dev": "bun --conditions=development --watch src/docker-entry.ts",
|
|
30
30
|
"start:docker": "bun --conditions=development src/docker-entry.ts",
|
|
31
|
-
"serverless-deploy": "node scripts/check-seed-encryption-infrastructure.mjs && node ../../../scripts/prepare-didkit-native-layer.cjs && serverless deploy",
|
|
31
|
+
"serverless-deploy": "node scripts/check-seed-encryption-infrastructure.mjs && node ../../../scripts/prepare-didkit-native-layer.cjs && node scripts/verify-node24-layer.cjs && serverless deploy",
|
|
32
32
|
"serverless-deploy-scouts": "serverless deploy --config serverless-scouts.yml",
|
|
33
33
|
"test:oidc:e2e": "node test/oidc-e2e/run.mjs",
|
|
34
|
-
"typecheck:oidc:e2e": "tsc --noEmit -p test/oidc-e2e/tsconfig.json"
|
|
34
|
+
"typecheck:oidc:e2e": "tsc --noEmit -p test/oidc-e2e/tsconfig.json",
|
|
35
|
+
"typecheck:bedrock": "tsc --noEmit -p tsconfig.bedrock.json"
|
|
35
36
|
},
|
|
36
37
|
"keywords": [],
|
|
37
38
|
"author": "WeLibrary, LLC (www.welibrary.io)",
|
|
@@ -49,29 +50,31 @@
|
|
|
49
50
|
"serverless": "3.40.0",
|
|
50
51
|
"serverless-certificate-creator": "^1.6.0",
|
|
51
52
|
"serverless-domain-manager": "^6.4.4",
|
|
52
|
-
"serverless-esbuild": "1.
|
|
53
|
+
"serverless-esbuild": "1.57.2",
|
|
53
54
|
"serverless-http": "3.2.0",
|
|
54
55
|
"serverless-offline": "^14.4.0",
|
|
55
56
|
"serverless-prune-plugin": "^2.1.0",
|
|
56
57
|
"shx": "^0.3.4",
|
|
57
|
-
"@playwright/test": "^1.42.0"
|
|
58
|
+
"@playwright/test": "^1.42.0",
|
|
59
|
+
"@types/pngjs": "^6.0.5"
|
|
58
60
|
},
|
|
59
61
|
"dependencies": {
|
|
62
|
+
"@aws-sdk/client-bedrock-runtime": "^3.1139.0",
|
|
60
63
|
"@aws-sdk/client-kms": "^3.1139.0",
|
|
61
64
|
"@aws-sdk/client-secrets-manager": "^3.1139.0",
|
|
62
65
|
"@fastify/cors": "^11.2.0",
|
|
63
66
|
"@fastify/formbody": "^8.0.2",
|
|
64
67
|
"@fastify/rate-limit": "^10.2.0",
|
|
65
68
|
"@fastify/static": "^10.1.1",
|
|
66
|
-
"@learncard/core": "9.4.
|
|
67
|
-
"@learncard/did-web-plugin": "1.1.
|
|
68
|
-
"@learncard/didkit-plugin": "1.10.
|
|
69
|
-
"@learncard/didkit-plugin-node": "0.3.
|
|
69
|
+
"@learncard/core": "9.4.41",
|
|
70
|
+
"@learncard/did-web-plugin": "1.1.41",
|
|
71
|
+
"@learncard/didkit-plugin": "1.10.6",
|
|
72
|
+
"@learncard/didkit-plugin-node": "0.3.6",
|
|
70
73
|
"@learncard/email-templates": "1.2.1",
|
|
71
|
-
"@learncard/helpers": "1.7.
|
|
72
|
-
"@learncard/init": "2.5.
|
|
73
|
-
"@learncard/types": "5.
|
|
74
|
-
"@learncard/sss-key-manager": "0.2.
|
|
74
|
+
"@learncard/helpers": "1.7.1",
|
|
75
|
+
"@learncard/init": "2.5.6",
|
|
76
|
+
"@learncard/types": "5.24.0",
|
|
77
|
+
"@learncard/sss-key-manager": "0.2.4",
|
|
75
78
|
"@sentry/serverless": "^7.61.0",
|
|
76
79
|
"@trpc/server": "11.8.0",
|
|
77
80
|
"@types/jsonwebtoken": "^9.0.8",
|
|
@@ -95,11 +98,13 @@
|
|
|
95
98
|
"mongodb": "^6.2.0",
|
|
96
99
|
"multiformats": "^11.0.1",
|
|
97
100
|
"node-forge": "^1.4.0",
|
|
98
|
-
"openai": "
|
|
101
|
+
"openai": "6.47.0",
|
|
99
102
|
"postmark": "^4.0.5",
|
|
100
103
|
"trpc-to-openapi": "^3.1.0",
|
|
101
104
|
"uuid": "^14.0.0",
|
|
102
|
-
"zod": "4.
|
|
105
|
+
"zod": "4.6.6",
|
|
106
|
+
"pngjs": "^7.0.0",
|
|
107
|
+
"@aws/bedrock-token-generator": "1.1.0"
|
|
103
108
|
},
|
|
104
109
|
"repository": {
|
|
105
110
|
"type": "git",
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
/* global module */
|
|
2
|
+
// Serverless 3.40 predates Node 24. Extend only LCA's runtime enum, keeping
|
|
3
|
+
// all other configuration validation enabled. AWS supports nodejs24.x.
|
|
4
|
+
module.exports = class Node24Runtime {
|
|
5
|
+
constructor(serverless) {
|
|
6
|
+
const runtimes = serverless.configSchemaHandler.schema.definitions.awsLambdaRuntime.enum;
|
|
7
|
+
if (!runtimes.includes('nodejs24.x')) runtimes.push('nodejs24.x');
|
|
8
|
+
}
|
|
9
|
+
};
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/* global require, module, process, Buffer, console */
|
|
2
|
+
/* eslint-disable @typescript-eslint/no-require-imports -- Node CommonJS deployment script. */
|
|
3
|
+
const assert = require('node:assert/strict');
|
|
4
|
+
const fs = require('node:fs');
|
|
5
|
+
const path = require('node:path');
|
|
6
|
+
const { execFileSync } = require('node:child_process');
|
|
7
|
+
|
|
8
|
+
const verifyLayer = (layerRoot, nodeMajor = Number(process.versions.node.split('.')[0])) => {
|
|
9
|
+
assert.equal(nodeMajor, 24, 'LCA deployment must run with Node 24');
|
|
10
|
+
assert.equal(process.platform, 'linux', 'LCA native layer requires Linux');
|
|
11
|
+
assert.equal(process.arch, 'x64', 'LCA native layer requires x86_64');
|
|
12
|
+
const modules = path.join(layerRoot, 'nodejs/node_modules/@learncard');
|
|
13
|
+
const native = path.join(modules, 'didkit-plugin-node');
|
|
14
|
+
const binary = path.join(native, 'index.linux-x64-gnu.node');
|
|
15
|
+
assert.ok(fs.existsSync(binary), 'Missing Lambda Linux x64 GNU DIDKit addon');
|
|
16
|
+
const binaryNames = fs.readdirSync(native).filter(name => name.endsWith('.node'));
|
|
17
|
+
assert.deepEqual(
|
|
18
|
+
binaryNames,
|
|
19
|
+
['index.linux-x64-gnu.node'],
|
|
20
|
+
'Layer must contain only the Lambda addon'
|
|
21
|
+
);
|
|
22
|
+
for (const pkg of ['didkit-plugin-node', 'didkit-plugin']) {
|
|
23
|
+
const directory = path.join(modules, pkg);
|
|
24
|
+
const manifest = JSON.parse(fs.readFileSync(path.join(directory, 'package.json'), 'utf8'));
|
|
25
|
+
assert.ok(
|
|
26
|
+
fs.existsSync(path.resolve(directory, manifest.main)),
|
|
27
|
+
`Missing ${pkg} runtime entry`
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
// Node 24 Lambda uses AL2023/glibc 2.34. Reject Ubuntu-built artifacts
|
|
31
|
+
// requiring newer libc symbols before Serverless can change the stack.
|
|
32
|
+
const versions = execFileSync('readelf', ['--version-info', binary], { encoding: 'utf8' });
|
|
33
|
+
for (const match of versions.matchAll(/GLIBC_(\d+)\.(\d+)/g)) {
|
|
34
|
+
assert.ok(
|
|
35
|
+
Number(match[1]) < 2 || (Number(match[1]) === 2 && Number(match[2]) <= 34),
|
|
36
|
+
`Addon requires ${match[0]}, newer than Lambda AL2023`
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
// Exercise stable N-API with a deterministic synthetic key; never log key material.
|
|
40
|
+
const addon = require(binary);
|
|
41
|
+
const key = addon.generateEd25519KeyFromBytes(Buffer.alloc(32, 1));
|
|
42
|
+
assert.match(addon.keyToDid('key', key), /^did:key:/);
|
|
43
|
+
};
|
|
44
|
+
|
|
45
|
+
module.exports = { verifyLayer };
|
|
46
|
+
if (require.main === module) {
|
|
47
|
+
verifyLayer(path.resolve('didkit-native-layer'));
|
|
48
|
+
console.log('LCA Node 24 native layer ABI, libc and package checks passed');
|
|
49
|
+
}
|
package/serverless-local.yml
CHANGED
|
@@ -7,6 +7,7 @@ frameworkVersion: '3'
|
|
|
7
7
|
useDotenv: true
|
|
8
8
|
|
|
9
9
|
plugins:
|
|
10
|
+
- ./scripts/node24-runtime.cjs
|
|
10
11
|
- serverless-esbuild
|
|
11
12
|
- serverless-offline
|
|
12
13
|
|
|
@@ -19,6 +20,7 @@ custom:
|
|
|
19
20
|
packager: pnpm
|
|
20
21
|
plugins: esbuildPlugins.cjs
|
|
21
22
|
external: ['snappy', '@learncard/didkit-plugin/dist/didkit_wasm_bg.wasm']
|
|
23
|
+
target: node24
|
|
22
24
|
sourcemap: external
|
|
23
25
|
watch:
|
|
24
26
|
pattern: ['src/**/*.ts', 'dids.ts']
|
|
@@ -30,7 +32,7 @@ package:
|
|
|
30
32
|
|
|
31
33
|
provider:
|
|
32
34
|
name: aws
|
|
33
|
-
runtime:
|
|
35
|
+
runtime: nodejs24.x
|
|
34
36
|
memorySize: 2048
|
|
35
37
|
timeout: 29
|
|
36
38
|
stage: ${opt:stage, "dev"}
|
|
@@ -24,10 +24,8 @@ const API_SECRETS = [
|
|
|
24
24
|
'KEYCLOAK_JWKS_URL_OVERRIDES',
|
|
25
25
|
];
|
|
26
26
|
|
|
27
|
-
// Non-secret operator toggles forwarded in bundle
|
|
28
|
-
|
|
29
|
-
// ESCROW_RELEASE_KILL_SWITCH must be flippable without editing the bundle.
|
|
30
|
-
const API_TOGGLES = ['ESCROW_ENCLAVE_MODE', 'ESCROW_RELEASE_KILL_SWITCH'];
|
|
27
|
+
// Non-secret operator toggles are forwarded in both bundle and fallback modes.
|
|
28
|
+
const API_TOGGLES = ['ESCROW_ENCLAVE_MODE', 'ESCROW_RELEASE_KILL_SWITCH', 'BEDROCK_BASE_URL'];
|
|
31
29
|
|
|
32
30
|
// Serverless v3 supplies CLI options to file functions and preserves CF intrinsics.
|
|
33
31
|
// No stage-file values are forwarded from the deployment environment.
|
package/serverless.yml
CHANGED
|
@@ -7,6 +7,7 @@ frameworkVersion: '3'
|
|
|
7
7
|
useDotenv: true
|
|
8
8
|
|
|
9
9
|
plugins:
|
|
10
|
+
- ./scripts/node24-runtime.cjs
|
|
10
11
|
- serverless-esbuild
|
|
11
12
|
- serverless-domain-manager
|
|
12
13
|
- serverless-certificate-creator
|
|
@@ -47,7 +48,7 @@ custom:
|
|
|
47
48
|
# serverless-esbuild default and must be preserved when overriding.
|
|
48
49
|
exclude: ['aws-sdk', '@learncard/didkit-plugin-node']
|
|
49
50
|
sourcemap: external
|
|
50
|
-
target: '
|
|
51
|
+
target: 'node24'
|
|
51
52
|
watch:
|
|
52
53
|
pattern: ['src/**/*.ts', 'dids.ts']
|
|
53
54
|
|
|
@@ -117,13 +118,13 @@ layers:
|
|
|
117
118
|
name: ${self:service}-${opt:stage, 'dev'}-didkit-native
|
|
118
119
|
description: Native DIDKit napi addon and runtime DIDKit dependency
|
|
119
120
|
compatibleRuntimes:
|
|
120
|
-
-
|
|
121
|
+
- nodejs24.x
|
|
121
122
|
compatibleArchitectures:
|
|
122
123
|
- x86_64
|
|
123
124
|
|
|
124
125
|
provider:
|
|
125
126
|
name: aws
|
|
126
|
-
runtime:
|
|
127
|
+
runtime: nodejs24.x
|
|
127
128
|
# Attach the native DIDKit layer to every function in this service.
|
|
128
129
|
layers:
|
|
129
130
|
- { Ref: DidkitNativeLambdaLayer }
|
|
@@ -272,6 +273,14 @@ resources:
|
|
|
272
273
|
# ESCROW_ENCLAVE_ENDPOINT_SERVICE_NAME (the enclave account's endpoint
|
|
273
274
|
# service). TLS trust for the hostname is pinned in enclaveTrustAnchors.ts.
|
|
274
275
|
Conditions:
|
|
276
|
+
LcaDevBedrockGeneration:
|
|
277
|
+
Fn::And:
|
|
278
|
+
- Fn::Equals:
|
|
279
|
+
- ${sls:stage}
|
|
280
|
+
- dev
|
|
281
|
+
- Fn::Equals:
|
|
282
|
+
- Ref: AWS::Region
|
|
283
|
+
- us-east-1
|
|
275
284
|
EscrowEnclavePrivateLink:
|
|
276
285
|
Fn::Not:
|
|
277
286
|
- Fn::Equals: ['${env:ESCROW_ENCLAVE_ENDPOINT_SERVICE_NAME, ""}', '']
|
|
@@ -283,6 +292,70 @@ resources:
|
|
|
283
292
|
Value:
|
|
284
293
|
Ref: SeedMigrationLambdaFunction
|
|
285
294
|
Resources:
|
|
295
|
+
LcaDevBedrockGenerationPolicy:
|
|
296
|
+
Type: AWS::IAM::Policy
|
|
297
|
+
Condition: LcaDevBedrockGeneration
|
|
298
|
+
Properties:
|
|
299
|
+
PolicyName: lca-api-dev-bedrock-generation
|
|
300
|
+
Roles:
|
|
301
|
+
- Ref: SigningAuthorityExecutionRole
|
|
302
|
+
PolicyDocument:
|
|
303
|
+
Version: '2012-10-17'
|
|
304
|
+
Statement:
|
|
305
|
+
- Sid: ShortTermBearerOnly
|
|
306
|
+
Effect: Allow
|
|
307
|
+
Action: bedrock:CallWithBearerToken
|
|
308
|
+
Resource: '*'
|
|
309
|
+
Condition:
|
|
310
|
+
StringEquals:
|
|
311
|
+
bedrock:bearerTokenType: SHORT_TERM
|
|
312
|
+
- Sid: ApprovedProfiles
|
|
313
|
+
Effect: Allow
|
|
314
|
+
Action:
|
|
315
|
+
- bedrock:GetInferenceProfile
|
|
316
|
+
- bedrock:InvokeModel
|
|
317
|
+
Resource:
|
|
318
|
+
- Fn::Sub: arn:${AWS::Partition}:bedrock:us-east-1:${AWS::AccountId}:inference-profile/us.openai.gpt-6.1-sol
|
|
319
|
+
- Fn::Sub: arn:${AWS::Partition}:bedrock:us-east-1:${AWS::AccountId}:inference-profile/us.openai.gpt-6-luna
|
|
320
|
+
- Sid: DefaultProjectForApprovedProfiles
|
|
321
|
+
Effect: Allow
|
|
322
|
+
Action: bedrock:InvokeModel
|
|
323
|
+
Resource:
|
|
324
|
+
Fn::Sub: arn:${AWS::Partition}:bedrock:us-east-1:${AWS::AccountId}:project/default
|
|
325
|
+
Condition:
|
|
326
|
+
StringEquals:
|
|
327
|
+
bedrock:ModelArn:
|
|
328
|
+
- Fn::Sub: arn:${AWS::Partition}:bedrock:us-east-1:${AWS::AccountId}:inference-profile/us.openai.gpt-6.1-sol
|
|
329
|
+
- Fn::Sub: arn:${AWS::Partition}:bedrock:us-east-1:${AWS::AccountId}:inference-profile/us.openai.gpt-6-luna
|
|
330
|
+
- Sid: SolBackingModelsThroughSolProfile
|
|
331
|
+
Effect: Allow
|
|
332
|
+
Action: bedrock:InvokeModel
|
|
333
|
+
Resource:
|
|
334
|
+
- Fn::Sub: arn:${AWS::Partition}:bedrock:us-east-1::foundation-model/openai.gpt-6.1-sol
|
|
335
|
+
- Fn::Sub: arn:${AWS::Partition}:bedrock:us-east-2::foundation-model/openai.gpt-6.1-sol
|
|
336
|
+
- Fn::Sub: arn:${AWS::Partition}:bedrock:us-west-2::foundation-model/openai.gpt-6.1-sol
|
|
337
|
+
Condition:
|
|
338
|
+
StringEquals:
|
|
339
|
+
bedrock:InferenceProfileArn:
|
|
340
|
+
Fn::Sub: arn:${AWS::Partition}:bedrock:us-east-1:${AWS::AccountId}:inference-profile/us.openai.gpt-6.1-sol
|
|
341
|
+
- Sid: LunaBackingModelsThroughLunaProfile
|
|
342
|
+
Effect: Allow
|
|
343
|
+
Action: bedrock:InvokeModel
|
|
344
|
+
Resource:
|
|
345
|
+
- Fn::Sub: arn:${AWS::Partition}:bedrock:us-east-1::foundation-model/openai.gpt-6-luna
|
|
346
|
+
- Fn::Sub: arn:${AWS::Partition}:bedrock:us-east-2::foundation-model/openai.gpt-6-luna
|
|
347
|
+
- Fn::Sub: arn:${AWS::Partition}:bedrock:us-west-2::foundation-model/openai.gpt-6-luna
|
|
348
|
+
Condition:
|
|
349
|
+
StringEquals:
|
|
350
|
+
bedrock:InferenceProfileArn:
|
|
351
|
+
Fn::Sub: arn:${AWS::Partition}:bedrock:us-east-1:${AWS::AccountId}:inference-profile/us.openai.gpt-6-luna
|
|
352
|
+
# generateImage default provider (docs/image-generation.md): one model,
|
|
353
|
+
# one region, non-streaming InvokeModel only.
|
|
354
|
+
- Sid: StableImageCoreUsWest2
|
|
355
|
+
Effect: Allow
|
|
356
|
+
Action: bedrock:InvokeModel
|
|
357
|
+
Resource:
|
|
358
|
+
Fn::Sub: arn:${AWS::Partition}:bedrock:us-west-2::foundation-model/stability.stable-image-core-v1:1
|
|
286
359
|
# Only the two signing entry points and the migration worker assume this role.
|
|
287
360
|
# Keep KMS references out of the role itself to avoid a Role -> Key -> Role cycle.
|
|
288
361
|
SigningAuthorityExecutionRole:
|
|
@@ -31,6 +31,20 @@ export const lcaApiEnvironmentShape = {
|
|
|
31
31
|
NETWORK_BRAIN_SERVICE_URL: optionalEnvironmentUrl,
|
|
32
32
|
GOOGLE_APPLICATION_CREDENTIAL: optionalEnvironmentString,
|
|
33
33
|
OPENAI_API_KEY: optionalEnvironmentString,
|
|
34
|
+
BEDROCK_BASE_URL: optionalEnvironmentString,
|
|
35
|
+
IMAGE_PROVIDER: optionalEnvironmentString
|
|
36
|
+
.transform(value => value ?? 'bedrock')
|
|
37
|
+
.pipe(z.enum(['openai', 'bedrock'])),
|
|
38
|
+
BEDROCK_IMAGE_REGION: optionalEnvironmentString
|
|
39
|
+
.transform(value => value ?? 'us-west-2')
|
|
40
|
+
.pipe(z.literal('us-west-2')),
|
|
41
|
+
BEDROCK_IMAGE_MODEL_ID: optionalEnvironmentString
|
|
42
|
+
.transform(value => value ?? 'stability.stable-image-core-v1:1')
|
|
43
|
+
.pipe(z.literal('stability.stable-image-core-v1:1')),
|
|
44
|
+
// Must finish inside the 29-second Lambda timeout (same 25-second ceiling as text generation).
|
|
45
|
+
IMAGE_TIMEOUT_MS: optionalEnvironmentString
|
|
46
|
+
.transform(value => (value === undefined ? 25_000 : Number(value)))
|
|
47
|
+
.pipe(z.number().int().min(1_000).max(25_000)),
|
|
34
48
|
METABASE_SECRET_KEY: optionalEnvironmentString,
|
|
35
49
|
SCOUTS_SSO_CLIENT_SECRET: optionalEnvironmentString,
|
|
36
50
|
KEYCLOAK_ISSUERS: optionalEnvironmentString,
|