Repository Memory

What Automativteaches

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.

10
Lessons Preserved
6
Architecture Decisions
8
Debugging Patterns
10
Maintenance Rules

Core mental model

Automativ is a saved graph plus a background runner. The editor is an authoring surface; PostgreSQL is what Inngest executes.

Data contract

Nodes and connections are normalized rows, while node-specific settings live in JSON. Context between nodes is a flat object.

Boundary to respect

Authenticated tRPC paths are user-scoped. Public trigger routes, arbitrary HTTP nodes, and telemetry are separate trust surfaces.

Lessons worth preserving

Execution

Save before enqueueing

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.

Evidence: ExecuteWorkflowButton awaits workflows.update before workflows.execute. The worker still loads the graph after enqueue, so executions are not immutable snapshots.
Watch for: Do not add a faster execute path that skips save. It will reintroduce runs that do not match what the user sees on the canvas.
Workflow engine

Sequential DAG execution keeps context understandable

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.

Evidence: topologicalSort rejects cycles during execution. Disconnected nodes are included, but ordering between independent branches is not a stable business promise.
Watch for: Parallel branch execution needs an explicit merge policy before it is safe to implement.
Prisma

Replace-all graph persistence is intentional

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.

Evidence: workflows.update preserves React Flow node IDs while recreating nodes because connections refer to those IDs.
Watch for: Concurrent editors are last-write-wins. Add revisions before promising collaborative editing or conflict warnings.
Data flow

Workflow context is a public internal API

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.

Evidence: Executors are expected to return the previous context plus their named output. A missing spread silently removes upstream data.
Watch for: Changing an executor's output shape can break saved workflows even when TypeScript still passes.
Credentials

Encryption and authorization solve different problems

Credential values are encrypted at rest, but executors still need an ownership predicate. Ciphertext is not a permission boundary.

Evidence: AI executors query by credential ID and workflow owner ID before decrypting server-side.
Watch for: The credential edit flow can double-encrypt unchanged ciphertext. Fixing that requires a UI/API contract change, not just a crypto helper change.
Inngest

Realtime status is presentation state

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.

Evidence: The browser subscribes to provider-specific channels and filters incoming messages by node ID.
Watch for: Node status should eventually be correlated to execution ID if simultaneous runs become common.
Operations

External side effects are not exactly-once by default

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.

Evidence: The HTTP executor already includes nodeId in its step identity; several repeated node-capable executors still use static step names.
Watch for: Use idempotency keys or node-specific durable step IDs before adding high-value side effects.
Security

Public trigger URLs need a trust model

Authenticated tRPC execution checks ownership. Public Google Form and Stripe route handlers are separate entry points and currently trust the workflow ID.

Evidence: Both routes parse request JSON and enqueue an Inngest event without signature validation, shared trigger secret validation, event age checks, or replay controls.
Watch for: Workflow IDs are identifiers. They should not be treated as long-lived webhook secrets.
Data classification

Stored workflow data can contain secret-like values

Provider API keys use the credential table, but Slack and Discord webhook URLs are saved inside node configuration JSON and returned to the editor.

Evidence: The graph load path returns node data so dialogs can be edited. That includes integration configuration stored directly on nodes.
Watch for: Do not assume all secrets go through Credential. Audit node data before exporting, logging, or broadening telemetry.
History

Executions are records of outcomes, not records of code

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.

Evidence: executeWorkflow loads the current workflow graph after enqueue. Execution.output is one JSON blob for the terminal context.
Watch for: Historical debugging will remain limited until graph snapshots or per-node execution rows are added.

Decision tradeoffs

Normalized Node and Connection rows

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.

Look atprisma/schema.prisma, workflows router

Replace graph on save

WhyA 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.

Look atsrc/features/workflows/server/routers.ts

Explicit provider modules

WhyOpenAI, 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.

Look atsrc/features/executions/components/*

Server prefetch plus browser hydration

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.

Look atsrc/trpc, feature prefetch modules

Separate dashboard and editor shells

WhyList 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.

Look atsrc/app/(dashboard)

Premium creation, protected maintenance

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.

Look atsrc/trpc/init.ts, feature routers

Where to look first

Graph save and execute procedures

src/features/workflows/server/routers.ts

Ownership checks, graph replacement, name generation, and Inngest event dispatch live here.

Workflow worker

src/inngest/functions.ts

Creates execution rows, loads the graph, registers realtime channels, runs executors, and records success or failure.

Graph utilities

src/inngest/utils.ts

Sends Inngest events and turns persisted connections into a topological node order.

Executor registry

src/features/executions/lib/executor-registry.ts

Maps persisted NodeType values to server-side executor implementations.

React Flow editor

src/features/editor/components/editor.tsx

Owns unsaved node and edge state until a save serializes it through tRPC.

Credential encryption

src/lib/encryption.ts

Wraps Cryptr and requires ENCRYPTION_KEY server-side.

Auth and premium middleware

src/trpc/init.ts

Builds protected and premium procedure middleware used by feature routers.

Public trigger routes

src/app/api/webhooks

Development-facing webhook entry points that currently treat workflowId as the capability.

Debugging discoveries

Execution stays RUNNING

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.

Canvas changes did not execute

Verify workflows.update completed before workflows.execute. Inspect persisted nodes and connections because the worker does not read unsaved browser state.

AI node says credential not found

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.

AI node cannot decrypt

Confirm ENCRYPTION_KEY has not changed. Then check whether an edit form submitted encrypted ciphertext as if it were plaintext.

Node status looks stale

Separate realtime state from execution history. Check provider channel subscription, node ID filtering, and whether another run reused the same node IDs.

Template output is malformed

Inspect the accumulated context shape and variable name. HTTP templates disable HTML escaping; AI/message executors compile or decode templates differently.

A repeated node behaves strangely

Review Inngest step IDs. Static step names are risky when the same node type can appear multiple times in one workflow.

External trigger runs unexpectedly

Inspect the public webhook route and payload. Current Google Form and Stripe endpoints do not validate sender authenticity or replay.

Risk register highlights

Webhook authenticity

High

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.

Server-side HTTP reachability

High

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 edit semantics

Medium

Credential list/detail procedures expose ciphertext to authenticated clients. Editing without entering a new plaintext value can turn ciphertext into newly encrypted ciphertext.

Observability data

Medium

Sentry replay, traces, logs, error stacks, AI telemetry, prompts, provider responses, and execution output need a data classification policy before sensitive workloads.

Maintenance invariants

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.