Skip to content

v0.6.95: data enrichment block, nullable workflow description fix#4786

Merged
TheodoreSpeaks merged 2 commits into
mainfrom
staging
May 29, 2026
Merged

v0.6.95: data enrichment block, nullable workflow description fix#4786
TheodoreSpeaks merged 2 commits into
mainfrom
staging

Conversation

@TheodoreSpeaks
Copy link
Copy Markdown
Collaborator

🤖 Generated with Claude Code

TheodoreSpeaks and others added 2 commits May 28, 2026 22:19
* feat(enrichment): workflow Enrichment block + /api/tools/enrichment/run

Add a generic Enrichment workflow block that runs a code-defined enrichment
(Work Email, Phone Number, Company Domain, Company Info, …) and returns its
outputs — usable in workflows, not just tables.

- New internal endpoint POST /api/tools/enrichment/run (checkInternalAuth +
  contract) runs the same runEnrichment provider cascade; injects the
  workspace's hosted/BYOK key via executeTool.
- New tool enrichment_run posts to it and surfaces hosted-key cost on the
  output so the workflow logging session bills it.
- New block blocks/blocks/enrichment.ts generated from the enrichment registry:
  operation dropdown = enrichments, per-enrichment conditional inputs, union of
  conditional outputs. New registry entries appear automatically.
- EnrichmentRunContext.tableId/rowId made optional (workflow path has no row).
- Register tool + block; bump api-validation route baseline; add EnrichmentIcon
  and generated docs page.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

* chore: re-trigger CI

* refactor(enrichment): share mapFieldType helper between block and tool

* fix(enrichment): reserved output keys (matched/provider) win over enrichment outputs

---------

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@vercel
Copy link
Copy Markdown

vercel Bot commented May 29, 2026

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

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
docs Skipped Skipped May 29, 2026 3:35am

Request Review

@cursor
Copy link
Copy Markdown

cursor Bot commented May 29, 2026

PR Summary

Medium Risk
New server route runs third-party enrichment cascades with workspace BYOK/hosted keys; schema change for nullable descriptions is low impact.

Overview
Adds a Data Enrichment workflow block and enrichment_run tool so workflows can run registry enrichments (work email, phone, company domain, etc.) through the same provider cascade as table enrichments, via a new internal-auth POST /api/tools/enrichment/run route. Block inputs/outputs are generated from ALL_ENRICHMENTS; cascade results now expose provider and mapFieldType is shared for block/tool typing. EnrichmentRunContext makes tableId/rowId optional for the workflow path.

Also allows metadata.description on workflow state wire payloads to be null, and bumps the API Zod route baseline to 758.

Reviewed by Cursor Bugbot for commit 066cd70. Bugbot is set up for automated code reviews on this repo. Configure here.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps Bot commented May 29, 2026

Greptile Summary

This PR introduces a new Data Enrichment workflow block that lets users run registry enrichments (Work Email, Phone Number, Company Domain, Company Info) directly in a workflow, using the same provider cascade and BYOK/hosted-key injection that table enrichments use. It also makes workflow description nullable in the Zod contract to fix a runtime validation failure.

  • Enrichment block: new EnrichmentBlock, enrichmentRunTool, /api/tools/enrichment/run route, and supporting types; the block is dynamically generated from the enrichment registry so future enrichments appear automatically.
  • Nullable fix: z.string().optional()z.string().nullable().optional() in workflowStateSchema to allow null descriptions stored in the DB.
  • EnrichmentRunContext relaxed: tableId/rowId made optional so the new workflow-block code path (which has no table/row context) can reuse the shared runEnrichment cascade runner.

Confidence Score: 4/5

Safe to merge; the enrichment block wires a new internal-only route through the existing provider cascade with proper auth, and the nullable-description fix is a one-line schema correction.

The core cascade logic, auth, Zod contracts, and block generation are all solid. Two minor issues exist: workspaceId can silently become absent in the JSON body when the executor context is not injected, and the matched computation in the route uses key-count rather than the value-presence check used inside the cascade. Neither affects the happy path.

apps/sim/tools/enrichment/run.ts — workspaceId may be silently dropped from the request body; apps/sim/app/api/tools/enrichment/run/route.ts — matched computed differently from the cascade's internal hasResult guard.

Important Files Changed

Filename Overview
apps/sim/app/api/tools/enrichment/run/route.ts New internal-auth–protected POST route that runs the provider cascade; matched computed as Object.keys(result).length > 0 differs slightly from hasResult's value-presence check but is equivalent in practice.
apps/sim/tools/enrichment/run.ts Tool config for enrichment_run; workspaceId sourced from _context?.workspaceId which is optional — if the executor omits it the Zod contract surfaces a confusing validation error.
apps/sim/blocks/blocks/enrichment.ts Dynamically generates subBlocks and outputs from ALL_ENRICHMENTS at module load; reserved keys (matched/provider) correctly assigned last to win over any future colliding enrichment output ids.
apps/sim/enrichments/run.ts Adds provider field to EnrichmentRunOutcome; provider label surfaced on hit and null on miss; no logic regressions.
apps/sim/enrichments/types.ts Makes tableId/rowId optional in EnrichmentRunContext to support the workflow-block path; backward-compatible with existing callers.
apps/sim/lib/api/contracts/workflows.ts One-line fix: description field in workflowStateSchema now accepts null values, matching the nullable DB column.
apps/sim/lib/api/contracts/tools/enrichment.ts Clean Zod contract for the new route; request and response schemas are well-typed and match the route handler's behavior.
apps/sim/enrichments/providers.ts Adds mapFieldType and EnrichmentFieldType; clean utility bridging enrichment field types to block/tool type unions.

Sequence Diagram

sequenceDiagram
    participant UI as Workflow UI
    participant Executor as Workflow Executor
    participant Route as /api/tools/enrichment/run
    participant Runner as runEnrichment()
    participant Provider as External Provider Tool

    UI->>Executor: Run workflow (enrichment block)
    Executor->>Route: "POST {enrichmentId, inputs, workspaceId}"
    Route->>Route: checkInternalAuth()
    Route->>Route: Zod validate body
    Route->>Route: getEnrichment(enrichmentId)
    Route->>Runner: "runEnrichment(enrichment, inputs, {workspaceId})"
    loop Provider cascade
        Runner->>Runner: buildParams(inputs) — null? skip
        Runner->>Provider: executeTool(toolId, params)
        Provider-->>Runner: response
        Runner->>Runner: mapOutput(response) + hasResult?
        alt hit
            Runner-->>Route: "{result, cost, provider}"
        else miss / 404
            Runner->>Runner: try next provider
        end
    end
    Runner-->>Route: "{result:{}, cost, error, provider:null}"
    Route-->>Executor: "{matched, result, cost, error, provider}"
    Executor->>Executor: transformResponse
    Executor-->>UI: Block output
Loading

Reviews (1): Last reviewed commit: "fix(schema) Make workflow description nu..." | Re-trigger Greptile

Comment on lines +50 to +54
body: (params: EnrichmentRunParams & { _context?: { workspaceId?: string } }) => ({
enrichmentId: params.enrichmentId,
inputs: params.inputs ?? {},
workspaceId: params._context?.workspaceId,
}),
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 Missing workspaceId causes a confusing validation error. When _context?.workspaceId is undefined, JSON.stringify silently drops the key, and the server-side Zod contract rejects the request with "workspaceId is required" — which surfaces to the user as a generic enrichment failure rather than a meaningful block error. A fallback empty string would still be rejected by the z.string().min(1) guard, but the request would at least include the field so the error message is clearer.

Suggested change
body: (params: EnrichmentRunParams & { _context?: { workspaceId?: string } }) => ({
enrichmentId: params.enrichmentId,
inputs: params.inputs ?? {},
workspaceId: params._context?.workspaceId,
}),
body: (params: EnrichmentRunParams & { _context?: { workspaceId?: string } }) => ({
enrichmentId: params.enrichmentId,
inputs: params.inputs ?? {},
workspaceId: params._context?.workspaceId ?? '',
}),

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

workspace id should always be passed so this should never occur.

Comment on lines +54 to +60
logger.info('Enrichment block run', {
enrichmentId,
matched: Object.keys(result).length > 0,
provider,
})
return NextResponse.json({
matched: Object.keys(result).length > 0,
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 matched flag is computed from Object.keys(result).length > 0, but the cascade's hasResult guard (in run.ts) requires at least one non-null, non-empty value before accepting a provider result. The two checks are currently equivalent in practice (the cascade guarantees result is either {} or has at least one non-empty value), but aligning the route to the same guard makes the intent explicit and prevents drift if the cascade logic changes.

Suggested change
logger.info('Enrichment block run', {
enrichmentId,
matched: Object.keys(result).length > 0,
provider,
})
return NextResponse.json({
matched: Object.keys(result).length > 0,
const matched = Object.values(result).some((v) => v !== undefined && v !== null && v !== '')
logger.info('Enrichment block run', {
enrichmentId,
matched,
provider,
})
return NextResponse.json({
matched,

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@TheodoreSpeaks TheodoreSpeaks merged commit 503432c into main May 29, 2026
31 checks passed
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