Core mental model
Automativ is a saved graph plus a background runner. The editor is an authoring surface; PostgreSQL is what Inngest executes.
This page is the in-product memory for engineering decisions, operational traps, debugging paths, and security boundaries. It is deliberately specific to this repository. It should help a future maintainer understand not only what exists, but why small-looking changes can have large effects.
Automativ is a saved graph plus a background runner. The editor is an authoring surface; PostgreSQL is what Inngest executes.
Nodes and connections are normalized rows, while node-specific settings live in JSON. Context between nodes is a flat object.
Authenticated tRPC paths are user-scoped. Public trigger routes, arbitrary HTTP nodes, and telemetry are separate trust surfaces.
The canvas and the worker do not read from the same state. React Flow owns unsaved browser state, while Inngest reads the persisted graph from PostgreSQL. The execute button therefore saves first and only emits the event after the graph update resolves.
Nodes are topologically sorted and run one at a time. This makes the context contract simple: each node receives one accumulated object and returns the next accumulated object.
A save deletes existing graph rows and recreates the submitted nodes and connections inside one transaction. This avoids partial writes and keeps the server translation easy to audit.
Trigger payloads and action outputs share a flat context object. Handlebars expressions in later nodes depend on exact top-level variable names and output shapes.
Credential values are encrypted at rest, but executors still need an ownership predicate. Ciphertext is not a permission boundary.
Realtime messages help the editor feel alive during a run. They are not the durable audit trail and they are not reconstructed from execution history.
Inngest steps are durable, but a provider can accept a request before the step result is recorded. Retrying Slack, Discord, arbitrary HTTP, or AI calls can repeat work.
Authenticated tRPC execution checks ownership. Public Google Form and Stripe route handlers are separate entry points and currently trust the workflow ID.
Provider API keys use the credential table, but Slack and Discord webhook URLs are saved inside node configuration JSON and returned to the editor.
An execution row stores aggregate status, final output, and failure details. It does not store the graph definition, executor version, per-node output, or model configuration used at the time.
WhyThe database can enforce workflow ownership, cascade graph deletion, and keep connection endpoints queryable instead of hiding the entire graph in one JSON field.
CostThe editor and server need a translation layer between React Flow objects and Prisma rows.
prisma/schema.prisma, workflows routerWhyA single transaction is straightforward, avoids partial graph persistence, and keeps complex diff logic out of the editor.
CostNo partial updates, no revision guard, and concurrent saves overwrite each other.
src/features/workflows/server/routers.tsWhyOpenAI, Anthropic, and Gemini each have provider-specific model behavior, UI copy, credentials, and status channels.
CostPrompt compilation, credential lookup, response extraction, and status publishing are repeated.
src/features/executions/components/*WhyRoute components can authenticate and prefetch while TanStack Query remains the browser mutation and cache layer.
CostPrefetch inputs, URL params, hook keys, and procedure schemas must stay synchronized.
src/trpc, feature prefetch modulesWhyList pages use normal dashboard chrome while the workflow editor gets the full browser viewport.
CostRoute groups make the file hierarchy more complex than the public URL hierarchy.
src/app/(dashboard)WhyNew workflows and credentials are gated by Polar state, while existing owned resources remain manageable through authentication and ownership checks.
CostThis product policy is easy to accidentally change while moving procedure middleware.
src/trpc/init.ts, feature routerssrc/features/workflows/server/routers.tsOwnership checks, graph replacement, name generation, and Inngest event dispatch live here.
src/inngest/functions.tsCreates execution rows, loads the graph, registers realtime channels, runs executors, and records success or failure.
src/inngest/utils.tsSends Inngest events and turns persisted connections into a topological node order.
src/features/executions/lib/executor-registry.tsMaps persisted NodeType values to server-side executor implementations.
src/features/editor/components/editor.tsxOwns unsaved node and edge state until a save serializes it through tRPC.
src/lib/encryption.tsWraps Cryptr and requires ENCRYPTION_KEY server-side.
src/trpc/init.tsBuilds protected and premium procedure middleware used by feature routers.
src/app/api/webhooksDevelopment-facing webhook entry points that currently treat workflowId as the capability.
Confirm both Next.js and the Inngest dev server are running. Inspect the Inngest run, then check whether onFailure could find the Execution row by event ID.
Verify workflows.update completed before workflows.execute. Inspect persisted nodes and connections because the worker does not read unsaved browser state.
Check credentialId in node data, confirm the credential belongs to the workflow owner, and verify the credential was not deleted after the node was configured.
Confirm ENCRYPTION_KEY has not changed. Then check whether an edit form submitted encrypted ciphertext as if it were plaintext.
Separate realtime state from execution history. Check provider channel subscription, node ID filtering, and whether another run reused the same node IDs.
Inspect the accumulated context shape and variable name. HTTP templates disable HTML escaping; AI/message executors compile or decode templates differently.
Review Inngest step IDs. Static step names are risky when the same node type can appear multiple times in one workflow.
Inspect the public webhook route and payload. Current Google Form and Stripe endpoints do not validate sender authenticity or replay.
Google Form and Stripe routes can enqueue workflows from arbitrary JSON. Add a generated trigger secret, provider signature verification where applicable, event age checks, and replay storage before trusting them in production.
HTTP nodes can request arbitrary URLs from the application runtime. Private network protection, redirect policy, timeouts, and response-size limits are not yet implemented.
Credential list/detail procedures expose ciphertext to authenticated clients. Editing without entering a new plaintext value can turn ciphertext into newly encrypted ciphertext.
Sentry replay, traces, logs, error stacks, AI telemetry, prompts, provider responses, and execution output need a data classification policy before sensitive workloads.
Resource ownership belongs in the Prisma query predicate.
An executor returns the complete next context, not only its own output.
React Flow node IDs and persisted connection endpoints must agree within one save.
Plaintext provider keys stay on the server and out of workflow context, logs, responses, and telemetry.
A repeated node type needs node-specific durable step identity.
Realtime status is a convenience; Execution rows are the durable aggregate record.
Applied Prisma migrations are historical records and are not rewritten.
nodebase-main can explain origins but never overrides Automativ behavior.
Public trigger routes are not trusted just because a request contains a workflow ID.
Provider model names are operational configuration and can become stale independently of the UI.
Deeper subsystem maps, operational warnings, and onboarding steps live in docs/ARCHITECTURE.md, PROJECT_MEMORY.md, docs/ONBOARDING.md, and docs/REPOSITORY_HEALTH.md.