Skip to content

feat(credential-groups): collect API keys from invited people - #7348

Closed
TheodoreSpeaks wants to merge 1 commit into
stagingfrom
feat/api-key-cred-group
Closed

feat(credential-groups): collect API keys from invited people#7348
TheodoreSpeaks wants to merge 1 commit into
stagingfrom
feat/api-key-cred-group

Conversation

@TheodoreSpeaks

Copy link
Copy Markdown
Collaborator

Summary

  • Credential Groups can now collect an API key from each invited person, not just an OAuth grant
  • New managed_api_key credential type + credential.encrypted_api_key, holding a versioned envelope of the provider's declared fields, so a service needing several values (AWS: key id, secret, region) needs no separate shape
  • New get_api_key block operation, authorized through the same resource policy as managed OAuth, so every existing API-key block works unchanged
  • Providers: AWS, Fireflies, Grain, Granola — each a credential an ordinary user can create for themselves
  • Includes two earlier commits from feat/all-oauth-cred-group (21 OAuth providers), which this work is built on

Notes

  • Sealed with encryptSecret, never encryptApiKey: the resolved-secret trace registry decrypts with decryptSecret, and encryptApiKey silently stores plaintext when its key is unset
  • One trace-registry catalog entry per secret field — a credential with two secrets needs two, or one goes out unredacted. A run without a registry fails the block rather than emitting a secret nothing can redact
  • Fields marked secret: false (a region) are stored but never catalogued; substituting a short recurring string would corrupt unrelated log output
  • Gong was excluded: its access keys are workspace-scoped and admin-only, so they are the company's credential rather than a person's
  • Migration 0313 compares enum values directly in the index predicate — an enum-to-text cast is STABLE, not IMMUTABLE, and Postgres rejects it there

Type of Change

  • New feature

Testing

Tested manually against the enrollment page. bun run lint, check:api-validation, check:migrations origin/staging, all 37 audits, and the full test suite pass. AWS SigV4 signing is pinned against AWS's published derivation vector plus a golden signature, both mutation-checked. Not yet verified end to end: a live STS round trip and the redaction check against a running workflow.

Checklist

  • Code follows project style guidelines
  • Self-reviewed my changes
  • Tests added/updated and passing
  • No new warnings introduced
  • I confirm that I have read and agree to the terms outlined in the Contributor License Agreement (CLA)

@vercel

vercel Bot commented Sep 1, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated
docs Ready Ready Preview Sep 1, 2026 7:50am UTC

Request Review

@greptile-apps

greptile-apps Bot commented Sep 1, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

The PR extends Credential Groups to collect, encrypt, authorize, and resolve invited users' API-key credentials in addition to OAuth credentials.

  • Adds public enrollment and provider-specific verification for AWS, Fireflies, Grain, and Granola.
  • Adds managed API-key persistence, database constraints, application authorization, and workflow resolution with trace-redaction provenance.
  • Updates the settings and enrollment interfaces to configure and connect API-key providers.
  • Adds the get_api_key Credential Group block operation.

Confidence Score: 4/5

The PR appears safe to merge after correcting the non-blocking Get API Key documentation so workflow authors receive valid provider and field examples.

Authorization, persistence, migration, and redaction paths preserve the relevant boundaries, but the new block guidance points users to an unsupported provider and nonexistent output keys.

Files Needing Attention: apps/sim/blocks/blocks/credential-group.ts

Important Files Changed

Filename Overview
apps/sim/lib/credential-groups/api-key.ts Transactionally persists verified API-key credentials with enrollment and option locking.
apps/sim/lib/credentials/managed-api-key-resolution.ts Resolves active managed API keys after workspace, entitlement, type, and provider validation.
apps/sim/executor/handlers/credential-group/credential-group-handler.ts Adds authorized get_api_key execution and registers each secret field for trace redaction before returning it.
apps/sim/app/api/credential-groups/enroll/[token]/api-key/[optionId]/route.ts Adds the rate-limited public route for validating and submitting invited users' API-key fields.
packages/db/migrations/0313_credential_managed_api_key_storage.sql Adds encrypted managed-key storage, consistency constraints, and widened option uniqueness.
apps/sim/blocks/blocks/credential-group.ts Adds the get_api_key block surface, but its help text names unsupported Gong fields instead of the supported AWS field contract.

Sequence Diagram

sequenceDiagram
  participant Invitee
  participant Enrollment as Enrollment API
  participant Provider
  participant DB
  participant Workflow
  participant Policy
  participant Registry as Redaction Registry
  Invitee->>Enrollment: Submit provider fields
  Enrollment->>Provider: Verify credentials
  Provider-->>Enrollment: Verified identity
  Enrollment->>DB: Encrypt and persist managed_api_key
  Workflow->>Policy: Authorize group and credential access
  Policy-->>Workflow: Allowed
  Workflow->>DB: Resolve encrypted credential
  Workflow->>Registry: Register secret provenance
  Registry-->>Workflow: Accepted
  Workflow-->>Workflow: Return fields to block
Loading

Reviews (1): Last reviewed commit: "feat(credential-groups): collect API key..." | Re-trigger Greptile

- "List Credentials" returns active, usable credentials only. Reconnect-needed and revoked credentials are excluded.
- Use "List People" to inspect invitation and connection progress without exposing credential secrets.
- "Send Invite" sends one email. Use a loop when invitations should come from a dynamic list.
- "Get API Key" returns one enrolled person's credential values under "fields" — reference them as <block.fields.apiKey>, or <block.fields.accessKey> and <block.fields.accessKeySecret> for Gong. Pass the credentialId from "List Credentials".

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Documentation names unsupported key fields

The new operation guidance names Gong, which is not registered as an API-key provider, and recommends accessKey and accessKeySecret, while the supported AWS provider consumes accessKeyId and secretAccessKey. Workflow authors following these examples will reference undefined output fields.

Credential Groups could only collect managed OAuth grants, so services whose
per-person credential is an API key — meeting recorders, AWS — had no way in.
An option can now ask each invited person for a key instead of a sign-in.

Storage is one new credential type and one new column. `credential.encryptedApiKey`
holds a versioned envelope of the provider's declared fields, so a service needing
more than one value (AWS: access key id, secret, region) needs no separate shape.
It is sealed with `encryptSecret`, never `encryptApiKey`: the resolved-secret trace
registry decrypts with `decryptSecret`, and `encryptApiKey` silently stores
plaintext when its key is unset.

Consumption is a new `get_api_key` block operation rather than a credential-id
socket, so every existing API-key block works unchanged. It authorizes through the
same resource policy as managed OAuth — actors may use their own credential, other
enrollments need a workflow access grant — then registers one catalog entry per
secret field with the run's trace registry before returning. A run without a
registry fails the block rather than emitting a secret nothing can redact.

Fields declared `secret: false` (a region) are stored but never catalogued:
substituting a short recurring string would corrupt unrelated log output. Secret
fields are held to the 8-character floor below which the matcher deliberately
never redacts.

Providers are AWS, Fireflies, Grain and Granola — each one a credential an ordinary
user can create for themselves. Gong was excluded on that test: its access keys are
workspace-scoped and admin-only, so they are the company's credential, not a
person's.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JcW9yNMBT7PkaB2LYJgEaM

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

14 issues found across 45 files

Confidence score: 2/5

  • apps/sim/lib/credentials/managed-api-key.ts can return a stored secret without a provenance entry, allowing the get_api_key block to expose data that has not been validated. Ensure every returned secret has matching provenance or reject the envelope.
  • apps/sim/lib/credential-groups/service.ts leaves existing managed_api_key credentials active after an API-key option is removed or its group is disabled, so revoked access may continue working. Include managed_api_key rows in the invalidation update.
  • apps/sim/lib/credentials/access.ts admits newly enrolled managed_api_key credentials before requireOrdinaryCredentialType throws, breaking list, detail, lookup, and membership endpoints. Update the query/type handling so these endpoints consistently exclude or support managed credentials.
  • apps/sim/lib/credential-groups/api-key-providers/grain.ts and aws.ts can reject valid Grain or GovCloud/China enrollments because verification uses incorrect request details. Match Grain’s required method and API-version header, and derive AWS’s STS host and signing region from the selected region.
Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="apps/sim/blocks/blocks/credential-group.ts">

<violation number="1" location="apps/sim/blocks/blocks/credential-group.ts:119">
P2: The documented field names do not match any supported provider, so workflows following this guidance will reference undefined fields. Update both this guidance and the `fields` description to document AWS as `accessKeyId`/`secretAccessKey` and the other providers as `apiKey`; Gong is not supported.</violation>
</file>

<file name="apps/sim/lib/credentials/managed-api-key.ts">

<violation number="1" location="apps/sim/lib/credentials/managed-api-key.ts:151">
P1: When a stored envelope omits a declared secret or contains an undeclared secret field, this loop returns the value without a provenance entry. The `get_api_key` block then marks provenance complete and can expose that credential in logs or model-visible output; validate the decrypted fields against `declaredFields` before building provenance.</violation>
</file>

<file name="apps/sim/lib/credential-groups/api-key-providers/grain.ts">

<violation number="1" location="apps/sim/lib/credential-groups/api-key-providers/grain.ts:21">
P1: When a valid Grain key is enrolled, this probe uses the wrong method and omits Grain's required API-version header, so Grain rejects the verification before enrollment succeeds. Match the existing Grain `/teams` request contract by using `POST` and sending `Content-Type` plus `Public-Api-Version: '2025-10-31'`.</violation>
</file>

<file name="apps/sim/lib/credential-groups/service.ts">

<violation number="1" location="apps/sim/lib/credential-groups/service.ts:292">
P1: When an API-key option is removed or its group is disabled, existing `managed_api_key` credentials remain active because the invalidation update targets only OAuth rows. Include `managed_api_key` in that status update so existing workflow references stop resolving the key.</violation>
</file>

<file name="apps/sim/lib/credential-groups/api-key-providers/aws.ts">

<violation number="1" location="apps/sim/lib/credential-groups/api-key-providers/aws.ts:19">
P2: When an invitee supplies a valid GovCloud or China region, this verifier still calls commercial `sts.amazonaws.com` and signs `us-east-1`, so AWS rejects the credential. Select the STS host and signing region from `fields.region`, or reject unsupported partitions before verification.</violation>

<violation number="2" location="apps/sim/lib/credential-groups/api-key-providers/aws.ts:86">
P2: After receiving STS's response, this path neither consumes nor cancels its body, leaving the undrained response to tie up the server's HTTP connection until cleanup. Cancel the body before status branching when only the status is needed.</violation>

<violation number="3" location="apps/sim/lib/credential-groups/api-key-providers/aws.ts:86">
P2: When STS stalls after the connection attempt, `verify` has no deadline and the enrollment request waits on the runtime's fetch timeout. Pass a bounded `AbortSignal.timeout(...)` to this request and map timeout to the existing retryable error.</violation>
</file>

<file name="apps/sim/lib/credentials/access.ts">

<violation number="1" location="apps/sim/lib/credentials/access.ts:37">
P1: When a newly enrolled `managed_api_key` reaches an ordinary credential query, the query admits it but `requireOrdinaryCredentialType` now throws. The list, detail, lookup, and membership endpoints therefore fail instead of omitting managed credentials; update all ordinary filters to exclude `MANAGED_CREDENTIAL_TYPES`.</violation>
</file>

<file name="apps/sim/lib/credential-groups/api-key.ts">

<violation number="1" location="apps/sim/lib/credential-groups/api-key.ts:69">
P2: When email delivery fails after this request resolves its context, the enrollment becomes `delivery_failed`, but this check still accepts it and persists the API key. Reject `delivery_failed` here so failed invitations cannot be enrolled during that race.</violation>
</file>

<file name="packages/db/migrations/meta/_journal.json">

<violation number="1" location="packages/db/migrations/meta/_journal.json:2204">
P2: These journal entries advance the migration history without matching schema snapshots for the enum, column, constraint, and index changes. Add the generated `0315`/`0316` migration snapshots so the next schema migration starts from the deployed managed-API-key state instead of diffing from `0314`.\n\n(Based on your team's feedback about deployable, synchronized migration artifacts.)</violation>
</file>

<file name="apps/sim/lib/credential-groups/api-key-providers/fireflies.ts">

<violation number="1" location="apps/sim/lib/credential-groups/api-key-providers/fireflies.ts:24">
P2: If Fireflies accepts the connection but stalls, this enrollment request can remain occupied for the transport default instead of failing promptly. Add a finite `AbortSignal.timeout(...)` to the provider request and let the existing transport-error handler return the retryable verification message.</violation>

<violation number="2" location="apps/sim/lib/credential-groups/api-key-providers/fireflies.ts:35">
P2: When Fireflies returns a rejected or otherwise non-2xx response, cancel the response body before throwing so invalid-key attempts do not retain the upstream connection. Apply the cancellation to both status branches.</violation>

<violation number="3" location="apps/sim/lib/credential-groups/api-key-providers/fireflies.ts:50">
P2: When Fireflies returns a malformed successful payload with a non-string `email` or `name`, this verifier throws a raw `TypeError` and the enrollment endpoint returns 500. Validate the response field types before normalizing them and convert malformed payloads to `CredentialGroupApiKeyVerificationError`.</violation>
</file>

<file name="packages/db/migrations/0316_credential_managed_api_key_storage.sql">

<violation number="1" location="packages/db/migrations/0316_credential_managed_api_key_storage.sql:21">
P2: When a concurrent index build is cancelled or fails, the migration runner retries this file from the top, but this constraint creation is not idempotent. Wrap the constraint creation in a duplicate-object guard so the replay can reach the index rebuild.</violation>
</file>

Heads up: you’re close to your included review allowance. Set a flex budget so reviews don’t pause.

Re-trigger cubic

throw new ManagedApiKeyFormatError('Invalid managed API key envelope')
}

const secretFieldIds = new Set(

@cubic-dev-ai cubic-dev-ai Bot Sep 1, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: When a stored envelope omits a declared secret or contains an undeclared secret field, this loop returns the value without a provenance entry. The get_api_key block then marks provenance complete and can expose that credential in logs or model-visible output; validate the decrypted fields against declaredFields before building provenance.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/sim/lib/credentials/managed-api-key.ts, line 151:

<comment>When a stored envelope omits a declared secret or contains an undeclared secret field, this loop returns the value without a provenance entry. The `get_api_key` block then marks provenance complete and can expose that credential in logs or model-visible output; validate the decrypted fields against `declaredFields` before building provenance.</comment>

<file context>
@@ -0,0 +1,162 @@
+    throw new ManagedApiKeyFormatError('Invalid managed API key envelope')
+  }
+
+  const secretFieldIds = new Set(
+    declaredFields.filter((field) => field.secret).map((field) => field.id)
+  )
</file context>
Fix with cubic

let response: Response
try {
response = await fetch(GRAIN_TEAMS_URL, {
method: 'GET',

@cubic-dev-ai cubic-dev-ai Bot Sep 1, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: When a valid Grain key is enrolled, this probe uses the wrong method and omits Grain's required API-version header, so Grain rejects the verification before enrollment succeeds. Match the existing Grain /teams request contract by using POST and sending Content-Type plus Public-Api-Version: '2025-10-31'.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/sim/lib/credential-groups/api-key-providers/grain.ts, line 21:

<comment>When a valid Grain key is enrolled, this probe uses the wrong method and omits Grain's required API-version header, so Grain rejects the verification before enrollment succeeds. Match the existing Grain `/teams` request contract by using `POST` and sending `Content-Type` plus `Public-Api-Version: '2025-10-31'`.</comment>

<file context>
@@ -0,0 +1,45 @@
+    let response: Response
+    try {
+      response = await fetch(GRAIN_TEAMS_URL, {
+        method: 'GET',
+        headers: { Authorization: `Bearer ${apiKey}` },
+      })
</file context>
Fix with cubic

const invalidatedOptionIds = existing.options
.filter((option) => {
const next = nextOptionById.get(option.id)
if (!next || body.status === 'disabled') return true

@cubic-dev-ai cubic-dev-ai Bot Sep 1, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: When an API-key option is removed or its group is disabled, existing managed_api_key credentials remain active because the invalidation update targets only OAuth rows. Include managed_api_key in that status update so existing workflow references stop resolving the key.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/sim/lib/credential-groups/service.ts, line 292:

<comment>When an API-key option is removed or its group is disabled, existing `managed_api_key` credentials remain active because the invalidation update targets only OAuth rows. Include `managed_api_key` in that status update so existing workflow references stop resolving the key.</comment>

<file context>
@@ -262,12 +289,15 @@ export async function updateCredentialGroup(
     const invalidatedOptionIds = existing.options
       .filter((option) => {
         const next = nextOptionById.get(option.id)
+        if (!next || body.status === 'disabled') return true
+        // An API-key option carries no scope policy, so nothing about editing it can
+        // invalidate a key its owner already pasted. Only removal or disabling does.
</file context>
Fix with cubic

export function requireOrdinaryCredentialType(type: CredentialType): OrdinaryCredentialType {
if (type === 'managed_oauth') {
throw new Error('Managed OAuth credential reached an ordinary credential surface')
if (isManagedCredentialType(type)) {

@cubic-dev-ai cubic-dev-ai Bot Sep 1, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: When a newly enrolled managed_api_key reaches an ordinary credential query, the query admits it but requireOrdinaryCredentialType now throws. The list, detail, lookup, and membership endpoints therefore fail instead of omitting managed credentials; update all ordinary filters to exclude MANAGED_CREDENTIAL_TYPES.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/sim/lib/credentials/access.ts, line 37:

<comment>When a newly enrolled `managed_api_key` reaches an ordinary credential query, the query admits it but `requireOrdinaryCredentialType` now throws. The list, detail, lookup, and membership endpoints therefore fail instead of omitting managed credentials; update all ordinary filters to exclude `MANAGED_CREDENTIAL_TYPES`.</comment>

<file context>
@@ -12,12 +12,30 @@ type ActiveCredentialMember = typeof credentialMember.$inferSelect
 export function requireOrdinaryCredentialType(type: CredentialType): OrdinaryCredentialType {
-  if (type === 'managed_oauth') {
-    throw new Error('Managed OAuth credential reached an ordinary credential surface')
+  if (isManagedCredentialType(type)) {
+    throw new Error('Managed credential reached an ordinary credential surface')
   }
</file context>
Fix with cubic

- "List Credentials" returns active, usable credentials only. Reconnect-needed and revoked credentials are excluded.
- Use "List People" to inspect invitation and connection progress without exposing credential secrets.
- "Send Invite" sends one email. Use a loop when invitations should come from a dynamic list.
- "Get API Key" returns one enrolled person's credential values under "fields" — reference them as <block.fields.apiKey>, or <block.fields.accessKey> and <block.fields.accessKeySecret> for Gong. Pass the credentialId from "List Credentials".

@cubic-dev-ai cubic-dev-ai Bot Sep 1, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: The documented field names do not match any supported provider, so workflows following this guidance will reference undefined fields. Update both this guidance and the fields description to document AWS as accessKeyId/secretAccessKey and the other providers as apiKey; Gong is not supported.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/sim/blocks/blocks/credential-group.ts, line 119:

<comment>The documented field names do not match any supported provider, so workflows following this guidance will reference undefined fields. Update both this guidance and the `fields` description to document AWS as `accessKeyId`/`secretAccessKey` and the other providers as `apiKey`; Gong is not supported.</comment>

<file context>
@@ -109,6 +116,7 @@ export const CredentialGroupBlock: BlockConfig<CredentialGroupBlockOutput> = {
   - "List Credentials" returns active, usable credentials only. Reconnect-needed and revoked credentials are excluded.
   - Use "List People" to inspect invitation and connection progress without exposing credential secrets.
   - "Send Invite" sends one email. Use a loop when invitations should come from a dynamic list.
+  - "Get API Key" returns one enrolled person's credential values under "fields" — reference them as <block.fields.apiKey>, or <block.fields.accessKey> and <block.fields.accessKeySecret> for Gong. Pass the credentialId from "List Credentials".
   - "Get Invite Link" issues a fresh seven-day bearer link without sending email. It invalidates the previous link for that email, so treat the output as a secret.
   `,
</file context>
Fix with cubic

"breakpoints": true
},
{
"idx": 315,

@cubic-dev-ai cubic-dev-ai Bot Sep 1, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: These journal entries advance the migration history without matching schema snapshots for the enum, column, constraint, and index changes. Add the generated 0315/0316 migration snapshots so the next schema migration starts from the deployed managed-API-key state instead of diffing from 0314.\n\n

(Based on your team's feedback about deployable, synchronized migration artifacts.)

View Feedback

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/db/migrations/meta/_journal.json, line 2204:

<comment>These journal entries advance the migration history without matching schema snapshots for the enum, column, constraint, and index changes. Add the generated `0315`/`0316` migration snapshots so the next schema migration starts from the deployed managed-API-key state instead of diffing from `0314`.\n\n

(Based on your team's feedback about deployable, synchronized migration artifacts.) </comment>

<file context>
@@ -2199,6 +2199,20 @@
       "breakpoints": true
+    },
+    {
+      "idx": 315,
+      "version": "7",
+      "when": 1788208210301,
</file context>
Fix with cubic

)
}

if (response.status === 401 || response.status === 403) {

@cubic-dev-ai cubic-dev-ai Bot Sep 1, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: When Fireflies returns a rejected or otherwise non-2xx response, cancel the response body before throwing so invalid-key attempts do not retain the upstream connection. Apply the cancellation to both status branches.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/sim/lib/credential-groups/api-key-providers/fireflies.ts, line 35:

<comment>When Fireflies returns a rejected or otherwise non-2xx response, cancel the response body before throwing so invalid-key attempts do not retain the upstream connection. Apply the cancellation to both status branches.</comment>

<file context>
@@ -0,0 +1,64 @@
+      )
+    }
+
+    if (response.status === 401 || response.status === 403) {
+      throw new CredentialGroupApiKeyVerificationError('Fireflies rejected this API key.')
+    }
</file context>
Fix with cubic

const apiKey = fields.apiKey
let response: Response
try {
response = await fetch(FIREFLIES_GRAPHQL_URL, {

@cubic-dev-ai cubic-dev-ai Bot Sep 1, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: If Fireflies accepts the connection but stalls, this enrollment request can remain occupied for the transport default instead of failing promptly. Add a finite AbortSignal.timeout(...) to the provider request and let the existing transport-error handler return the retryable verification message.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/sim/lib/credential-groups/api-key-providers/fireflies.ts, line 24:

<comment>If Fireflies accepts the connection but stalls, this enrollment request can remain occupied for the transport default instead of failing promptly. Add a finite `AbortSignal.timeout(...)` to the provider request and let the existing transport-error handler return the retryable verification message.</comment>

<file context>
@@ -0,0 +1,64 @@
+    const apiKey = fields.apiKey
+    let response: Response
+    try {
+      response = await fetch(FIREFLIES_GRAPHQL_URL, {
+        method: 'POST',
+        headers: { 'Content-Type': 'application/json', Authorization: `Bearer ${apiKey}` },
</file context>
Suggested change
response = await fetch(FIREFLIES_GRAPHQL_URL, {
response = await fetch(FIREFLIES_GRAPHQL_URL, {
signal: AbortSignal.timeout(15_000),
Fix with cubic

}

const user = payload?.data?.user
const email = user?.email ? normalizeEmail(user.email) : undefined

@cubic-dev-ai cubic-dev-ai Bot Sep 1, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: When Fireflies returns a malformed successful payload with a non-string email or name, this verifier throws a raw TypeError and the enrollment endpoint returns 500. Validate the response field types before normalizing them and convert malformed payloads to CredentialGroupApiKeyVerificationError.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At apps/sim/lib/credential-groups/api-key-providers/fireflies.ts, line 50:

<comment>When Fireflies returns a malformed successful payload with a non-string `email` or `name`, this verifier throws a raw `TypeError` and the enrollment endpoint returns 500. Validate the response field types before normalizing them and convert malformed payloads to `CredentialGroupApiKeyVerificationError`.</comment>

<file context>
@@ -0,0 +1,64 @@
+    }
+
+    const user = payload?.data?.user
+    const email = user?.email ? normalizeEmail(user.email) : undefined
+    if (!user?.user_id || !email || !isValidEmailSyntax(email)) {
+      throw new CredentialGroupApiKeyVerificationError(
</file context>
Fix with cubic

-- Added NOT VALID so it never takes a validating lock against live writes; no stored row can
-- violate it (none is `managed_api_key` yet), so the VALIDATE below is a formality that takes
-- only a SHARE UPDATE EXCLUSIVE lock.
ALTER TABLE "credential" ADD CONSTRAINT "credential_managed_api_key_source_check" CHECK ((type::text <> 'managed_api_key') OR (

@cubic-dev-ai cubic-dev-ai Bot Sep 1, 2026

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: When a concurrent index build is cancelled or fails, the migration runner retries this file from the top, but this constraint creation is not idempotent. Wrap the constraint creation in a duplicate-object guard so the replay can reach the index rebuild.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At packages/db/migrations/0316_credential_managed_api_key_storage.sql, line 21:

<comment>When a concurrent index build is cancelled or fails, the migration runner retries this file from the top, but this constraint creation is not idempotent. Wrap the constraint creation in a duplicate-object guard so the replay can reach the index rebuild.</comment>

<file context>
@@ -0,0 +1,54 @@
+-- Added NOT VALID so it never takes a validating lock against live writes; no stored row can
+-- violate it (none is `managed_api_key` yet), so the VALIDATE below is a formality that takes
+-- only a SHARE UPDATE EXCLUSIVE lock.
+ALTER TABLE "credential" ADD CONSTRAINT "credential_managed_api_key_source_check" CHECK ((type::text <> 'managed_api_key') OR (
+        encrypted_api_key IS NOT NULL
+        AND credential_group_enrollment_id IS NOT NULL
</file context>
Fix with cubic

@TheodoreSpeaks

Copy link
Copy Markdown
Collaborator Author

Parking this — going with MCP connections first.

Branch feat/api-key-cred-group stays pushed at 04d78734f7 (one commit, rebuilt onto current staging). Green on type-check, lint, check:migrations, and 846 tests.

Two things to pick up if this is revived:

  • The module-graph baseline for the settings page needs a look — it came in at +43 against an allowance of +42. Staging alone passes that audit, so the last +1 is from this branch.
  • Migrations are numbered 0315/0316; renumber if staging has moved past them again.
  • Never verified end to end: a live STS round trip for AWS, and the redaction check against a running workflow.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant