spine-framework 0.4.4 → 0.5.0-rc.4
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/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-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/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/admin-data.ts +39 -28
- package/.framework/functions/ai-agents.ts +10 -6
- package/.framework/functions/api-keys.ts +301 -122
- package/.framework/functions/apps.ts +43 -26
- 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 +145 -76
- 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 +277 -147
- 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/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/hooks/useEntityList.ts +12 -9
- package/.framework/src/hooks/useListSchema.ts +108 -58
- package/.framework/src/instructions/README.md +57 -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 +162 -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/invites.md +60 -0
- package/.framework/src/instructions/kb.md +80 -0
- package/.framework/src/instructions/logs.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 +148 -0
- package/.framework/src/instructions/operations/troubleshooting.md +170 -0
- package/.framework/src/instructions/operations/upgrade.md +116 -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 +150 -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/functions/account-nodes.md +75 -0
- package/.framework/src/instructions/reference/functions/admin-data.md +195 -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-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/package-and-install-an-app.md +127 -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 +82 -0
- package/.framework/src/instructions/triggers.md +69 -0
- package/.framework/src/instructions/types.md +93 -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/templates/agent/AGENTS.md +150 -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 +72 -0
- package/.framework/templates/agent/skills/spine-migration/SKILL.md +75 -0
- package/.framework/templates/agent/skills/spine-new-app/SKILL.md +67 -0
- package/.framework/templates/agent/skills/spine-new-function/SKILL.md +65 -0
- package/.framework/templates/agent/skills/spine-new-type/SKILL.md +65 -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 +392 -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/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/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,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)
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
---
|
|
2
|
+
title: Add an integration
|
|
3
|
+
audience: developers, agents
|
|
4
|
+
kind: task
|
|
5
|
+
verified_against: e13127d
|
|
6
|
+
verified_on: 2026-10-30
|
|
7
|
+
source_files:
|
|
8
|
+
- .framework/functions/integrations.ts
|
|
9
|
+
- .framework/functions/integration-outbound.ts
|
|
10
|
+
- .framework/functions/_shared/resolve-credentials.ts
|
|
11
|
+
- .framework/functions/_shared/permissions.ts
|
|
12
|
+
- .framework/migrations/004_core_rpc_and_broker.sql
|
|
13
|
+
---
|
|
14
|
+
|
|
15
|
+
# Add an integration
|
|
16
|
+
|
|
17
|
+
## Goal
|
|
18
|
+
|
|
19
|
+
Create a `public.integrations` row — the config + credentials handle for
|
|
20
|
+
outbound HTTP calls (`integration-outbound`) and inbound webhook routing
|
|
21
|
+
(`integration-routes`). See
|
|
22
|
+
[Integrations and webhooks](../concepts/integrations-and-webhooks.md) for
|
|
23
|
+
the full model.
|
|
24
|
+
|
|
25
|
+
## Prerequisites
|
|
26
|
+
|
|
27
|
+
- A `system_admin` login — the integrations function enforces it
|
|
28
|
+
(`.framework/functions/integrations.ts`, `isSystemAdmin` signal).
|
|
29
|
+
|
|
30
|
+
## Steps
|
|
31
|
+
|
|
32
|
+
1. **Choose the integration shape.**
|
|
33
|
+
|
|
34
|
+
- `integration_type`: semantic category — `webhook`, `oauth`, `api`,
|
|
35
|
+
`scheduled_sync` (column comment,
|
|
36
|
+
`.framework/migrations/004_core_rpc_and_broker.sql`).
|
|
37
|
+
- `provider`: the vendor name — `stripe`, `openai`, `generic`, …
|
|
38
|
+
(used as the fallback inbound adapter name).
|
|
39
|
+
- `slug`: unique per `(app_id, account_id)` —
|
|
40
|
+
`integrations_app_account_slug_key`
|
|
41
|
+
(`.framework/migrations/004_core_rpc_and_broker.sql`).
|
|
42
|
+
|
|
43
|
+
2. **Configure `config` and `credentials`.**
|
|
44
|
+
|
|
45
|
+
- `config.outbound_adapter` selects the adapter module for outbound
|
|
46
|
+
calls — built-ins: `adapters/openai-outbound`,
|
|
47
|
+
`adapters/generic-webhook-outbound`, `adapters/resend-outbound`
|
|
48
|
+
(`.framework/functions/integration-outbound.ts`). `action_config.adapter`
|
|
49
|
+
can override it per call.
|
|
50
|
+
- `credentials` is a map `{ field: { source, value } }`:
|
|
51
|
+
`source: "value"` stores a literal secret; `source: "env_var"` resolves
|
|
52
|
+
`value` as a `process.env` name server-side at call time
|
|
53
|
+
(`.framework/functions/_shared/resolve-credentials.ts`).
|
|
54
|
+
|
|
55
|
+
```json
|
|
56
|
+
{
|
|
57
|
+
"slug": "acme-api", "name": "Acme API",
|
|
58
|
+
"integration_type": "api", "provider": "acme",
|
|
59
|
+
"config": {
|
|
60
|
+
"base_url": "https://api.acme.test",
|
|
61
|
+
"outbound_adapter": "adapters/generic-webhook-outbound"
|
|
62
|
+
},
|
|
63
|
+
"credentials": {
|
|
64
|
+
"api_token": { "source": "env_var", "value": "ACME_API_TOKEN" }
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
`is_configured` is recomputed on every save — `true` only when every
|
|
70
|
+
credential entry is present and non-empty
|
|
71
|
+
(`.framework/functions/integrations.ts`).
|
|
72
|
+
|
|
73
|
+
3. **Create the integration.** Admin UI → **Configs → Integrations → New**,
|
|
74
|
+
or `POST /api/integrations` — `name`, `integration_type`, and `provider`
|
|
75
|
+
required (`.framework/functions/integrations.ts`).
|
|
76
|
+
|
|
77
|
+
4. **Call it outbound.** `POST /api/integration-outbound` (or a pipeline
|
|
78
|
+
`http_request`/webhook stage) with `{ integration_slug, action_config }`.
|
|
79
|
+
`action_config` carries `endpoint`/`url`, `body`, `credential_field`
|
|
80
|
+
(default `api_token`), `action_slug`, `max_attempts`, and optional
|
|
81
|
+
`adapter` (`.framework/functions/integration-outbound.ts`).
|
|
82
|
+
Deliveries land in `webhook_deliveries` with retry bookkeeping — see
|
|
83
|
+
[integration-outbound reference](../reference/functions/integration-outbound.md).
|
|
84
|
+
|
|
85
|
+
5. **For inbound**, point senders at `integration-routes` and pick an
|
|
86
|
+
adapter — see [Add a webhook](./add-a-webhook.md).
|
|
87
|
+
|
|
88
|
+
## Verify
|
|
89
|
+
|
|
90
|
+
- `GET /api/integrations?action=list` — `is_configured: true` once every
|
|
91
|
+
credential resolves.
|
|
92
|
+
- A `webhook_deliveries` row is written per outbound call with
|
|
93
|
+
`response_status` and retry fields.
|
|
94
|
+
|
|
95
|
+
## Common mistakes
|
|
96
|
+
|
|
97
|
+
- **An empty or absent credential means "no auth", not an error.**
|
|
98
|
+
`execute` skips resolution for an absent entry or an empty
|
|
99
|
+
`source: "value"` literal and the call goes out without a credential —
|
|
100
|
+
the stock `generic-http` seed (`{"api_token":{"source":"value","value":""}}`,
|
|
101
|
+
`.framework/migrations/004_core_rpc_and_broker.sql`) therefore works
|
|
102
|
+
unauthenticated out of the box. Whether a credential is required is the
|
|
103
|
+
*adapter's* call: `generic-webhook-outbound` only attaches
|
|
104
|
+
`Authorization: Bearer` when a credential resolved, while
|
|
105
|
+
`openai-outbound`/`resend-outbound` throw in `buildRequest`. What still
|
|
106
|
+
fails hard: an `env_var` entry naming an unset variable
|
|
107
|
+
(`credential_env_missing`).
|
|
108
|
+
- **Expecting credentials in reads.** Integrations are a protected
|
|
109
|
+
second-surface table — non-system-admin/machine reads are sanitized to
|
|
110
|
+
minimal fields; secrets are only resolved server-side
|
|
111
|
+
(`.framework/functions/_shared/permissions.ts`).
|
|
112
|
+
- **`env_var` pointing at a missing env** — `resolveCredential` throws
|
|
113
|
+
before the request is sent and emits `integration.credential_*` audit
|
|
114
|
+
rows.
|
|
115
|
+
- **Slug not unique within `(app_id, account_id)`** — the DB unique
|
|
116
|
+
constraint rejects it; account-scoped and global rows share the key.
|
|
117
|
+
|
|
118
|
+
## Reference
|
|
119
|
+
|
|
120
|
+
- [integrations entity page](../integrations.md) ·
|
|
121
|
+
[integrations function reference](../reference/functions/integrations.md) ·
|
|
122
|
+
[integration-outbound reference](../reference/functions/integration-outbound.md) ·
|
|
123
|
+
[integration-routes reference](../reference/functions/integration-routes.md)
|
|
124
|
+
- [Integrations and webhooks](../concepts/integrations-and-webhooks.md)
|