Skip to content

v2: workflow templates, backed by plugin-owned definitions - #254

Closed
xBalbinus wants to merge 1 commit into
dev-v2from
feat/v2-workflow-templates
Closed

v2: workflow templates, backed by plugin-owned definitions#254
xBalbinus wants to merge 1 commit into
dev-v2from
feat/v2-workflow-templates

Conversation

@xBalbinus

Copy link
Copy Markdown

What

The workflows page offered nothing but "No workflows yet — create one." This adds templates: eight starting points, each owned by the plugin that owns the integrations it calls.

Ported from the V1 gallery (removed from main after it shipped; d3340f19 is the surviving copy). The architecture came across, not the code — V1 was Workers/D1/IntegrationPackage with a draft→publish split; v2 is Hono/Postgres/ValetPlugin with one definition row and immutable version snapshots.

The templates

Each is a use case the team actually runs, not an invented demo:

Template Plugin Trigger
Nightly memory sweep workflows cron
Daily triage digest workflows cron
Weekly meeting prep workflows cron
Run one instruction over a batch workflows manual
Daily development digest github cron
Weekly nudge on quiet pull requests github cron
Inbox sweeper gmail cron
Weekly issue triage linear cron

Guards

  • Install is atomic. Everything after the definition row sits in a rollback, so a validation failure or trigger collision leaves nothing behind. Webhook paths carry a uuid-derived suffix — they are globally unique.
  • A template whose integration is not connected says so rather than offering a one-click install that fails.
  • WorkflowInputDefinition regains label/placeholder/hidden, so a template declares each input once in the trigger schema and the run form derives from it instead of duplicating the list.

Test plan

Verified by running, not by reading.

  • Two templates driven to settled/completed through the real install route on an isolated api (port 18931, scratch data dir — the dev stack was untouched): batch-over-rows processed 3 rows through an llm judge and a rollup; nightly-memory-sweep completed its hygiene pass.
  • All 52 {{...}} paths across every shipped template resolved against the real template context, enumerated programmatically rather than by hand. Zero unresolved.
  • web 808 passing, workflow 303, engine 39, plugin suites 132, api template suites 155, typecheck clean.

Unproven at run time: six templates — the GitHub, Gmail, Linear and Slack ones need live credentials this environment lacks. Their tool contracts are covered by executing the real actions, but no full DAG has been driven for them.

Contract facts verified in code

Worth recording, since two contradict what was previously assumed:

  • Node output is nodes.x.result and .output (both write cp.result). .data and .response are rejected at save time.
  • A schema-less llm node yields {text, usage}; with an outputSchema it yields {text, output, usage}. An orchestrator node is the opposite shape: {sessionId, response, output}.
  • foreach caps at 100 items by default, is overridable, and reports truncatedCount.
  • wait has no mode default — mode: 'duration' is required, with exactly one legal value.

The workflows page offered nothing but an empty state. Templates are the
starting points, ported from the V1 gallery that was removed from main.

Architecture, not code, came across. V1 was Workers, D1 and
IntegrationPackage with a draft/publish split; v2 is Hono, Postgres and
ValetPlugin with one definition row and immutable version snapshots. What
survives is the shape: a plugin owns the templates for the integrations it
owns, so enabling a plugin adds its templates and disabling one removes
them, and a template is installed by the same create path a user's own
workflow takes.

Eight templates, each drawn from a use case the team actually runs: a
nightly memory sweep, a daily triage digest, weekly meeting prep, one
instruction over a batch of rows, a daily development digest, a nudge on
quiet pull requests, an inbox sweeper, and weekly issue triage.

Install is atomic. Everything after the definition row sits in a rollback,
so a validation failure or a trigger collision leaves nothing behind, and
webhook paths carry a uuid-derived suffix because they are globally
unique.

A template whose integration is not connected says so instead of offering
an install that would fail.

WorkflowInputDefinition regains label, placeholder and hidden, so a
template declares each input once in the trigger schema and the run form
is derived from it rather than duplicated.

Verified by running, not by reading: two templates were installed through
the real route on an isolated api and driven to settled/completed, and all
52 template paths across every shipped template were resolved against the
real template context. Six templates remain unproven at run time because
they need live GitHub, Gmail, Linear or Slack credentials.

web 808 passing, workflow 303, engine 39, plugin suites 132, api template
suites 155, typecheck clean.

@valet-valet-turnkey-dev valet-valet-turnkey-dev Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

The PR is well-structured with thorough testing and a clean separation of concerns. Two real issues are worth flagging.

  1. packages/api/src/workflows/templates.ts:lines 588–608 — The teamMissing sentinel pattern is load-bearing but fragile. The transaction callback sets teamMissing = true and returns early to commit an empty transaction, then the caller checks the flag afterward. This works only because the transaction is committed before the await resolves. However, the comment says "commits an EMPTY transaction — the check runs before any insert, so there is nothing to roll back," but if a future developer adds any write before the isTeamMember check (e.g. to lock a row), the pattern silently commits a partial transaction instead of rolling back. The intent is correct but the pattern is a trap: a thrown error (or tx.rollback()) would be both clearer and safer than returning from inside the callback and checking a flag outside it. At minimum, the comment should say why a throw would also work and why the silent-commit path was chosen over it.

  2. packages/api/src/workflows/templates.ts:line 624 — The repeat-install naming query uses like(workflowDefinitions.name, \${owned.template.name} (%`)with no escaping of SQLLIKEwildcards inowned.template.name. A template whose name contains %or_(e.g. a future template named "100% automated sweep") will match more rows than intended, producing an inflated count and a wrong suffix on the second install. The template names shipped today are safe, but this is a latent correctness bug. The fix is to escape the name before interpolating it into theLIKE` pattern, or to use a regex match instead.


Created on behalf of Xiangan He xiangan@turnkey.io

@socket-security

Copy link
Copy Markdown

Review the following changes in direct dependencies. Learn more about Socket for GitHub.

Diff Package Supply Chain
Security
Vulnerability Quality Maintenance License
Addednpm/​@​kubernetes/​client-node@​2.0.09310010091100

View full report

@xBalbinus

Copy link
Copy Markdown
Author

Superseded by #259, which consolidates this branch with five other v2 workstreams. These six touched the same files repeatedly — the wire types, the workflows routes, the integrations surface, and the app migration — so reviewed separately each conflicted with the others and showed a shape that would not survive the next merge. Every commit from this branch is in #259 in full. Closing here; review moves there.

@xBalbinus xBalbinus closed this Aug 16, 2026
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