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 | |
|---|---|---|---|---|---|
| 23374074 | Triggered | Generic Password | 5c1d197 | .claude/skills/frigg-canary-test/assets/harness/docker-compose.yml | View secret |
| 35818272 | Triggered | Username Password | 769f999 | packages/core/modules/requester/oauth-2.test.js | View secret |
| 35818272 | Triggered | Username Password | 7a06d54 | packages/core/modules/requester/oauth-2.test.js | View secret |
| 37580184 | 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 |
| 37580186 | Triggered | JSON Web Token | 48b1ba4 | packages/core/logs/fixtures/secrets.js | View secret |
| 37580183 | 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 |
| 37580185 | Triggered | Generic High Entropy Secret | 48b1ba4 | packages/core/logs/fixtures/secrets.js | View secret |
| 37580187 | Triggered | Generic High Entropy Secret | 48b1ba4 | packages/core/logs/fixtures/secrets.js | View secret |
| 37580179 | Triggered | Generic High Entropy Secret | 48b1ba4 | packages/core/logs/fixtures/secrets.js | View secret |
| 37582161 | Triggered | Generic High Entropy Secret | b23b0ea | packages/core/logs/fixtures/vectors.js | View secret |
| 37582810 | Triggered | Bearer Token | c1d8958 | packages/core/logs/record.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.
|
Adversarial review found that marking a row ERROR on ON_CREATE failure interacted badly with reconcileAuthStatus's reuse-time healing (#613): a retry with valid-but-unrelated-to-the-failure credentials would silently flip ERROR -> ENABLED without setup ever having run, recreating the exact 'ENABLED with no webhooks' zombie state born-PROCESSING exists to eliminate. reconcileAuthStatus's heal only special-cases ERROR, so leaving the row at its born PROCESSING value instead (recording an 'ON_CREATE Failed' message for visibility) is inert to that healing — _reuseExisting needs no changes, so #613's reuse/race-backstop tests are untouched and verified still green. Full retry-safe setup re-run still needs remote webhook idempotency (P7); this closes the false-heal gap without it.
The update command's result was discarded, so a rejected write (n:0, writeErrors) fell through to the read-back, which returned the unchanged document, and patchConfig reported success with the patch keys silently missing — the exact silent-config-write-failure class this feature exists to eliminate, on the one adapter that doesn't do an atomic single-round-trip write. Also gives a correct 'not found' error for a nonexistent id instead of the misleading 'consistency issue' message.
Fully-synchronous read-then-write meant the 'concurrent disjoint patches' regression test passed vacuously — no interleaving was possible, so it would pass against any implementation, correct or not (verified by mutation: reintroducing a stale-snapshot read-modify-write did not fail it). The double now yields between existence-check and merge and re-reads the record at write time rather than off a pre-yield snapshot, mirroring the atomicity guarantee every real adapter gets from the database (postgres row lock, mongo/documentdb atomic single-document ops). Re-verified by mutation: reintroducing the stale-snapshot bug now fails the test as expected.
Second-pass review found the ON_CREATE-failure fix (485962e) only closed the demotion path inside CreateIntegration's own catch block. Two other, pre-existing status writers can ALSO demote a still-PROCESSING row to ERROR while ON_CREATE is running, reopening the exact false-heal hole: reconcileAuthStatus's own '!authPassed -> ERROR' branch (fires whenever _reuseExisting's testAuth check fails, unconditionally on current status), and receiveNotification's CREDENTIAL_INVALIDATED handler (fired by Module.markCredentialsInvalid on a 401 encountered mid-setup — the single most common real ON_CREATE failure mode, e.g. a webhook API call made before an OpenPhone key has finished propagating). Both now skip the ERROR demotion when status is already PROCESSING, consistent with the born-PROCESSING invariant: a row that never completed creation stays honestly incomplete instead of becoming ERROR and getting silently healed to ENABLED on the next successful auth check.
…ndler throws
delete-integration-for-user.js deleted the integration row only after
integrationInstance.send('ON_DELETE') resolved. Any vendor-side webhook
deregistration failure inside onDelete (network error, expired token,
already-revoked webhook) aborted the entire uninstall after partial
cleanup, leaving the row alive with vendor webhooks in an unknown state
and no way for the customer to retry (the app still shows "installed").
onDelete failures are now caught, logged, and the deletion proceeds.
Cleanup is inherently best-effort; letting it block row deletion is
worse than leaving orphaned webhooks for out-of-band reconciliation.
DummyIntegration's send() now dispatches ON_DELETE to onDelete(), matching
its existing ON_UPDATE dispatch, so test doubles can exercise this path.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ging The catch block added to swallow onDelete failures read error.message unconditionally. An onDelete that rejects with null/undefined (or any non-Error value) made that line itself throw, escaping the catch and skipping deleteIntegrationById — the exact failure mode the fix was meant to prevent. Found by @chatgpt-codex-connector's review on #615. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…lure fix(core): don't abort integration deletion when ON_DELETE handler throws
Note that the merge is top-level only and replaces each key's value wholesale (nested objects are overwritten as a block, not deep-merged), and point deletions/null writes at UpdateIntegrationConfig.
Reverts the status-lifecycle half of this PR: born-PROCESSING schema defaults, the ON_CREATE-failure status handling, the default onCreate persistStatus change, and the PROCESSING guards in reconcileAuthStatus and receiveNotification (the R746 note and R768 guard block). Integrations are ENABLED by default again. Post-creation setup that needs follow-up is expressed by the integration opting into NEEDS_CONFIG rather than a framework-imposed PROCESSING birth state. The atomic config-write half of the PR (patchConfig/updateConfig, the three adapters, use-cases, shared patch validator, and command facade) is kept unchanged. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Integrations are born IN_CREATION (schema default + documentdb adapter). The default onCreate validates config via the now-fixed validateConfig — which reads the react-jsonschema-form jsonSchema.required shape, records a warning per missing field, and moves the integration to NEEDS_CONFIG — and enables it only when nothing is missing. A throwing onCreate leaves the row IN_CREATION: a visibly incomplete create rather than a healthy looking one. Children can override onCreate. Deletion marks IN_DELETION before teardown, in the delete use-case, so it is set before any child onDelete teardown (which typically calls super at the end). A teardown that throws before the row is deleted leaves a visibly half-deleted integration. The queue worker discards events for IN_DELETION integrations so incoming webhooks don't race teardown. validateConfig previously threw "configOptions is not iterable" against the real getConfigOptions() shape and was effectively dead code; it now works and returns whether config is still needed. Postgres migrations are split (enum ADD VALUE, then SET DEFAULT) because a new enum value cannot be used in the same transaction that adds it. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…status-lifecycle # Conflicts: # packages/core/integrations/use-cases/delete-integration-for-user.js
Three gaps surfaced by adversarial review of ce91780: - _findDuplicate treated an IN_DELETION row (mid-teardown, or a zombie left by a failed deleteIntegrationById) as a live duplicate, so a reinstall attempt got handed a row nothing ever moves out of IN_DELETION — a fake "success" the queue worker then discards forever. IN_DELETION rows are now excluded from duplicate matching; a fresh integration is created instead, and the zombie is left for out-of-band reconciliation, matching how a failed onDelete is already handled. - _reuseExisting silently returned a still-IN_CREATION row (ON_CREATE never finished) through the same path as a healthy reused integration. Not auto-healed — safely retrying setup needs idempotent remote-resource creation first, a separate, larger change — but now logged loudly so it's diagnosable instead of silently masquerading as success. - Default onUpdate called validateConfig() but never persisted the submitted config, and never had a path back to ENABLED once NEEDS_CONFIG — before this PR that call always threw ("not iterable"), so this bug was masked by a louder one. Now merges the submitted config in via patchConfig and moves NEEDS_CONFIG to ENABLED once nothing required is still missing. Other statuses are left alone; a config edit shouldn't silently un-pause a DISABLED integration or heal ERROR/IN_CREATION/ IN_DELETION on its own. Also: reordered both Prisma enum declarations so IN_CREATION/IN_DELETION are declared last, matching the physical order Postgres actually produces (ALTER TYPE ADD VALUE always appends) — avoids a schema/DB drift warning on the next `prisma migrate dev`. Added rollback and deploy-ordering notes to the two new migrations (unknown-enum-value read failures on rollback; transient enum-missing errors if code deploys before migrations run). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
queryMappings reads through $queryRawUnsafe and $runCommandRaw, which bypass the Prisma encryption extension. When a custom encryption.schema once encrypted a nested path such as mapping.apiSecret and the app later opted it out, older rows still match (their mapping is an object), but the path came back as ciphertext, while findMappingsByIntegration and the DocumentDB adapter return it plain. decryptQueriedMappings runs the returned rows through the extension's FieldEncryptionService, reading every IntegrationMapping field the schema lists, opted-out paths included. It is built once per process together with the write-side check, from the same encryption config, so a stage without keys still warns once. It hands the rows back untouched while encryption is off, or when the schema lists nothing besides mapping, which a queried row always holds as an object. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Adds a scenario to the parity suite for each adapter: a row written while a custom schema encrypted mapping.apiSecret, then a row written after the app opted that path out. queryMappings must return both plain and equal to findMappingsByIntegration, match the path by value only on the row written after the opt-out, and keep an omitted path out of the rows. Each leg's setUp takes an optional cryptor: the MongoDB and PostgreSQL legs extend their client with the encryption extension, the DocumentDB leg injects it into its encryption service. Encryption is switched on after setUp, because a generated Prisma client may load a .env file when it is constructed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
queryMappings now decrypts on read like findMappingsByIntegration. A nested mapping path written encrypted before its opt-out reads plain, but conditions and ordering on it still see the ciphertext until the row is written again. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
How to use this.logger in integrations and API modules: which ids come for free per entry point, messages and eventNames, levels, the error rule, late ids with child(), what not to log, levels per app, queries, tests with a memory sink, migrating console calls, and a full example. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… start serverless-offline copies only AWS_* shell variables into handlers, so FRIGG_LOG_LEVEL=warn frigg start had no effect and IS_OFFLINE forced DEBUG. In local mode the composer now puts the shell (or .env) value into the function env, where it wins over logging.level, and emits logging.level INFO too. Deploys never read the shell value. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… cost - One table of record-contract keys (logs/contract-keys.js) for record.js and the denylist protection; one isError, deepFreeze and isPlainObject. - The error field is serialized once; bindings are copied once; the scope is serialized once per invocation (WeakMap on the ALS store); the record is stringified once unless a field is dropped. - Keys are normalized once per walk, with one suffix regex. - Call sites pass only the extra request details under invocation, so record.js merges with a plain spread. - Dead code removed: the scrubTokens segment mode, a duplicated sink retry block, a try/catch that could not catch. A scoped info record with a child binding: ~37 µs → ~18 µs. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A slow usage DB write could use up the remaining Lambda time and skip the telemetry and sink flush. The deadline is now fixed at flush start and the usage rollup runs next to the bounded flush. droppedKeys lists only real conflicts, not a binding that repeats the scope value. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
EncryptionLogger maps TRACE to DEBUG and FATAL to ERROR. Every denylist registration warns once per skipped record-contract key (logs/denied-keys.js), also on the Prisma registry path. User update errors carry the user id. The websocket router requires the real repository factory path, so the Lambda loads. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…cess is gone The worker record for that failure now carries error.code, like the process use cases already do. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Queue messages may carry integrationId and processId at the top level or under data. Webhook and Definition.routes dispatches with an :integrationId route param put that id in the logger scope only (never the telemetry context, which drives usage writes). A hydrated id in the store wins over a logger-only id. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
feat(core): add queryMappings to IntegrationMappingRepository
feat(core): tell queue handlers their last delivery attempt
# Conflicts: # packages/core/CLAUDE.md # packages/core/core/Worker.js
A call-site invocation merged into the scope invocation was never a trim candidate, so many request header or query names could push frigg.handler.invoked/failed past the cap. Each merged detail key is now trimmed like any call-site field (listed as invocation.<key>), and the scope's invocation summary stays. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
feat(core): structured logging (ADR-048)
A FetchError with no response named only the cause code or name, for example "GET <url> FetchError". Adopters that detect a Zoho 204 empty body by "invalid json response body" in error.message stopped matching after the node-fetch wrap in next.113, so a no-match search threw. The message now appends the scrubbed cause message. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…equester Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The previous change copied the scrubbed node-fetch message into the FetchError message. For a body that is not JSON, that message quotes the start of the response body, and scrubString removes only credentials, so personal data could reach error.message. The message now adds the fixed phrase "invalid json response body" for an invalid-json cause and keeps the label alone for other causes. ADR-048 rule 8 names the phrase. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…e-message fix(core): FetchError message drops the cause reason, so Zoho no-match searches throw
Conflicts in app-handler-helpers.js, module.js and process-authorization-callback.js. Took next's ADR-048 structured logging where both sides changed the same lines (flushDebugLog no longer exists in those files), and kept the PR's [Frigg][OAuth] step logs. Dropped the PR's per-request console.log middleware in createApp: it printed the raw req.path (key-like segments leak) and failed the frigg.http no-console tests on next. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…thenticate-user Replace the console.* calls added by this PR (and the neighbouring ones in process-authorization-callback.js) with getLogger records. Steps are DEBUG; integration restores and entity repoints are INFO; a failed best-effort restore is ERROR. Ids and key names go in fields, token and header values never do. The testAuth-false path no longer logs before it throws, because the boundary logs the error. Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
…l-issue fix(logging): enhance debug logging for auth headers
$runCommandRaw serialises a JS Date as an ISO string, so the Integration
document ended up with a string updatedAt and every later Prisma read failed
with P2023. Use extended JSON ({ $date }) in patchIntegrationConfig and
ProcessRepositoryMongo.applyProcessUpdate, which had the same pattern.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…t-date fix(core): store patchConfig updatedAt as a BSON date in Mongo
… helpers Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…apping feat(core): add insert-only createMapping and IntegrationBase mapping helpers
…in queues offline
- schemas: app-definition accepts adminScripts, reports and admin
(enableScheduling, includeBuiltinReports), which core and devtools already read
- devtools: AdminScriptBuilder registers AdminScriptQueue and ReportQueue under
custom, so the serverless plugin creates them in LocalStack on frigg start
- serverless-plugin: offline, env vars holding { Ref: <created queue> } (provider
and function scope) are set to the LocalStack URL, e.g. ADMIN_SCRIPT_QUEUE_URL
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…and-local-queue fix(admin-scripts): allow admin keys in the app schema and create admin queues offline
|




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