spine-framework 0.4.4 → 0.5.0-rc.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +13 -3
- package/.framework/cli/commands/bootstrap.ts +392 -0
- package/.framework/cli/commands/dev.ts +91 -218
- package/.framework/cli/commands/doctor.ts +132 -26
- package/.framework/cli/commands/generate.ts +38 -298
- package/.framework/cli/commands/init.ts +20 -1
- package/.framework/cli/commands/install-app.ts +183 -13
- package/.framework/cli/commands/migrate.ts +36 -30
- package/.framework/cli/commands/migrations.ts +16 -56
- package/.framework/cli/commands/upgrade.ts +35 -0
- package/.framework/cli/commands/validate-app.ts +127 -1
- package/.framework/cli/commands/validate-schema.ts +147 -0
- package/.framework/cli/context.ts +223 -110
- package/.framework/cli/index.ts +6 -0
- package/.framework/cli/lib/project-files.ts +241 -1
- package/.framework/functions/_shared/app-manifest.ts +2 -0
- package/.framework/functions/_shared/hash-api-key.ts +183 -79
- package/.framework/functions/_shared/logging.ts +562 -224
- package/.framework/functions/_shared/middleware.ts +47 -44
- package/.framework/functions/_shared/permissions.ts +4 -0
- package/.framework/functions/admin-data.ts +51 -28
- package/.framework/functions/ai-agents.ts +10 -6
- package/.framework/functions/api-keys.ts +301 -122
- package/.framework/functions/apps.ts +63 -29
- package/.framework/functions/cil-ingest.ts +48 -15
- package/.framework/functions/debug-auth.ts +14 -8
- package/.framework/functions/deliveries.ts +39 -12
- package/.framework/functions/embeddings.ts +149 -78
- package/.framework/functions/integration-outbound.ts +67 -27
- package/.framework/functions/integrations.ts +254 -127
- package/.framework/functions/logs.ts +273 -133
- package/.framework/functions/observability.ts +278 -148
- package/.framework/functions/pipeline-executions.ts +1 -1
- package/.framework/functions/pipelines.ts +14 -9
- package/.framework/functions/prompt-configs.ts +7 -4
- package/.framework/functions/roles.ts +8 -5
- package/.framework/functions/security.ts +30 -17
- package/.framework/functions/system-cron.ts +240 -127
- package/.framework/functions/tests.ts +7 -6
- package/.framework/functions/timers.ts +310 -107
- package/.framework/functions/triggers.ts +28 -13
- package/.framework/functions/types.ts +13 -10
- package/.framework/migrations/069_log_stats_window.sql +54 -0
- package/.framework/migrations/070_app_type_schema_rpcs.sql +166 -0
- package/.framework/migrations/071_timer_interval_keys.sql +96 -0
- package/.framework/migrations/072_slug_autofill.sql +77 -0
- package/.framework/migrations/073_seed_member_roles.sql +26 -0
- package/.framework/migrations/074_app_route_prefix_domains.sql +168 -0
- package/.framework/migrations/075_security_definer_grants.sql +157 -0
- package/.framework/src/apps/admin/index.tsx +1141 -775
- package/.framework/src/components/ErrorBoundary.tsx +136 -0
- package/.framework/src/components/admin/AdminListPage.tsx +81 -73
- package/.framework/src/components/admin/AdminSidebar.tsx +88 -79
- package/.framework/src/components/admin/EntitySelect.tsx +181 -0
- package/.framework/src/components/admin/PageInstructions.tsx +86 -127
- package/.framework/src/components/app-shell/GenericDetailPage.tsx +37 -13
- package/.framework/src/components/app-shell/GenericListPage.tsx +28 -32
- package/.framework/src/components/layout/Sidebar.tsx +300 -177
- package/.framework/src/components/runtime/DataDetailPage.tsx +67 -35
- package/.framework/src/components/runtime/DataHeader.tsx +96 -48
- package/.framework/src/components/runtime/DataListPage.tsx +3 -26
- package/.framework/src/components/runtime/DataStats.tsx +10 -10
- package/.framework/src/components/runtime/DataTable.tsx +12 -10
- package/.framework/src/components/ui/sheet.tsx +24 -26
- package/.framework/src/components/ui/textarea.tsx +74 -48
- package/.framework/src/contexts/AppContext.tsx +5 -12
- package/.framework/src/contexts/AuthContext.tsx +17 -9
- package/.framework/src/hooks/useApps.ts +18 -22
- package/.framework/src/hooks/useEntityList.ts +12 -9
- package/.framework/src/hooks/useListSchema.ts +108 -58
- package/.framework/src/instructions/README.md +74 -0
- package/.framework/src/instructions/accounts.md +97 -0
- package/.framework/src/instructions/ai-agents.md +69 -0
- package/.framework/src/instructions/alerts.md +80 -0
- package/.framework/src/instructions/api-keys.md +87 -0
- package/.framework/src/instructions/apps.md +77 -0
- package/.framework/src/instructions/concepts/ai-and-prompts.md +150 -0
- package/.framework/src/instructions/concepts/apps-and-renderers.md +228 -0
- package/.framework/src/instructions/concepts/auth-and-api-keys.md +133 -0
- package/.framework/src/instructions/concepts/automation.md +186 -0
- package/.framework/src/instructions/concepts/concurrency-and-idempotency.md +125 -0
- package/.framework/src/instructions/concepts/core-vs-custom.md +124 -0
- package/.framework/src/instructions/concepts/design-schema.md +152 -0
- package/.framework/src/instructions/concepts/integrations-and-webhooks.md +127 -0
- package/.framework/src/instructions/concepts/observability.md +116 -0
- package/.framework/src/instructions/concepts/permissions.md +150 -0
- package/.framework/src/instructions/concepts/runtime-records.md +111 -0
- package/.framework/src/instructions/concepts/scope-and-accounts.md +112 -0
- package/.framework/src/instructions/concepts/spine-way.md +172 -0
- package/.framework/src/instructions/deliveries.md +59 -0
- package/.framework/src/instructions/guide/00-orientation.md +106 -0
- package/.framework/src/instructions/guide/01-install-and-first-run.md +253 -0
- package/.framework/src/instructions/guide/02-project-map.md +147 -0
- package/.framework/src/instructions/guide/03-domain-model.md +223 -0
- package/.framework/src/instructions/guide/04-first-app.md +227 -0
- package/.framework/src/instructions/guide/05-glossary.md +183 -0
- package/.framework/src/instructions/integrations.md +96 -0
- package/.framework/src/instructions/internal/cil-markers.md +83 -0
- package/.framework/src/instructions/internal/docs-contract.md +158 -0
- package/.framework/src/instructions/invites.md +60 -0
- package/.framework/src/instructions/kb.md +80 -0
- package/.framework/src/instructions/logs.md +77 -0
- package/.framework/src/instructions/notifications.md +77 -0
- package/.framework/src/instructions/observability.md +61 -0
- package/.framework/src/instructions/operations/deployment.md +115 -0
- package/.framework/src/instructions/operations/migrations.md +125 -0
- package/.framework/src/instructions/operations/security-checklist.md +170 -0
- package/.framework/src/instructions/operations/troubleshooting.md +170 -0
- package/.framework/src/instructions/operations/upgrade.md +116 -0
- package/.framework/src/instructions/people.md +91 -0
- package/.framework/src/instructions/pipeline-executions.md +62 -0
- package/.framework/src/instructions/pipelines.md +78 -0
- package/.framework/src/instructions/prompts.md +95 -0
- package/.framework/src/instructions/reference/api/admin-data.md +162 -0
- package/.framework/src/instructions/reference/api/auth.md +113 -0
- package/.framework/src/instructions/reference/api/errors-and-envelopes.md +113 -0
- package/.framework/src/instructions/reference/app-manifest.md +184 -0
- package/.framework/src/instructions/reference/cli.md +124 -0
- package/.framework/src/instructions/reference/database.md +132 -0
- package/.framework/src/instructions/reference/design-schema.md +280 -0
- package/.framework/src/instructions/reference/env-vars.md +133 -0
- package/.framework/src/instructions/reference/frontend-sdk.md +207 -0
- package/.framework/src/instructions/reference/functions/account-nodes.md +75 -0
- package/.framework/src/instructions/reference/functions/admin-data.md +197 -0
- package/.framework/src/instructions/reference/functions/admin-funnel.md +90 -0
- package/.framework/src/instructions/reference/functions/admin-install-funnel.md +107 -0
- package/.framework/src/instructions/reference/functions/admin-signals.md +102 -0
- package/.framework/src/instructions/reference/functions/ai-agents.md +148 -0
- package/.framework/src/instructions/reference/functions/ai-interactions.md +156 -0
- package/.framework/src/instructions/reference/functions/api-keys.md +130 -0
- package/.framework/src/instructions/reference/functions/apps.md +140 -0
- package/.framework/src/instructions/reference/functions/auth.md +99 -0
- package/.framework/src/instructions/reference/functions/debug-auth.md +91 -0
- package/.framework/src/instructions/reference/functions/deliveries.md +110 -0
- package/.framework/src/instructions/reference/functions/embeddings.md +166 -0
- package/.framework/src/instructions/reference/functions/eval-harness.md +149 -0
- package/.framework/src/instructions/reference/functions/index.md +103 -0
- package/.framework/src/instructions/reference/functions/integration-outbound.md +200 -0
- package/.framework/src/instructions/reference/functions/integration-routes.md +151 -0
- package/.framework/src/instructions/reference/functions/integrations.md +130 -0
- package/.framework/src/instructions/reference/functions/invites.md +182 -0
- package/.framework/src/instructions/reference/functions/item-progress.md +107 -0
- package/.framework/src/instructions/reference/functions/kb.md +166 -0
- package/.framework/src/instructions/reference/functions/logs.md +141 -0
- package/.framework/src/instructions/reference/functions/observability.md +105 -0
- package/.framework/src/instructions/reference/functions/pipeline-executions.md +132 -0
- package/.framework/src/instructions/reference/functions/pipeline-runner-background.md +121 -0
- package/.framework/src/instructions/reference/functions/pipelines.md +136 -0
- package/.framework/src/instructions/reference/functions/prompt-configs.md +121 -0
- package/.framework/src/instructions/reference/functions/roles.md +121 -0
- package/.framework/src/instructions/reference/functions/security.md +140 -0
- package/.framework/src/instructions/reference/functions/system-cron.md +136 -0
- package/.framework/src/instructions/reference/functions/system.md +106 -0
- package/.framework/src/instructions/reference/functions/tests.md +97 -0
- package/.framework/src/instructions/reference/functions/timers.md +117 -0
- package/.framework/src/instructions/reference/functions/trigger-scheduler.md +102 -0
- package/.framework/src/instructions/reference/functions/triggers.md +140 -0
- package/.framework/src/instructions/reference/functions/types.md +146 -0
- package/.framework/src/instructions/reference/seeds.md +126 -0
- package/.framework/src/instructions/reference/shared-modules.md +127 -0
- package/.framework/src/instructions/reference/spine-config.md +114 -0
- package/.framework/src/instructions/roles.md +80 -0
- package/.framework/src/instructions/runtime-collaboration.md +106 -0
- package/.framework/src/instructions/runtime-detail.md +70 -0
- package/.framework/src/instructions/runtime-list.md +77 -0
- package/.framework/src/instructions/security.md +67 -0
- package/.framework/src/instructions/tasks/add-a-pipeline.md +110 -0
- package/.framework/src/instructions/tasks/add-a-role.md +96 -0
- package/.framework/src/instructions/tasks/add-a-timer.md +114 -0
- package/.framework/src/instructions/tasks/add-a-trigger.md +106 -0
- package/.framework/src/instructions/tasks/add-a-type.md +151 -0
- package/.framework/src/instructions/tasks/add-a-webhook.md +108 -0
- package/.framework/src/instructions/tasks/add-an-ai-agent.md +108 -0
- package/.framework/src/instructions/tasks/add-an-integration.md +124 -0
- package/.framework/src/instructions/tasks/build-a-custom-page.md +118 -0
- package/.framework/src/instructions/tasks/document-your-app.md +117 -0
- package/.framework/src/instructions/tasks/package-and-install-an-app.md +136 -0
- package/.framework/src/instructions/tasks/register-an-app.md +137 -0
- package/.framework/src/instructions/tasks/test-your-code.md +108 -0
- package/.framework/src/instructions/tasks/write-a-function.md +132 -0
- package/.framework/src/instructions/tasks/write-a-migration.md +110 -0
- package/.framework/src/instructions/testing.md +59 -0
- package/.framework/src/instructions/timers.md +84 -0
- package/.framework/src/instructions/triggers.md +69 -0
- package/.framework/src/instructions/types.md +93 -0
- package/.framework/src/lib/app-mount.ts +88 -0
- package/.framework/src/pages/NotFoundPage.tsx +62 -31
- package/.framework/src/pages/admin/AIAgentDetailPage.tsx +117 -106
- package/.framework/src/pages/admin/AIAgentsPage.tsx +86 -48
- package/.framework/src/pages/admin/APIKeyDetailPage.tsx +153 -152
- package/.framework/src/pages/admin/APIKeysPage.tsx +91 -90
- package/.framework/src/pages/admin/AlertsConfigPage.tsx +29 -18
- package/.framework/src/pages/admin/AppDetailPage.tsx +6 -60
- package/.framework/src/pages/admin/AppsPage.tsx +104 -66
- package/.framework/src/pages/admin/DeliveriesPage.tsx +5 -5
- package/.framework/src/pages/admin/EmbeddingDetailPage.tsx +5 -23
- package/.framework/src/pages/admin/EmbeddingsPage.tsx +5 -26
- package/.framework/src/pages/admin/IntegrationDetailPage.tsx +7 -7
- package/.framework/src/pages/admin/IntegrationsPage.tsx +85 -43
- package/.framework/src/pages/admin/LogsPage.tsx +144 -76
- package/.framework/src/pages/admin/ObservabilityDashboard.tsx +223 -140
- package/.framework/src/pages/admin/PipelineDetailPage.tsx +134 -113
- package/.framework/src/pages/admin/PipelineExecutionsPage.tsx +108 -45
- package/.framework/src/pages/admin/PipelinesPage.tsx +160 -115
- package/.framework/src/pages/admin/PromptConfigDetailPage.tsx +96 -98
- package/.framework/src/pages/admin/PromptConfigsPage.tsx +101 -59
- package/.framework/src/pages/admin/RoleDetailPage.tsx +90 -79
- package/.framework/src/pages/admin/RolesPage.tsx +86 -43
- package/.framework/src/pages/admin/SecurityPage.tsx +1 -1
- package/.framework/src/pages/admin/TestingDashboard.tsx +16 -28
- package/.framework/src/pages/admin/TimerDetailPage.tsx +117 -100
- package/.framework/src/pages/admin/TimersPage.tsx +90 -62
- package/.framework/src/pages/admin/TriggerDetailPage.tsx +90 -83
- package/.framework/src/pages/admin/TriggersPage.tsx +124 -99
- package/.framework/src/pages/admin/TypeDetailPage.tsx +152 -138
- package/.framework/src/pages/admin/TypesPage.tsx +90 -48
- package/.framework/src/pages/admin/cil/CILAgentPage.tsx +17 -25
- package/.framework/src/pages/admin/cil/CILChunkDetailPage.tsx +17 -27
- package/.framework/src/pages/admin/cil/CILDashboardPage.tsx +72 -40
- package/.framework/src/pages/admin/cil/CILFileListPage.tsx +321 -129
- package/.framework/src/pages/admin/cil/CILFileSelectPage.tsx +18 -29
- package/.framework/src/pages/admin/cil/CILGraphPage.tsx +26 -38
- package/.framework/src/pages/admin/cil/CILIngestionPage.tsx +34 -46
- package/.framework/src/pages/admin/cil/CILProjectsPage.tsx +189 -103
- package/.framework/src/pages/admin/cil/CILSearchPage.tsx +246 -136
- package/.framework/src/pages/admin/cil/CILSuggestionsPage.tsx +11 -22
- package/.framework/src/pages/admin/cil/CILVersionsPage.tsx +9 -20
- package/.framework/src/pages/admin/cil/FlexiblePipelinePage.tsx +26 -38
- package/.framework/src/pages/admin/kb/KBAdminPage.tsx +7 -26
- package/.framework/src/pages/auth/LoginPage.tsx +3 -2
- package/.framework/src/pages/auth/RegisterPage.tsx +16 -8
- package/.framework/templates/agent/AGENTS.md +167 -0
- package/.framework/templates/agent/skills/spine-add-ai-agent/SKILL.md +58 -0
- package/.framework/templates/agent/skills/spine-add-automation/SKILL.md +62 -0
- package/.framework/templates/agent/skills/spine-add-integration/SKILL.md +63 -0
- package/.framework/templates/agent/skills/spine-architect/SKILL.md +86 -0
- package/.framework/templates/agent/skills/spine-install-app/SKILL.md +79 -0
- package/.framework/templates/agent/skills/spine-migration/SKILL.md +75 -0
- package/.framework/templates/agent/skills/spine-new-app/SKILL.md +79 -0
- package/.framework/templates/agent/skills/spine-new-function/SKILL.md +71 -0
- package/.framework/templates/agent/skills/spine-new-type/SKILL.md +72 -0
- package/.framework/templates/agent/skills/spine-schema/SKILL.md +77 -0
- package/.framework/templates/agent/skills/spine-test/SKILL.md +67 -0
- package/.framework/templates/agent/skills/spine-troubleshoot/SKILL.md +74 -0
- package/.framework/templates/agent/skills/spine-upgrade/SKILL.md +69 -0
- package/.framework/templates/project.json +4 -1
- package/.framework/tests/unit/account-scoping-gates.test.ts +394 -0
- package/.framework/tests/unit/agent-files.test.ts +355 -0
- package/.framework/tests/unit/agent-runner.test.ts +479 -44
- package/.framework/tests/unit/api-keys-create-scope-gate.test.ts +160 -0
- package/.framework/tests/unit/api-keys-omit-secret-fields.test.ts +38 -0
- package/.framework/tests/unit/app-mount.test.ts +80 -0
- package/.framework/tests/unit/bootstrap.test.ts +242 -0
- package/.framework/tests/unit/cli-context-api-key.test.ts +114 -0
- package/.framework/tests/unit/debug-auth.test.ts +38 -0
- package/.framework/tests/unit/deliveries-account-scoping.test.ts +185 -0
- package/.framework/tests/unit/integration-outbound-credentials.test.ts +167 -0
- package/.framework/tests/unit/integrations-audit-redaction.test.ts +130 -0
- package/.framework/tests/unit/logging-app-id.test.ts +75 -0
- package/.framework/tests/unit/security-definer-grants.test.ts +154 -0
- package/.framework/tests/unit/system-context-denial.test.ts +145 -0
- package/.framework/tests/unit/system-cron.test.ts +240 -0
- package/.framework/tests/unit/timers.test.ts +159 -0
- package/README.md +125 -166
- package/config/tsconfig.cli.json +2 -2
- package/package.json +15 -4
- package/templates/project/package-lock.json +2 -2
- package/.framework/docs/admin-routes.md +0 -76
- package/.framework/docs/api-docs-progress.md +0 -38
- package/.framework/docs/api-governance.md +0 -146
- package/.framework/docs/api-testing-results.md +0 -212
- package/.framework/docs/assembly-launch-guide.md +0 -138
- package/.framework/docs/audit-results.md +0 -590
- package/.framework/docs/db-api-inventory.md +0 -95
- package/.framework/docs/gap-remediation-backlog.md +0 -103
- package/.framework/docs/ui-adoption-verification.md +0 -111
- package/.framework/docs/ui-api-coverage.md +0 -84
- package/.framework/docs/v2-compatibility-audit.md +0 -228
- package/.framework/mcp/index.ts +0 -133
- package/.framework/mcp/load-env.ts +0 -60
- package/.framework/mcp/shared/app-manifest.ts +0 -275
- package/.framework/mcp/shared/db.ts +0 -477
- package/.framework/mcp/shared/kb.ts +0 -118
- package/.framework/mcp/tools/architect.ts +0 -431
- package/.framework/mcp/tools/cil/context.ts +0 -345
- package/.framework/mcp/tools/cil/graph.ts +0 -242
- package/.framework/mcp/tools/cil/ingest.ts +0 -1217
- package/.framework/mcp/tools/cil/pipeline-orchestrator.ts +0 -693
- package/.framework/mcp/tools/cil/pipeline.ts +0 -767
- package/.framework/mcp/tools/cil/project.ts +0 -791
- package/.framework/mcp/tools/cil/retrieval.ts +0 -336
- package/.framework/mcp/tools/cil/search-core.ts +0 -454
- package/.framework/mcp/tools/cil/search.ts +0 -97
- package/.framework/mcp/tools/cil/suggest.ts +0 -186
- package/.framework/mcp/tools/cil/validate.ts +0 -1369
- package/.framework/mcp/tools/kb/delete.ts +0 -139
- package/.framework/mcp/tools/kb/index.ts +0 -52
- package/.framework/mcp/tools/kb/ingest.ts +0 -84
- package/.framework/mcp/tools/kb/list-types.ts +0 -102
- package/.framework/mcp/tools/kb/publish.ts +0 -100
- package/.framework/mcp/tools/kb/reindex.ts +0 -86
- package/.framework/mcp/tools/kb/revoke.ts +0 -85
- package/.framework/mcp/tools/kb/search.ts +0 -101
- package/.framework/mcp/tools/kb/upsert.ts +0 -125
- package/.framework/mcp/tools/observability.ts +0 -510
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Add a timer
|
|
3
|
+
audience: developers, agents
|
|
4
|
+
kind: task
|
|
5
|
+
verified_against: e13127d
|
|
6
|
+
verified_on: 2026-10-30
|
|
7
|
+
source_files:
|
|
8
|
+
- .framework/functions/timers.ts
|
|
9
|
+
- .framework/functions/system-cron.ts
|
|
10
|
+
- .framework/functions/trigger-scheduler.ts
|
|
11
|
+
- .framework/migrations/020_schema_reconciliation.sql
|
|
12
|
+
- .framework/migrations/068_cron_schedule_timer_rpcs.sql
|
|
13
|
+
- .framework/src/pages/admin/TimerDetailPage.tsx
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
# Add a timer
|
|
17
|
+
|
|
18
|
+
## Goal
|
|
19
|
+
|
|
20
|
+
Create a `public.timers` row that runs a pipeline every
|
|
21
|
+
`config.interval_minutes` via the system cron sweep.
|
|
22
|
+
|
|
23
|
+
## Prerequisites
|
|
24
|
+
|
|
25
|
+
- A pipeline to point at ([Add a pipeline](./add-a-pipeline.md)) —
|
|
26
|
+
`get_due_timers` ignores timers with `pipeline_id` NULL.
|
|
27
|
+
|
|
28
|
+
## Steps
|
|
29
|
+
|
|
30
|
+
1. **Create the timer.** Admin UI → **Configs → Timers → New**
|
|
31
|
+
(`configs/timers/new`), or `POST /api/timers` — `name` and `timer_type`
|
|
32
|
+
required (`.framework/functions/timers.ts`). Set
|
|
33
|
+
`pipeline_id` and `config.interval_minutes`:
|
|
34
|
+
|
|
35
|
+
```json
|
|
36
|
+
{
|
|
37
|
+
"name": "Hourly sync",
|
|
38
|
+
"timer_type": "interval",
|
|
39
|
+
"pipeline_id": "<pipeline-uuid>",
|
|
40
|
+
"config": { "interval_minutes": 60 }
|
|
41
|
+
}
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
`timer_type` is a label (`interval` | `cron` | `once` in the admin
|
|
45
|
+
detail page, `.framework/src/pages/admin/TimerDetailPage.tsx`) — the
|
|
46
|
+
only field that actually reschedules is `config.interval_minutes`
|
|
47
|
+
(see below).
|
|
48
|
+
|
|
49
|
+
2. **`next_execution` is armed for you.** On create or toggle-on, the API
|
|
50
|
+
computes `next_execution` from `config.interval_minutes` (or
|
|
51
|
+
`config.interval_seconds`, seconds ÷ 60); `once` timers arm from an
|
|
52
|
+
ISO `config.run_at`/`config.schedule`. You may also pass
|
|
53
|
+
`next_execution` explicitly. If `next_execution` ever ends up NULL on
|
|
54
|
+
an active timer it cannot fire — `get_due_timers` requires
|
|
55
|
+
`next_execution IS NOT NULL` — so check it first when a timer never
|
|
56
|
+
runs.
|
|
57
|
+
|
|
58
|
+
3. **Understand the clock.** `trigger-scheduler` is the Netlify scheduled
|
|
59
|
+
function (`* * * * *` in `netlify.toml`); each minute it invokes the
|
|
60
|
+
`system-cron` handler under the system principal, which calls
|
|
61
|
+
`get_due_timers(now())`, then `runPipeline(timer.pipeline_id, …)` with a
|
|
62
|
+
`timer:<id>` machine principal, then `update_timer_after_run`
|
|
63
|
+
(`.framework/functions/system-cron.ts`).
|
|
64
|
+
|
|
65
|
+
**`netlify dev` does not run scheduled functions.** Clockwork only exists
|
|
66
|
+
on deployed Netlify — locally, `trigger-scheduler` is loaded but never
|
|
67
|
+
invoked on schedule. To emulate a tick, invoke it manually:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
curl -X POST http://localhost:8888/.netlify/functions/trigger-scheduler \
|
|
71
|
+
-H 'x-nf-event: schedule' \
|
|
72
|
+
-d "{\"next_run\": \"$(date -u +%Y-%m-%dT%H:%M:%S.000Z)\"}"
|
|
73
|
+
# or: npx netlify functions:invoke trigger-scheduler
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
4. **Rescheduling is automatic — or never.** `update_timer_after_run`
|
|
77
|
+
stamps `last_execution`, increments `execution_count` /
|
|
78
|
+
`success_count` / `failure_count`, and sets
|
|
79
|
+
`next_execution = now() + interval_minutes` when
|
|
80
|
+
`config.interval_minutes` is a positive number — otherwise `NULL`, so
|
|
81
|
+
the timer fires once and stops
|
|
82
|
+
(`.framework/migrations/068_cron_schedule_timer_rpcs.sql`).
|
|
83
|
+
|
|
84
|
+
## Verify
|
|
85
|
+
|
|
86
|
+
- `SELECT id, next_execution, last_execution, execution_count FROM timers`
|
|
87
|
+
shows `next_execution` in the future, then advancing each tick.
|
|
88
|
+
- **Observability → Executions** shows pipeline runs with a
|
|
89
|
+
`timer:<id>` principal.
|
|
90
|
+
- `timer.execution_count` increments; `success_count` / `failure_count`
|
|
91
|
+
reflect outcomes.
|
|
92
|
+
|
|
93
|
+
## Common mistakes
|
|
94
|
+
|
|
95
|
+
- **`interval_seconds` vs `interval_minutes`.** The admin detail page seeds
|
|
96
|
+
`config` with `{ "interval_seconds": 3600 }`, but `update_timer_after_run`
|
|
97
|
+
only reads `config.interval_minutes` — a timer saved with the default
|
|
98
|
+
config fires once (if `next_execution` was set) and never reschedules.
|
|
99
|
+
- **No `next_execution`.** The API can't set it; timers created through
|
|
100
|
+
`/api/timers` or the admin UI sit dormant until the column is seeded.
|
|
101
|
+
- **`toggle` is blind.** `?action=toggle` flips `is_active` to its negation
|
|
102
|
+
with no target-state argument — read the row first.
|
|
103
|
+
- **Cron expressions do nothing.** There is no cron-expression evaluator;
|
|
104
|
+
`timer_type: "cron"` is only a label — scheduling is interval arithmetic
|
|
105
|
+
on `next_execution`.
|
|
106
|
+
- **Timer with no `pipeline_id`** — filtered out of `get_due_timers`.
|
|
107
|
+
|
|
108
|
+
## Reference
|
|
109
|
+
|
|
110
|
+
- [timers entity page](../timers.md) ·
|
|
111
|
+
[timers function reference](../reference/functions/timers.md) ·
|
|
112
|
+
[system-cron reference](../reference/functions/system-cron.md) ·
|
|
113
|
+
[trigger-scheduler reference](../reference/functions/trigger-scheduler.md)
|
|
114
|
+
- [Automation](../concepts/automation.md) — *Timers and the scheduler*
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Add a trigger
|
|
3
|
+
audience: developers, agents
|
|
4
|
+
kind: task
|
|
5
|
+
verified_against: e13127d
|
|
6
|
+
verified_on: 2026-10-30
|
|
7
|
+
source_files:
|
|
8
|
+
- .framework/functions/triggers.ts
|
|
9
|
+
- .framework/functions/_shared/trigger-engine.ts
|
|
10
|
+
- .framework/functions/admin-data.ts
|
|
11
|
+
- .framework/functions/pipeline-runner-background.ts
|
|
12
|
+
- .framework/src/pages/admin/TriggerDetailPage.tsx
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# Add a trigger
|
|
16
|
+
|
|
17
|
+
## Goal
|
|
18
|
+
|
|
19
|
+
Create a `public.triggers` row so a record event (`item_created`,
|
|
20
|
+
`item_updated`, …) dispatches a pipeline execution.
|
|
21
|
+
|
|
22
|
+
## Prerequisites
|
|
23
|
+
|
|
24
|
+
- The pipeline the trigger will point at ([Add a pipeline](./add-a-pipeline.md)).
|
|
25
|
+
- `INTERNAL_INVOKE_SECRET` set on the deployed functions — dispatch POSTs to
|
|
26
|
+
`pipeline-runner-background` with an `X-Internal-Secret` header and logs
|
|
27
|
+
`cannot dispatch trigger pipeline` (then silently no-ops) when it is
|
|
28
|
+
unset (`.framework/functions/_shared/trigger-engine.ts`). See
|
|
29
|
+
[Environment variables](../reference/env-vars.md).
|
|
30
|
+
|
|
31
|
+
## Steps
|
|
32
|
+
|
|
33
|
+
1. **Pick the `event_type`.** Snake_case `<entity>_<verb>` — the engine's
|
|
34
|
+
`EventType` union covers `item`, `account`, `person`, `thread`,
|
|
35
|
+
`message`, `attachment`, and `link` × `created`/`updated`/`deleted`
|
|
36
|
+
(`.framework/functions/_shared/trigger-engine.ts`). Use
|
|
37
|
+
`item_created`, not `item.created` — the admin UI's own placeholder text
|
|
38
|
+
(`e.g., item.created`) is misleading
|
|
39
|
+
(`.framework/src/pages/admin/TriggerDetailPage.tsx`).
|
|
40
|
+
|
|
41
|
+
Scheduled and webhook entry points are **not** trigger event types —
|
|
42
|
+
use a [timer](./add-a-timer.md) or an
|
|
43
|
+
[inbound webhook](./add-a-webhook.md) for those.
|
|
44
|
+
|
|
45
|
+
2. **Add filter config (optional).** `config.entity_type` scopes the row to
|
|
46
|
+
one table; `config.type_slug` scopes it to one type (resolved from
|
|
47
|
+
`type_id` when the record carries only that);
|
|
48
|
+
`config.filters` supports `$eq`, `$ne`, `$gt`, `$gte`, `$lt`, `$lte`,
|
|
49
|
+
`$in`, `$nin`, `$exists` on `data->>'field'` paths; and
|
|
50
|
+
`config.fire_on_transition` fires only when the record moves *into* a
|
|
51
|
+
matching state (`.framework/functions/_shared/trigger-engine.ts`).
|
|
52
|
+
|
|
53
|
+
```json
|
|
54
|
+
{
|
|
55
|
+
"name": "New ticket",
|
|
56
|
+
"event_type": "item_created",
|
|
57
|
+
"pipeline_id": "<pipeline-uuid>",
|
|
58
|
+
"config": { "entity_type": "items", "type_slug": "ticket" }
|
|
59
|
+
}
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
3. **Create the trigger.** Admin UI → **Configs → Triggers → New**, or
|
|
63
|
+
`POST /api/triggers` — `name`, `event_type`, and `pipeline_id` required
|
|
64
|
+
(`.framework/functions/triggers.ts`).
|
|
65
|
+
|
|
66
|
+
4. **Keep it enabled.** `is_active: true` (default). Only active triggers
|
|
67
|
+
are matched on an event; `?action=toggle` flips it
|
|
68
|
+
(`.framework/functions/triggers.ts`). Deleting a trigger is a soft
|
|
69
|
+
delete (`is_active = false`) — execution history survives.
|
|
70
|
+
|
|
71
|
+
## Verify
|
|
72
|
+
|
|
73
|
+
- Create or update a matching record via `/api/admin-data` — creates and
|
|
74
|
+
updates call `fireCreateTriggers` / `fireUpdateTriggers` after the write
|
|
75
|
+
(`.framework/functions/admin-data.ts`).
|
|
76
|
+
- A `pipeline_executions` row appears in `queued`, then `running` /
|
|
77
|
+
`completed` as `pipeline-runner-background` picks it up — watch
|
|
78
|
+
**Observability → Executions**.
|
|
79
|
+
- The `record_trigger_fire` RPC bumps `triggers.trigger_count` /
|
|
80
|
+
`last_triggered`, and `trigger.fired` / `trigger.failed` audit rows land
|
|
81
|
+
in `public.logs`.
|
|
82
|
+
|
|
83
|
+
## Common mistakes
|
|
84
|
+
|
|
85
|
+
- **Dotted event names.** `item.created` matches nothing — event names are
|
|
86
|
+
`item_created` etc.
|
|
87
|
+
- **Expecting `item_deleted`.** The enum includes it, but the `admin-data`
|
|
88
|
+
delete path never invokes `fireDeleteTriggers` — only create and update
|
|
89
|
+
fire (`.framework/functions/admin-data.ts`;
|
|
90
|
+
`.framework/functions/_shared/trigger-engine.ts`). Use an update
|
|
91
|
+
(`is_active = false`) if you need delete-like notification.
|
|
92
|
+
- **Missing `INTERNAL_INVOKE_SECRET`.** The trigger inserts the queued
|
|
93
|
+
execution row, but the dispatch to `pipeline-runner-background` cannot be
|
|
94
|
+
authenticated, so nothing runs — check function logs for the
|
|
95
|
+
`INTERNAL_INVOKE_SECRET not set` error.
|
|
96
|
+
- **Filtering on `type_slug` without realizing the resolution.** Records
|
|
97
|
+
carry `type_id`, not the slug; the engine resolves the slug once per
|
|
98
|
+
event when any trigger filters on `config.type_slug`
|
|
99
|
+
(`.framework/functions/_shared/trigger-engine.ts`).
|
|
100
|
+
|
|
101
|
+
## Reference
|
|
102
|
+
|
|
103
|
+
- [triggers entity page](../triggers.md) · [Automation](../concepts/automation.md)
|
|
104
|
+
- [trigger-scheduler reference](../reference/functions/trigger-scheduler.md) ·
|
|
105
|
+
[pipeline-runner-background reference](../reference/functions/pipeline-runner-background.md)
|
|
106
|
+
- [Add a pipeline](./add-a-pipeline.md) · [Add a timer](./add-a-timer.md)
|
|
@@ -0,0 +1,151 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Add a type
|
|
3
|
+
audience: developers, agents
|
|
4
|
+
kind: task
|
|
5
|
+
verified_against: e13127d
|
|
6
|
+
verified_on: 2026-10-30
|
|
7
|
+
source_files:
|
|
8
|
+
- .framework/functions/types.ts
|
|
9
|
+
- .framework/functions/_shared/schema-utils.ts
|
|
10
|
+
- .framework/functions/_shared/permissions.ts
|
|
11
|
+
- .framework/src/pages/admin/TypeDetailPage.tsx
|
|
12
|
+
- .framework/migrations/015_types_kind_slug_unique.sql
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# Add a type
|
|
16
|
+
|
|
17
|
+
## Goal
|
|
18
|
+
|
|
19
|
+
Create a `public.types` row whose `design_schema` drives validation,
|
|
20
|
+
permissions, and the generic list/detail UI for a new record shape. Records
|
|
21
|
+
created through `/api/admin-data` stamp this schema onto themselves at write
|
|
22
|
+
time, so a broken schema propagates into every record.
|
|
23
|
+
|
|
24
|
+
## Prerequisites
|
|
25
|
+
|
|
26
|
+
- A `system_admin` login or an API key with `*:*` scope — `POST /api/types`
|
|
27
|
+
throws `System admin required` for anyone else
|
|
28
|
+
(`.framework/functions/types.ts`).
|
|
29
|
+
- The target `kind` decided up front (`item`, `thread`, `message`, `link`,
|
|
30
|
+
`attachment`, `watcher`, `person`, `account`). The kind chooses the runtime
|
|
31
|
+
table and cannot be implied later — see
|
|
32
|
+
[Runtime records](../concepts/runtime-records.md).
|
|
33
|
+
|
|
34
|
+
## Steps
|
|
35
|
+
|
|
36
|
+
1. **Draft the `design_schema`.** Work through the checklist in
|
|
37
|
+
[Design schema](../concepts/design-schema.md); the field/display enums are
|
|
38
|
+
in [Design schema reference](../reference/design-schema.md). The schema
|
|
39
|
+
below is the minimum for the generic renderer:
|
|
40
|
+
|
|
41
|
+
```json
|
|
42
|
+
{
|
|
43
|
+
"scope": "account",
|
|
44
|
+
"fields": {
|
|
45
|
+
"title": { "data_type": "text", "label": "Title", "required": true, "system": true },
|
|
46
|
+
"status": { "data_type": "select", "label": "Status", "required": true, "system": true,
|
|
47
|
+
"options": ["open", "closed"] }
|
|
48
|
+
},
|
|
49
|
+
"views": {
|
|
50
|
+
"default_list": {
|
|
51
|
+
"type": "list", "display": "table", "label": "Tickets",
|
|
52
|
+
"fields": {
|
|
53
|
+
"title": { "sortable": true, "display_type": "text" },
|
|
54
|
+
"status": { "sortable": true, "display_type": "badge" },
|
|
55
|
+
"created_at": { "sortable": true, "display_type": "timestamp" }
|
|
56
|
+
},
|
|
57
|
+
"default_sort": { "field": "created_at", "direction": "desc" }
|
|
58
|
+
},
|
|
59
|
+
"default_detail": {
|
|
60
|
+
"type": "detail", "label": "Ticket",
|
|
61
|
+
"sections": [
|
|
62
|
+
{ "title": "Core",
|
|
63
|
+
"fields": { "title": { "display_type": "input" },
|
|
64
|
+
"status": { "display_type": "select" } } }
|
|
65
|
+
]
|
|
66
|
+
}
|
|
67
|
+
},
|
|
68
|
+
"record_permissions": {
|
|
69
|
+
"system_admin": ["create", "read", "update", "delete"],
|
|
70
|
+
"support": ["create", "read", "update"]
|
|
71
|
+
},
|
|
72
|
+
"functionality": null
|
|
73
|
+
}
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
- `fields`: every entry needs `data_type`, `label`, `required`. `select`,
|
|
77
|
+
`multiselect`, and `radio` need `options` — `generateValidationSchema`
|
|
78
|
+
derives allowed values from them (`.framework/functions/_shared/schema-utils.ts`).
|
|
79
|
+
- `record_permissions` keys are `roles.slug` values matched **verbatim** —
|
|
80
|
+
`system_admin`, not `system-admin`. A misspelled or nonexistent slug
|
|
81
|
+
silently grants nothing (`.framework/functions/_shared/permissions.ts`).
|
|
82
|
+
- **Reserved column names.** `items` has real columns `title`,
|
|
83
|
+
`description`, and `status` — a field with one of those names and
|
|
84
|
+
`system: true` maps onto the column; the same name with `system: false`
|
|
85
|
+
lands in `record.data` and list/detail views show the column's value
|
|
86
|
+
(e.g. lifecycle `active`) instead of yours. Pick `system: true` for
|
|
87
|
+
these names or rename the field.
|
|
88
|
+
- `record_permissions` is the **base, most restrictive** grant; add
|
|
89
|
+
`fields.*.permissions` only to loosen one field or to restrict a
|
|
90
|
+
`system: true` field — see [Permission model](../concepts/permissions.md).
|
|
91
|
+
- Generic apps (`renderer: "generic"`) need `views.default_list` and
|
|
92
|
+
`views.default_detail` referencing real fields with known
|
|
93
|
+
`display_type` values; custom apps can omit views but still need
|
|
94
|
+
`fields` + `record_permissions` so `admin-data` can stamp and validate.
|
|
95
|
+
|
|
96
|
+
2. **Create the type.** Either:
|
|
97
|
+
|
|
98
|
+
- Admin UI → **Configs → Types → New** (`configs/types/new`), fill in
|
|
99
|
+
name, slug, kind, and paste the schema into the Design Schema JSON
|
|
100
|
+
field — the page POSTs to `/api/types?action=create`
|
|
101
|
+
(`.framework/src/pages/admin/TypeDetailPage.tsx`).
|
|
102
|
+
- API: `POST /api/types` with a body containing `name`, `slug`, `kind`,
|
|
103
|
+
and `design_schema` — all three fields are required
|
|
104
|
+
(`.framework/functions/types.ts`).
|
|
105
|
+
|
|
106
|
+
3. **Let the API generate `validation_schema`.** Do not send it yourself —
|
|
107
|
+
the create handler replaces a missing `validation_schema` with
|
|
108
|
+
`generateValidationSchema(design_schema)` before insert, and update
|
|
109
|
+
regenerates it whenever `design_schema` changes
|
|
110
|
+
(`.framework/functions/types.ts`).
|
|
111
|
+
|
|
112
|
+
4. **Set the row-level fields.** `ownership` (`system`/`app`/`tenant`),
|
|
113
|
+
`is_active`, `icon`, `color`, and optional `app_id` are columns on the
|
|
114
|
+
`types` row, not inside `design_schema`.
|
|
115
|
+
|
|
116
|
+
## Verify
|
|
117
|
+
|
|
118
|
+
- `GET /api/types?action=get&slug=<slug>&include_schema=true` returns the row
|
|
119
|
+
with a non-null `validation_schema`.
|
|
120
|
+
- Create a record: `POST /api/admin-data?action=create` with
|
|
121
|
+
`{ "entity": "items", "type_slug": "<slug>", "title": "…", "data": {…} }`
|
|
122
|
+
— the stored record carries the stamped `design_schema`.
|
|
123
|
+
- If the app uses the generic renderer, open its list page — an unknown
|
|
124
|
+
`display_type` or missing `default_list` view shows up immediately as a
|
|
125
|
+
broken page.
|
|
126
|
+
|
|
127
|
+
## Common mistakes
|
|
128
|
+
|
|
129
|
+
- **Slug collisions.** Slugs must be unique per `kind`
|
|
130
|
+
(`.framework/migrations/015_types_kind_slug_unique.sql`); create throws
|
|
131
|
+
`Type with slug '<slug>' already exists for kind '<kind>'`. App-install
|
|
132
|
+
seeds go further — `conflict: "kind,slug"` makes type slugs effectively
|
|
133
|
+
global, so prefix app types (`support_ticket`, not `ticket`) — see
|
|
134
|
+
[Seeds](../reference/seeds.md).
|
|
135
|
+
- **`system-admin` vs `system_admin`.** Seeded role slugs use underscores;
|
|
136
|
+
`system-admin` grants nothing.
|
|
137
|
+
- **Editing `data_type` on a live field.** Existing records keep their old
|
|
138
|
+
stored values; the schema change does not migrate data.
|
|
139
|
+
- **Using `generate` to scaffold types.** `npx spine-framework generate`
|
|
140
|
+
emits a stale legacy schema shape — author via the admin UI or `/api/types`
|
|
141
|
+
(see [Build your first app — Gaps](../guide/04-first-app.md)).
|
|
142
|
+
|
|
143
|
+
## Reference
|
|
144
|
+
|
|
145
|
+
- [Design schema (concept)](../concepts/design-schema.md) ·
|
|
146
|
+
[Design schema reference](../reference/design-schema.md)
|
|
147
|
+
- [Permission model](../concepts/permissions.md) ·
|
|
148
|
+
[Runtime records](../concepts/runtime-records.md)
|
|
149
|
+
- [types entity page](../types.md) ·
|
|
150
|
+
[types function reference](../reference/functions/types.md) ·
|
|
151
|
+
[admin-data function reference](../reference/functions/admin-data.md)
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Add a webhook
|
|
3
|
+
audience: developers, agents
|
|
4
|
+
kind: task
|
|
5
|
+
verified_against: e13127d
|
|
6
|
+
verified_on: 2026-10-30
|
|
7
|
+
source_files:
|
|
8
|
+
- .framework/functions/integration-routes.ts
|
|
9
|
+
- .framework/functions/_shared/webhook-registry.ts
|
|
10
|
+
- .framework/functions/_shared/webhook-registration.ts
|
|
11
|
+
- .framework/functions/integration-outbound.ts
|
|
12
|
+
- .framework/functions/adapters/generic-webhook-inbound.ts
|
|
13
|
+
- .framework/migrations/004_core_rpc_and_broker.sql
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
# Add a webhook
|
|
17
|
+
|
|
18
|
+
Two directions, two different mechanisms: **inbound** webhooks arrive at the
|
|
19
|
+
`integration-routes` function and are normalized by a registry adapter into
|
|
20
|
+
`items` records; **outbound** webhooks leave through
|
|
21
|
+
`integration-outbound` (usually from a pipeline stage). See
|
|
22
|
+
[Integrations and webhooks](../concepts/integrations-and-webhooks.md).
|
|
23
|
+
|
|
24
|
+
## Goal — inbound
|
|
25
|
+
|
|
26
|
+
Receive an external POST and turn it into a record.
|
|
27
|
+
|
|
28
|
+
### Steps
|
|
29
|
+
|
|
30
|
+
1. **Create the integration** ([Add an integration](./add-an-integration.md))
|
|
31
|
+
the webhook posts against. Set `config.event_path` if the event name
|
|
32
|
+
lives inside the payload (`"type"` or `"data.event"`), and
|
|
33
|
+
`config.handler_name` to force an adapter — otherwise the adapter name
|
|
34
|
+
falls back to `integration.provider`, then `generic-webhook`
|
|
35
|
+
(`.framework/functions/integration-routes.ts`).
|
|
36
|
+
|
|
37
|
+
2. **Mint an API key for the sender.** `integration-routes` requires a
|
|
38
|
+
machine principal (`x-api-key`), and the key is authorized per
|
|
39
|
+
`account_id` — optionally pinned to `integration_id` and `app_id`
|
|
40
|
+
(`.framework/functions/integration-routes.ts`). See
|
|
41
|
+
[Auth and API keys](../concepts/auth-and-api-keys.md).
|
|
42
|
+
|
|
43
|
+
3. **Register or reuse an adapter.** `webhook_handlers` rows map a handler
|
|
44
|
+
name → `function_name`; lookup prefers the app/account-scoped row and
|
|
45
|
+
falls back to a global one, then `loadHandler` dynamically imports the
|
|
46
|
+
module and takes `adapt` → `handler` → `default`
|
|
47
|
+
(`.framework/functions/_shared/webhook-registry.ts`). Migration
|
|
48
|
+
004 seeds a global `generic-webhook` handler bound to
|
|
49
|
+
`adapters/generic-webhook-inbound`, which stores the raw payload as a
|
|
50
|
+
`webhook_event` item (`.framework/migrations/004_core_rpc_and_broker.sql`).
|
|
51
|
+
|
|
52
|
+
4. **Adapter contract.** The adapter receives `(payload, ctx)` where `ctx`
|
|
53
|
+
carries `integration`, `apiKey`, `accountId`, `appId`, `event`,
|
|
54
|
+
`headers`, and a `logger`; it returns
|
|
55
|
+
`{ type_slug, data, title?, status?, thread?, links?, external_id?, error?, retryable? }`.
|
|
56
|
+
A truthy `error` marks the delivery `failed` (or `pending` when
|
|
57
|
+
`retryable`); a result with `type_slug` is created through `admin-data`
|
|
58
|
+
as an `items` record (`.framework/functions/integration-routes.ts`).
|
|
59
|
+
|
|
60
|
+
5. **Post to it.** `POST /api/integration-routes?slug=<integration-slug>`
|
|
61
|
+
with `x-api-key: <key>` and the payload body. The event name comes from
|
|
62
|
+
the `x-event-type` header first, then `config.event_path`
|
|
63
|
+
(`.framework/functions/integration-routes.ts`).
|
|
64
|
+
|
|
65
|
+
## Goal — outbound
|
|
66
|
+
|
|
67
|
+
Call an external URL from a pipeline.
|
|
68
|
+
|
|
69
|
+
### Steps
|
|
70
|
+
|
|
71
|
+
1. Add an `http_request` stage (arbitrary HTTP) or a webhook action whose
|
|
72
|
+
`handler_module: 'webhook'` routes to `executeWebhookHandler` — the
|
|
73
|
+
webhook URL comes from the action's config
|
|
74
|
+
(`.framework/functions/_shared/pipeline-runner.ts`).
|
|
75
|
+
2. For credentialed or retried sends, go through `integration-outbound`
|
|
76
|
+
instead — see [Add an integration](./add-an-integration.md). Deliveries
|
|
77
|
+
are tracked in `webhook_deliveries` with exponential backoff.
|
|
78
|
+
|
|
79
|
+
## Verify
|
|
80
|
+
|
|
81
|
+
- Inbound: `webhook_deliveries` shows `direction: 'inbound'` rows moving
|
|
82
|
+
`pending → delivered`; the created item appears via `/api/admin-data`.
|
|
83
|
+
- Outbound: `webhook_deliveries` shows `direction: 'outbound'` rows with
|
|
84
|
+
`response_status`.
|
|
85
|
+
|
|
86
|
+
## Common mistakes
|
|
87
|
+
|
|
88
|
+
- **Human JWT instead of `x-api-key`** — the route requires a machine
|
|
89
|
+
principal and returns `Machine principal required` (401).
|
|
90
|
+
- **API key pinned to a different integration/app** — `403 API key is not
|
|
91
|
+
authorized`.
|
|
92
|
+
- **`x-event-type` vs `event_path` confusion** — the header wins; if the
|
|
93
|
+
sender can't set headers, put the event inside the payload and set
|
|
94
|
+
`config.event_path`.
|
|
95
|
+
- **Unregistered handler name** — `config.handler_name` (or `provider`)
|
|
96
|
+
that has no `webhook_handlers` row and no global fallback returns
|
|
97
|
+
`Handler not found` (404).
|
|
98
|
+
- **Registering handlers under the wrong scope** — an app- or
|
|
99
|
+
account-scoped `webhook_handlers` row shadows the global fallback for
|
|
100
|
+
that scope only.
|
|
101
|
+
|
|
102
|
+
## Reference
|
|
103
|
+
|
|
104
|
+
- [integration-routes reference](../reference/functions/integration-routes.md) ·
|
|
105
|
+
[integration-outbound reference](../reference/functions/integration-outbound.md) ·
|
|
106
|
+
[deliveries entity page](../deliveries.md)
|
|
107
|
+
- [Integrations and webhooks](../concepts/integrations-and-webhooks.md) ·
|
|
108
|
+
[Add a pipeline](./add-a-pipeline.md)
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Add an AI agent
|
|
3
|
+
audience: developers, agents
|
|
4
|
+
kind: task
|
|
5
|
+
verified_against: e13127d
|
|
6
|
+
verified_on: 2026-10-30
|
|
7
|
+
source_files:
|
|
8
|
+
- .framework/functions/ai-agents.ts
|
|
9
|
+
- .framework/functions/_shared/agent-runner.ts
|
|
10
|
+
- .framework/functions/_shared/llm.ts
|
|
11
|
+
- .framework/functions/_shared/pipeline-runner.ts
|
|
12
|
+
- .framework/functions/prompt-configs.ts
|
|
13
|
+
- .framework/functions/integration-outbound.ts
|
|
14
|
+
---
|
|
15
|
+
|
|
16
|
+
# Add an AI agent
|
|
17
|
+
|
|
18
|
+
## Goal
|
|
19
|
+
|
|
20
|
+
Create an `ai_agents` row plus a `prompt_configs` row, then run it either
|
|
21
|
+
conversationally (`ai-agents?action=run` on a thread) or inside a pipeline
|
|
22
|
+
(`agent_inference` stage). See
|
|
23
|
+
[AI agents, prompt configs, and embeddings](../concepts/ai-and-prompts.md).
|
|
24
|
+
|
|
25
|
+
## Steps
|
|
26
|
+
|
|
27
|
+
1. **Create the prompt config.** Admin UI → **Configs → Prompts → New**, or
|
|
28
|
+
`POST /api/prompt-configs`. Key fields: `system_prompt`,
|
|
29
|
+
`context_template` (substitutes `{{user_message}}` and
|
|
30
|
+
`{{conversation_history}}` only), `model` (default `gpt-4o`),
|
|
31
|
+
`temperature` (default `0.7`), `max_tokens` (default `4000`),
|
|
32
|
+
`is_multi_turn`, `max_history_messages` (default `20`)
|
|
33
|
+
(`.framework/functions/prompt-configs.ts`).
|
|
34
|
+
|
|
35
|
+
2. **Create the agent.** Admin UI → **Configs → AI Agents → New**, or
|
|
36
|
+
`POST /api/ai-agents` — `name` and `agent_type` required; optional
|
|
37
|
+
`model_config`, `system_prompt`, `tools`, `capabilities`,
|
|
38
|
+
`constraints`, `metadata` (`.framework/functions/ai-agents.ts`).
|
|
39
|
+
Link the prompt config via `metadata.default_prompt_config_id`.
|
|
40
|
+
|
|
41
|
+
3. **Configure the LLM integration.** Inference routes through
|
|
42
|
+
`integration-outbound` against the integration slug `openai` with
|
|
43
|
+
`credential_field: 'api_token'` (`.framework/functions/_shared/llm.ts`).
|
|
44
|
+
Migration 004 seeds `openai` with
|
|
45
|
+
`api_token: { source: 'env_var', value: 'OPENAI_API_KEY' }` — set that env
|
|
46
|
+
var or update the credential. Without it the runner returns a
|
|
47
|
+
`[Mock Response]` string instead of calling the model
|
|
48
|
+
(`.framework/functions/_shared/agent-runner.ts`).
|
|
49
|
+
|
|
50
|
+
4. **Conversational path — `ai-agents?action=run`.**
|
|
51
|
+
`POST /api/ai-agents?action=run` with `{ thread_id, user_message, mode }`;
|
|
52
|
+
`mode` is `step` | `complete` (default) | `episode`. The runner resolves
|
|
53
|
+
the agent from `thread.data.agent_id` → `design_schema.default_agent_id`,
|
|
54
|
+
and the prompt config from `thread.data.prompt_config_id` →
|
|
55
|
+
`agent.metadata.default_prompt_config_id` — both must be `is_active`
|
|
56
|
+
(`.framework/functions/_shared/agent-runner.ts`). A
|
|
57
|
+
prompt-injection preflight reads `config.security.prompt_injection`
|
|
58
|
+
(`off` | `flag` | `block`) on the agent
|
|
59
|
+
(`.framework/functions/ai-agents.ts`).
|
|
60
|
+
|
|
61
|
+
5. **Pipeline path — `agent_inference` stage.** Add a stage with
|
|
62
|
+
`stage_type: 'agent_inference'` and `config.context` (required;
|
|
63
|
+
`{{trigger.*}}`/`{{record.*}}` templates resolve). With a
|
|
64
|
+
`config.webhook_url` the stage POSTs `{ context, model, temperature,
|
|
65
|
+
max_tokens }` to that URL via the `generic-http` integration; without
|
|
66
|
+
one it returns a mock response —
|
|
67
|
+
`[Mock Response] Received N chars of context`
|
|
68
|
+
(`.framework/functions/_shared/pipeline-runner.ts`).
|
|
69
|
+
|
|
70
|
+
## Verify
|
|
71
|
+
|
|
72
|
+
- `POST /api/ai-agents?action=run` on a thread whose `data.agent_id` points
|
|
73
|
+
at the agent — returns a completion (or the mock string when the OpenAI
|
|
74
|
+
credential is missing).
|
|
75
|
+
- `agent_step` rate-limit debits and injection audits appear in
|
|
76
|
+
`public.logs`.
|
|
77
|
+
- The `agent_inference` stage output appears in the execution's stage
|
|
78
|
+
results (**Observability → Executions**).
|
|
79
|
+
|
|
80
|
+
## Common mistakes
|
|
81
|
+
|
|
82
|
+
- **`threads.target_type` is `NOT NULL`.** A chat thread must be bound to a
|
|
83
|
+
target record — there is no unbound "conversation" row. Point it at
|
|
84
|
+
something stable such as `target_type: 'account'` +
|
|
85
|
+
`target_id: <account id>` and keep `data.agent_id` /
|
|
86
|
+
`data.prompt_config_id` on the thread.
|
|
87
|
+
- **No `default_prompt_config_id` on the agent and no
|
|
88
|
+
`prompt_config_id` on the thread** — the run has no prompt config to
|
|
89
|
+
assemble from; always set the agent fallback.
|
|
90
|
+
- **Missing `OPENAI_API_KEY` / unconfigured `openai` integration** — you
|
|
91
|
+
get a mock response, not an error; check the reply text for
|
|
92
|
+
`[Mock Response]`.
|
|
93
|
+
- **`agent_inference` with no `webhook_url`** — returns a mock. This stage
|
|
94
|
+
is an HTTP bridge, not the in-process `runAgent` path.
|
|
95
|
+
- **Agent or prompt config `is_active: false`** — resolution fails before
|
|
96
|
+
inference.
|
|
97
|
+
- **`context_template` with unsupported placeholders** — only
|
|
98
|
+
`{{user_message}}` and `{{conversation_history}}` substitute; anything
|
|
99
|
+
else stays literal (`.framework/functions/_shared/agent-runner.ts`).
|
|
100
|
+
|
|
101
|
+
## Reference
|
|
102
|
+
|
|
103
|
+
- [ai-agents entity page](../ai-agents.md) ·
|
|
104
|
+
[ai-agents function reference](../reference/functions/ai-agents.md) ·
|
|
105
|
+
[prompt-configs function reference](../reference/functions/prompt-configs.md) ·
|
|
106
|
+
[prompts entity page](../prompts.md)
|
|
107
|
+
- [AI agents, prompt configs, and embeddings](../concepts/ai-and-prompts.md) ·
|
|
108
|
+
[Add a pipeline](./add-a-pipeline.md) · [Add an integration](./add-an-integration.md)
|