Repository navigation
Conversation
| // Removes "Bearer " and trims | ||
| const token = authorizationHeader.split(' ')[1].trim(); | ||
| // Load user for later middleware/routes to use | ||
| req.user = await User.newUser({ token }); |
There was a problem hiding this comment.
User.newUser is misleading, it does not create a new user, instead it retrieves a user based on the JWT token.
There was a problem hiding this comment.
User.fromToken({ token }) or User.getByToken({ token }) would be a better fit.
There was a problem hiding this comment.
Yup agreed. I was using existing methods though.
| const appDefinition = backendJsFile.Definition; | ||
|
|
||
| const backend = createFriggBackend(appDefinition); | ||
| const loadRouterFromObject = (IntegrationClass, routerObject) => { |
There was a problem hiding this comment.
"Object" is too vague, I have no clue what can be in there or what that means
There was a problem hiding this comment.
I think we could call it loadIntegrationRoutes
There was a problem hiding this comment.
Also the routerObject is already inside of IntegrationClass isn't it?
There was a problem hiding this comment.
The overall method of loadIntegrationRoutes makes sense, or integration defined routes.
The load from object method was intended to load from a... route object? Dunno what to call it. The "method", "path", "event" object. I'm not even sure where I got that concept from except it's a common way to represent an http endpoint. Minus the event thing. That I want to be an easy way for a dev to reference when creating event handlers.
|
|
||
| for (const routeDef of IntegrationClass.Definition.routes) { | ||
| if (typeof routeDef === 'function') { | ||
| router.use(basePath, routeDef(IntegrationClass)); |
There was a problem hiding this comment.
I don't understand this.
You're looping through IntegrationClass.Definition.routes and from every routeDef you're passing the same integration as parameter?
There was a problem hiding this comment.
Do you have an example of a place where we declare routes as a function and need it's own integration class as parameter?
There was a problem hiding this comment.
Why not stick to one type of route definition? Either function or object
There was a problem hiding this comment.
I think the rationale was that in some cases, you need to have some logic that runs outside of express route generation. https://github.com/lefthookhq/frigg-2.0-prototyping/blob/460ad99b85b5a53bac5a859de8b8d3780b85937d/backend/src/testRouter.js#L8
In other cases, relying on the normal instantiation is fine and for those you can reach for either the straight express route or the static object.
I wanted to provide a "quick and easy", "moderate complexity", "high complexity" set of options.
That said I'm not sure what I did was the way to do it.
| router[method.toLowerCase()](path, async (req, res, next) => { | ||
| try { | ||
| const integration = new IntegrationClass({}); | ||
| await integration.loadModules(); |
There was a problem hiding this comment.
We already loadModules in the IntegrationBase constructor, we could registerEventHandlers in the constructor as well.
There was a problem hiding this comment.
I think I had an issue of not wanting to load event handlers yet because of some dynamic issues? Or something else. But, happy to not duplicate invoking!
| await integration.loadModules(); | ||
| await integration.registerEventHandlers(); | ||
| const result = await integration.send(event, {req, res, next}); | ||
| res.json(result); |
There was a problem hiding this comment.
Do we really have to return the result here?
Developers will by instict call the http response when it's available to them. I was not aware that this existed until now and probably other developers won't either.
There was a problem hiding this comment.
Well, the intent was to remove the need to think about express inside the handler function. Just, do what you need, then if you need to return something, return it.
There was a problem hiding this comment.
But if someones sees a "res" object, one would not simply return and ignore "res".
BTW, one of the reasons I don't like ruby on rails is because it does too much magic and I don't know why and how 🫠
| for (const [entityId, key] of Object.entries( | ||
| integrationRecord.entityReference | ||
| )) { | ||
| const moduleInstance = |
There was a problem hiding this comment.
Are the modules not already instantiated in IntegrationBase -> loadModules() when we do:
const instance = new integrationClass({
userId,
integrationId: params.integrationId,
});
There was a problem hiding this comment.
OK, looks like loadModules doesn't actualy loads modules but it simply instantiates the module api.
There was a problem hiding this comment.
There's a difference between "load module definitions" and "load module entities into the module instance"
| integrationRecord.config.type | ||
| ); | ||
|
|
||
| const instance = new integrationClass({ |
There was a problem hiding this comment.
rename to integrationInstance
| // for each entity, get the moduleinstance and load them according to their keys | ||
| // If it's the first entity, load the moduleinstance into primary as well | ||
| // If it's the second entity, load the moduleinstance into target as well | ||
| const moduleTypesAndKeys = |
There was a problem hiding this comment.
I think this comment also does not make sense anymore, right?
There was a problem hiding this comment.
The first line does make sense. The second and third lines are for backwards compatibility, where we enforced a "primary" and "target" concept via naming conventions.
There was a problem hiding this comment.
I don't think we need to maintain that though. Let things error and people correct the errors.
| moduleClass && | ||
| typeof moduleClass.definition.getName === 'function' | ||
| ) { | ||
| const moduleType = moduleClass.definition.getName(); |
There was a problem hiding this comment.
moduleType also comes from the name?
There was a problem hiding this comment.
Yes, though we can debate that
| const integrationClassIndex = this.integrationTypes.indexOf(type); | ||
| return this.integrationClasses[integrationClassIndex]; | ||
| } | ||
| getModuleTypesAndKeys(integrationClass) { |
There was a problem hiding this comment.
I dont get what this does
There was a problem hiding this comment.
I don't think this implementation is fully what I intended. But anywho, the goal is that we grab an integration definition, and from it we can determine which api modules are part of it, which allows us to get the required module entity instances in order to create a complete integration record (TODO allow for a module to be optional and not required on creation of an integration record, potentially make them required on a per event basis, ie we throw an error/force a user to assign a connection or create a new connection if they go to use a feature that is only available if they have a specific module instance added).
The direct use case is during the management inside the frontend experience. The ui should see "user wants to create a HubSpot integration. The HubSpot integration requires two modules. The user has one module connection (entity) available to use, but needs the other one. I'll run the auth flow for the other one and then ask them to confirm the use of that new connection."
The reason I say this implementation may not be what I intended is that we should allow multiple modules of the same type to be assigned different module names so you have something like "slackUser" and "slackApp" both pointing to the slack api module.
seanspeaks
left a comment
There was a problem hiding this comment.
Did my comments to your comments and a few added comments in there
| { | ||
| "$schema": "node_modules/lerna/schemas/lerna-schema.json", | ||
| "version": "1.2.2", | ||
| "version": "2.0.0-next.0", |
There was a problem hiding this comment.
I have no idea if this should be committed 😅
| const appDefinition = backendJsFile.Definition; | ||
|
|
||
| const backend = createFriggBackend(appDefinition); | ||
| const loadRouterFromObject = (IntegrationClass, routerObject) => { |
There was a problem hiding this comment.
The overall method of loadIntegrationRoutes makes sense, or integration defined routes.
The load from object method was intended to load from a... route object? Dunno what to call it. The "method", "path", "event" object. I'm not even sure where I got that concept from except it's a common way to represent an http endpoint. Minus the event thing. That I want to be an easy way for a dev to reference when creating event handlers.
| router[method.toLowerCase()](path, async (req, res, next) => { | ||
| try { | ||
| const integration = new IntegrationClass({}); | ||
| await integration.loadModules(); |
There was a problem hiding this comment.
I think I had an issue of not wanting to load event handlers yet because of some dynamic issues? Or something else. But, happy to not duplicate invoking!
|
|
||
| getIntegrationById: async function(id) { | ||
| return IntegrationModel.findById(id); | ||
| getIntegrationById: async function (id) { |
There was a problem hiding this comment.
I really need to lock this concept into my brain. Git repo takes the entire definition of "repository" in my head.
| const integration = | ||
| await integrationFactory.getInstanceFromIntegrationId({ | ||
| integrationId: integrationRecord.id, | ||
| userId: getUserId(req), |
There was a problem hiding this comment.
I think we discussed this on our call. Likely just was throwing things that stuck, for context on why this is here.
| @@ -349,9 +375,7 @@ function setEntityRoutes(router, factory, getUserId) { | |||
| throw Boom.forbidden('Credential does not belong to user'); | |||
There was a problem hiding this comment.
I think there were moments where this failed? Dunno what those moments were/are though.
| const {ModuleConstants} = require("./ModuleConstants"); | ||
| const { ModuleConstants } = require('./ModuleConstants'); | ||
|
|
||
| class Auther extends Delegate { |
There was a problem hiding this comment.
All for it. At one point @MichaelRyanWebber and I were debating both naming and intent of the class (hi Michael! Not to rope you in but, you likely can either find the note somewhere or comment on the future improvements you/we had in mind).
| this.EntityModel = definition.Entity || this.getEntityModel(); | ||
| } | ||
|
|
||
| static async getInstance(params) { |
There was a problem hiding this comment.
Async construction.
It's a debate in nodejs world. Might be a resolved debate in node 20? But basically "if you need to await/promise something in order to instantiate a class, do you make it an async constructor, or do you create a static async instantiation method?" Aka the get
That's the root of this decision at any rate.
There was a problem hiding this comment.
CodeQL found more than 20 potential problems in the proposed changes. Check the Files changed tab for more details.
|
| GitGuardian id | GitGuardian status | Secret | Commit | Filename | |
|---|---|---|---|---|---|
| 37582161 | Triggered | Generic High Entropy Secret | b23b0ea | packages/core/logs/fixtures/vectors.js | View secret |
| 37580179 | Triggered | Generic High Entropy Secret | 48b1ba4 | packages/core/logs/fixtures/secrets.js | View secret |
| 37580180 | Triggered | Generic High Entropy Secret | 48b1ba4 | packages/core/logs/fixtures/secrets.js | View secret |
| 37580181 | Triggered | Generic High Entropy Secret | 48b1ba4 | packages/core/logs/fixtures/secrets.js | View secret |
| 37580182 | Triggered | Generic High Entropy Secret | 48b1ba4 | packages/core/logs/fixtures/secrets.js | View secret |
| 37580183 | Triggered | Generic High Entropy Secret | 48b1ba4 | packages/core/logs/fixtures/secrets.js | View secret |
| 37582810 | Triggered | Bearer Token | c1d8958 | packages/core/logs/record.test.js | View secret |
| 37580184 | Triggered | Generic High Entropy Secret | 48b1ba4 | packages/core/logs/fixtures/secrets.js | View secret |
| 37580185 | Triggered | Generic High Entropy Secret | 48b1ba4 | packages/core/logs/fixtures/secrets.js | View secret |
| 37580186 | Triggered | JSON Web Token | 48b1ba4 | packages/core/logs/fixtures/secrets.js | View secret |
| 37580187 | Triggered | Generic High Entropy Secret | 48b1ba4 | packages/core/logs/fixtures/secrets.js | View secret |
| 35818272 | Triggered | Username Password | 769f999 | packages/core/modules/requester/oauth-2.test.js | View secret |
🛠 Guidelines to remediate hardcoded secrets
- Understand the implications of revoking this secret by investigating where it is used in your code.
- Replace and store your secrets safely. Learn here the best practices.
- Revoke and rotate these secrets.
- If possible, rewrite git history. Rewriting git history is not a trivial act. You might completely break other contributing developers' workflow and you risk accidentally deleting legitimate data.
To avoid such incidents in the future consider
- following these best practices for managing and storing secrets including API keys and other credentials
- install secret detection on pre-commit to catch secret before it leaves your machine and ease remediation.
🦉 GitGuardian detects secrets in your source code to help developers and security teams secure the modern development process. You are seeing this because you or someone else with access to this repository has authorized GitGuardian to scan your pull request.
|
There was a problem hiding this comment.
SonarCloud found more than 20 potential problems in the proposed changes. Check the Files changed tab for more details.
|
✅ Deploy Preview for friggframework-org ready!
To edit notification comments on pull requests, go to your Netlify project configuration. |
…ter updates UpdateProcessMetrics wrote twice per call: phase 1 applied counter increments atomically via applyProcessUpdate (jsonb_set), then phase 2 persisted derived fields (duration, recordsPerSecond, estimatedCompletion) by writing the WHOLE context/results blobs back through the legacy update(). Under concurrent callers a stale phase-2 write replayed its phase-1 snapshot over the row and permanently discarded increments other callers had landed in between. Observed in production (HubSpot<->Clockwork initial sync, 88 concurrent batch handlers): processedRecords ended exactly one 25-record chunk short of totalFetched, so the completion gate (processed >= totalFetched) never fired and the sync sat in PROCESSING_BATCHES forever. A simulation against real Postgres lost 1025 of 2200 increments under an 88-way burst with the old code, and 0 with this fix. Phase 2 now writes only the three derived paths it owns through the same atomic primitive. The derived values stay best-effort under concurrency, same as before; they just can no longer destroy counters. All three repository backends (postgres, mongo, documentdb) already support the set op. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
…s-lost-update fix(core): write derived process metrics atomically to stop lost counter updates
Responds to the review on PR #637 (changes requested): - Cut from 1027 to 337 lines (~67%). - Restructured around the review's four options. The decision is options 4 and 2 together: a reactive database check before refresh and on 401 as the mechanism, and proactive scheduled refresh via the admin scripts as the companion. - Folded in both inline review findings: the adoption test keys on the refresh token (not the access token), and only definitive authorization rejections may invalidate a credential — transport failures (timeout, 429, 5xx) stay retryable. - Addressed the database-propagation caveat with the measured 716ms window, bounded re-read backoff, and the readPreference=primary requirement. - Compare-and-swap moved from load-bearing to deferred hardening. The serialization family stays deferred behind the named metric. - The full prior analysis remains available at commit 42d1e6f. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The scheduled-refresh companion no longer requires core changes. The script hydrates each module and calls refreshAuth() serially; the existing setTokens -> DLGT_TOKEN_UPDATE -> onTokenUpdate -> upsertCredential chain persists the result with no new code. Expiry tracking (the persist/hydrate work and the null-expires_in computation fix) moves from prerequisite to optional later optimization, taken only if the bounded waste of blind refreshes ever matters. Option 4 is unchanged: the database lookup before any refresh, and the re-read-and-adopt on 401/invalid_grant, remain the mechanism and ship first. The sequence shrinks from three PRs to two. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ng it Implements ADR-031 option 4 (frigg PR #637), the cross-invocation half of the refresh race. Separate Lambda invocations hydrate the same credential, refresh from stale memory, and the loser gets invalid_grant — then flags the shared credential invalid, which silently killed 95 records in a production app over four months. refreshAuth() now asks its delegate for the stored credential (new DLGT_CREDENTIAL_RELOAD, handled read-only by Module.reloadCredential) and adopts it when the stored refresh_token differs from the in-memory one: another invocation already refreshed, so adopting always beats spending a rotation. The comparison is keyed on the refresh token, never the access token — a provider can rotate one and return the other unchanged (review finding on the ADR). Escalation is now restricted to proof of death: a definitive authorization rejection (invalid_grant / invalid_client) AND no newer token appearing across a bounded re-read backoff (default 500/1000/ 1500ms; the observed winner-write-to-loser-failure gap in production was 716ms). Transport failures — timeouts, 429s, 5xx, unmarked 400s — now rethrow as retryable instead of invalidating a healthy credential (second review finding). Two counters expose the outcome: frigg.auth.refresh_race_recovered and frigg.auth.refresh_race_lost. Four existing oauth-2 tests encoded the old any-error-invalidates contract and were updated to the new one; their original intents (no secret leak to delegates, no retry after a failed refresh) are preserved and still asserted. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Four defects found by the two-axis review, all verified against source before fixing: 1. The credential reload was dead code. reloadCredential required a nested `data` key, but every findCredentialById adapter returns the decrypted token fields spread at the top level. The reload always returned null, so adoption never ran. The tests hid it by mocking the nested shape. Fixed to accept both shapes; tests now mock the real adapter shape and keep one nested-shape case. 2. Rejection detection failed in production. FetchError sanitizes the response body outside dev, so a real invalid_grant carried no marker and was classified as transport — a dead credential would never invalidate. The status code now decides: 400/401 from the token endpoint is definitive (RFC 6749 §5.2), 429/5xx is always transport even when the body text mentions invalid_grant, and the word-bounded marker fallback remains only for statusless SDK errors. 3. The transport rethrow could leak the refresh request body (which carries client_secret in dev-stage FetchError messages). Transport failures now rethrow a fresh error carrying only the status code. 4. The silent-ack companion was missing from this PR even though the ADR scopes it into PR A. New checkIntegrationRunnable in backend-utils: ERROR now rejects the message so SQS retries and DLQs; DISABLED and IN_DELETION keep the intentional silent ack; FRIGG_LEGACY_ERROR_ACK=true is the kill switch. One worker test encoded the old ERROR-silent-ack behavior — the incident amplifier — and now asserts the new contract, plus a kill-switch case. Also: the recovered-race counter now fires only on a real recovery (adoption after a rejection), not on pre-refresh adoption. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ential-refresh ADR-031: Concurrent OAuth Credential Refresh Across Lambda Invocations
…straction Comments now carry only the non-obvious why; the narration that restated the PR body is gone. The dense promise chain in _refreshAuthOnce() splits into _runMarkedRefresh(), the ALS check gets a name (_isInsideRefreshFlow), the 401 branch names its condition (tokenReplacedWhileInFlight), and the refreshAuth() catch block delegates to _transportFailureError() and _adoptNewerCredentialWithBackoff(). No behavior change; all 111 touched tests pass. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The queue-worker change (ERROR throws instead of silently acking, with the FRIGG_LEGACY_ERROR_ACK kill switch) is independent of the requester refresh work and is the one default-on behavior change affecting every app — the ADR-027 exception a maintainer must ratify on its own terms. It will return as its own small PR; the implementation stays recoverable in this branch's history (0b28b4a and earlier). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…heck Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The fresh.data fallback guarded a shape no adapter produces — all three findCredentialById implementations spread the decrypted token fields at the top level — so it is gone, along with its speculative test. The result variable now says where the row comes from, and the doc states what a null return means to the requester. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Addresses the Graphite findings: _adoptNewerCredential no longer bumps _authGeneration (the slot in _refreshAuthOnce owns the bump, so one adoption bumps exactly once — the reload test now asserts through the slot and would fail on +2), and the access-token copy is a plain guard instead of a ??-fallback. The keep-old-token semantics stay: rejecting an adoption over a missing access_token would leave the recovery inert for modules that do not persist one, and access and refresh tokens are presented independently — a dead old access token costs one extra 401, which then refreshes with the adopted refresh token. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…lnerability-e5a747 fix(deps): bump stale security overrides and express/body-parser ranges
…ture app-definition key One registry maps each AWS architecture name (x86_64, arm64) to the Prisma engine it runs. resolveLambdaArchitecture() returns the name, the Prisma binary target and whether the app set the key, so an unset key keeps today's template. An unknown value throws. The schema key is an enum with no default: applyAppDefinitionDefaults writes schema defaults, and a default here would add fields to every app's template (contract rule 4). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…ents Both schemas add linux-arm64-openssl-3.0.x to binaryTargets, so the clients published with core carry the engine an arm64 (Graviton) Lambda loads next to the rhel one x86_64 uses. A *.db.test.js suite checks each generated client for both engines, so the db job (the one CI job that runs prisma:generate) fails before a publish could ship a client without them. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…hitectures from the app definition Only an explicit `architecture` adds the two fields; an unset key keeps the template as it was, apart from the layer description, which no longer names the rhel engine because the engine now follows the architecture. osls applies provider.architecture to every function, so no function gets a field of its own. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… architecture buildPrismaLayer and ensurePrismaLayerExists take the Prisma binary target, resolved from the app definition's architecture. The build keeps only that target's engines: the darwin, debian, musl and windows patterns stay, and every libquery_engine-*, query-engine-* and schema-engine-* file whose name lacks the target goes. Verification is per client: each generated client must hold the target query engine and no other Linux engine. A missing engine names the core version and the target, since it means the installed core was generated before that target was added to its schemas. The PRISMA_CLI_BINARY_TARGETS on the layer's npm install is gone. The layer installs only @prisma/client, which has no engine postinstall, so the variable never did anything. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…and versions The .build-complete marker is now JSON recording the Prisma binary target, the generated clients, and the core and @prisma/client versions the layer was built from. Every early return (marker present, marker appearing during a concurrent build, final check after the wait) reuses the layer only when those match the requested inputs; anything else rebuilds. A legacy plain-text marker recorded none of them, so it rebuilds once. A concurrent build that finishes for other inputs no longer makes the waiter sit out the 60 s timeout: it rebuilds as soon as the marker appears. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…the deployed architecture `prisma migrate` needs two engines in node_modules/@prisma/engines/: schema-engine-<target> and libquery_engine-<target>.so.node. The @prisma/engines postinstall puts only the deploy host's own pair there, so the composer now runs that postinstall with PRISMA_CLI_BINARY_TARGETS=<target> before packaging, whenever the migration worker is built, and fails packaging if either engine is still missing. The postinstall adds only missing targets, so the host's engines stay. The worker and router packages exclude every other engine: the host platforms (darwin, debian, musl, windows) and the other Lambda architecture's target, both read from the registry. The worker pins PRISMA_SCHEMA_ENGINE_BINARY and PRISMA_QUERY_ENGINE_LIBRARY to the shipped files, so the CLI neither downloads into the read-only /var/task at cold start nor picks another file. prisma-runner spreads process.env into the CLI, so core needs no change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…the deployed architecture With usePrismaLambdaLayer=false every function ships the generated clients, which carry an engine per binaryTarget. The base, integration and admin-script package configs now exclude the other architectures' engines and the host platforms', read from the registry, so a bundled function holds one engine. This also drops the debian copy x86_64 bundles shipped until now. With the layer on, Prisma is excluded as a whole, so those configs do not change. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Explains what an explicit architecture changes, what an unset key keeps, and what to check before flipping an app to arm64: the core release, CDN access on the deploy host, native addons, the mixed-architecture window during the stack update, and the layer rebuild. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The foreign-engine package excludes were bare platform globs such as **/*windows* and **/*darwin*, so they also dropped runtime dependencies like is-windows. Each pattern now starts with a Prisma engine file prefix. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…dules Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…iles fetched Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…nces Cryptor instances now share one module-level KMS data-key source that owns a single KMSClient and a TTL/LRU cache keyed by the encrypted data key. The cache holds in-flight promises, so concurrent decrypts of one key make a single Decrypt call, and failed calls are evicted. Encrypt seeds the cache with the key it generates. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
FieldEncryptionService.encryptUpsert reuses create's ciphertext for update where the same field path holds the same value, so the upsert hook makes one call and encryptFields keeps its original signature. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…r test cases The three function builders each resolved the architecture and picked the engine excludes for a layer-off bundle, and each copied the same test cases. bundledPrismaEngineExcludes() now holds that rule in the registry, and the builder suites share one set of cases. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… parser Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…viton) Records why the architecture key has no default, why one registry owns the Prisma binary targets, and why the layer, the migration worker and layer-off bundles each keep only the deployed target's engines. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…viton) Records why the architecture key has no default, why one registry owns the Prisma binary targets, and why the layer, the migration worker and layer-off bundles each keep only the deployed target's engines. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…gine fetch With PRISMA_SCHEMA_ENGINE_BINARY or PRISMA_QUERY_ENGINE_LIBRARY set on the deploy host, the @prisma/engines postinstall skips those engines instead of downloading the target's, and packaging fails blaming the CDN. The README now states the real version rule: the worker's CLI and its engines both come from the app's own node_modules. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Correct the layer cache match set, the migration engine version rule, the pre-change x86_64 worker condition and the Prisma 6 driver-adapter status; list every template change; cite ADR-051's contract test; add the fetch-at-layer-build alternative. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…gage
When Definition.telemetry.exporter is unset, honor OTEL_EXPORTER_OTLP_ENDPOINT
by resolving to { type: 'otlp' } (explicit exporter still wins). Stamp
frigg.integration_id / frigg.user_id / integration_type / frigg.version from
active baggage/ALS onto exported spans only — never metric labels (ADR-011).
Co-authored-by: Sean Matthews <sean.matthews@lefthook.co>
When Definition.telemetry.exporter is omitted, register OTEL_EXPORTER_OTLP_* Lambda passthroughs if the deploy shell has a non-empty endpoint or the key is SSM-offloaded. Explicit exporter.type still wins. Isolate OTEL endpoint env in stage-default telemetry-config and environment-builder tests. Co-authored-by: Sean Matthews <sean.matthews@lefthook.co>
…n-attrs-7af3 fix(telemetry): zero-code OTLP env activation + span attribution from baggage (D-SCHEM-3)
skipEsbuild function packages excluded node_modules/**/node_modules/**. npm nests a package only when the root copy cannot satisfy its dependant, so the exclusion made nested packages resolve the hoisted version, and dropped a package entirely when its root copy is a dev dependency. Two apps have crashed at Lambda init this way (undici under jsforce; qs's side-channel), each time with the lambda.keepNestedNodeModules opt-in available but unset. Keep nested node_modules by default, still excluding nested Frigg, AWS SDK and, with the layer, Prisma copies. lambda.keepNestedNodeModules: false restores the old exclusion. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
perf(core): cache KMS data keys in Cryptor; ship nested node_modules by default
…re-arm64 docs(adr): ADR-068 Lambda architecture selection (arm64 / Graviton)
feat(devtools): deploy Lambdas on arm64 (Graviton) via an architecture app-definition key
|




Version
Published prerelease version:
v2.0.0-next.127Changelog
🐛 Bug Fix
@friggframework/admin-scripts,@friggframework/core,@friggframework/devtools@friggframework/admin-scripts,@friggframework/core,@friggframework/devtools,@friggframework/eslint-config,@friggframework/prettier-config,@friggframework/schemas,@friggframework/serverless-plugin,@friggframework/test,@friggframework/uiAuthors: 1