Template Subscriptions
Per-project subscriptions and auto-subscribe.
Template subscriptions control which official SDLC templates are active for each project. The subscription model decouples the canonical template bundle (owned by the operator) from per-project enablement - one template can be subscribed across hundreds of projects without creating a workflow clone per project.
How subscriptions work
Every official template published by the operator has a row in the templates table with isOfficial = true. For each (project, template) pair, the platform maintains a project_template_subscriptions row with a status of published or unpublished.
The event router checks this table on every inbound webhook: if the matching subscription is published, the template fires; if it is unpublished, the template is dormant for that project.
inbound webhook
↓
event router
↓
project_template_subscriptions
↓
status = published? ──yes──→ trigger.subscriptions → workflow fires
↓ no
drop (no template execution)There is a known desync between the UI "Published" badge and the event router: the UI reads project_template_subscriptions.status while the router reads trigger_subscriptions. If you archive a workflow directly from the workflow editor rather than toggling the subscription, the UI badge may show "Published" while the template is actually dormant. Always use the subscription toggle (not direct archive) to pause a template on a project. This is a known issue being tracked.
Auto-subscribe on project creation
When a new project is created, the platform calls ensureProjectSubscriptionsToOfficialTemplates for that project with status = 'published'. This means:
- Every current official template is immediately active for the new project.
- No operator action is required for new projects to receive the default SDLC.
Backfill on new official template publish
When a template is first marked as official (or first published as official), the platform runs fanoutSubscriptionToAllProjects with status = 'unpublished'. This means:
- Every existing project gets a subscription row for the new template.
- The status defaults to unpublished - existing projects must opt in explicitly.
- New projects created after the template is official get it as published automatically.
This asymmetry is intentional: you never want a new SDLC template to silently start firing on every existing project without operator review.
Subscription status semantics
| Status | Meaning |
|---|---|
published | Template fires for this project on matching events |
unpublished | Template is dormant for this project. The workflow exists but no triggers route to it |
Sticky unpublish: once a subscription is unpublished, subsequent backfills (from new official templates or template republishes) never reset it back to published. The operator must explicitly re-enable it. This is the primary mechanism for per-project opt-out.
Managing subscriptions
There is no rensei CLI surface for template subscriptions today - manage them from the UI panel or the API below.
Via the UI
Open the project's Workflows page. The System Templates panel at the top lists every official-template subscription for the project - template name, slug, a link to the read-only template preview, and a Published / Unpublished toggle. Loading the panel also lazily backfills a subscription row (at published) for any official template the project is missing, so projects created before a template became official still surface it.
Via the API
# List the project's template subscriptions
curl https://rensei.ai/api/projects/{projectId}/workflow-subscriptions \
-H "Authorization: Bearer rsk_live_..."
# Toggle a subscription using the `id` from the list response
curl -X PATCH https://rensei.ai/api/projects/{projectId}/workflow-subscriptions \
-H "Authorization: Bearer rsk_live_..." \
-H "Content-Type: application/json" \
-d '{
"subscriptionId": "pts_...",
"status": "unpublished"
}'When you toggle a subscription, the platform calls syncSubscriptionTriggers immediately - trigger routing updates take effect within seconds, not at the next cron cycle.
Subscription lifecycle sync
The platform keeps project_template_subscriptions in sync with the underlying workflow status automatically:
| Workflow status change | Subscription effect |
|---|---|
| Workflow archived | Subscription flipped to unpublished |
| Workflow published | Subscription flipped to published |
| Workflow set to draft | No-op (intermediate state) |
This sync runs via syncProjectSubscriptionsToWorkflow whenever a workflow's status changes. It uses the template slug stored in metadata.templateInstall.templateSlug (stamped at install time) to find the matching subscription row.
Only workflows installed via the template install path carry a templateInstall.templateSlug metadata stamp. Workflows created manually on the canvas (even if they implement the same logic) are not linked to a subscription row and are not affected by this sync.
Required workflow policies
For enterprise operators who need to guarantee that certain templates are always active on specific project sets, Rensei provides required workflow policies. Policies operate independently of the subscription model and can override the unpublished state.
| Policy mode | Behavior |
|---|---|
seed-and-enforce | Install the template if missing; reset to published if unpublished |
seed-only | Install the template if missing; do not reset existing unpublished subscriptions |
Policies are authored in the admin panel (Admin → Required Workflow Policies) and reconciled every minute by the /api/cron/required-workflows cron. Operators can trigger an immediate run on demand:
# Same endpoint Vercel Cron calls (CRON_SECRET bearer auth)
curl -X POST https://rensei.ai/api/cron/required-workflows \
-H "Authorization: Bearer $CRON_SECRET"
# Or from a platform checkout
pnpm tsx scripts/reconcile-required-workflows.ts --applySee Required Workflow Policies for the full policy authoring reference.
Subscription table shape
For teams building on the API or writing their own tooling, the key fields on a subscription row:
| Field | Type | Description |
|---|---|---|
id | pts_<hex> | Opaque subscription ID |
workspaceId | UUID | The org that owns the subscription |
projectId | UUID | The project the subscription applies to |
templateId | UUID | References templates.id |
status | published | unpublished | Current enablement state |
createdAt | timestamp | When the subscription was created |
updatedAt | timestamp | Last status toggle time |
The (projectId, templateId) pair is unique - there is exactly one subscription row per (project, template) combination. Idempotent upserts use ON CONFLICT DO NOTHING on this pair.
Troubleshooting
Template fires on wrong projects
Check that the subscription is published only for the intended projects - open each project's Workflows → System Templates panel, or list per project via the API:
curl https://rensei.ai/api/projects/{projectId}/workflow-subscriptions \
-H "Authorization: Bearer rsk_live_..."If the subscription was created by the backfill fan-out (when you first published the template as official), it defaults to unpublished for existing projects. Confirm that the rows you expect to fire have status = published.
Template stopped firing after archiving and republishing
If you archived and then republished a workflow directly from the editor, the subscription may still be unpublished (the archive toggled it; the republish synced it back). Check the status in the System Templates panel (or the GET above). If it is unpublished, re-enable it with the panel toggle or:
curl -X PATCH https://rensei.ai/api/projects/{projectId}/workflow-subscriptions \
-H "Authorization: Bearer rsk_live_..." \
-H "Content-Type: application/json" \
-d '{ "subscriptionId": "pts_...", "status": "published" }'New projects not getting the template
Confirm the template is marked as official in the admin panel (Admin → Templates → [template] → Official). Only isOfficial = true templates are auto-subscribed on project creation.
UI badge shows "Published" but template is not firing
This is the known project_template_subscriptions vs trigger_subscriptions desync. Use the subscription toggle (not direct archive) to disable the template, wait a few seconds, then re-enable it. The syncSubscriptionTriggers call on toggle rebuilds the trigger_subscriptions rows.